Running an agent as a durable workflow¶
An ordinary in-process AgentRunner run lives in one process unless checkpointing is
configured. A pod roll, an OOM kill, or a scale-down otherwise loses the active work.
AgentWorkflow is the durable path for runs that take minutes to hours: it makes model and
tool calls replayable activities so a replacement worker does not pay for completed work or
repeat an effect.
AgentWorkflow drives the same loop as the in-process runtime, with one difference: it
performs no I/O. Model calls and tool calls are the only execution points, and each one is
recorded in a Journal under a step id derived from the run's position. A worker that dies
and restarts replays the loop, finds the completed steps, and pays for none of them again.
from tesserix_adk.workflows import ActivityContext, AgentWorkflow, WorkflowState
workflow = AgentWorkflow(activities=activities, model="claude-opus-5", journal=replayed)
final = await workflow.run(
WorkflowState(run_id=run_id, history=handle),
context=ActivityContext(run_id=run_id, tenant=tenant, user=user, trace_id=trace),
)
Nothing here imports a workflow engine. The kit supplies the deterministic driver and the
activity payloads; binding them to Temporal activities is the deployment's job, through the
optional tesserix-adk[temporal] extra. The package imports cleanly with nothing installed,
so a consumer that does not want durability does not pay for it.
The two activities¶
| Activity | Input | Result |
|---|---|---|
model_call |
ModelCallInput — context, step, attempt, model, history handle, tools |
ModelCallResult — the validated ModelResponse and the new history handle |
tool_call |
ToolCallInput — context, step, attempt, tool, call id, arguments |
ToolCallResult — content or a handle, whether it failed, the new history handle |
Everything else on the workflow path is arithmetic: the iteration counter, the usage ledger, the pending approval, the autonomy grant id and the terminal decision. That is what makes a replay produce the same decisions as the original run.
What travels, and what does not¶
The transcript never travels. It outgrows the payload limit long before a long run ends, so a model activity receives a handle to the history in the run's store, resolves it, calls the provider, appends the turn and returns the new handle. An input with an empty handle is refused at construction rather than being sent with no history.
Nothing is truncated to fit. An input or result over PAYLOAD_LIMIT_BYTES raises
PayloadTooLargeError naming the step and the size. A retrieval result cut in half is still a
plausible-looking answer, and the run would continue on evidence nobody chose.
Tenant is never inferred. A worker serves every tenant, so an ActivityContext without one
raises MissingTenantContextError — the worker's own identity is not a default. User, scopes
and trace id ride along on every input, so an activity cannot widen its own authority and its
span attaches to the run's trace rather than the worker's.
Failure, retry and cancellation¶
An unavailable provider is retried up to attempts times; when they are exhausted the run
fails with ProviderUnavailableError carrying the attempt count and the step. The state and
the journal stay inspectable, and no invented completion is ever returned.
Cancellation is both checked before each activity and raced against the one in flight: a
streaming completion nobody is listening to still consumes tokens and still bills. Pass the
runtime's CancellationToken as token.
Deployment boundary¶
Worker charts and Temporal namespace provisioning live in tesserix-k8s, not here. This
repository defines the replay-safe activity and journal contracts; the organisation's
cluster chart repository owns workload identity, network policy, scaling, probes, secrets,
backups, and the durable engine itself.
Known limitations¶
- Token streaming is not available on the workflow path. An activity returns once. A run
that needs tokens as they arrive stays in process;
STREAMING_UNSUPPORTEDsays so rather than the kit degrading quietly. - Replay-safety checks and compensation are separate package surfaces rather than implicit
AgentWorkflowbehavior. Run the replay-safety gate in CI and declare compensation for effects that need it. - The journal is held by the workflow object. Persisting it is the engine's job — under Temporal that is the event history, and under a test it is the object itself.