Extending the shared pipeline
One rule governs almost every architectural decision in Kaizen:
Implement cross-agent behavior once, in the shared pipeline and runtime. Streaming, journal projection, retries, timeout salvage, durability, grounding, lifecycle, policy, and error handling must apply uniformly to every agent. Never patch these concerns into an individual agent.
Agent-specific code may define only genuinely domain-specific persona, capability, tool-grant, or source-selection behavior.
Grounding asks for retrieval when a claim needs evidence beyond the current member message. If the member supplies enough quoted passages for a bounded answer, the agent can answer from those passages directly, attributing their source IDs as member-provided evidence. Those IDs are not Kaizen retrieval citations, even when the member requests retrieval syntax. Their labels use inline code so UUID-shaped IDs remain literal text and cannot become source cards through citation lookup. A UUID supplied in the current member message cannot be rehydrated from the corpus merely because the model repeats it; a tool must retrieve that source during the current turn before it can become a retrieved citation. UUID labels in steering that may have reached the active run receive the same protection; a confirmed rejection adds no new labels. This bounded inventory exists only for that run. Older conversation history remains subject to current ACL revalidation rather than a cross-run exclusion ledger. Questions that require verification, fresh state, or additional facts still use the authorized retrieval tools.
Private-chat steering uses the same durable turn identity as replay and cancellation. The browser submits the exact orchestration and an idempotency key; PostgreSQL accepts it only while that owner/conversation turn holds a live lease. The execution owner claims commands with a short recovery lease before passing them to the runtime. Process-local run maps remain callback capabilities, not cross-replica coordination state.
Shared-chat steering preserves the active input's selected model. Admission locks the room, reads only that input's durable deployment ID, and resolves its runtime details from the steering member's authenticated inventory. It never borrows the original member's credentials or substitutes the current default. When there is no active input, admission first uses the steering member's latest accepted message or steer, including a queued input. If that member has no accepted model history, the room's persisted selection remains the compatibility fallback. Another member's input never supplies this idle target. Missing or inaccessible selections reject steering with an actionable error; send a normal message with an available model to establish a new selection.
An idempotent retry revalidates its own previously accepted target, scoped to the same actor, conversation and key. Accepted steering retains its captured model through queueing and lease recovery even if the room selection changes. Stop and cancel remain available without model discovery.
Uploaded-document scope is resolved at the shared member turn boundary for
every agent. Conversation references and explicitly selected document ids are
unioned under the authenticated viewer. The model receives a bounded pointer
summary; query_document searches the complete ACL-visible set and returns
citable chunks. Images and unsupported files are stored with no extracted
text, so a selection made only of such files is not offered query_document,
and its pointer summary tells the model that no search or document tool can
read it. A library image whose pixels are withheld is described as unreachable
by any tool. Do not move this selection or text injection into a specific
agent. The separate prompt-warming hint still accepts at most 20 document ids;
large selections skip optional warming, not the actual turn.
The runtime's conversation_id is the journal UUID, not the attachment key.
Resolve document references under the public ID recovered from the server-owned
WEB_<id> channel; never query them under the journal UUID or drop the
selection from the model's context.
Claimed durable chat inputs also pass through the canonical pre-runtime routing resolver. The selected room agent remains the execution authority; routing may mint only the same bounded one-turn child grant used by direct chat. The grant is attached before the runtime is constructed and the resulting decision is audited against the durable input ID, so queueing, retry, or collaboration cannot silently bypass specialist routing.
Task turns use the same durable conversation input. A repeated key whose input already completed is a successful replay even when the current drain processes zero new turns; failed and salvaged inputs are not treated as completed. Corpus repairs use a new key only after a completed answer fails record validation. Retries after an uncertain worker interruption keep the original key so they cannot repeat an ambiguous tool effect.
Orchestration child runs cross a versioned SubagentInputEnvelope and return a
TaskResultProjection carrying answer and result digests plus
runtime-observed evidence bindings. Add fields at that common projection
boundary; never pass a child transcript or invent a parallel per-workflow
handoff shape.
Reviewed pipeline controllers
Kaizen ships three agent pipelines without duplicating protected runtime code. A controller owns when work continues, yields, or settles. The shared host owns model access, tool execution, authorization, budgets, journals, checkpoints, usage, event projection, and terminal settlement.
Start with the General view, then switch to Tomo, Codex, or OMP for each controller's real sequencing flow. Protected model, tool, journal, budget, authorization, and settlement effects remain shared.
Other views: General · Tomo · Codex · OMP
- Tomo keeps Kaizen's pure Turn Engine reducer and effect driver.
- Codex transfers Codex's
Session -> Task -> Turn -> Stepownership. One Codex turn contains repeated model sampling and tool observations until that turn finishes. Input already queued when sampling finishes joins that turn before its next request. Input that remains after the turn settles starts a new owned turn. - OMP transfers OMP's outer input queue and inner agent loop. Tool continuation remains inside the inner loop. The outer loop polls late input only after the inner loop would otherwise yield. Each provider call and its ordered tool results belong to one inner turn.
Best-fit tasks
These recommendations follow each controller's sequencing model. They are not benchmark rankings. This release does not bundle valid comparative live benchmark results, so Tomo remains the safe default until reviewed evidence supports a different assignment.
| Pipeline | Best fit | Why it fits |
|---|---|---|
| Tomo | Direct answers, grounded retrieval, and short, predictable tool chains. | The pure reducer chooses one bounded effect at a time and preserves the baseline trace. |
| Codex | Code changes, artifact creation, and multi-step jobs where corrections should join active work. | Session, task, turn, and step ownership keeps repeated model and tool observations inside one task. Queued steering can join before the next model request. |
| OMP | Exploratory diagnosis, research, and tool-heavy work where the current tool loop should stay coherent before late input is polled. | The inner loop keeps provider and tool continuation together. The outer loop accepts late input at a safe yield boundary. |
The transferred behavior is based on openai/codex commit
0818b6550bacacd06b9f8b175789fab1389163e4 and can1357/oh-my-pi tag
v18.1.17, commit 3b3a6dc9bbd85102ce19d0b1c11bf6870915f6ec.
Kaizen does not import either upstream runtime. A shared protected-effect
runtime retains Kaizen's stronger server trust, finite tool concurrency,
mutation ambiguity, durable journal, and terminal-settlement contracts.
Adding a controller must not add provider, credential, journal, or tool
clients.
Register a controller
Controller registration is a source change, not a runtime API:
-
Add one
pipeline-<name>.tscontroller that implementsrun,abort,steer,interruptForSteering,inactivityTimedOut, andinspect. -
Add one descriptor to
PRODUCTION_PIPELINE_DESCRIPTORSinpipeline-registry.ts. Use a unique lowercase ID and positive integer version. -
Declare inclusive host-protocol and run-manifest compatibility ranges. The process rejects an incompatible or duplicate descriptor before it can run.
-
Run the shared conformance test:
cd sidecar/pinpm run buildNODE_PATH=$PWD/node_modules node --test dist/pipeline-contract.test.js
The registry copies and freezes every descriptor, builds its complete lookup once during process initialization, and exposes no registration or mutation method. Do not add dynamic discovery or a management endpoint.
A controller may own orchestration sequence, controller-local lifecycle state, steering and cancellation response, a bounded diagnostic projection, and one terminal intent. It may request model, tool, compaction, and checkpoint effects only through the shared host.
A controller must not import provider clients, callback clients, journal writers, public-event emitters, tool executors, credentials, or another pipeline variant. The architecture boundary test enforces these restrictions.
Increment the controller version when its observable sequencing or lifecycle contract changes. Keep the admitted ID and version immutable for a running turn. Update compatibility ranges only after the matching host protocol or run manifest contract passes conformance; never widen a range to bypass a failed check.
Register a reviewed lifecycle extension
Use a shared lifecycle extension only for bounded behavior at an existing host phase. The extension must be statically imported product code. Add its descriptor to the source-owned registry with an exact ID, positive version, unique order, compatible host-protocol range, and explicit hook allowlist. Manifest references are an unordered enabled set; they cannot select code or control hook order.
Activation is atomic. The host loads every enabled factory into temporary Pi state, inspects all handlers and registrations, and publishes one immutable descriptor-ordered chain only after the complete set passes. A failed load, duplicate order, incompatible version, unsupported registration, or undeclared hook leaves the prior chain unchanged.
Hooks receive bounded, credential-free projections. The restricted Pi API does not provide provider routes or headers, model or budget controls, identity, sessions, journals, UI, shell execution, direct messages, terminal publication, or tool executors. Context and provider changes must retain the accepted input and valid message history. Tool-call changes must pass the admitted schema. A tool result cannot hide a failure or reduce usage. Each phase commits only after all handlers complete within their deadlines and the final draft passes.
Shared extensions are trusted code in the sidecar process. Runtime projections reduce accidental authority, but they do not sandbox reviewed source or its dependencies. Static source and supply-chain review must reject ambient Node.js I/O, undeclared resource access, and direct effects. Member-authored Pi packages remain in disposable validation and the secretless package runner; they cannot register these hooks.
Controlled rollout
AGENT_PIPELINE_SELECTOR_ENABLED defaults to off. Chat's model and run settings
still show the public Tomo baseline as one disabled option in the leftmost
pipeline pane, but the profile returns no selectable options and protected
selection routes answer 404. Enabling the flag grants
nothing by itself: live Kaizen Admin authority and the deployment-bundled opaque
scope allowlist remain mandatory. The experiment manifest keeps Tomo at
100% weight and both derived pipelines at zero until reviewed evidence permits
another rollout stage.
Chat-promoted background jobs, including corpus jobs, pin the originating conversation's persisted pipeline assignment to their private Task thread. Directly created Tasks use their own assignment. Runtime admission still checks the pinned ID and version against the active manifest before model or tool work.
Controlled comparison
A valid comparison freezes the authenticated model deployment, runtime model, reasoning behavior, Plain-speak setting, member authority, agent capability ceiling, tool descriptors, approval policy, budgets, starting history, host protocol, extension set, and grader versions. All controllers in one case use the same frozen values.
The version-controlled corpus contains 100 content-safe fixture references across direct answers, retrieval, read tools, mutations, artifacts, streaming, compaction, recovery, delegation, and shared durability. Deterministic cases run once. Non-deterministic cases run at least three times and report pass@3, score distributions, p50 and p95 latency, usage, and recovery outcomes.
Live compatibility and protected-corpus UAT use the pinned Promptfoo package
under uat/promptfoo/. Promptfoo owns matrix expansion, repetition, serial
execution, assertions, and report output. Kaizen's custom provider owns only
authenticated API calls, durable terminal observation, exact preference
restoration, and conversation cleanup. It returns redacted measurements rather
than model answers or Thinking. The Make target disables Promptfoo sharing,
cache, telemetry, update checks, environment-template access, and local logs.
Deterministic Python tests remain the correct layer for schema, aggregation,
redaction, and controller-state invariants; they are not live UAT evidence.
An on-demand browser comparison covers three distinct public cases for each of direct answers, one-tool work, clarification across turns, and failed-tool recovery on one operator-selected healthy chat deployment. Every pipeline receives the same case input. Validity gates are separate from task quality, and durable tool events provide the recovery oracle. A task with any invalid cell is incomparable. A unique task-quality leader may be reported; a quality tie produces no recommendation. Latency and token measurements stay descriptive because this small lane cannot establish an efficiency winner. The command is not part of the release contract, full UAT, targeted UAT, or changed-area UAT.
A result is invalid or incomparable when any frozen input differs or attribution is incomplete. Such a result never enters ranking. Authorization, approval, audit, finite-budget, cancellation, replay, terminal, shadow-write, and extension-boundary checks are hard gates. A safety failure blocks promotion; quality, speed, or cost cannot compensate for it.
Promotion stages
Promotion is manual and evidence-based:
- Contract fixtures prove shared safety, baseline trace equivalence, complete result validation, and bounded no-model dispatch overhead.
- A non-mutating shadow stage compares read-only work without publishing member-visible events.
- Explicitly eligible administrators validate sticky assignments through the protected product surface.
- A named owner may approve a bounded weighted cohort for new conversations.
No stage changes an active run. Rollback changes only future assignments. Restricted reports may contain bounded identifiers, timings, usage, and terminal metadata. They never contain credentials, member identity, prompts, answers, raw Thinking, provider errors, or private tool payloads.
Why the rule is absolute
Without it, "every agent behaves the same way" becomes a convention, and conventions decay. With it, uniformity is a structural property:
- a guardrail cannot be quietly weaker in one corner of the product;
- an agent cannot be accidentally more privileged because its feature landed on a different code path;
- a fix to durability fixes it everywhere at once;
- a security review can reason about one pipeline instead of N agents.
Boundary tests walk the source to enforce it. A failing boundary test usually means a design rule was crossed, not that a test needs updating.
The seams
When you need agent-visible behavior to change, extend one of these rather than adding an agent-specific path.
1. The tool boundary
handle_tool_exec is where the runtime's request meets the backend's
authority. It re-checks the authority bundle, re-resolves permissions, applies
the capability ceiling, executes as the member, and returns a bounded
projection.
For delegated work, the bundle also carries only trusted in-memory Core authority: workload role/revision and activation epoch, run/claim, immutable authority deadline and fence, plus the exact protected-resource revision and request digest. The shared boundary reserves one Core effect, waits for exact approval where policy requires it, and sends the sender-constrained capability to the registered resource guard. The guard consumes it before protected code; an individual tool never implements this exchange.
Extend it for anything that must happen to every tool result. This is where Tool-result compression lives, after authorization and policy filtering are complete.
Failure handling is part of this seam, not a per-tool concern. Each invocation runs under the tool's declared bound and is cancelled if it overruns or if the caller disconnects, so an abandoned call releases its session, connections, and sandbox lease instead of running for nobody. Each invocation also records exactly one outcome metric. A tool that raises is settled by capability: a read tool's exception becomes an error result the model can recover from, while a write or sensitive tool still closes the turn because the runtime cannot know whether the change landed. Cancellation and turn-lifecycle breaches are never settled that way. Do not re-implement any of this inside an individual tool.
A denied call never runs, but a denial is not by itself the end of the turn. The first denial for a tool name in a turn reads back to the model as an error result carrying the reason: arguments that fail the advertised schema, a tool the captured step did not offer, or a policy refusal from the decision seam. The model can then correct the call or tell the member what could not be done. A repeat denial for the same tool name, or one beyond three distinct names in a turn, is not read back again, and a batch made only of such denials ends the turn, so a model that ignores the reason cannot loop. A member's own denial, an approval that expired or stopped being valid, an expired run, a decision seam that could not be reached, a reused call id or a call that could not enter the execution queue, a batch the turn has already stopped, and a required-tool gate's own block all stay terminal from the first occurrence. Recovery is decided for the whole batch: if the turn was cut short or stopped, a write in the batch is waiting on the member's approval, or the member denied any call in it, every denial in that batch ends the turn. When a denied change is the last thing the turn did, the member's answer reports the original reason, never the recovery instructions the model was given. Before this rule, a denial-only batch ended the turn on its first occurrence, and the member saw only whatever the model wrote before the call.
When a failed write is the last thing a turn did, the shared sidecar grounding
replaces the model's closing text with a product-authored account of the
failure. A call that declared the files it would create (through its
descriptor's run_artifact_targets_field, such as run_code's
artifact_targets) is reported as "I couldn't create the PowerPoint file
<name>" with a single-line cause. The cause comes from the payload's
structured error, then the first sandbox diagnostic, then the sandbox
worker's own dependency-install failure (only when its marker opens stderr
and no exception line follows), then the exception line that ends stderr. A
run_code whose dependency install failed carries a structured
dependency_install_failed error naming the package and whether the registry
was unreachable, so that is the cause the member reads; terminal escapes, control characters, and host paths are
removed and it is capped at 200 characters. Serialized tool output is never
quoted to the member — with no readable cause the text says only that the tool
reported an error. The host remembers which files earlier calls in the same turn
retained, so a failed refinement of a file that already exists is reported as a
change that was not applied, not as a file that was never made. A failed run_code payload also carries a
repair_instruction that names the exception and asks the model to fix and
rerun unless the member forbade retries; the payload, not the summary, is what
the model reads.
A batch that the turn's own inactivity budget cuts short is still settled whole.
Once that timeout has fired, every call not yet decided is settled as blocked
without asking Kaizen for a decision, and a call that was already granted is
never dispatched afterwards: a decision or dispatch sent on the spent signal
could only fail, and would be misread as another approval wait. The settlement
writes that remain — each call's journal request and result, the tool_result
extension hook, and the grounded answer telling the member that a parked change
still needs approval — run on one fresh 15-second deadline per turn instead of
the aborted signal. Only the inactivity timeout earns that deadline. A member
Stop ends the turn on the spent signal, so nothing it interrupted is settled
afterwards. A failed journal append fails only itself; the session's append
chain stays usable for every write after it.
Persistent sandbox code re-enters tools through this same seam. The worker
controller holds the run capability, and a kernel names only a read tool and
arguments. /internal/sandbox/tool-exec resolves the bearer to the active run,
refuses writes and recursive code execution, and then invokes the ordinary
snapshot- and descriptor-bound execution path. Do not place a platform token or
tool implementation inside a kernel.
read_workspace and edit_workspace share the existing sandbox service,
composite workspace lease, and WriteToolGuard. Reads retain normalized full
text and seen-line provenance in trusted owner/workspace-scoped storage outside
the workspace tar. The model receives only [PATH#TAG] and numbered lines.
Edits send the OMP-compatible patch grammar to a pinned MIT helper that operates
on bounded in-memory file content. Python remains the filesystem authority:
every parsed or recovered path is revalidated, all sections preflight, and the
resulting write/delete plan commits with rollback before trusted snapshot and
named-register state advances. The helper receives no identity, bearer, object
store key, or direct filesystem grant.
Prompt capability attachments join immediately before this boundary. The
Prompt parser recognizes only structural Tool and Connector tokens; the
runner-scoped resolver validates them against the effective step Agent and the
current member/deployment catalog before compiling. The executor then derives
an execution-only AgentSpec for that step. Do not implement this as an Agent
special case or persist the overlay back to Agent Studio.
Package attachments carry exact active package UUID and Tool-name pairs on that
ephemeral spec. packages/runtime_tools.py reloads the active pin, repeats the
capability clamp and collision checks, and binds it to the ordinary dispatcher.
Platform and Connector attachments extend their existing request-scoped
selection fields. All paths still converge on the shared callback, approval,
collaboration, audit, and sandbox machinery.
Tool failures may carry a trusted repair directive beside the model-visible
payload. The tool owns domain facts: whether retry is possible, whether the
same tool is required, which arguments stay locked, and whether substitution is
safe. The Turn Engine owns enforcement and the bounded response opportunity.
A non-retryable, non-substitutable failure lets the model explain the exact
reason once; any later tool attempt is blocked and terminates through the
existing mutation-failure projection. A failure the model may substitute for
is refused, not terminated, and a refusal can be resent indefinitely — so the
Turn Engine host also fails the turn once one tool has failed three times with
byte-identical arguments (repeated_tool_failure_exhausted, surfaced to the
member as repeated_tool_failure). The count is per input: a changed argument
set starts its own, and a success on that input clears it. Do not encode any
of this as prompt wording or browser-specific retry logic. The shared host also
stops a turn after four executed tool_search calls without a successful
non-discovery tool between them, even when each search used different arguments.
This prevents an unproductive discovery loop from consuming the entire model
step budget. Both dispositions produce a failed terminal and a member-visible
non-delivery statement.
Every failed result also carries a closed-vocabulary error_class
(invalid_argument, not_found, forbidden, rate_limited, timeout,
provider_error, tool_error), derived by the same classifier the repair
directives use. It rides the callback result onto the tool end frame and into
the journal's tool object on a failed step. A caller-domain failure is
always tool_error: its text is the caller's own output and is never read.
An error result also declares whose failure it was. ToolResult.failure_domain
is tool by default, meaning the tool could not do its job — that is evidence
about the tool's health, and the runtime may stop the model calling it once it
has failed enough times across different arguments. Set it to caller when the
tool ran correctly and is relaying an error produced by its input, which is what
run_code does for a non-zero exit or a wall-time stop; both arm the repeat
guard, so a non-terminating program resent unchanged is refused rather than
paying the wall-time limit again. A caller-domain failure
stays fully model-visible and repairable, but it is excluded from the tool's
health tally and from every text-matching classification rule, because the text
being matched is the caller's own output rather than the tool's report. The
sole rule that still applies is the identical-repeat block.
A write that already committed is not run twice — for the tools that ask to be
treated that way. Suppression is opt-in per tool via suppress_identical_repeat
(default False), because capability tier does not imply idempotence by
arguments: run_code must be able to re-run the same program after the
workspace moved, and act_on_browser_page must be able to repeat a click the
member confirmed. The tier is still required on top of the declaration, so a
read can never be suppressed. create_artifact opts in; the version-bound
update_artifact and edit_artifact do not, because a stale repeat already
fails on its own expected-version check. The execution seam keeps a per-run
ledger of every successful opted-in call, keyed by tool name and the exact
argument digest, and answers an identical repeat with the first call's projected
payload instead of invoking the tool. The replay reports
outcome="success" — the state the model asked for exists — and carries
repeat_suppressed plus a non-retryable, non-substitutable repair directive so
the model reads what happened rather than believing it wrote something new. It
carries no presentation, because that out-of-band member surface was already
materialized by the first call. Suppression is deliberately narrow: read tools
are untouched, the staged-artifact protocol tools (begin_artifact,
append_artifact, finalize_artifact, discard_artifact) are exempt by name
because repetition is their normal shape, only the literal outcome="success"
counts as a commit, a staged confirmation is not a commit and is never recorded,
a failed write leaves nothing behind and may be retried unchanged, the ledger is
bounded and evicts oldest-first, and it dies with the run. Implement this once here; never in
an individual tool.
Suppression is enforced at two layers against one ledger, because a streamed
create opens its durable row before the execution seam ever sees the call.
ArtifactGenerationCheckpoints.observe materializes a member_artifact row via
checkpoint_generation as soon as the streamed argument JSON completes, keyed by
(owner, turn_id, call_id) — so a repeat refused only at execution would leave
the row behind, and leave it unfinished. The checkpoint layer therefore asks the
same ledger, read-only, at the first moment the identity is computable, and
declines to open a row for a create that already committed. The execution seam
still suppresses as well: it covers the non-streamed path and is what reports
the repeat back to the model. The read-only lookup counts nothing, so only the
execution seam claims an attempt and the two layers cannot disagree.
The ledger also records the step_snapshot_id that committed, and a later call
carrying that same step identity is treated as a sibling in one tool batch
rather than a repeat. Identical calls the model emitted together are a
deliberate parallel request; the pathology is a model that reads a success and
decides again, which is a new step every time. A call with no step identity
cannot prove it was batched and is treated as a later step, so a runtime that
supplies none is still bounded. Each turned-away repeat increments tool_run
with outcome="repeat_suppressed", because a write that quietly does not happen
needs to be as visible to an operator as one that does.
A refused repeat is returned as an error result, never raised. Raising answers
the sidecar with a non-2xx, which its tool client throws; the throw escapes
dispatch_tool_batch, classifies as ambiguous_write, and fails the whole turn
closed. Reserve that for authorization and policy decisions that genuinely must
fail closed.
Because a refusal never reaches the tool, nothing about it bounds a model that simply resends. Refusals are counted per tool and argument set, and past a few the directive escalates from "change the input" to "stop calling this and answer with what you have". Escalating does not stop the calls — nothing here does — but a refused call still spends one of the run's tool rounds, so without the escalation a resend loop reaches the member as the same dead turn the refusal exists to prevent.
2. The system-prompt composition chain
Instructions that every agent should receive join one composition chain, in order: grounding, temporal awareness, member identity, then anything added afterwards.
Member identity states the signed-in member's display name once per run, so every agent addresses the person by name instead of narrating "the user". The name comes only from the forwarded platform envelope, is flattened to one bounded line of plain text, and is labeled as a label — it grants no identity, role, or permission, and an envelope with no usable name composes no block at all. Durable harness sessions are actor-scoped, so a shared room never carries one participant's name into another participant's session.
Prompt trim is the seam's counterpart on the member's own message: the raw message stays the run's logical message, and only the model-visible copy composed beside document, artifact, and sub-agent context is trimmed. Add a member-message transformation there, not in a persona or an agent adapter.
Plain-speak joins here. So does the server-authored pipeline settings projection — appended after member-authored resource context, so resource text cannot override it. A router-issued specialist grant also joins here and is reinforced by the current turn's required-tool policy. It is not copied into the member's message. A resumed durable session can retain its original system prefix, so the Turn Engine's manifest-bound interlock and bounded retry remain the execution authority rather than relying on prompt wording alone.
Extend this for instructions that must apply uniformly. Never inject them into a persona.
3. The guardrail chain
Budgets, citation discipline, redaction, and loop control run at defined points
in the turn. Add a guardrail in guardrails/builtins/.
Guardrails are backend-side by construction. A guardrail inside the runtime would be advice to a system designed to be flexible.
4. The model gateway
Routing, policy, budgets, effort, retries, and normalization. Extend here for anything about how model calls are made.
The platform proxy receives one server-resolved connection value containing the target, model authority, request-scoped authorization, TLS policy, and timeout. Streaming lifecycle options are separate. Provider error frames are projected through the shared safe SSE adapter before they reach any runtime and finalize the shared LLM audit as failures even when their HTTP response was 200.
The direct gateway client that server-side work uses — manual chat compaction
and orchestration planning — holds the same property from the other side. Its
chokepoint records one audit row for every failure the provider raises,
including a status it does not map to a named condition, so an unexplained
server-side model call always leaves an outcome and an error_class behind.
Recording the failure never changes it: a caller that has to tell a malformed
request apart from a provider refusal still receives the original exception.
Never let a component talk to a provider directly.
A delegated model call enters the same seam. Core's registered model guard
attributes it to both the consenting member and acting workload, atomically
reserves the member charge and workload concurrency/spend ceilings, consumes
the exact model effect before provider I/O, and settles from trusted usage.
Production Tasks select Core's closed /api/serving/delegated-models proxy
from the authenticated deployment ID and public origin. Its proof binds the
exact proxy URL; the existing engine runtime route remains the member path.
The Core proxy adopts that engine behind the shared guard and preserves the
same correlation and cancellation fence without acquiring its lifecycle.
The Pi sidecar follows the same rule. Kaizen's durable Turn Engine is the
served path; its low-level Pi Agent is only the rollback-compatible execution
path. Both call models through the run-bound Kaizen proxy, use the same tool
decision and execution callbacks, and preserve the same terminal
normalization. Pi's higher-level AgentHarness is not an architectural seam,
so no Kaizen contract depends on it.
Pi 0.85 replaced its previous V4 session facade with a context-aware repository protocol. Kaizen keeps its immutable, backend-owned V4 entry log behind a narrow local facade. This preserves journal authority and does not create a second persistence protocol inside the sidecar.
Conditional response correction belongs to the Turn Engine. Rule-enabled samples buffer model-visible events until accepted; a match yields a reducer transition that commits no model item, appends a fixed server-authored correction, captures a new snapshot, and resamples within a manifest-bound attempt limit. Adding a correction to one persona is prohibited.
5. The repository layer
Authorization predicates live in SQL. Extend the repository query — never fetch candidates and filter them in application code.
Generated-file reconciliation resolves the room UUID, the stored web channel, and the channel's public conversation key into one server-authored artifact scope. The same scope admits task-specific orchestration branches and rejects lookalike prefixes. Invitation-time pinning and post-run linking both use this shared seam, so an adapter's internal session-key representation cannot orphan a file when the chat is shared later.
Follow-up use of a retained binary stays at this shared seam too. Model-context projection emits bounded file metadata, while the sandbox projector loads the same currently authorized reference and sends its digest-verified bytes through the read-only worker envelope. All runtime adapters inherit that split; no agent or Office-file format owns a continuation path.
6. The tool registry
A closed set with a cardinality trip-wire. Adding a tool is a deliberate expansion of the loop's trust surface. See Building a tool or package.
7. The audit funnel
One append-only funnel with bounded reason codes. Individual agents do not implement their own audit path — which is why enabling a new agent cannot create a gap in the record.
8. Terminal answer normalization
Every runtime adapter passes its final member-safe answer and durable artifact projection through one response normalizer before persistence and emission. This is where native-file delivery claims are checked. A matching durable file preserves the answer; an unsupported success claim is corrected; a clarification or honest failure remains intact.
Output intent is bound to the action phrase. A source format after from,
using, or another source-material boundary is not treated as a second output;
for example, “create a PDF from the shared PowerPoint” requires the durable PDF
and treats the PowerPoint as input. This keeps cross-format conversions from
being rewritten into false failures after successful persistence.
Keep this contract harness-neutral. Local and remote adapters may assemble their artifact projections differently, but neither owns a weaker version of the member-facing delivery rule.
The collaboration runtime stages the normalized answer rather than publishing it immediately. The answer and exactly one terminal event are appended and committed together; only then are durable journal heads announced. A runtime that ends after yielding an answer is salvaged as a usable answer, not surfaced as a contradictory failed turn.
9. Streaming and generative file presentation
Model-message boundaries are explicit runtime data. The first visible delta of
a later assistant message carries segment_start; clients preserve exact token
adjacency within a message and add a paragraph only at that typed boundary.
They must not infer spacing from punctuation or reset a local cursor after a
tool call.
Provider deltas remain off the persistence critical path. Visible model activity continues across structural stream events, Thinking and answer bytes relay immediately, and generated artifact source advances through bounded checkpoints before the exact tool-execution boundary forces its final snapshot. Reasoning checkpoints drain through a bounded ordered journal writer; the next provider delta does not wait for PostgreSQL, while every later non-reasoning or terminal projection flushes that writer as an ordering barrier. The sidecar's bounded reconnect relay appends steady-state deltas in constant time and only rebuilds its replay window when the byte cap requires compaction. Room-local publication lanes preserve order without awaiting one Valkey round trip per provider fragment; a bounded process-wide command pool and one multiplexed subscription keep connection growth independent of browser count. The durable journal-head announcement is the lane's completion barrier, so lower-latency delivery does not weaken replay or terminal ordering. Live token estimates accumulate admitted input across all provider calls in the turn, while the context meter retains the latest request-local prompt.
Provider output is reduced as typed message, Thinking, and tool-call deltas, including interleaved content indexes. Completed assistant items and every ordered tool result are added to the next model context before another model step. A length-truncated response rejects its entire tool batch before policy or execution, then exposes those error results to a same-effort follow-up. One structurally empty length stop may receive a typed semantic continuation; it is bounded to one attempt and is distinct from transport retry.
A transport-successful response carrying neither text nor a tool call is retried as a pre-commit provider failure, per request payload, up to the configured provider retry count. Visible Thinking is progress, not an answer or tool-call commit: a reasoning-only empty response or later transport failure remains eligible for that bounded retry. Thinking already shown stays in the member's trace. With the turn engine enabled, incomplete attempts are never appended to provider conversation history or included in retry requests. The legacy compatibility rollback does not provide this model-history isolation. A retried model may produce new Thinking, which is also shown. Answer text and tool-call commits still close the retry window.
The per-payload retry budget is latched rather than cleared when it runs out: the first exhaustion still hands the empty response to the reducer, but a repeat on the same payload ends the turn instead of re-arming the budget. With the turn engine enabled, content-free assistant responses are omitted from model-history commits, so reducer recovery cannot persist incomplete Thinking or blank assistant items; the terminal response's usage accounting and member-visible progress remain intact. This commit guard does not change accounting for earlier retry attempts.
That latch only fires when the same request recurs, so a second, digest-blind
bound sits above it: a turn ends once it has seen twice the per-payload budget's
worth of consecutive responses carrying neither text nor a tool call, however
much the payload drifts between them. Without it a run whose payload changes each
cycle while the responses stay empty falls back to the model-step budget, which
empty-response retries are exempt from — a bound measured in tens of provider
calls. Both rules are about progress rather than liveness: the inactivity
watchdog is re-armed by any provider delta and so cannot bound a run that is busy
producing nothing. Any response carrying content resets both counters, so a
productive turn can never approach either. A turn stopped
this way fails with provider_empty_response_exhausted rather than the generic
engine error, so the member is told the request keeps coming back empty instead
of being invited to wait.
When the agent has the staged writer, an exact new-artifact request prefers the
closed begin/append/finalize/discard protocol to the ordinary single-call
writer. The preference is a first-step ranking, not a required tool: the agent's
other tools that fit in the bounded direct set stay directly advertised, and
tool discovery reaches the rest. When the delivery check reads such a request as
asking for a native PDF, Word, PowerPoint, or Excel file and the agent also has
run_code, the request prefers run_code instead. Either way, the request's
per-agent discovery allowance is raised only to the existing shared turn-step
ceiling, never beyond it, so a specialist with a small search allowance can
still finish the transaction. Each append commits an ordered digest receipt in a
short transaction, while the private execution fence prevents an expired worker
from continuing after recovery. The final tool result is the only transition
that makes the artifact eligible for terminal answer projection.
Any file-producing tool can declare a descriptor field containing its intended
relative output filenames. Tool start projects those names as presentation-only
building references; tool completion removes the matching placeholders and
projects the retained conversation references. The generic runtime owns this
mapping. PDF, PowerPoint, or a particular agent must not add a parallel storage
or streaming path.
The transcript projector also preserves resource type. Only an
uploaded_document selected on a member message becomes a document chip or
Workspace import. A run_artifact remains the originating answer's authoritative
output and is opened through its owner or exact-room content route; its room
reference ID must never be reinterpreted as an upload ID. This keeps live,
reload, owner, and collaborator views on the same retained-file contract.
Document scope retains every authorized conversation attachment for retrieval
and sandbox tools. If the member attaches a new image on a later turn, the
inline image parts on that turn contain the new attachment rather than
reintroducing older images as if they were newly sent. A follow-up without a
new image can still use the previously attached image.
Model-bound tool results and member-facing tool detail are deliberately different projections. The former includes bounded stdout, stderr, diagnostics, and artifact metadata needed for repair. The latter contains only sanitized status and durable references. Adding a tool must preserve this split.
10. The delegated-workload gate
Readiness and current-state authority are cross-agent concerns. One shared gate covers member capability projection, chat task tools, scheduler, queue, dispatcher, worker, model gateway, credential broker, and resource effects. It requires the local rollout plus the complete fresh Core v1 graph; dependency loss closes every new boundary together while retained history remains readable. No individual agent may infer readiness or add a local fallback.
11. Goals and bounded background work
Agent Goals are Task definition metadata, not a second orchestration system.
The shared inspect_tasks and manage_tasks tools normalize the outcome,
constraints, and verification checks, then bind their digest to the exact Task,
accepted input, and Agent version. One-shot background work may carry a Goal;
create_loop additionally requires explicit time and occurrence bounds.
Each occurrence still enters the ordinary Task runtime, private Task thread,
progress-event stream, approval path, budgets, and side-effect fence. A Goal
terminal marker can only report continue, complete, needs-input, or blocked; it
cannot grant an effect. Needs-input releases the execution lease and resumes
the same run only after answer_goal_input binds the exact accepted member
input. steer_goal likewise loads accepted member text server-side and targets
only the owner-bound live orchestration. Models never supply steering or answer
text as tool arguments.
conversation_input_execution.py owns accepted-input publication and shared
runner dispatch. task_chain_execution.py runs a pinned prompt chain through
that same path; task_execution.py maps Task targets and applies an exact
approved call before resuming. The successful tool summary becomes the waiting
step's durable output. Later steps must execute against that receipt, not against
their pre-approval outputs.
Loops force missed-occurrence skip, zero backfill, and no overlap. The
dispatcher disables the schedule at the first of Goal completion, ends_at,
or max_occurrences. Do not implement watches with a detached process, an idle
worker, or an Agent-specific polling path.
Worked examples
"Web Researcher should search differently." → Source selection and tool grants on the agent definition. Not a new retrieval path.
"Task runs need governed external effects." → One Core reservation and resource-guard consumption path carried through the shared gateway, tool registry, guardrails, budgets, sandbox, journal, and terminal projection. Not a task-only agent loop.
"Delegation should be bounded." → invoke_agent is a tool, so it inherits
the tool boundary, capability ceilings, budgets, and audit. Not an
orchestration-specific authority model.
"A routed specialist must actually run." → The shared prompt composer,
tool exposure policy, and Turn Engine bind the exact one-turn invoke_agent
grant. Not a special case in Default or Web Researcher.
"Compression should shorten tool results." → One authorized seam at the tool boundary, plus one seam in the prompt chain. Not per-agent implementations.
"Agents should answer questions about their own settings." → A server-authored projection appended to the shared prompt chain, from authoritative runtime values. Not per-agent knowledge.
"A generated file is ready for download." → The shared terminal answer normalizer verifies a matching durable PDF or Office artifact for every runtime adapter. In a collaborative run, the metadata-only tool result and room journal link use that same retained reference. Not an adapter-specific prompt or persona instruction.
The pattern is consistent: find the seam, extend it once, and let every agent inherit it.
When you genuinely need agent-specific behavior
Ask which of the four permitted categories it falls into:
| Category | Example |
|---|---|
| Persona | Tone, scope, what to do when evidence is thin |
| Capability | Which tools this agent may use |
| Tool grant | Whether run_code is offered to it |
| Source selection | Which collections or connectors it retrieves over |
If it does not fit one of those, it is cross-agent behavior wearing a disguise, and it belongs in the pipeline.
The escape hatch that is not one
A recurring temptation: "this agent is special, just this once." It never is — the second special agent arrives within a release, and the third stops inheriting the fix that the first two got.
If a genuine exception exists, it needs a spec change and an updated boundary test, not a conditional branch.