Agent runtime reference
Understand the agent loop and its component boundaries.
Manual for v0.9.5. Stable release: v0.9.5.
Responsibilities#
octet-agent owns one mutable session, reconstructs canonical provider context, opens and consumes provider streams, executes registered tools, persists complete semantic records, and emits frontend-neutral events. Provider wire formats stay in octet-ai; terminal policy stays in octet-coding-agent.
Commit and cancellation invariants#
- Streaming deltas are provisional and never enter the session. Opt-in provider lifecycle feedback is likewise forwarded only as transient
AgentEventtelemetry; it does not mutate context, assembled assistant content, durable telemetry, or session records. - A complete assistant message is persisted before any emitted tool is executed.
- Each tool result is persisted immediately after its execution outcome is committed.
- A completed call marked schema-invalid by
octet-ai's request snapshot receives a static bounded paired error; it never reaches effect classification, hooks, or the tool implementation. - Crash replay requires both
ReplaySafety::Safeand an exact host classification ofPureorWorkspaceRead. Every other unresolved call becomes an indeterminate error and is not executed. - One level-triggered abort signal is selected against provider open/body consumption, retries, tools, and autonomous compaction. Cancellation wins same-poll races. A cancelled compaction persists neither usage nor summary.
- Every driven run emits exactly one
RunFinishedand one durable checkpoint. - Optional telemetry is an observer outside the session ledger. When installed, it receives an opaque run-start hook and coarse request/tool/compaction boundaries; it records hashes and bounded measurements, never raw prompts, arguments, results, or provider payloads.
Agent::prompt_without_toolsstarts with a sticky tool-free policy.RunControl::finish_nowpersists its input at the next safe turn boundary and makes that policy sticky for the remainder of the run. Subsequent provider requests contain no tool schemas and setToolChoice::None; calls emitted by an already-open provider request are paired with synthetic errors rather than executed. Effects already admitted at the time of the control settle under the ordinary cancellation and commit rules.
Streamed tool-call start/delta/end events describe provisional generation; they never execute a tool. Automatic speculative Bash execution is removed: shell command spelling, broker admission, and later argument equality cannot establish read-only effects, independence from earlier mutations, or durable ordering. Cancellation cannot undo an early observation or effect. Mixed/dependent tool batches retain emitted execution order; eligible independent observations may still overlap through the effect-checked post-persistence parallel path. This deliberately gives up unsafe overlap rather than promising equivalent latency. See the performance contract.
Qualified Responses controls#
Model and endpoint ResponsesFeatures must both authorize each feature; names,
Lite, and WebSocket availability alone grant nothing. Ordinary routes retain
synchronous tools and queued steering.
- Async tools: only complete, durably committed calls from a wholly eligible batch enter the run-owned registry (at most one read wave). Advertised parallel tools are still checked against exact arguments, the effect broker, and hooks. Independent observations may overlap the next response. Ready results are settled durably in original call order before its immutable request snapshot; unfinished jobs do not delay that snapshot and settle after the response that did not consume them. Sync, effectful, mixed, and nonparallel batches remain barriers. Hard cumulative ceilings serialize tool accounting; pending jobs disable compaction. Driven terminals cancel and settle jobs; dropping a run aborts task handles. Unresolved async calls after a crash receive indeterminate paired errors, never automatic redispatch, even for replay-safe observations.
- Reasoning: host controls coalesce at response boundaries. Typed
ResponsesReasoningrecords pin the request baseline and retain chronological updates separately from effective host selection. Provider-authored opaque configuration updates are rejected. Resume restores effective selection; explicit idle overrides useAgent::set_reasoning. Prewarming uses the same replay-dependent reasoning and advertised async tool schemas. Successful local compaction rebases the retained prefix; failed compaction leaves the pin unchanged. Without complete same-route output sidecars, normal Responses requests replay canonical conversation with effective reasoning as their request baseline instead of emitting incomplete chronological updates; native mode still requires complete opaque replay. Unsupported native compaction never strips updates (autonomous qualified runs use local compaction). Qualified Ultra/V2 transitions use a host baseline reset, not an ordinary configuration update: prior effort updates are superseded in replay while canonical messages and opaque outputs remain intact. This may invalidate the reasoning cache prefix, but does not require a new session. Entering Ultra requires an installed child-session observation runtime. Idle setters apply immediately; active controls apply at the next safe response boundary (after any owned native successor). The existing delegation manager stays installed at lower efforts, new workers inherit the effective selection, and existing workers retain their pinned model and reasoning. - Native steering: qualified WebSocket runs without hard ceilings prepare a
local receipt, persist intent, then dispatch. Each completed response retains
independent usage. Applied input follows the completed prefix, atomically
linked by
EntryMetadata.native_steeringto its operation/local ID, before the successor. Required tool results return on the same socket without repeating steering input. The host update bridge holds at most 128 events and fails closed on overflow. Controls queued between native segments wait until the owned successor settles; FinishNow's tool prohibition remains sticky.
A native intent remains usage-uncertain until its accounted successor has a
ResponsesSteering.completed settlement marker. Thus abrupt drop, disconnect,
or a crash between intent/application/user materialization/settlement cannot
silently lose submission identity or imply zero billing. Resume refuses a new
prompt on any unresolved operation rather than guessing from text or replaying
input. A crash after accounting but before settlement is conservatively uncertain;
start a new session instead of automatic reconciliation. Explicit failure usage
uncertainty remains sticky. misalignment_policy_violation is permanent: no
inference/workflow retry, with pending jobs cancelled/settled and completed
accounting/effects retained.
In-process provider recovery#
Recovery surrounds one unfinished provider request, never Agent::prompt or
previously committed tool effects. The initial qualified contract is an
OpenAiResponses model with the host-selected
ResponsesRuntimeProfile::Codex. Selecting that profile now also asserts that
this route performs generation with locally dispatched function tools; an
embedding host must not assign it to an arbitrary compatible endpoint merely
for wire formatting. Model names, provider labels, Responses Lite, event counts,
and invisible output cannot grant recovery authority.
Canonical local function tools qualify, including mutations that remain provisional. Opaque input is restricted to message, reasoning, function-call, function-result, and compaction items. Unknown/provider-hosted item types, server-side continuation IDs, explicit storage, and arbitrary context management fail closed. The codec/transport owns wire facts and socket retirement; the agent alone owns autonomous replacement policy.
Qualified unknown response.failed and response.incomplete reasons (before
assistant persistence), transient provider server_error/internal/unavailable
errors,
body interruptions, codec-annotated provider-frame JSON failures, HTTP 408, and
terminal EOF errors permit at most eleven stream replacements per logical
turn. Qualified Connect/ResponseHeaders transport failures (including timeouts)
and all HTTP 5xx with permanent-code vetoes share a separate
29-replacement opening/admission allowance. Both share a 35-replacement cumulative cap; neither transport
fallback nor pre-send network waiting resets these counters. Thus stream-only
failures permit twelve attempts, HTTP-admission-only failures thirty, and mixed
failures at most thirty-six. This covers the pinned local Codex 3d3df0a0
default count envelope: five WS stream retries followed by HTTP fallback, with
five physical HTTP sends (request_max_retries=4, inclusive loop) for each of six
outer HTTP attempts. Octet's flattened host-owned policy is not a reproduction
of Codex's nested scheduling or a promise of identical outcomes for every error
sequence. No hidden HTTP replay is added to octet-ai.
Admission retries do not consume the independent stream allowance: twenty HTTP
503 failures followed by an EOF can still recover. The emitted cumulative
max_attempts ceiling reflects the remaining allowance for the current failure
class and never exceeds 35; it can change when failure classes change. Arbitrary
local-request JSON, UTF-8, schema validation, resource, pricing, auth, and permanent
request failures do not qualify. Only codec-owned StreamFailure wrapping
Decode::Json or Decode::InvalidUtf8 grants provider-stream parse recovery;
raw decode errors do not. Typed ResponsesFailed provenance authorizes unknown
qualified terminal errors, not arbitrary Provider errors. Explicit permanent
provider codes outrank retry- or context-sounding messages, including policy,
auth, quota, server_is_overloaded, and slow_down denials. The counter survives re-preparation and WebSocket-to-HTTP fallback;
it resets only on a completed assistant response. The failed stream is dropped
before hooks, compaction, waiting, or opening another attempt. Remote cancellation
and exactly-once provider charges are not promised.
A separately classified, definitely pre-send AiError::NetworkUnavailable
or credential AuthError::Unavailable on the qualified route permits sustained
cancellable waiting, without consuming the finite inference budget. Generic
Connect errors (including unknown DNS/TLS/certificate failures) do not authorize
unbounded waiting. Backoff starts around five seconds, doubles with run-specific
jitter, and is capped at sixty seconds. Valid provider Retry-After is never
shortened. set_max_network_wait(Some(duration)) bounds elapsed outage recovery
for embedding hosts; it does not extend external job/child deadlines. Ordinary
cancellation remains level-triggered and wins same-poll races.
ProviderRetry invalidates all provisional text, reasoning, media and tool
presentation owned by the failed attempt. ProviderWaitingForNetwork represents
unbounded-count pre-send waiting without a fictional retry denominator. Both
leave the run, session, accepted controls, and children alive. Controls are
received during main-request backoff; replacement re-enters the existing safe
preparation boundary so steering delivery, sticky FinishNow and schema/tool
implementation snapshots remain coherent. Follow-ups retain their ordinary
queue semantics. Retry hooks cannot expand eligibility or budgets;
ProviderRetryContext.max_attempts is None for pre-send waiting, serialized as
JSON null, and its kind is waiting_for_network; qualified inference replacement
uses interrupted_inference. ProviderRetryContext.operation is the typed
auxiliary operation, or None (JSON null) for a main assistant request. Hooks
receive the owning run/resource identity and cannot shorten host/provider delay.
Post-generation rate_limit_exceeded is retryable on the qualified route, with
Codex-style "try again in" seconds/milliseconds hints honored; permanent quota,
usage-not-included and request codes remain excluded.
Autonomous local compaction, native compaction, and terminal-gate calls share the
same classification/budget guardrails through an auxiliary helper. Their immutable
session snapshot remains exclusively borrowed until settlement. Recovery emits
ProviderOperationRetry with its actual typed operation and route diagnostic,
never a main-answer rollback event. Only successful completed auxiliary calls
reach existing usage/checkpoint commits. Auxiliary drives drain accepted controls
while the immutable request is pending, then deliver at the safe settlement
boundary. A successful terminal gate cannot return ahead of queued input;
FinishNow remains sticky. A serialized final admission/drain boundary includes
controls submitted in the gate's final poll or while TurnFinished is yielded:
successful submissions continue the run; submissions after closure return
RunEnded. Natural completion uses the same atomic drain/close admission
boundary without holding its mutex across a yielded event. Cancellation has
priority over control/event traffic.
Manual native-compaction calls also use bounded recovery and retry hooks.
Local summary and gate calls expose opening separately from body collection:
outage deadlines cover opening/backoff, never a healthy reconnected response
body. Outage deadlines also preempt pending retry hooks, even when shorter than
the hook's advisory timeout. Backoff longer than the remaining allowance waits
until the actual outage deadline rather than expiring early or shortening
Retry-After. Native compaction uses the AI client's explicit
HTTP-header opening boundary for both manual and autonomous calls: credential
and header opening remain outage-bounded, while a healthy reconnected body uses
its provider body deadlines.
If the outage deadline interrupts opening after possible dispatch, main and
auxiliary paths emit and persist usage uncertainty before returning
NetworkWaitLimit { usage_unknown: true, .. }. Credential-only waiting and
pre-send backoff do not invent provider usage. Dispatch is conservatively
tracked at the AI boundary, including opaque host transports; missing headers
never establish nonacceptance. Resumed hard ceilings respect the durable marker.
Failed-attempt usage is unknown, not zero. Every observed accepted failure
is durably recorded before replacement, independently of successful usage and
session branches. HTTP 5xx and 408 responses also carry unknown usage: a gateway
failure can hide accepted upstream work. Replay authority is not zero-billing
evidence. A planning input estimate, prior observed usage, or models.dev context
window does not establish an upper bound on provider-tokenized input. Current
routes expose output caps but no sound enforced input admission bound. Finite
session total-token or cost ceilings therefore fail closed before dispatch
with InputLimitUnavailable (or OutputLimitUnavailable when even an output cap
is unavailable). Uncapped execution is unchanged. Hard-ceiling generation is an
explicitly unsupported route dimension, not a fully qualified budgeting feature.
Each new ambiguous attempt retains unbounded unknown usage; successful later
turns cannot make that earlier exposure finite. Historical bounded ledger records
retain their accounting arithmetic but do not authorize a new finite-capped
request. Child exposure is mirrored by agent ID,
recording only the increase in each child's bound before its known usage subtotal.
ProviderUsageUncertain is emitted on the first observed uncertainty and at
run start for an already-uncertain session; successful turns do not clear it.
Subsequent cost/token numbers are known subtotals, not complete totals.
Context-size estimates still use successful provider usage, never invented
failed-attempt tokens.
Accepted OAuth rotation with indeterminate completion is not autonomously
replayed merely to refresh credentials. Generic Connect/DNS/TLS failures have
only the finite opening allowance, never indefinite outage waiting. Codex's
Responses SSE reader (codex-api/src/sse/responses.rs) skips frames that fail to
deserialize and maps a malformed completed response to a retryable stream error.
Octet replaces the unfinished qualified request within its finite stream budget,
recording unknown usage, rather than continuing an incompletely decoded stream.
Post-parsing field/resource/state-machine validation remains fail-closed.
Deterministic tests include interrupted reasoning/text/provisional tools, durable mutation ordering, cancellation, exhaustion, hard budgets, permanent/unqualified routes, steering/FinishNow and operation-scoped terminal-gate recovery. A virtual clock exercises over fourteen days of pre-send waiting with a constant session ledger and no finite inference-budget consumption. A loopback WebSocket-to-HTTP fixture combines an initial interruption, 20,200 credential-unavailable waits, and recovery on the twelfth accepted request or finite exhaustion. Auxiliary callsite tests hold more than eight controls, test sticky FinishNow, verify healthy bodies beyond outage deadlines, and exercise hook Stop/bounded delay. Manual and autonomous native fixtures separately hold credential opening, HTTP headers, and healthy response bodies. HTTP 520 sequences/exhaustion and unknown failed/incomplete terminal partial generations retain the same finite envelopes. Four terminal EOFs followed by success also retain durable unknown usage. This is not a live-provider interruption, real-terminal qualification, weeks-long wall-clock soak, or a claim of complete unattended-runtime qualification.
Interrupted-attempt progress#
AgentEvent::RecoveredOutput { channel, text } carries a durable partial text
or reasoning prefix from a previous interrupted attempt. It is historical
progress, not a current OutputDelta, completed assistant response, provider
replay input, or usage/accounting contribution. Consumers may display it in a
separate recovery view; they must not append it to the current answer. Partial
tool calls, media, and provider metadata are not recovered as output events.
Effect admission boundary#
Every registered Tool classifies the exact parsed call through host-owned code. The model cannot provide or lower this classification, and the trait default is Unknown. Unknown effects fail closed under every policy, including UnsafeHost. Before any hook or tool implementation receives a call, the agent constructs a bounded canonical EffectIntent over the principal, run, tool-catalog generation, provider call ID, tool name, effect, arguments, and policy version.
The default Controlled broker policy admits Pure and WorkspaceRead, requests exact one-shot confirmation for workspace mutation and every bash or powershell HostProcess call, and otherwise denies host reads/mutations, native processes, network, delegation, executable extensions, and unknown effects. Shell names, paths, syntax and parsed arguments never prove read-only execution authority. ControlledBashApproval has the same shell requirement. Policy version 2 binds the changed contract into every intent. UnsafeHost admits classified effects but is not containment. The coding product
selects it by default; --safe-mode selects ControlledBashApproval. Product code
must additionally prevent executable-extension process startup under controlled
policies because a broker check at later tool invocation cannot contain an
already-running executable.
Shell and workspace-mutation approval creates a random, short-lived capability bound to the canonical intent digest. Tokens are atomically single-use, stored by one-way verifier, redacted in debug output, and never supplied to tools. Dispatch reserves admission before before_tool_call, then commits and consumes the exact grant only after all hooks pass and immediately before calling Tool::execute. Hook denial or cancellation drops and revokes an uncommitted reservation; cancellation after commit cannot restore it. after_tool_call runs only for a committed effect.
Sequential, parallel, and crash-recovery dispatch all use this boundary. The
ordered live wave path intersects static ToolConcurrency with exact host
classification and explicit policy admission, admitting contiguous,
model-ordered waves of exact Pure, WorkspaceRead, or HostRead calls from
Parallel tools and, while the broker is UnsafeHost so that no call can
prompt, exact HostProcess calls from ParallelProcess tools: one
self-contained shell command each, as Pi overlaps them. HostRead
is eligible for these live waves but remains non-replayable. A wave holds up to
one observation per CPU the process may use, at least four and at most the
32-call turn limit (Agent::set_parallel_read_wave_width pins it); process
calls wait on their own children, so only the turn limit bounds them, except
that a width of one runs every call in turn. The width bounds resources, not
safety: classification alone admits a call to a wave, and every other call
(edits, writes, extensions, anything that could prompt) is a barrier, so a wider
wave admits nothing a narrower one would refuse. A wave call's progress streams
live with the same settlement, pacing and partial-output checkpoints as a
sequential call's, and results are committed in emitted order. Composition's
nested calls share the same overlap rule.
Crash replay separately intersects ReplaySafety::Safe with exact host
classification and permits only exact Pure and WorkspaceRead calls. A broker or argument denial is returned to the provider as a paired tool error before hooks or executable code; a trusted hook may veto an otherwise admitted call before dispatch.
The broker is a deterministic admission reference monitor, not an OS sandbox. Both controlled policies require approval for every shell call and deny effect classes that still lack isolation or dedicated brokers. ControlledBashApproval remains the policy selected by --safe-mode. The default UnsafeHost policy still lets classified command and process effects use ambient host authority without approval.
ToolPolicyDecision is secret-safe host evidence emitted after ToolStarted and
before its matching ToolFinished. An allowed decision is finalized only after
trusted hooks and reservation commit succeed, not when a reservation is first
created. Stable denials distinguish secondary_hook_denied,
effect_reservation_commit_denied, and invalid_tool_arguments; their
model-visible messages never include hook, parser, broker, argument, or approval
details.
Each decision carries an EffectiveToolPolicy snapshot. effect_policy,
workspace_confinement, allow_edit, allow_write, allow_process,
allow_shell, shell_path, bash_timeout_ms, max_output_bytes, and
allow_remote_read are { value, source } values. shell_path.value.selection
is only one of configured, system_bash, path_bash, sh_fallback, or
unavailable, derived by the same resolver used for Bash execution; it contains
neither a path nor a digest.
Sessions#
Sessions are append-only JSONL records containing entries, head updates, provider usage, and checkpoints. Entries form a parent-linked tree and the latest durable head selects the active branch. Compaction adds a structured summary, first_kept boundary, active-skill snapshot, and cumulative readFiles/modifiedFiles details without deleting ancestry. Both product-triggered and autonomous compaction use the same serialized handoff contract.
Before every provider turn, the agent estimates the complete request and retains a fixed 16K output reserve (or a larger explicit reasoning floor). The provider-advertised maximum completion size remains the model ceiling; the individual request is clamped only to the context space remaining after input. The default compaction threshold is the full context window, so the fixed reserve is not combined with an additional percentage buffer. If a provider nevertheless ends at the output limit while emitting tools, the assistant envelope is persisted, every call is paired with a synthetic error without execution, and a corrective continuation asks the model to reissue complete arguments.
Writes use an advisory exclusive lock, compare the observed file length under that lock, append complete record buffers, and call sync_data before updating in-memory state. Read-only inspection uses a shared lock and never repairs or truncates. Writable open performs explicit torn-tail recovery while exclusively locked. Files are 0600 on Unix and parsing is bounded by bytes and record count.
Effective provider-context preparation#
Native ProviderContextHook registrations run once, in order, against the last
validated effective Request before context planning, output sizing, budget
reservation, request freezing or a real dispatch. Their host context fences the
Session resource owner, host session ID (not provider cache affinity), starting
branch head and advertised tool generation. Only canonical messages and system
prompt can be replaced; the canonical Session, provider route, credentials,
tools and execution authority are unchanged. Complete tool-call/result pairs
may be omitted and result text summarized; call identity, arguments, async
execution authority, result status and unresolved calls must be preserved.
Every intermediate request is strictly validated and bounded to 64 MiB of
serialized canonical request. Hook errors are redacted and fail closed before
provider I/O; ordered hooks share a five-second deadline capped by the endpoint
timeout, with cancellation winning same-poll completion.
The real preparation driver can activate a ProviderContextSessionWait while
an owned hook snapshot future waits. Readiness is borrow-free; only that driver
receives &mut Session to consume typed, bounded private append leaves. The
service must revoke unclaimed leaves on drop, and a receipt can activate durable
projection state only after a synced append. A legitimate private append freezes
against the post-hook head and does not rerun a mutating hook merely because its
own append advanced the branch. This native seam alone is not a Pi compatibility
claim: a process adapter needs an authenticated direct producer and an actual
consumer, not a foreground broadcast event or optimistic success.
Optional synchronous idle Responses prewarming refuses when async projection
hooks are registered; real inference and its exact cache refresh retain the
validated effective request. Opaque Responses replay remains an explicit
limitation: a changed canonical projection is refused when a hidden input
window would ignore it. The host never silently drops provider replay state to
make a projection appear to work. Effective request estimates are planning data,
never a sound provider input upper bound: finite token/cost ceilings still refuse
before dispatch when that authoritative bound is unavailable.
Prompt-cache warming#
The same session owner polls CacheWarmer alongside provider opening, body
consumption and tool waits; retained hosts and delegated workers also poll its
idle driver. Timer, advisory-hook and refresh futures live in the Agent, not a
temporary select! branch, so competing input does not drop or repeat an
accepted provider request. Real provider opens remain pinned across warming
wakeups. Explicit cancellation synchronously drops the refresh source and durably
records unbounded uncertainty if dispatch was possible.
Each real inference captures its exact request before dispatch. A refresh changes only the output cap to one; generated content and calls are discarded. Declared retention-specific TTLs and an enforceable wire cap gate eligibility. The scheduler follows pi's default streaming and opt-in idle profiles, pre-expiry delay, fixed age horizons and expected-savings decision. Ordered, bounded extension advice can change the economic action, never replay safety or hard ceilings. Admission accounts for the concurrent real request's reservation; freshness is checked again before a deferred first provider poll.
Successful refresh usage and exact cost are session-only ledger records plus a
CacheWarmed event, never an assistant turn, context mutation or throughput
sample. Payload-free lifecycle records retain the route, prefix anchor and
extension-override flag. An unfinished refresh after a crash stays uncertain
and disables auxiliary warming rather than failing an uncapped real run.
Resume does not reconstruct a live cache/timer from history. See
cache warming for product configuration, cancellation,
accounting and qualification limits.
V2 task delegation#
For explicit orchestration boundaries (hosted delegation vs local delegated children, sandbox/approval/env/cwd inheritance, extension trust propagation, and explicit non-goals), see docs/design/extension-capability-and-orchestration-boundaries.md.
octet-agent owns host execution for AgentDelegation::V2; the model capability in
octet-ai is metadata only. The generic Agent::enable_v2_delegation API can
install the native collaboration surface for embedders that explicitly choose
it. The coding product instead enables the manager in extension-only mode: only
the trusted, enabled octet-subagents extension receives the owner-bound
agent_sessions service, and the root model never receives the parallel native
spawn_agent, followup_task, send_message, wait_agent, list_agents, or
interrupt_agent tools. Available/proactive mode guidance applies only to the
generic API; product orchestration and observation remain extension-owned.
Each child has a stable ID and ancestry path, an isolated append-only Session,
and its own agent loop. It inherits the effective root system prompt at spawn
time, approved extension host/tool set, sandbox, model, reasoning and cache
settings, compaction model/policy, completion policy, output modalities, resolved
context/output limits, retry policy, turn limit, optional session token ceiling,
session cost ceiling, and the root's cloned effect broker. A missing root session
token ceiling remains missing in the child; the host does not invent one. Each
child starts a fresh independent context. Its settled usage is mirrored into the
root ledger for accounting and cost-limit checks, never inserted into the
parent's prompt context or charged to the parent's own-context token ceiling. The
broker clone preserves a shared policy/grant store; it is not yet child-specific
authority attenuation. Controlled therefore denies the Delegation effect
entirely, while UnsafeHost delegation must be treated as ambient-authority
compatibility mode. Children
can message peers, steer active work, queue messages for an idle worker, receive
follow-up runs, wait without lost notifications, and spawn within the remaining
depth and concurrency bounds.
Each child snapshot, agent_sessions spawn/list result, and durable spawn record
also includes its effective_tool_policy and bounded
orchestration_provenance. The latter records only parent_inherited or
child_override for sandbox, effect policy, approval authority, environment,
working directory, extension trust, tool scope, and execution limits. An
extension-requested child scope or limit is a host-validated child_override;
it does not transfer sandbox, broker, approval, environment, cwd, or trust
control to the extension. No paths, environment values, approval tokens,
extension identifiers, or model arguments are included.
The default team limit is ten concurrent agents including the root, depth two,
and thirty-two total agents during each owning run. Host
validation permits 2–32 concurrent agents, depth 1–8, and at most 256 total,
with total capacity never below concurrent capacity. The first-party
octet-subagents service that the coding product actually uses is stricter: its
children sit exactly one level below the root and are bounded to eight active
children per parent with thirty-two retained records per resource owner, and a
worker inherits the parent's full standard tool scope (read,
edit, write, bash) unless the spawn narrows it. A semaphore and ancestry
checks enforce those limits independently of model behavior; an idle worker is
reserved as Pending before a follow-up is published so concurrent follow-ups
cannot start overlapping runs. Each worker command channel is capped at 32;
the accepted follow-up backlog is capped at 32 messages and 4,325,376 bytes.
Accepted steering and follow-up reservations remain charged until the child
emits its durable delivery acknowledgement. Direct messages moved into a child
prompt likewise remain reserved until the prompt append succeeds; a failed append
restores them at the front. Failure, interruption, and control backpressure
requeue unacknowledged work in FIFO order rather than releasing or discarding it.
Pending direct messages are capped at 96 and 4,325,376 bytes per child, including
in-flight and prompt-delivery reservations; overflow and inputs above the 128 KiB
durable-text limit are rejected before provenance is written rather than evicting
or truncating accepted work.
Agent mailboxes retain at most 64 messages and 1 MiB; automatic status
notifications evict only the oldest unleased automatic notifications when
necessary and are dropped when no such entry can be evicted. Accepted direct
messages are never evicted, and direct messages to a full root mailbox are
rejected. wait_agent leases a UTF-8-safe bounded page and exposes continuation
metadata when one message spans pages. The lease commits only after the complete,
untruncated tool result is durably appended to the owning agent's session;
cancellation, persistence failure, serialization failure, or generic output
truncation restores the page. Concurrent wait_agent calls are capped at the
configured total-agent limit and released by cancellation-safe RAII guards.
Delegation state is stored under a descriptor-bound, owner-only random team
directory. provenance.jsonl records team_started, agent_spawned,
agent_status, message, interrupt_requested, and team_shutdown; child
session files and the journal are private. Directory allocation, child-session
creation, activation rollback, and cleanup are descriptor-relative and no-follow;
a failed activation removes only its exact empty private team directory and
reports both activation and rollback failures if cleanup cannot complete. Every
spawn, message, follow-up, status transition, and interrupt is appended and
sync_data-ed before delivery or visible state mutation. If append or sync
fails, the manager records a visible persistence diagnostic, rejects new work,
and cancels every worker rather than operating without provenance.
Cancellation propagates down ancestry. Every owning run terminal (including
normal completion, failure, max-turn termination, explicit abort, and incomplete
Run drop), root Agent drop, worker interruption/failure, closed command
channels, and team shutdown cancel worker tokens and send shutdown commands;
descendants are stopped with their parent. A normally driven root terminal also
waits up to two seconds for extension-owned descendants to settle, aggregates
each child session's durable disjoint usage and exact category cost (including
picodollar remainder), and appends one UsageRecordKind::DelegatedAgent entry
per child to the root session before its checkpoint. The child remains the
detailed source of truth; the root mirror is the cumulative accounting and cost-
limit ledger.
Filesystem tools#
Workspace-only path shapes reject absolute roots and parent components. On Unix, file operations canonicalize the accepted target and then walk every component using directory descriptors and O_NOFOLLOW. Reads open the final object nonblocking, require a regular file from descriptor metadata, and stream at most limit+1 bytes. Mutations retain the open parent descriptor, write a sibling create_new temporary, re-read and compare the target immediately before commit, and rename relative to the same descriptor. Parent symlink replacement therefore cannot redirect the operation.
The path guard applies to explicit built-in paths. It is not process containment: commands admitted by UnsafeHost have the current user's authority. When external paths are enabled, local file tools conservatively classify every call as a host effect so a path-resolution race cannot lower admission authority; the coding product forces external paths off under controlled policies, including --safe-mode.
Resource limits#
- Local file read/edit/preview: 32 MiB per file.
- Tool calls per assistant turn: 32.
- Default model-visible text per tool result: 50 KiB (host-configurable).
- Delegation provenance text per task/message/status payload: 128 KiB; configurable teams remain capped at 32 concurrent, depth 8, and 256 total agents.
- Progress: bounded messages and chunks.
- Session replay: 256 MiB and 1,000,000 records.
- Command timeout/output: host-configured with product-level upper bounds.
Extension boundary#
All tools implement Tool and register through ExtensionHost; core tools are not privileged inside the run loop. A product policy filters the host before Agent::new, ensuring provider definitions and executable implementations are the same set. Tool implementations own effect metadata and default to Unknown; provider schemas and model arguments cannot select authority. Executable extension tools classify as Extension, remain non-replayable and sequential, and the coding product prevents their process from starting under Controlled.