Putting It Together
An AI agent becomes a production system when all of its parts operate as one coherent architecture. The model reasons, tools connect that reasoning to the world, state carries progress across turns, policies define authority, checkpoints preserve work, and observability makes the entire process understandable. Each capability shapes the others.
Start Sufficient #
Architecture begins with choosing the right amount of autonomy. Every additional loop, tool, memory store, and agent adds useful capability along with latency, cost, and operational surface area.
| Level | Architecture | Best fit | Additional responsibility |
|---|---|---|---|
| 1 | Single model call | Classification, extraction, rewriting, summarization | Prompt and output validation |
| 2 | Model call with retrieval | Grounded answers over a known corpus | Indexing, retrieval quality, citations |
| 3 | Deterministic workflow | Stable, repeatable multi-step processes | Step contracts, retries, workflow state |
| 4 | Single agent with tools | Tasks whose path emerges during execution | Tool safety, budgets, loop control, recovery |
| 5 | Multi-agent system | Work requiring specialization or separate authority boundaries | Delegation, shared state, coordination, distributed tracing |
A useful decision sequence is:
- Begin with one model call and a structured output contract.
- Add RAG when the task needs private, current, or domain-specific knowledge.
- Add a workflow when the steps are known in advance.
- Add an agent loop when the model needs to choose actions dynamically from environmental feedback.
- Add multiple agents when specialization, scale, or security boundaries create measurable value.
This progression keeps the system legible. Each level earns its place through an observed requirement.
The Production Reference Architecture #
The architecture has three broad regions: an experience boundary, an agent runtime, and a set of platform services. A control plane governs all three.
┌──────────────────────────────────────────────────────────────────────────────┐
│ Experience Boundary │
│ │
│ Web / Mobile / API / Event / Schedule │
│ │ │
│ ▼ │
│ Identity ──► Request validation ──► Task grant ──► Admission control │
└──────────────────────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Agent Runtime │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────────────────────┐ │
│ │ Context assembler│─────►│ Orchestrator / agent loop │ │
│ │ │ │ │ │
│ │ instructions │ │ plan ─► select ─► act ─► observe ─► evaluate │ │
│ │ session state │ └───────────────┬──────────────────────────────┘ │
│ │ memory + RAG │ │ │
│ │ tool catalog │ ┌───────┴────────┐ │
│ └──────────────────┘ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │Model gateway │ │Tool gateway │ │
│ │routing │ │policy enforcement│ │
│ │budgets │ │approval │ │
│ │fallbacks │ │credential broker │ │
│ └──────────────┘ └─────────┬────────┘ │
│ │ │
│ Checkpoints ◄── state transitions ◄───────────────────┘ │
│ Guardrails ◄── inputs, actions, outputs │
│ Tracing ◄── model turns, tool calls, decisions, costs │
└──────────────────────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────────────┐
│ Platform Services │
│ │
│ Model providers Tool servers / MCP Sandboxes APIs Databases │
│ Session store Workflow store Memory RAG Event bus │
│ Secrets manager Policy engine Audit log Traces Evaluation store │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Control Plane │
│ │
│ Prompt versions │ Model policy │ Tool registry │ Permissions │ Evals │
│ Releases │ Rollbacks │ Budgets │ Monitoring │ Governance │
└──────────────────────────────────────────────────────────────────────────────┘
The diagram shows a key architectural property: the model participates in the runtime while trusted application code owns execution. The model proposes tool calls. The runtime validates, authorizes, executes, records, and returns observations. This separation gives the system both flexible reasoning and deterministic control.
The End-to-End Request Lifecycle #
A production run moves through a sequence of explicit boundaries.
1. Admit the Task #
The entry point authenticates the caller, validates the request shape, assigns a correlation ID, and creates a task-level authority grant. Rate limits and workload limits apply here, before model or tool costs begin.
Interactive requests arrive through an API or user interface. Event-driven integrations arrive through queues, webhooks, or change streams. Temporal agents also begin from schedules, deadlines, or wake-up events.
The admission result contains:
- the authenticated principal
- the task and its input data
- the permitted capability scopes
- budget and deadline constraints
- a session ID and run ID
- provenance for the triggering event
2. Assemble Context #
The context assembler builds the model input from distinct sources:
- system instructions and policy guidance
- the user's task and recent conversation
- durable workflow state
- relevant long-term memory and resolved user preferences
- retrieved knowledge and citations
- the tools available for this task
- the remaining token, cost, and time budgets
Each source keeps its provenance and trust label. User messages, retrieved documents, and tool outputs remain untrusted data. System instructions and runtime-generated constraints occupy higher-priority channels. The personalization layer resolves task-relevant preferences with scope, confidence, provenance, and consent before rendering them through reviewed instruction templates. The context budget determines how much of each source enters the next model turn.
3. Select the Execution Mode #
The runtime chooses a deterministic path, agentic path, or hybrid path. Stable operations such as validating an account, calculating a price, or writing an audit record stay in code. Open-ended steps such as interpreting intent, choosing a search strategy, or proposing a plan use the model.
This creates a productive division of labor:
- code owns invariants, permissions, state transitions, and irreversible effects
- the model owns interpretation, synthesis, planning, and flexible selection
- humans own consequential judgments defined by the approval policy
4. Run the Model Turn #
The model gateway selects a model according to task complexity, latency target, data policy, and budget. It records the prompt version, model version, sampling configuration, token use, and timing.
The model returns one of two useful outcomes:
- a candidate final answer
- one or more structured tool calls
Constrained output turns this boundary into a typed contract. Schema validation happens before the runtime interprets the result.
5. Authorize the Action #
Every tool call passes through the tool gateway. Authorization, delegation, and consent connect the original principal to the proposed effect. The gateway resolves the tool from the registry, validates its arguments, classifies its effect, and evaluates the current grant.
Authority becomes narrower as the request moves inward:
user or service identity
│
▼
session permissions
│ scope attenuation
▼
task capability grant
│ tool + argument policy
▼
single-use execution credential
The runtime can approve a read immediately, evaluate a bounded write through policy, and route a high-impact action through human approval. Credentials remain inside the trusted gateway or tool server. The model works with symbolic tool names and receives the resulting data.
6. Execute in the Right Boundary #
The execution environment matches the tool's risk:
- pure calculations can run inside the application process
- internal APIs run behind authenticated service boundaries
- reusable remote tools run through tool servers or MCP
- generated code and computer operations run inside sandboxes
- irreversible writes use idempotency keys and transaction boundaries
The tool returns a structured result with status, data, error classification, timing, and provenance. The runtime filters and bounds the result before adding it to model context.
7. Observe and Continue #
The tool result becomes an observation in the ReAct loop. The model can synthesize an answer, choose another tool, revise the plan, request clarification, or escalate.
Loop control remains explicit. The runtime tracks turns, tool attempts, elapsed time, token use, monetary cost, repeated actions, and progress signals. These budgets provide deterministic termination around flexible reasoning.
8. Checkpoint Durable Progress #
A checkpoint follows every meaningful state transition and every external side effect. The checkpoint records the plan, completed work, pending approvals, tool results, idempotency keys, budgets, and context summary.
Durable execution can then resume the run after a restart, timeout, scheduled delay, or human response. The event history provides both recovery material and an audit trail.
9. Validate the Outcome #
Output guardrails validate safety, grounding, schema compliance, policy, and task-specific quality. High-value tasks can add a review-and-critique loop or a deterministic verifier.
The result includes provenance appropriate to the task: citations for research, test results for code, transaction IDs for actions, and approval records for governed workflows.
10. Deliver and Learn #
The runtime persists final state, closes the trace, reports usage, and returns the result through the originating channel. Production telemetry feeds evaluation suites and lifecycle management. Curated failures become regression cases, while successful trajectories can support retrieval-augmented actions.
Data Plane and Control Plane #
Separating the data plane from the control plane keeps runtime execution responsive while preserving centralized governance.
Data Plane #
The data plane handles live tasks:
- accepts requests and events
- assembles context
- invokes models and tools
- reads and writes run state
- applies policies and guardrails
- streams progress and results
- emits traces, metrics, and audit events
It scales according to request volume, model concurrency, tool latency, and long-running workflow count.
Control Plane #
The control plane defines what the data plane is allowed and expected to do:
- publishes prompt and agent versions
- manages model routing rules
- registers tools and capability metadata
- distributes authorization and approval policies
- configures budgets and rate limits
- runs offline evaluations and release gates
- performs canary releases and rollback
- monitors fleet-wide quality, cost, and safety
The control plane changes at deployment cadence. The data plane changes state at task cadence. Keeping those cadences separate makes releases reproducible and incidents easier to contain.
State Has Several Lifetimes #
The word state covers information with different ownership and retention requirements. A production architecture stores each category according to its lifetime.
| State category | Example | Lifetime | Typical store |
|---|---|---|---|
| Turn state | Current tool call and observation | One model turn | Process memory |
| Run state | Plan, budgets, completed steps | One task | Durable workflow store |
| Session state | Conversation and active workspace | Several related tasks | Session database |
| Long-term memory | Stable preferences and learned facts | Across sessions | Memory store with retrieval |
| Knowledge | Documents, records, embeddings, graphs | Domain lifecycle | Source systems and indexes |
| Audit state | Approvals, actions, policy decisions | Governance retention period | Append-only audit store |
| Evaluation state | Test cases, traces, scores, regressions | Product lifecycle | Evaluation platform |
This separation improves context quality as well as operations. The model receives a selected view of state, while the runtime preserves the complete authoritative record. Memory and context engineering determines which facts enter future model turns. The workflow store determines where execution resumes. The audit store explains what occurred.
Trust Boundaries and Authority #
Agent security becomes clearer when the architecture labels both trust and authority.
The Model Boundary #
The model is a reasoning component that processes untrusted text. It proposes decisions through structured outputs. Trusted runtime code verifies every proposal against schemas, permissions, policy, and current state.
The Tool Boundary #
Tools expose narrow capabilities with explicit schemas, effect classifications, and ownership. The tool design determines what the model can express. The tool gateway determines which requested action receives authority.
The Credential Boundary #
Credentials live in a secrets manager, gateway, or isolated tool server. The runtime issues short-lived credentials scoped to one tool, resource, task, and audience. Revocation and expiry bound the lifetime of delegated authority.
The Data Boundary #
Retrieved content and tool results carry source, sensitivity, tenant, and freshness metadata. Policy filters data before context assembly and inspects egress before delivery. This supports tenant isolation, privacy, and defenses against indirect prompt injection.
The Human Boundary #
Approval requests present the proposed action, affected resource, expected effect, evidence, and alternatives. The decision becomes part of durable state and the audit history. Approval policies use risk and impact to focus human attention where judgment creates the most value.
Together, these boundaries form a chain of accountable execution: identity establishes the principal, policy narrows authority, the runtime enforces it, tools perform the action, and the audit trail records the outcome.
Reliability Is Part of Control Flow #
Agent failures arise at several layers, so recovery also operates at several layers.
| Failure | Runtime response | Architectural mechanism |
|---|---|---|
| Invalid model output | Validate and request a corrected structure | Schema contract |
| Tool timeout | Retry within policy or choose a fallback | Timeout and retry policy |
| Duplicate write | Return the original result | Idempotency key |
| Process restart | Reload the last committed state | Durable checkpoint |
| Context growth | Summarize completed work and retrieve details on demand | Context compaction |
| Model degradation | Route to a validated fallback | Model gateway |
| Policy denial | Return a bounded observation or request approval | Policy engine |
| Partial multi-agent failure | Preserve completed outputs and reassign remaining work | Orchestrator state |
| User interruption | Propagate cancellation through tools and sub-agents | Cancellation token |
| Quality failure | Revise, escalate, or return a qualified result | Evaluator and confidence policy |
Error handling and recovery provides the local mechanisms. The reference architecture connects them to checkpoints, budgets, observability, and deployment controls so recovery remains consistent across the full run.
A Framework-Neutral Python Runtime #
The following skeleton expresses the core runtime contract. Infrastructure-specific adapters provide the model, context, state, policy, approvals, tools, guardrails, and tracing. Trusted code owns the loop and every effectful boundary.
from __future__ import annotations
import json
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Protocol
from uuid import uuid4
class Risk(str, Enum):
READ = "read"
WRITE = "write"
HIGH_IMPACT = "high_impact"
class PolicyEffect(str, Enum):
ALLOW = "allow"
REQUIRE_APPROVAL = "require_approval"
DENY = "deny"
@dataclass(frozen=True)
class Principal:
subject: str
scopes: frozenset[str]
@dataclass(frozen=True)
class RunRequest:
task: str
principal: Principal
session_id: str
run_id: str = field(default_factory=lambda: str(uuid4()))
@dataclass(frozen=True)
class ToolSpec:
name: str
description: str
risk: Risk
required_scopes: frozenset[str] = field(default_factory=frozenset)
@dataclass(frozen=True)
class ToolCall:
call_id: str
name: str
arguments: dict[str, Any]
@dataclass(frozen=True)
class ModelTurn:
message: dict[str, Any]
tool_calls: tuple[ToolCall, ...] = ()
final_text: str | None = None
@dataclass(frozen=True)
class PolicyDecision:
effect: PolicyEffect
reason: str
@dataclass(frozen=True)
class ToolExecution:
ok: bool
data: Any = None
error_code: str | None = None
@dataclass(frozen=True)
class GuardDecision:
allowed: bool
reason: str = ""
@dataclass(frozen=True)
class RunEvent:
kind: str
details: dict[str, Any]
@dataclass
class RunState:
run_id: str
session_id: str
status: str = "running"
result_text: str | None = None
finish_reason: str = ""
turns: int = 0
tool_attempts: int = 0
messages: list[dict[str, Any]] = field(default_factory=list)
pending_calls: list[ToolCall] = field(default_factory=list)
events: list[RunEvent] = field(default_factory=list)
@dataclass(frozen=True)
class RunResult:
run_id: str
status: str
text: str | None = None
reason: str = ""
class ModelGateway(Protocol):
async def next_turn(
self,
messages: list[dict[str, Any]],
tools: tuple[ToolSpec, ...],
) -> ModelTurn: ...
class ContextAssembler(Protocol):
async def build(
self,
request: RunRequest,
state: RunState,
) -> list[dict[str, Any]]: ...
class PolicyEngine(Protocol):
async def authorize(
self,
principal: Principal,
tool: ToolSpec,
arguments: dict[str, Any],
) -> PolicyDecision: ...
class ApprovalGateway(Protocol):
async def request(
self,
request: RunRequest,
tool: ToolSpec,
arguments: dict[str, Any],
) -> bool: ...
class ToolExecutor(Protocol):
async def execute(
self,
principal: Principal,
tool: ToolSpec,
arguments: dict[str, Any],
idempotency_key: str,
) -> ToolExecution: ...
class StateStore(Protocol):
async def load(self, run_id: str) -> RunState | None: ...
async def save(self, state: RunState) -> None: ...
class OutputGuard(Protocol):
async def check(
self,
request: RunRequest,
text: str,
) -> GuardDecision: ...
class TraceSink(Protocol):
async def emit(
self,
run_id: str,
event: RunEvent,
) -> None: ...
class ToolRegistry:
def __init__(self, tools: list[ToolSpec]) -> None:
self._tools = {tool.name: tool for tool in tools}
def all(self) -> tuple[ToolSpec, ...]:
return tuple(self._tools.values())
def get(self, name: str) -> ToolSpec | None:
return self._tools.get(name)
class AgentRuntime:
def __init__(
self,
*,
model: ModelGateway,
context: ContextAssembler,
policy: PolicyEngine,
approvals: ApprovalGateway,
executor: ToolExecutor,
state_store: StateStore,
output_guard: OutputGuard,
traces: TraceSink,
tools: ToolRegistry,
max_turns: int = 12,
max_tool_attempts: int = 30,
) -> None:
self.model = model
self.context = context
self.policy = policy
self.approvals = approvals
self.executor = executor
self.state_store = state_store
self.output_guard = output_guard
self.traces = traces
self.tools = tools
self.max_turns = max_turns
self.max_tool_attempts = max_tool_attempts
async def run(self, request: RunRequest) -> RunResult:
state = await self.state_store.load(request.run_id)
if state is None:
state = RunState(
run_id=request.run_id,
session_id=request.session_id,
)
elif state.status != "running":
return RunResult(
run_id=state.run_id,
status=state.status,
text=state.result_text,
reason=state.finish_reason,
)
messages = state.messages or await self.context.build(request, state)
await self._record(state, "run_started", {"task": request.task})
pending_result = await self._execute_pending(request, state, messages)
if pending_result is not None:
return pending_result
for turn_number in range(state.turns + 1, self.max_turns + 1):
state.turns = turn_number
turn = await self.model.next_turn(messages, self.tools.all())
messages.append(turn.message)
state.messages = messages
await self._record(
state,
"model_turn",
{
"turn": turn_number,
"tool_calls": len(turn.tool_calls),
"has_final": turn.final_text is not None,
},
)
if turn.final_text is not None:
guard = await self.output_guard.check(request, turn.final_text)
if guard.allowed:
return await self._finish(
state,
status="completed",
text=turn.final_text,
)
return await self._finish(
state,
status="blocked",
reason=guard.reason,
)
if not turn.tool_calls:
return await self._finish(
state,
status="failed",
reason="Model returned neither a final answer nor a tool call",
)
# Persist the proposed calls before executing them. After a crash,
# the runtime retries each pending call with the same stable key.
state.pending_calls = list(turn.tool_calls)
await self.state_store.save(state)
pending_result = await self._execute_pending(request, state, messages)
if pending_result is not None:
return pending_result
return await self._finish(
state,
status="budget_exhausted",
reason="Turn budget exhausted",
)
async def _execute_pending(
self,
request: RunRequest,
state: RunState,
messages: list[dict[str, Any]],
) -> RunResult | None:
while state.pending_calls:
if state.tool_attempts >= self.max_tool_attempts:
return await self._finish(
state,
status="budget_exhausted",
reason="Tool-attempt budget exhausted",
)
call = state.pending_calls[0]
state.tool_attempts += 1
execution = await self._execute_call(request, state, call)
messages.append(self._tool_message(call, execution))
state.messages = messages
state.pending_calls.pop(0)
# A crash after the external effect leaves the call pending. The
# next worker repeats the stable idempotency key and receives the
# original result from an idempotent tool.
await self.state_store.save(state)
return None
async def _execute_call(
self,
request: RunRequest,
state: RunState,
call: ToolCall,
) -> ToolExecution:
tool = self.tools.get(call.name)
if tool is None:
execution = ToolExecution(ok=False, error_code="unknown_tool")
await self._record_tool(state, call, execution)
return execution
missing_scopes = tool.required_scopes - request.principal.scopes
if missing_scopes:
execution = ToolExecution(ok=False, error_code="missing_scope")
await self._record_tool(state, call, execution)
return execution
decision = await self.policy.authorize(
request.principal,
tool,
call.arguments,
)
if decision.effect == PolicyEffect.DENY:
execution = ToolExecution(ok=False, error_code="policy_denied")
await self._record_tool(state, call, execution)
return execution
if decision.effect == PolicyEffect.REQUIRE_APPROVAL:
approved = await self.approvals.request(
request,
tool,
call.arguments,
)
if not approved:
execution = ToolExecution(ok=False, error_code="approval_denied")
await self._record_tool(state, call, execution)
return execution
try:
execution = await self.executor.execute(
request.principal,
tool,
call.arguments,
idempotency_key=f"{request.run_id}:{call.call_id}",
)
except Exception as exc:
# The trace records a bounded error class. Infrastructure logs can
# retain approved diagnostic detail outside the model context.
execution = ToolExecution(
ok=False,
error_code=f"executor_{type(exc).__name__}",
)
await self._record_tool(state, call, execution)
return execution
async def _record_tool(
self,
state: RunState,
call: ToolCall,
execution: ToolExecution,
) -> None:
await self._record(
state,
"tool_result",
{
"call_id": call.call_id,
"tool": call.name,
"ok": execution.ok,
"error_code": execution.error_code,
},
)
@staticmethod
def _tool_message(
call: ToolCall,
execution: ToolExecution,
) -> dict[str, Any]:
content = json.dumps(
{
"ok": execution.ok,
"data": execution.data,
"error_code": execution.error_code,
},
default=str,
)
return {
"role": "tool",
"tool_call_id": call.call_id,
"content": content,
}
async def _record(
self,
state: RunState,
kind: str,
details: dict[str, Any],
) -> None:
event = RunEvent(kind=kind, details=details)
state.events.append(event)
await self.traces.emit(state.run_id, event)
async def _finish(
self,
state: RunState,
*,
status: str,
text: str | None = None,
reason: str = "",
) -> RunResult:
state.status = status
state.result_text = text
state.finish_reason = reason
await self._record(state, "run_finished", {"status": status})
await self.state_store.save(state)
return RunResult(
run_id=state.run_id,
status=status,
text=text,
reason=reason,
)
The skeleton demonstrates several architectural commitments:
- the runtime controls the loop and its budgets
- model outputs cross a typed boundary
- tool authority comes from the authenticated principal and policy engine
- approval applies to a specific proposed action
- tool effects use stable idempotency keys
- checkpoints follow tool execution
- traces capture decisions without placing secrets in model context
- output guardrails run before delivery
A production implementation adds schema validation, deadlines, cancellation propagation, retry classifications, context compaction, model usage accounting, tenant isolation, and durable approval suspension. These features extend the same boundaries while preserving the core shape.
Deployment Topology #
The logical architecture can run in several physical forms.
Focused Service #
A single service hosts the API, runtime, model adapter, and embedded read-only tools. Managed databases provide sessions and retrieval. This topology suits a focused agent with short tasks and a small trusted tool set.
Durable Worker Architecture #
An API accepts tasks and writes them to a durable queue or workflow engine. Stateless workers execute agent turns, while a workflow store preserves checkpoints. Tool services and sandboxes run separately. This topology suits long tasks, asynchronous approvals, schedules, and recovery across process restarts.
Agent Platform #
A gateway admits tasks into a shared runtime. Registries describe models, tools, and agents. Policy, secrets, tracing, evaluation, and deployment services form the control plane. Specialized agents and tool servers scale independently. This topology suits several product teams and many agent workloads.
Physical separation should follow scale, ownership, and trust boundaries. A service boundary earns its operational cost when it isolates credentials, limits blast radius, enables independent scaling, or creates a clear team contract.
A Practical Architecture Review #
Before production release, review the system through the following questions.
Purpose and Complexity #
- Does the task require an agent loop?
- Which steps have stable structure and belong in deterministic code?
- What measurable requirement justifies each additional agent?
- What is the simplest fallback when agentic execution reaches a limit?
Context and Knowledge #
- Which context sources enter each model turn?
- How are provenance, trust, sensitivity, and freshness represented?
- How does the runtime compact long histories?
- Which facts enter long-term memory, and when do they expire?
- How are retrieval precision and answer faithfulness measured?
Tools and Authority #
- Does each tool expose one clear capability with a tight schema?
- Which tools read, write, or create high-impact effects?
- Where are user and service permissions enforced?
- How are credentials scoped, injected, rotated, and revoked?
- Which actions require policy approval or human judgment?
- Which writes have idempotency and transaction protection?
State and Recovery #
- What state survives a model turn, process restart, and full deployment?
- Where does each state transition commit?
- Can a run resume safely after every external side effect?
- How do deadlines, cancellation, retry limits, and budgets propagate?
- How does the system handle partial success?
Quality and Operations #
- Can one trace reconstruct every model turn, tool call, handoff, and policy decision?
- Which deterministic tests cover tools, policies, and state transitions?
- Which evaluations cover task success, safety, grounding, and cost?
- How are production failures converted into regression tests?
- What release gates, canary metrics, and rollback signals protect deployment?
Governance #
- Which data reaches model providers, tools, logs, memory, and evaluation stores?
- How are tenant isolation and retention enforced?
- Can the audit trail explain consequential actions?
- Who owns policy changes, tool registration, and emergency shutdown?
- How can a user inspect, correct, or delete retained information?
Clear answers make the architecture operable. Unclear answers identify the next design task.
Conclusion #
A production agent is a governed execution system built around a probabilistic reasoning component. Its quality comes from the relationships among context, tools, state, policies, recovery, evaluation, and operations.
The reference architecture follows a few durable principles:
- begin with the smallest architecture that satisfies the task
- keep model reasoning inside deterministic runtime boundaries
- express tools and outputs through typed contracts
- apply personalization as a scoped, evidence-backed context layer
- narrow authority from principal to task to individual action
- preserve progress with durable state and idempotent effects
- treat context as a curated view of authoritative state
- make traces, evaluations, and release controls part of the system
- align physical service boundaries with trust, scale, and ownership