Anatomy of a chat turn
A chat turn is coordinated across two services with deliberately different levels of trust. This page walks one turn end to end and names the guard at each hop.
Run the trace to follow durable acceptance, controller sequencing, current-authority tool execution, and one terminal journal outcome.
1 · Resolve authority
The request arrives at the Member API carrying a validated Kamiwaza identity envelope. Kaizen resolves, in one place:
- the member — platform user id, and the lowercased verified email that is the document-ACL principal;
- their permissions, as of now;
- the selected agent and its exact version;
- any one-turn specialist route, constrained to the selected agent's effective source-reviewed and member-configured roster;
- the room's audience version, if the chat is shared;
- the pinned resource manifest — the exact documents and artifacts this turn is allowed to see.
Together these form the turn's authority bundle. Every later step must present the same bundle; a callback that arrives with a different member, conversation, audience, agent, or resource scope is rejected rather than reconciled.
Identity is never read from a request body or query parameter. See Identity & the platform envelope.
2 · Accept input
The message is appended to the conversation journal at a durable position, with an idempotency key.
This is what makes the turn survivable. Once input is accepted:
- a worker or backend restart preserves one accepted logical turn instead of creating a second input; an exact artifact-checkpoint repair may issue a replacement model attempt after the abandoned attempt is fenced;
- a browser disconnect does not cancel anything;
- a duplicate submission converges on the same logical input rather than running twice.
In a collaborative chat this position is also the queue order. One room has one first-in-first-out lane and at most one active run, so two backend replicas cannot start competing turns.
3 · Drive the turn
The backend admits one immutable pipeline contract. The agent-runtime creates one controller from its static registry for that run.
The controller can order model steps, tool requests, compaction, checkpoints, steering, and completion. It cannot execute these effects itself. A shared host applies the fixed budgets, lifecycle rules, and callback contracts.
The runtime has no credentials, member data, policy, or journal authority. It cannot reach an external system directly.
4 · Execute the tool callback
Whenever the runtime wants to act, it calls back into the backend. The backend:
- re-checks the authority bundle,
- re-resolves the member's current permissions — not the ones cached at turn start,
- executes the tool as that member,
- captures citation and presentation lineage,
- returns a bounded, member-safe projection.
Tools reachable this way include retrieval, document reading,
conversation-history search, run_code, artifact creation, task actions,
task management, and delegation to a sub-agent.
The important property: the tool runs with your permissions, resolved at call time. An agent acting for you can only see and do what you could.
Steering a private turn follows the same identity boundary. A durable command names the exact orchestration, owner, and conversation; it is not addressed to "whatever is currently running". A different API replica may accept the request, while only the fenced execution owner can claim and deliver it. Client retries reuse the command ID and converge on one stored instruction.
5 · Guardrails and the write guard
Before a result is returned — and again before anything with an external effect takes place — the backend applies its guardrail chain:
- budgets, bounding how much work and how many tokens a turn may spend;
- citation discipline, so answers stay grounded in retrieved sources;
- redaction, where configured;
- loop control, stopping runaway cycles and out-of-scope actions.
Anything that writes to the outside world additionally crosses the write guard and, where policy requires it, an approval gate. The assistant can propose an action; it never authorizes one. See Approvals & the write guard.
6 · Commit and stream
The answer, its citations, any artifacts, and the terminal outcome are committed to the journal. Tokens, tool activity, and the final cited answer stream to the browser as they happen.
Sandbox-produced native files remain owner records. In a collaborative turn, the backend re-checks the initiating actor's current room access when the tool settles, creates an immutable room resource reference for each retained file, and projects that exact reference with the answer. Reload uses the same durable reference rather than trying to read the room owner's personal artifact list. Content reads re-authorize the viewer and verify the retained digest before returning bytes.
A later turn can select one of those retained binary references as input.
Prompt composition contributes bounded metadata rather than decoding binary
bytes as text. If the agent uses run_code, the sandbox data plane separately
projects the exact digest-verified bytes through its read-only resource
manifest. The two projections share the same authorized reference but do not
collapse the model-context and code-execution trust boundaries.
The sandbox and transcript use different server-owned identifiers for the same
chat: a room UUID, a WEB_ channel, and the public conversation key;
orchestration tasks add a bounded suffix. One shared scope resolver maps those
forms before either post-run linking or invitation-time reconciliation. It
matches complete keys and explicit task prefixes only, so a similarly named
conversation cannot acquire another room's file.
The initial chat response does not expose HTTP success until the member input and orchestration owner have crossed the durable acceptance boundary. This prevents an immediate navigation from cancelling a generator that has not yet created its run. After acceptance, the response stream is only an observer and disconnecting it cannot delete the work.
Python settles usage and publishes exactly one terminal conversation event. This remains true when controller completion, timeout salvage, cancellation, retry, and process recovery race.
Model calls
Model calls are not an exception to the loop. They go through Kaizen's model gateway, which applies routing, policy, budgets, and effort before reaching Kamiwaza, and normalizes the response on the way back. The runtime never holds a model credential. See Model gateway.
What survives what
| Event | Effect on the turn |
|---|---|
| Browser tab closed | None. The room runner continues |
| Network interruption | Client reconnects from its last journal position |
| Orderly backend/runtime termination | New work stops; active producers receive a bounded durable drain |
| Forced pod loss, OOM, or node failure | The old lease expires, recovery fences the run, and a freshly authorized browser resumes it; with no browser, the background worker salvages it after one further lease period |
| Input lease lost while the runner is alive | The runtime is stopped and the turn ends failed, so the room never waits on a dead run |
| Runtime failure mid-turn | Durable checkpoint or timeout salvage produces one safe terminal outcome |
| Membership revoked mid-turn | Re-checked before publication; publication is refused |
| Model step budget exhausted | One forced synthesis step answers from evidence already gathered, rather than failing the run |
While a runtime call is active, the room runner heartbeats the exact input,
conversation, runner, and lease-token fence. A renewal can extend only an
unexpired running claim with no terminal event. Heartbeat failure cancels the
runtime and prevents stale publication; a healthy completion receives one
final renewal so terminal persistence retains the same authority.
A lost lease still ends the turn. Once the runtime is stopped, the runner
records a failed terminal with reason input_lease_lost and clears the room's
active input, so members see a failed turn rather than an indefinite spinner.
That write is fenced by the same lease token rather than by lease expiry: it
lands only while no other runner has re-claimed the input, and otherwise the
runner leaves the outcome to the new owner.
The lease can also be lost after the runtime has already committed its own terminal, when only the final renewal fails. The runner then attaches that terminal instead of recording a failure, so a finished answer is never relabelled as failed and the room is released at once.
The forced-loss row is intentionally independent of process shutdown hooks. Recovery may wait for the old lease to expire, because reclaiming it early would allow the dead-or-partitioned process and its replacement to execute the same turn concurrently. A page reload is safe during that interval: it replays what is already durable, waits for a claimable state, and presents current browser authorization before execution continues.
Host task cancellation and member cancellation are separate signals. Only a durable, actor-authorized Stop or Cancel activates the runtime's abort controller and records a cancelled terminal. A host task disappearing detaches from the runtime without manufacturing member intent; the accepted input stays nonterminal until its lease is fenced and recovered.
If that reload overlaps an API pod replacement, the browser retries only the read-only conversation projection for a bounded rollout window. Network, rate-limit, and service-unavailable failures are eligible; unauthorized, forbidden, and not-found responses remain immediate and fail closed. A successful projection then performs the normal fresh-authority recovery claim.
Temporal context
Every turn receives the current date, time, weekday, and timezone from the backend, so agents resolve "today", "yesterday", and "next Friday" against one clock. Kaizen uses UTC and states the timezone explicitly so date-boundary answers are unambiguous.
The clock snapshot is runtime context, added when the request reaches the model. It is not part of your saved message, which keeps history, retries, and idempotency stable.
On every model call in the turn, including the follow-up calls after a tool runs, the snapshot rides on the message that started the turn rather than arriving as a message of its own. A separate runtime message after a tool result looked to the model like a new, empty message from you, and it would sometimes answer that phantom message instead of the request you actually sent.
The snapshot is attached after any pipeline extensions have shaped the request,
so an extension's context and before_provider_request hooks see, and are
checked against, your message exactly as you sent it. Correction and synthesis
prompts the runtime adds mid-turn never carry the snapshot, so it stays on the
same message for every step of the turn. Earlier turns are sent without it,
which means a provider's prompt cache stops matching at the previous turn's
message when the next turn begins.
Who the turn is for
Every turn also carries the signed-in member's display name, taken from the forwarded platform envelope, so an agent addresses the person by name rather than reporting on "the user" in its answer and its thinking. The name is flattened to one bounded line and is carried as a label: it confers no identity, role, or permission, and nothing written in a profile name can instruct the model. An envelope with no usable name is left alone rather than labeled with a placeholder.
Runtime sessions are scoped per member, so in a shared chat each participant's turn carries only their own name.
Related
- Agent runtime & the turn engine
- Tools & the tool boundary
- Conversation journal & durability
- Anatomy of a search — the same request, without a model