An agent demo is a loop:
prompt -> model -> tool -> model -> answerA production agent is a long-running distributed workflow in which model calls are nondeterministic, tools have side effects, humans may approve work hours later, credentials expire, deployments change code, and any process can crash between “effect happened” and “state recorded.”
The core architecture question is not which framework can call tools. It is: what durable state lets the system explain, resume, and safely compensate every step?
Separate reasoning from execution #
Treat the model as a planner that proposes the next action, not as the source of truth for completed actions.
goal
-> planner proposes action
-> policy checks authority and budget
-> workflow records intent
-> tool executor performs effect
-> workflow records evidence
-> planner receives normalized resultThe workflow owns lifecycle state. The model never decides that an email was sent merely because it asked a tool to send one.
A minimal state model might be:
type RunState = {
runId: string
goal: string
status: 'running' | 'waiting_approval' | 'completed' | 'failed'
policyVersion: number
modelPolicy: { provider: string; model: string; maxTokens: number }
budget: { tokens: number; toolCalls: number; moneyCents: number }
steps: StepEvidence[]
}Store prompts and model outputs according to an explicit privacy policy. For many systems, retaining structured decisions, hashes, redacted arguments, and tool evidence is safer than retaining every raw conversation indefinitely.
Durable execution changes the failure model #
Workflow systems such as Temporal persist an event history and reconstruct workflow state after worker failure. The important property is not “the process runs forever.” It is that the orchestration can resume from recorded history rather than improvising from a partially updated row.
Keep workflow logic deterministic. Put network calls, model invocations, clocks, randomness, and side effects in activities:
workflow (replayable decisions)
activity: call model
activity: fetch account
wait: human approval signal
activity: update CRM
activity: send notificationAn activity may execute more than once if a worker completes the external effect and crashes before recording completion. Therefore durable orchestration does not remove idempotency; it tells you exactly where idempotency is required.
Give every effect a stable identity #
Tool calls need an operation ID derived from durable workflow state, not generated anew on every attempt:
operation_id = run_id + step_number + tool_contract_versionPass it to downstream systems as an idempotency key. Persist the request fingerprint and outcome:
INSERT INTO tool_effects(operation_id, tool, request_hash, status)
VALUES ($1, $2, $3, 'started')
ON CONFLICT (operation_id) DO NOTHING;If an external tool does not support idempotency, define an ambiguity strategy:
- query the remote system for evidence;
- require human reconciliation;
- make the effect append-only and compensatable;
- or prohibit automatic retry.
“Retry the tool” is not a universal recovery policy.
Authorization belongs at execution time #
The model’s proposed arguments are untrusted input. Tool descriptions and retrieved documents are also untrusted because they may contain prompt injection.
Authorize the concrete action immediately before execution:
principal: sales_rep_17
tenant: acme
tool: crm.update_contact
resource: contact/882
fields: [next_follow_up]
reason: approved campaign workflow
policy_version: 42The Model Context Protocol authorization specification uses OAuth-based patterns for protected servers and explicitly defines resource-server behavior. Regardless of protocol, do not hand a general-purpose bearer token to the model. The executor should hold credentials, bind them to a trusted service identity, and expose only policy-filtered capabilities.
Recheck authorization after a long wait. A user who approved an action yesterday may have lost access today; a customer may have revoked the integration; a policy may have changed.
Human approval is a durable state #
Do not implement approval as an HTTP request waiting for a button click. Record a transition:
proposed -> awaiting_approval -> approved -> executing -> evidenced
\-> rejected
\-> expiredThe approval request should display the actual effect: recipient, amount, changed fields, data leaving the system, and cost. An approval for “continue” is not informed consent.
Bind approval to a hash of the proposed action. If the planner changes arguments after approval, require a new decision. Set an expiry and record who approved, under which role and policy version.
Version workflows without corrupting open runs #
Agent runs can outlive deployments. A new prompt, tool schema, or branching rule can make replay diverge or reinterpret old state.
Version at least:
- workflow code paths;
- system prompt and model policy;
- tool contracts;
- authorization policy;
- structured output schemas;
- evaluation rubric.
Existing runs should continue on compatible behavior or pass through an explicit migration. “Deploy and hope no run is between steps” is not a release strategy.
Temporal documents workflow replay and versioning constraints because nondeterministic code changes can break reconstruction. The same issue exists in home-grown orchestrators; it is simply less visible until recovery fails.
Budget loops before they become incidents #
Bound every dimension that can grow:
- model tokens and calls;
- wall-clock duration;
- tool calls by class;
- external spend;
- retrieved bytes;
- repeated planning without state change;
- delegated child agents;
- approval wait time.
Detect cycles semantically. Three differently worded plans that call the same tool with the same arguments are one stuck loop.
if same_effect_fingerprint >= 2 and no_new_evidence:
stop(reason="non-progressing loop")Budgets should be visible to the planner but enforced outside it.
Observe evidence, not chain-of-thought #
You do not need private reasoning traces to operate an agent. You need structured evidence:
- run and step IDs;
- model and prompt policy versions;
- input/output token counts;
- proposed tool and normalized arguments;
- policy decision and reason code;
- approval state;
- tool latency, retries, and outcome;
- external effect identifier;
- final outcome and evaluation result.
OpenTelemetry’s generative-AI semantic conventions provide a developing vocabulary for model and agent telemetry. Treat the conventions as evolving, and keep sensitive content capture opt-in.
CTO review questions #
- What durable record proves each side effect happened?
- Which operations can execute more than once, and how are they deduplicated?
- Is authorization evaluated against the concrete action at execution time?
- Can a human see and approve the exact effect?
- Can open runs survive a deployment and policy change?
- What stops a non-progressing loop?
- Can we reconstruct an incident without storing unnecessary private reasoning?
The reliable agent is not the one with the cleverest loop. It is the one whose progress is durable, whose authority is bounded, whose effects are evidenced, and whose failures produce a recoverable state instead of a mystery.