WebCodex uses the word session for two independent systems. They share casual vocabulary only. They must not be merged, cross-wired, or inferred from each other.
The default durable event tail retains up to 2,000 events per Session, while
one model-facing summary remains capped at 200 events (50 by default). These are
independent bounds: longer forensic/recovery retention does not enlarge one model
response, and the ledger remains a bounded tail rather than an archive. If
retention_truncated is true, event-derived summary counts describe the retained
ledger rather than claiming lifetime-complete history.
Executable constraints that agents must obey live in
AGENTS.md; this document is the Workflow Sessions domain
source linked from §6. Standing architecture summary:
architecture-decisions.md §1.
| Formal name | Casual aliases (avoid in design) | Implementation home |
|---|---|---|
| Workflow Session | coding session, tool ledger session, wc_sess_* session |
tool_runtime::sessions |
| Action Audit Session | HTTP action session, audit session, operator action trail | Internal module action_audit_sessions (SQLite table still named action_sessions for compatibility) |
When writing code, docs, or reviews, prefer the formal names above. If a statement is true for only one kind, name that kind explicitly.
Adapters may derive a bounded, domain-separated ClientWindow from host-owned window metadata such as _meta["openai/session"]. The raw host value is never persisted or exposed. A principal-scoped wc_peer_* id may be derived from the hashed Window for lightweight collaboration routing, but neither identity is a Workflow Session selector, Project authority, credential, model-context-retention proof, implicit recorder, model-turn id, or liveness proof. Stateless MCP still never treats caller-supplied Mcp-Session-Id as hidden continuity.
Window correlation supports ActionAudit, agent-loop observations, bounded Peer awareness/messaging, and other explicitly designed non-authority features, while ordinary coding continuity remains the canonical Workflow Session lifecycle. work_on_project(session_id=...) resumes only that exact authorized Session; omission creates a fresh Session. Credentials, Project ids, windows, connections, Peer ids, and prior requests never select a Workflow Session implicitly.
The durable Agent/Conversation/Wake domain is also not a session type. A Server-minted Agent may participate in Conversations and own asynchronous Agent Tasks while concrete execution uses zero or more independent Workflow Sessions. Agent Task / Agent TaskAttempt remain a distinct work-ownership domain; similar names do not imply shared lifecycle, window binding, storage, or authority. See ../architecture/durable-agent-runtime.md.
Window Peer state is a small communication plane, not a third Session domain. Discovery requires the same authenticated principal, exact Project, and meaningful Window activity within a 10-minute recency window. Discovery produces a retained-state-deduplicated peer_awareness hint; the word "recent" is literal and does not imply online/presence state.
After discovery, a wc_peer_* route is principal-scoped but Project-independent. post_peer_message therefore does not become invalid merely because either collaborator later works in another Project/worktree; bounded Peer retention still applies. The route conveys only the explicit bounded message and safe message metadata. It grants no visibility into the peer's current Project, Workflow Session, files, branch, activity, assignment, or handoff. Session business tools retain their own target/project authorization, including the existing exact-project equality fence for recorder-to-target Workflow Session collaboration.
Peer delivery is ambient on model-facing ToolResults. Ordinary messages are persisted and marked after one projection attempt; ACK-required messages are eligible again whenever the current request omits their id. Persistent first_projected_at_ms, last_projected_at_ms, projection_count, and first_ack_observed_at_ms are analysis/observability facts only. They must not be described as delivery, reading, acceptance, current memory, or work completion.
Bounded coding-task continuity and evidence for MCP and runtime tools. It records what happened in a task so review, validation, handoff, and finish can reason about the same unit of work.
- Coding task start / finish lifecycle
- Tool-call evidence (bounded, redacted)
- Checkpoint-related task continuity
- Session-local message board
- Validation evidence and closeout summaries
- Handoff / finish tooling (
read_session_handoff,finish_coding_task, …)
Workflow Session lifecycle is independent from the durable wc_goal_* Goal domain. A Session may be explicitly correlated to a Goal, but that correlation grants no Session/Project authority and does not make the Session the Goal's lifecycle owner. In particular, finish_coding_task does not transition a Goal to completed; any Goal transition is a separate explicit Goal-domain mutation.
| Aspect | Contract |
|---|---|
| ID form | Canonical wc_sess_* (SESSION_ID_PREFIX); model-facing bootstrap/discovery/handoff may additionally issue principal-scoped session_ref values such as ~s1 |
| Business field | session_id on tools that take a workflow session as input; model-facing business calls may pass either the canonical id or a Server-issued session_ref |
| Coding resume field | session_id on canonical external work_on_project; the internal startup primitive's resume_session_id is not a wire/API field |
| Recorder field | Generic API/adapter wrappers may use recording_session_id; Stateless MCP 2026 uses _wc.record (metadata only; either form accepts canonical wc_sess_* or an explicitly supplied principal-scoped session_ref, then canonicalizes before recorder authorization and concrete dispatch) |
A session_ref is only a short model selector. The Server owns a durable mapping scoped to the authenticated principal and pins the ref to one exact canonical Workflow Session incarnation. Resolving it produces the canonical wc_sess_* before business authorization/dispatch; the ordinary Project visibility, Session authority, lifecycle, and guard checks then run unchanged. The ref is not a credential, bearer capability, recorder identity, or "current/recent Session" inference. If the pinned Session disappears or its exact incarnation cannot be proven, the old ref fails closed and is never recycled or silently retargeted. When the caller's own ref still pins that exact incarnation and a retention tombstone for that canonical id is still present, resolution returns session_retention_expired for that canonical id and does not continue the call. A foreign, malformed, missing, incarnation-mismatched, or already-aged-out ref remains unknown_session_ref and is never rewritten. Canonical Session ids remain authoritative for persistence, audit, diagnostics, internal joins, and explicit API/CLI consumers.
For exact work_on_project checkout resume, session_id + instruction is sufficient: the shared startup engine authorizes the selected Session, obtains its bound canonical Project, and runs ordinary current Project resolution/authorization and the same Session/Project, lifecycle, capability and recorder fences. An explicit Project must still match. Fresh work and managed-worktree mode require an explicit Project source. An unscoped Session cannot supply one. Neither missing/Closed/inaccessible Session state nor recorder/Window identity selects a replacement. Session refs continue to pin Session incarnation; Project refs separately pin canonical Project/root identity. This convenience does not add a historical filesystem snapshot or change either identity contract.
The original Window's workflow.resume discovery emits an optional project_ref only after the candidate Session and current Project are authorized and a current root fingerprint is available. Canonical identity remains present; refs are convenience selectors, not new authority or automatic resume.
Model-facing session_id input schemas must admit ~sN wherever the kernel accepts a Session selector, including exact resume and card presentation. Canonical-id-only input regexes would let a strict Host reject the ref before resolution. This does not widen output session_id, persisted IDs, or other identity domains: outputs retain canonical identity, and the separate session_ref field carries the selector. Unknown, malformed, foreign, or stale refs still fail in the existing resolver and authorization path. A retained Closed Session follows the same lifecycle rules as its canonical id: historical reads may succeed, but a selector never reopens it or permits a mutation denied to the canonical id.
Canonical Session identity/retention is separate from in-memory residency. Active and Closed are business lifecycle states; hot/cold residency and LRU ordering never transition or delete an Active identity. The hot_session_capacity_target bounds expanded records using the existing access order; zero permits one mutation target. Overflow compacts older records into canonical JSON retained in RAM, not disk-backed paging. Exact mutations hydrate the selected Active record under the store lock. Queries materialize temporary snapshots without promoting history scans. Startup sanitizes one disk row at a time and retains Active and Closed rows Cold. Live round trips preserve instruction snapshots and replay metadata without rerunning disk-restore sanitization. This is a count-based working-set target, not a hard heap-byte limit; Cold bytes, metadata, in-flight snapshots and live instruction bodies remain allocated. See Active Session residency.
Closed historical rows use an independent bounded retention policy. Closed Sessions are coldified to compact durable JSON and remain queryable while retained; mutation remains denied and retention never reopens them. historical_session_retention_limit bounds retained Closed history only. When that explicit historical policy removes an old Closed row, the v2 ledger keeps a row-local retention_tombstone for that exact canonical id, owning principal, and incarnation fingerprint, together with an expiry_ordinal that stores only relative expiration order. A restored ordinal with no greater successor is compacted, before the next allocation, into a dense increasing range over the retained tombstones; canonical id, owner, and incarnation stay the same, and the oldest tombstone remains the eviction victim. Tombstones share historical_session_retention_limit as a separate ceiling and do not count as retained, closed, hot, or cold Sessions. A later lookup by the owning principal returns session_retention_expired and does not reopen the Session. Any other principal receives unknown_session_id, the same result as an id that was never present. After a tombstone ages out, that id is no longer distinguishable from one that was never present. Active Sessions are never removed or tombstoned to satisfy hot capacity. An older v2 reader ignores tombstone rows and keeps Session rows. The compatibility max_sessions status field now aliases the hot capacity target and must not be interpreted as permission to delete durable Active Sessions.
Access recency is maintained by a store-owned ordered index with separate Closed eligibility; it is not business lifecycle or activity. Exact reads update that index without appending ledger events or scheduling a write. The background whole-ledger writer coalesces ordinary asynchronous dirty marks for at most a fixed 20 ms scheduling window from the first pending mark. Progress never resets that deadline. Explicit generation flushes and shutdown bypass coalescing, while preserving existing write ordering and persistence-error reporting. I/O can take longer; this is neither a fsync nor a power-loss guarantee. Ledger version 2 and its per-cycle full snapshot format are unchanged. Implementation evidence and tradeoffs: SessionStore access and writes.
Per-Session event and message tails remain independently bounded (DEFAULT_MAX_EVENTS_PER_SESSION and DEFAULT_MAX_MESSAGES_PER_SESSION); preserving a canonical Active identity does not turn its event/message history into an unbounded archive. The persistence wire shape remains ledger version 2. A retention tombstone is an additional row-local object in the same sessions array, not a Session row and not a new ledger version. Existing Session rows already deleted by an older Server cannot be reconstructed by upgrading: tombstones apply only to Closed rows removed after this behavior is present.
recording_session_id remains a separate provenance contract. It may explicitly carry the same principal-scoped session_ref returned for that exact Session; Runtime canonicalizes the ref before recorder authorization and durable provenance recording. This does not make recorder provenance a business Session target, and omission never selects a recorder implicitly or creates sticky recorder context.
Stateless MCP 2026 never derives a Workflow Session recorder identity from transport/window continuity. ChatGPT may supply _meta["openai/session"] as a hashed ClientWindow, but that identity is intentionally not a Workflow Session selector or trusted provenance source. Its tools/list schema therefore projects one optional closed _wc invocation envelope; recorder provenance is _wc.record. A call may carry _wc.record=W while the concrete tool body carries business session_id=C; the MCP adapter removes the envelope before concrete parsing and the kernel independently authorizes W before it can record evidence or supply trusted collaboration provenance. This does not revive legacy mcp-session-id, grant target authority, or infer a recorder from credentials, project identity, connection state, or ClientWindow.
Session collaboration attention has a narrower fallback that never becomes recorder identity. When an ordinary Stateless MCP Project tool omits _wc.record, the same strict Window/principal/exact-Project affinity used by the recorder-gap diagnostic may locate one latest authorized Active Workflow Session solely to project open ACK-required messages and accept request-scoped ACK ids. The result names that exact session_id and marks source=window_affinity. This fallback cannot append ToolCall events, supply business session_id, inherit execution context, resolve a message, complete a todo, change Goal/Task authority, or mutate Session lifecycle. An explicit recorder always selects attention instead.
Window activity correlation is observational and does not weaken that targeting
rule. Stateless MCP may persist the hashed ClientWindow on ActionAudit events
and attach a normalized Window↔Workflow Session relation only after the existing
Session/Project authority path has already established the fact: an authorized
outer recording_session_id yields a recording relation, while a successful
canonical work_on_project create/resume yields a work_on_project relation from
its typed projection. These relations are intentionally many-to-many and confer
no lease, ownership, authority, or implicit recorder selection.
Agent-loop timing uses the same observational boundary without adding a Session
identity. Adjacent meaningful MCP calls are eligible to pair only when the hashed
ClientWindow and canonical authenticated principal correlation both match.
Project remains an event dimension and current-visibility boundary, not the
continuity identity. A matching Window does not prove a matching model turn:
WebCodex receives no reliable turn/generation/response id and never infers one
from elapsed time. Missing or malformed host Window metadata therefore leaves
loop continuity unavailable rather than falling back to Workflow Session,
credential, Project, connection, trace, or MCP Session identity.
For a later successful meaningful Project tool call with no explicit recorder, WebCodex may diagnose a recorder continuity gap when the same hashed Window, canonical principal, and exact Project have a recent explicit Session affinity. The candidate must still be Active, match the exact Project, and pass the current caller through the ordinary Session authority check. The diagnostic may suggest that exact Session to the model, but the missing call is not backfilled into the Session ledger and the candidate never becomes a sticky recorder. Status and discovery calls are not meaningful gap evidence. This preserves the original Session event order, revisions, ACK state, validation evidence, and permission facts while making an otherwise silent recorder discontinuity observable.
One real kernel tool request also receives one trusted runtime-generated logical invocation correlation id. The outer recorder event pair and any inner concrete business-execution event pair inherit that id while retaining independent pair-level call_id values. A small recorder/business role discriminator lets Session-local semantic projections deterministically prefer authoritative business execution facts when both pairs land in the same Workflow Session. Raw ledger facts remain intact. Correlation never grants authority and is not a permission identity, retry token, idempotency key, execution identity, lifecycle key, or model-supplied input. If recorder Session W and business Session C differ, each Session keeps its own one-invocation semantic evidence; correlation is never used for cross-Session global deduplication. Current-v2 ledger events without the additive correlation fields remain uncorrelated and are projected conservatively per event; restore never invents an id or rewrites persisted history.
Stateless MCP 2026 projects two request-scoped ACK evidence forms. _wc.ack is the bounded exact-ID request form and can acknowledge both Session and Window Peer messages. For Session attention, the Server also returns an optional compact session_attention.ack_ref, which a later request echoes as _wc.ack_ref. Each returned ref represents the exact Session ACK set explicitly retained across the current exchange: ACK evidence accepted on this request plus ACK-required messages newly projected in this response. As further bounded messages are projected, the next ref carries that retained set forward in one value. Echoing the ref on a later ordinary tool call expands only after the exact authorized attention Session has been established: the explicit recorder when present, otherwise the narrow authorized Window-affinity fallback above. The ref is Session-only, principal/Session-bound by the existing authority path, and fenced against the full current open ACK-required Session membership; adding or removing membership makes an older ref acknowledge nothing rather than absorb a different set. ACK bookkeeping itself does not change membership, so exact replay remains safe. Peer ACK continues to use explicit _wc.ack.
The adapter removes both ACK forms before concrete tool parsing. Neither grants authority, resolves a message, accepts work, creates sticky Session/Window context, or gates the concrete tool effect. Any Session or Peer message kind/priority may request ACK. Accepted Session evidence suppresses only the represented body/bodies for the current response; later omission makes unresolved Session messages eligible for bounded re-projection with a current ack_ref. Retained Peer ACK messages follow the existing exact-ID behavior. Historical ACK state is never used to infer current model-context retention.
An ACK-required Session message may persist first_ack_observed_at; an ACK-required Peer message persists the analogous window-message timestamp. For Session messages only the first accepted ACK advances message-observation revision; repeated echoes do not create revision churn. These fields mean only that the Server once observed an explicit ACK echo. They are not delivery/read receipts and do not by themselves change business status. resolve_session_message remains the durable processed-state transition for Session messages.
Window Peer transport is bounded retained communication, not a durable task queue. Retention pruning may eventually remove old Peer messages or discovery edges, so ACK-required Peer re-projection lasts only while the message remains retained; durable work ownership and completion continue to use explicit Workflow Session or Agent Task primitives.
Workflow Session targeting is explicit in 0.4. Canonical external work_on_project creates a fresh Workflow Session when session_id is omitted and continues only the exact existing wc_sess_* when it is supplied. The retired start_coding_task wire/API name is not a second continuation path. Ordinary project tools do not infer a Workflow Session from caller identity, window identity, project identity, or prior calls. To record a call in a Workflow Session, pass an explicitly authorized recording_session_id; when a tool has its own Session business input, that explicit id is authorized independently.
Project scope is fail-closed. An explicit project-scoped business Session or recorder must match the canonical resolved request project before business execution or Session mutation. There is no cross-project warning/escape mode. complete_session_message records an answer author only from an explicitly authorized recorder; without one, author Session provenance is absent rather than inferred.
The JSON ledger restores only the current version-2 top-level shape and canonical current Session rows. Pre-current ledger versions are rejected rather than migrated. Within v2, fields explicitly declared optional/default may be absent and restore conservatively. The retired context_revision members are accepted only through explicit read-only compatibility sinks and are never restored into live Session state or re-emitted. A retention_tombstone object is a recognized non-Session row in the v2 sessions array. A malformed tombstone drops that row only. Unknown members on a Session row still fail that row closed. A live Session row wins over a tombstone with the same canonical id. General ClientWindow support remains available to explicitly designed non-Workflow observations; Workflow Sessions do not use it for selection or authority.
Optional explicit control mutations may also use the Stateless MCP 2026
_wc.control sidecar contract. Each phase admits one mutation
with its canonical authority and replay fences; standalone tools remain valid.
Executable todo completion is assignment-fenced in 0.4. A worker first calls get_session_assignment for the exact coordinator session_id + message_id; one atomic store snapshot returns the open todo, every retained direct reply within the bound, and an opaque Session/todo-bound assignment_fence. Current complete_session_message requests require that exact token as expected_assignment_fence together with the independent caller completion_key. Assignment-local semantic changes stale the fence before mutation; unrelated Session traffic and ACK bookkeeping do not. A stale result has state_changed=false and includes the current assignment plus a fresh durable fence only when that exact current state remains provable. Retention loss or an oversized direct-reply set is non-completable from stale context.
The fence and completion key are different identity domains: the fence proves the semantic assignment snapshot, while the completion key correlates one accepted intent across uncertain retries. ack_session_message_ids and compact Session ack_ref are request-scoped ACK evidence; message-observation tokens are a separate cursor domain. session_ref is a principal-scoped exact-Session selector and never ACK evidence. The field carrying it determines the role: business session_id remains the independently authorized target, while recording_session_id remains provenance only and never supplies business guards or execution defaults. None substitutes for assignment/completion identity. Current persisted rows must carry the assignment-history and completion-fence tracking metadata required by the v2 format; restore never invents a missing fence, fingerprint, or tracking state, and a row that cannot prove the current shape is discarded rather than admitted for replay.
Workflow Sessions no longer maintain a model-context checkpoint revision, ACK
baseline, or ToolDefinition checkpoint classification. Finished tool calls use the
same canonical ledger append path regardless of whether their ToolResult is
model-facing. Durable consequence evidence remains in Session events, including
bounded context_result_summary, validation/effect evidence, observed/changed
paths, repository-edit sticky state, and Job evidence. events_observed remains
the cumulative Session event count, including events later removed by retention;
it is not a model-context token or cursor.
Normal continuous work has no context-revision ACK input, automatic recovery
delta, or handoff suggestion on unrelated tool results. When task context is
missing, the model explicitly calls read_session_handoff with the exact
session_id. No identity, Project, window, transport, or recent-call state can
select a Session implicitly. The authorized business Session supplies its Project
when project is omitted; an explicit Project still passes the normal equality
and authority checks. An outer recorder never changes the recovery target.
The default result contains session_id, project, and handoff_brief. The
shared deterministic brief is hard-bounded at 8 KiB and carries root/latest task
instructions, workspace state, progress/changed paths/recent files, validation,
active/recovering Job attention, open collaboration counts, next actions, and
basis completeness. diagnostic=true explicitly adds detailed ledger and closeout
evidence. include_workspace, include_validation, include_checkpoints, and
limit continue to select bounded observations; omitted/unavailable evidence is
reported truthfully in the brief. There is no implicit handoff or ACK baseline.
Task context also carries source-referenced relevant files, failure locations, recorded decisions/progress, advisory next steps, and an exact continuation entry. The bounded task-context contract defines its 4 KiB envelope, task-boundary identity and conservative repeated-failure diagnostic. These recovery pointers do not become execution or completion proof.
Handoff assembly captures an internal Session snapshot fence before gathering
workspace/Job/Session evidence and compares it again afterwards. The Session fence
has exactly two independent mutation dimensions: events_observed for ledger event
mutations and message_observation_revision for collaboration/message mutations.
External reports remain a separate evidence plane, so the handoff also captures a
bounded external-report snapshot and compares that retained projection again after
the other recovery reads. A Session fence change reports
session_changed_during_snapshot; an accepted external-report change reports
external_observations_changed_during_snapshot. Either makes basis.complete=false
and returns an explicit exact-handoff reread action before dependent work. An
Active Session's progress is insufficient_evidence until a fresh observation;
Job attention and the captured task/evidence sections remain observations, not
permission to restart execution. The caller re-reads latest instructions,
decisions, progress and validation. This detects evidence
races without claiming atomicity across independent Runner workspace or Job reads,
and without promoting external reports into Session revision or native execution
truth. No handoff generation or replacement model-context revision is introduced or
returned.
Collaboration is independent: ack_session_message_ids still proves exact
ACK-required Session/Peer message ids, while session_attention.ack_ref is the
compact exact-set form carrying accepted Session ACK evidence plus messages newly
projected in the current exchange.
session_attention suppression/re-projection, message resolution, assignment
fences, completion keys, message-observation tokens, and their durable revision
keep their existing semantics. A handoff neither ACKs nor resolves a message and
grants no authority.
Stateless MCP 2026 tools also accept an explicit bounded _wc.context envelope sidecar request. It is independent of collaboration ACKs and handoff recovery and is removed before concrete ToolCall parsing. A static canonical material registry authorizes every requested material before its provider is read: webcodex.workflow is public; project.instructions requires the resolved Project plus project:read; jobs.attention requires the exact resolved Project plus canonical runtime:read and reuses the authorized active-Job summary (at most eight recent Jobs) without selecting a business Session; skills.catalog additionally requires the admitted Skill runtime protocol capability; plugins.catalog requires the resolved Project plus both project:read and plugin:inspect; and memory.bootstrap requires the admitted Memory protocol capability plus both project:read and memory:read. Scope or material-capability denial is nonfatal to the main ToolResult and returns a bounded unavailable material without provider content. Unknown material keys are nonfatal and remain open-ended at the MCP schema layer. Sidecar material is projected only after the main tool effect or observation has completed, never grants authority, never retroactively makes requested guidance a precondition of that effect, never records caller-read state, and never infers a Project or model-memory state from a Workflow Session, connection, credential, Mcp-Session-Id, or hidden window identity. A model that has lost Project rules or durable Memory guidance must recover project.instructions and/or memory.bootstrap on an observation call, use read_memory when detailed Memory content is needed, reason over that context, and only then issue a later mutation that must obey it. Legacy MCP and generic REST do not expose this sidecar request contract.
Project Memory is a separate durable knowledge plane from Workflow Session continuity. search_memory/read_memory require both project:read and memory:read; set_memory/delete_memory require both project:write and memory:manage, with mutations still passing the independent permission evaluator. Direct shared-key runtime credentials explicitly carry both Memory scopes, while Open Anonymous, ProjectCredential, Project Share, and legacy/default OAuth client scope sets do not gain them from project scopes. A Memory memory_key is logical semantic identity, memory_id identifies the current incarnation, the internal definition_hash identifies canonical model-relevant content, and model-facing revision is a generation-bound state ETag/CAS identity; delete and identical recreate therefore produce a different memory_id and revision. Session events never create or consolidate Memory automatically. ack_session_message_ids and Session ack_ref are limited to ACK-required collaboration messages and never acknowledge Memory. Memory reads/searches may leave bounded metadata-only consequences in Session history, but Memory bodies, summaries, search results, and memory.bootstrap projections are not copied into durable Session recovery. Re-registering the same runtime Project id to a different authoritative registered root resolves to a distinct internal Memory scope rather than inheriting the old root's Memory.
The Session-local message board has a separate durable monotonic message-observation revision used by observe_session_messages. The public observation token is bounded and opaque; it binds the exact Workflow Session plus durable cursor state without exposing the internal revision as the caller cursor. It is observation state only and grants no authority. Malformed, oversized, wrong-Session, and future-revision tokens fail closed.
A no-token call establishes the current baseline and returns no historical messages. Later token calls return retained messages whose latest observable state changed after that cursor, optionally with one bounded wait. Posts are observable mutations; resolve advances only on a real field/status change; a new complete_session_message observes both the todo resolution and answer creation; exact idempotent replay does not advance. This is current-state delta, not an audit/event log, so multiple changes to one retained message may collapse to its final state.
Retention correctness does not infer continuity from deque length or position. Retained messages keep their latest internal observation revisions and the Session persists a low-watermark for removed observation history, including non-FIFO completion retention holes. history_lost=true tells callers when a cursor predates state that can no longer be reconstructed. With pagination, a token advances only through the last returned change while has_more=true. Observation-token issuance fences the ledger generation containing the cursor revision, so tokens issued by the current implementation remain valid across Server restart when the Session itself restores.
delivery_key replay is likewise durable but bounded by message retention rather than an unbounded global idempotency ledger. While the keyed Session or Peer message remains retained, exact same-payload retries return the original message_id across Server restart and conflicting key reuse fails closed. Once the corresponding retained message/replay metadata is evicted by the existing bounded retention policy, that old key is no longer promised as replay authority.
Waiting uses process-local notification only as a wake signal; durable revision state remains the truth. No Session-store or persistence-writer mutex is held across the bounded await, unrelated Session mutation can only cause a spurious recheck, and timeout is a successful unchanged result rather than a tool failure.
Message observation is not a delivery receipt, not model-context retention, not a subscription/stream, and not an Agent Wake. Durable Agent/Conversation/Participant/Delivery/Wake state already exists as a separate Server-owned domain; this Workflow Session cursor neither observes nor mutates it. Presence, typing, Agent Task scheduling/worker-pool behavior, automatic worker spawning, and routing remain separate additive capabilities rather than reinterpretations of this cursor.
Every Workflow Session admitted to the in-memory store carries one canonical creation-time authority-group fingerprint. The ledger keeps only that domain-separated SHA-256 fingerprint under the historical owner_authority_fingerprint field; raw user, shared-key, project-grant, credential, or window identity material is never persisted as Session authority. Project authorization and creation-time Session authority are separate checks: access to a project does not authorize another authority group's Session, and a matching Session fingerprint does not bypass project authorization or project equality.
An existing registered-project-bound Workflow Session may persist a closed set
of strongly typed execution defaults. The wire/type name remains
SessionExecutionContext for compatibility:
SessionExecutionContext {
default_cwd: project-relative path? | remote path?,
default_shell: sh | bash?,
resource: named Runner SSH resource?
}
These fields are execution defaults, not arbitrary model context. They cannot
contain environment variables, credentials, SSH host/configuration, keys,
passwords, SSH state, shell input, connection data, or custom options.
resource is only a safe named resource configured on the Runner that owns the
Session project. It is persisted as a name, never as an SSH transport or
authentication material.
Without resource, default_cwd is validated and normalized as a
project-relative path: absolute paths, URI forms, control characters, and
parent traversal fail without changing Session state. Filesystem existence,
canonicalization, symlink, and allowed-root checks remain in the normal Runner
execution path and fail closed there without retrying from the project root.
With resource, default_cwd is instead a bounded remote path; it is not
checked against the Runner project root, and the remote shell reports an
unenterable cwd explicitly.
Inheritance is intentionally closed and per field:
| Tool | default_cwd |
default_shell |
named resource |
|---|---|---|---|
run_process |
inherited when cwd is omitted |
not applicable | unsupported; fails before process start |
run_script |
inherited when cwd is omitted |
not applicable | unsupported; fails before script start |
run_shell |
inherited when cwd is omitted |
inherited when shell is omitted |
routes this call through the named SSH resource |
run_job |
inherited when cwd is omitted |
inherited when shell is omitted |
routes this Job through the named SSH resource |
open_session_shell |
inherited when cwd is omitted |
inherited when shell is omitted |
opens the persistent shell through the named SSH resource |
Structured Cargo/Go tools (cargo_fmt, cargo_check, cargo_test, go_test)
do not inherit default_cwd or default_shell; a named resource is also
rejected for those tools rather
than silently falling back to the Runner-host project. File, Git, LSP, and
checkpoint tools do not inherit any execution default.
run_shell and run_job remain independent-process tools. When an SSH
resource is selected, run_shell, run_job, and a newly opened
open_session_shell execute through that remote resource. Remote cwd
precedence for one-shot SSH commands is:
per-call cwd
> exact active project-matched Workflow Session default_cwd
> SSH resource default_cwd
> remote login default directory
Each SSH run_shell / run_job command gets an independent remote exec
channel and requires the Runner's ssh_shell capability. Unix may reuse a
Runner-local authenticated ControlMaster transport for the same
Session/resource/generation; Windows starts one direct ssh.exe process for
each one-shot/background execution and creates no mux state. Neither transport
preserves cd, exports, aliases, functions, umask, or shell-process state between
commands. A generation change affects future preparation only: an already-spawned
command is never redirected, replayed, or blindly retried. This
one-shot/background capability is independent of named SSH persistent-shell
support.
Raw shell text has one shared model-authored ceiling of 16,000 UTF-8 bytes for
run_shell, raw run_job, and execute_session_shell. Larger shell program text
belongs in run_script; large literal data belongs in stdin, files, or
artifacts. Control may internally expand an explicit sh/bash command while
POSIX-quoting it for the existing Runner wire, so the internal raw-shell wire
envelope is separately capped at 64 KiB and revalidated by the Runner. That
transport headroom is not an additional model-facing command allowance.
A concrete OS/shell launch envelope may still be narrower (notably Windows
CreateProcess after shell-specific wrapping); such a request remains a
pre-start failure rather than weakening the authored bound or silently changing
execution semantics. Large or quote-dense program text should use run_script.
A missing Session leaves execution unchanged. A mismatched Session fails or, on an explicitly authorized cross-project escape path, executes without inheriting its context.
The cross-project escape is a low-level compatibility/debug control. It remains auditable when explicitly used, but model-facing ToolSpecs and flattened Action arguments do not advertise it. Read-only mismatches may continue with a factual warning and without inheriting Session context; write/shell boundaries that require the escape fail closed on ordinary model paths.
Execution intent (purpose=diagnostic, test, and so on) is evidence metadata, not
proof that a failed process is harmless. A started shell/process failure therefore
remains actionable by default. Model-facing execution and structured-validation tools
may instead declare a bounded result_expectation before execution: the omitted
success default requires ordinary success; failure expects a completed known
business failure; and observe accepts either completed known business result. The
legacy internal expected_failure / expected_failure_kind recorder fields remain a
separate compatibility/testing mechanism and are not exposed as the normal model
contract.
run_process additionally accepts a bounded accepted_exit_codes set for commands
whose exit status is itself a result (for example a boolean Git predicate). Matching
expectations change only ledger expectation/actionability classification. Validation
evidence keeps three facts separate: the real execution/validator outcome, whether the
pre-declared expectation was satisfied, and whether validation itself passed. A matched
negative/observation failure is neutral expected-result evidence: it does not become a
validation pass and cannot resolve an earlier real validation failure with the same
identity. ToolResult, exit code, effect evidence, authorization decision, and execution
state are never rewritten. Pre-start rejection, permission/guard denial, transport failure,
malformed result, timeout/cancellation, and unknown/lost outcomes remain fail-closed.
A Job admission is not a terminal match: structured validation Jobs inherit the
pre-declared expectation and classify it only when terminal evidence is materialized.
If the originating expectation evidence is no longer retained, terminal projection
falls back to the normal success-required behavior rather than guessing.
The canonical runtime has a separate, command-oriented PersistentShell model:
open_session_shell
execute_session_shell
get_session_shell_status
close_session_shell
Opening creates one real long-lived Runner-owned shell process, at most one
active shell per Workflow Session: sh/bash on Unix, or the configured
PowerShell program/profile on Windows. For an agent:<client>:<project> the Runner owns
and controls the shell. Without execution_context.resource, it runs against
the registered project host. With a named resource, the Runner opens a remote
persistent shell through that SSH resource; this requires persistent_shell +
ssh_persistent_shell, not the separate one-shot/background ssh_shell
capability, and never silently falls back to the registered Project host. Unix may reuse its OpenSSH mux;
Windows owns one direct long-lived ssh.exe channel while the remote shell remains
sh/bash. No PTY/ConPTY or terminal-control protocol is implied. Registered
Project execution is Runner-owned; the Server has no Project-local process or
persistent-shell fallback. read_only Sessions cannot open or execute a persistent
shell. The pre-0.4 inspect Session mode is retired.
Runner-project open resolution is explicit cwd/shell, then the exact Session's
default_cwd/default_shell, then the project/Runner defaults. The explicit
sh/bash override is Unix-only; Windows callers omit it and the Runner uses
the configured PowerShell program/profile, failing closed for incompatible
configuration. For an SSH persistent shell, cwd precedence is explicit open
cwd, Session default_cwd,
the named resource's default cwd, then the remote login default; the selected
Session default_shell is also inherited when shell is omitted. Profile
environment and initialization run once at open. Later commands retain the
same process's cwd, exports, unset state, umask, functions, and ordinary shell
variables. The shell record retains the execution location selected at open, so
updating Session execution defaults never redirects, moves, restarts, or
changes an already-open shell; close and reopen it to apply new defaults.
run_shell and run_job never reuse this process.
The random shell_id is bound to the exact Session, runtime project, executor,
Runner client when applicable, dialect/profile, initial cwd, and timestamps.
Every operation rechecks caller authorization and active Session/project
identity; before exec/status the Runner also rechecks its current project,
raw-shell, cwd, allowed-root, profile, and shell policy. Close remains available
for cleanup after an execution-policy change. A closed id remains terminal and
cannot address a subsequently opened shell. Explicit close is idempotent.
Session close, project disable/unregister, idle expiry, shell exit, Runner
disconnect/shutdown, and detected process/control-channel damage release the
process group and its pipes.
Commands are serialized; a concurrent command receives shell_busy. Output is
bounded independently for stdout and stderr. Completion uses transport-private
control framing with a high-entropy per-command token, never an ordinary output
marker: Unix uses inherited control descriptors; Windows uses a private control
file plus exact stdout/stderr drain boundaries. On timeout the owner performs
only the platform's safe bounded recovery attempt. If framing synchronization
cannot be proved, it terminates the owned process tree, marks the shell
poisoned/lost, returns shell_reset_required, and never writes another command
to that process.
Persistent shells are process-local and are not durable Job records. Neither a
Server nor Runner restart claims to recover or reattach one from ledger data.
There is no PTY, raw keystroke/input stream, terminal resize, WebSocket terminal
UI, or full-screen terminal application support.
The Session ledger stores bounded lifecycle and permission evidence
(shell_id, action, shell/execution state, error code, and completion flags),
never command text, stdout/stderr, the complete environment, internal shell
state, credentials, or unbounded command output.
The context is an additive serde-defaulted ledger-version-1 field, so older
ledgers load it as {} without a version bump. Startup and Session summaries
return the complete current context. Context changes record bounded structured
metadata only; no command, environment, token, or secret content is added to
the audit ledger.
work_on_project is the canonical external full-runtime start-or-continue
aggregate. The former start_coding_task tool name and advanced direct/API
schema are retired and fail closed before dispatch. No internal ToolCall
variant remains for that retired name; work_on_project calls the shared coding
workflow engine directly, with diagnostic projection controls available only to
tests rather than as a Session-selection or compatibility surface.
Requested project.instructions context on work_on_project reuses that call's
resolved Project and instruction observation. Material scopes remain independently
checked; incomplete observations are not re-read or promoted to complete. The
semantic-navigation status probe runs concurrently with required bootstrap
observations. Startup does not wait solely for LSP: not_observed with null
availability means required observations finished first. Explicit LSP tools still
obtain current provider truth.
work_on_project also owns the optional managed-worktree bootstrap without
creating a new authority or Session concept. The canonical model-facing form is
a fresh project + mode=worktree call. The Server first resolves and
reauthorizes that registered source Project, derives its authoritative
Runner/source checkout/root fingerprint, and passes that identity fence to the
Runner. The Runner re-resolves the exact registered source, validates that the
identity still matches, resolves base_ref (or the source HEAD) to an exact
commit, derives its internal managed namespace from the source checkout,
creates an isolated detached worktree, registers that worktree as an ordinary
runtime Project, and only then creates the Workflow Session on that final
Project. The model never selects the managed destination and does not need to
reconstruct client_id or an absolute source path.
client_id + path + mode=worktree remains a compatibility/bootstrap form.
Its source path still passes the ordinary Runner filesystem/path-authority
checks and it converges on the same Runner-owned managed-worktree manager. The
Runner chooses the managed namespace inside its current filesystem policy so the
resulting Project remains usable by ordinary file/shell/process tools without
special path authority. If an explicit allowed_roots policy is narrowed to the
source checkout itself and leaves no isolated sibling location, bootstrap fails
closed with managed_worktree_root_unavailable; Project-first selection never
widens the operator's filesystem policy.
Creating a managed Project from a source Project is a Project transition, not a
Session retarget. A source Project's session_id therefore fails closed with
the ordinary exact-Project Session mismatch before managed creation. The
caller continues with the returned managed Project/ref and its new Workflow
Session. The compatibility path form may exactly resume an already-authorized
managed Project; in that case the Runner re-observes the registered worktree
and source provenance rather than creating another worktree, and a provided
base_ref must still resolve to the stored exact base commit.
recording_session_id, ClientWindow, ACK metadata, source-path knowledge,
and managed operation ids do not select or authorize the Project. Finishing or
closing the Workflow Session does not remove the managed worktree or unregister
its Project; later review, commit, push, PR, handoff, and investigation remain
possible until a future explicit lifecycle operation says otherwise.
work_on_project deliberately does not use Workflow Session identity, transport
identity, a client-window key, credentials, project identity, or Server lifetime
as evidence that the current model still retains static bootstrap content. The
same wc_sess_* may be explicitly resumed by multiple independent ChatGPT
conversations. Its primary result therefore stays compact: static Project
instruction bodies and WebCodex workflow guidance are projected only when the
caller explicitly requests project.instructions and/or webcodex.workflow
through _wc.context. Omission means no static material, not an inferred
retention state. include_extension_catalog remains a separate caller-explicit
selection-metadata preference.
guidance_profile is a request-local presentation enum. Explicit selection wins;
when omitted on MCP, the current request's McpHostRuntimePolicy.profile supplies
the default (X-WebCodex-MCP-Profile, otherwise deployment WEBCODEX_MCP_HOST_PROFILE).
The MCP adapter creates an immutable runtime view; it never stores this preference
in the Session or changes shared execution ownership. Non-MCP/internal omission
falls back to direct. Available explicit values are direct, host_code_mode for
Host-supplied native orchestration in every build, or code_mode for WebCodex nested
orchestration only in Experimental Code Mode builds. Host-native guidance favors canonical batches and search_file_context,
allows independent cross-tool observations and dependent branching within one
Host cell, keeps raw ToolResults in that cell, and warns against Job polling and
stale validation after covered source changes. It does not assert that WebCodex
verified Host capability or require nested Code Mode. Exact resume may choose any available profile
without a Session transition; omission is resolved from the current transport/Host policy,
never a remembered choice. It is not persisted in Session state or event arguments and
changes no admission, authority, effects, validation or Job semantics. An unavailable
profile fails parsing. work_on_project startup and later webcodex.workflow context
refreshes use the same effective-profile resolver, preventing Direct/HostCodeMode drift.
read_current_window_activity observes persisted ActionAudit activity for the exact
ClientWindow supplied by the current adapter request. Its input cannot select
another Window. It remains available through the canonical adaptive runtime
gateway and read_tool_manifest without expanding the default direct tool inventory.
It requires runtime:read and a non-anonymous authenticated
principal, fixes that principal,
and reapplies current Project visibility to every event and Workflow Session
link. Runtime Console and the model tool share the sanitized event and timing
projection. The tool scans at most 200 records and returns at most 50 events
within a smaller serialized byte ceiling. It exposes descriptive timing and
counts only, without arguments, outputs, paths, credentials, or principal IDs.
response_handed_at_ms means WebCodex constructed the response and handed it
to the HTTP framework or returned from the handler. It does not prove client
receipt, MCP Host receipt, ChatGPT ingestion, or model continuation. A
next_call_gap_ms exists only when a later canonical meaningful call was
actually observed; elapsed wall time alone never supplies that value. The
diagnostic call is nonmeaningful and excludes itself from active requests.
Repository instruction files are still re-observed and Session metadata/delta
status still update even though instruction bodies are absent from the primary
output. The default bounded
extension catalog contains selection metadata only: Skills are drawn from the
same canonical union as list_skills (project .agents/skills, Runner-configured
live skills.roots, and active Runner-managed Skill Store packages), while
Plugins are limited to ready committed providers whose configured cwd matches
the authoritative Project root. Skill bodies, Plugin schemas, bindings, native
paths, commands, environments, and provider identities are not projected; using
a selected Plugin still requires plugin_tool describe before call.
Startup selection is strict and ordered:
The durable projection stores only:
The canonical hash input is the principal kind/id, transport, already-hashed
stable window identity, resolved project, and already-hashed canonical
repository root. It uses fixed field order and length prefixes under
webcodex.workflow-current-binding.v1. Raw MCP session ids, hosted
conversation ids, cookies, credentials, authorization headers, and repository
paths never enter this projection, and neither binding hashes nor component
hashes are returned to the model.
The binding field is an additive, serde-defaulted field in ledger version 1, so
older ledgers load it as empty without migration and keep their existing
Session events/messages. Restore accepts only bounded, lowercase SHA-256 keys
that reference known active wc_sess_* records. Malformed, duplicate,
conflicting, missing, closed, project-mismatched-on-lookup, and excess entries
are discarded without rejecting valid Session data. Internal status exposes
only bounded counts (durable_binding_count, restored_binding_count,
discarded_binding_count) and never a binding key.
This durable binding remains Workflow Session state and is separate from ActionAudit/window observations. It does not create a second coding-task lifecycle, and window correlation never infers or mutates a Workflow Session.
This is not the same state machine as Action Audit Sessions. Lifecycle
tools and error kinds (unknown_session_id, session_retention_expired, session_closed, mode denials,
guard failures) apply only to Workflow Sessions.
continuation_feedback is a deterministic, read-only projection surfaced
by finish_coding_task, read_session_handoff, and (as validation_delta)
read_validation_summary. The internal coding-startup implementation also consumes
this projection while building canonical work_on_project startup state. It is derived only from existing
persistent state — the Workflow Session ledger, validation evidence, bounded
Job metadata, and the session message board — and it is never a substitute for
a finish_coding_task verdict.
- Read-only projection contract: building it directly never executes shell,
reads project files, enqueues Agent/Runner requests, mutates the ledger,
refreshes activity, consumes or auto-resolves guidance, or calls an LLM.
Canonical coding startup still appends its legitimate new
task_instruction. Public MCP, REST, or runtime dispatch also records the enclosing tool's uniformtool_call_started/tool_call_finishedtelemetry, soevents_total,updated_at, and activity telemetry are not required to stay unchanged across a public call. Those recorder facts are separate from the projection's business semantics. - Startup describes the previous attempt: for reused, explicitly resumed,
and restored-after-restart sessions, canonical
work_on_projectstartup snapshots the pre-instruction state before appending the new instruction, socontinuation_feedback.attemptdescribes the previous attempt's bounded, redacted instruction excerpt, activity, changes, current unresolved failure identities, and validation — not the empty new attempt. When an unresolved identity is available, the first suggested action names that concrete target. A fresh session reportsstatus = not_applicable,reason_code = fresh_session. - Attempt boundary: the attempt window is segmented by the most recent
task_instructionretained in the ledger window. When that instruction has been evicted by the bounded event limit, the boundary is reported assource = unavailable,reason_code = attempt_boundary_evicted, andevent_range.complete = false— the projection never masquerades a truncated retained window assession_startwithcomplete = true. - Exploration workset:
attempt.explorationprojects only successful, structured evidence from focusedread_files,search_project_texts, and typed LSP navigation calls. The existing ledger retains only a bounded set of validated project-relative paths; it never retains search patterns or previews, file contents, symbol/hover/diagnostic bodies, arbitrary result JSON, shell commands/output, or the absolute repository root for this workset. Paths are deduplicated newest successful observation first. Enumeration tools such asread_project_overview,list_project_files, andlist_project_tracked_files, Git diff lists, failed calls, error text, and shell output are not exploration evidence. The workset is segmented by the same attempt boundary; when that boundary was evicted,exploration.complete = falseas well. - Continuation reuse, not execution: automatic continuation, explicit
resume, read-only to normal mode upgrades, and ledger restoration reuse the
prior attempt's workset. Startup returns at most 3 paths in
minimaland 12 instandard(including the core embedded byfull); full continuation feedback returns at most 100 with the real total and truncation state. This is a hint for model judgment only: startup never reads, searches, or navigates those paths automatically. - Handoff is independent of the display limit:
read_session_handoffbuilds its display list from the caller-suppliedlimit, butcontinuation_feedbackreads an independent bounded evidence snapshot (the maximum retained event window), so a small display limit cannot shrink the attempt boundary.include_validation = falsedoes not fabricate validation; it reportsvalidation_not_requestedrather thannot_run. - Validation delta comparability:
validation_deltaisavailableonly when the latest and prior runs are proven comparable — same validation kind/tool/cwd and structured scope (package, filter, features, targets, purpose), with complete evidence on both sides and a consistent parser identity. Otherwise it reports a stable reason code (no_previous_validation,validation_scope_changed,previous_evidence_incomplete,current_evidence_incomplete,parser_changed,parser_identity_unavailable,test_identity_unavailable,insufficient_scope_identity,validation_not_requested). Count deltas are signed integers (a decrease in passed tests yields a negativepassed_delta); zero-test success never resolves a prior test failure. - Async terminal validation evidence: structured validation Job metadata carries the same opaque
validation_target_idas the originating validation attempt. When an authorized validation-summary or Runtime Console Session read observes a retained terminal Job, WebCodex idempotently materializes one boundedvalidation_job_terminalevent in that exact Workflow Session before projecting validation state. Idempotence does not depend on that event remaining in the 200-event Session FIFO: the version-1 ledger also persists a serde-defaulted exact Job-id marker set bounded to the Runner authoritative terminal inventory limit (64), and a new materialization evicts only markers absent from the current terminal-candidate snapshot. The marker check, marker insertion, and event append commit under one Session-store mutation, so concurrent reconcilers append at most once and restart restoration keeps the same suppression identity. Terminal reconciliation also serializes authoritative candidate-snapshot acquisition through marker/event materialization within one runtime: a later snapshot cannot commit first, so an older snapshot never gains eviction authority over a marker established from newer inventory. Synthetic evidence uses the authoritative Jobfinished_at; reconciliation never advances Session activity to the wall-clock read time. This is recovery/materialization only: it never re-runs validation, never treats acceptance/handoff as terminal success, and never exposes raw Job output. A later terminal success for the same structured target can therefore resolve an older retained failure even after the acceptance event is gone; the materialized terminal evidence then follows normal Session persistence/retention across Server restart. - Cargo test-count postconditions: a
cargo_testcaller may require a bounded minimum count withrequire_tests/min_tests. These are request-scoped evidence assertions: they are persisted with local and Runner Job metadata so the same invocation is evaluated consistently after handoff or restart, but they are deliberately excluded from the structured validation target identity and do not become durable Workflow Session requirements. If the validator exits successfully while the requested count is below the minimum, or bounded output cannot prove the count, the immutable raw ToolCall failure remains in history while validation projects the execution separately as an evidence gap rather than a code/test correctness failure. A later sufficient validation of the same execution target therefore need not inherit the earlier assertion threshold. Omitted assertions preserve zero-test execution compatibility. - Opaque scope identity:
comparison.scope_identityis a domain-separated, opaque stable identity (validation_scope:v1:<sha256>) over the normalized structured scope. It never returns a raw command, absolute path, or test filter — command text is not re-exposed through another field. - Jobs report only proven status: the
attempt.jobsblock reports counts computed over the full bounded active Job aggregate, never the truncatedrecentlist, so a hidden recovering job is never misreported as healthy. Fields that cannot be reliably proven are not reported. - No new persistence model: continuation feedback introduces no new durable table and no second attempt state machine. Exploration adds only a serde-defaulted field to the existing version-1 event ledger, so older ledgers restore it as empty without a version bump; feedback remains a projection over that existing state.
record_external_observation and list_external_observations expose bounded,
explicitly authorized external reports for one exact Project and Workflow Session.
They do not append synthetic native execution/validation facts, consume the native
Session event tail, mutate Goal state, or derive a Session from a window or local
directory. The report's adapter/event IDs provide scoped replay correlation, not
authentication or execution proof. Unknown outcomes remain unknown. The first
adapter has no durable source sequence, so list results explicitly report incomplete
coverage and must not be interpreted as complete capture or source execution order.
See ../../integrations/codex/README.md for
the optional adapter, capacity/recovery contract and unverified Host boundaries.
The authorized read_session_handoff handoff brief now includes a bounded
external_observations section, separate from native progress and validation.
It shows the last five retained reports in server timestamp and identity order,
with exact adapter/event IDs, tool,
reported status, and server receipt time, plus total/returned/truncated and
unknown counts. The section's provenance is always external_report and its
coverage.complete is always false: receipt ordering cannot prove source
execution ordering or complete capture. No Session Project, unavailable store,
or failed read produces status=unavailable with null observations and counts,
never an apparent empty result. The exact Project and Session association comes
from the surrounding handoff output and handoff_brief.session.session_id.
list_sessions(project, lifecycle?, offset?, limit?) discovers retained Active
and Closed identities when the caller has no exact Session selector, including
a new Host Window/account without workflow.resume links. The kernel requires
runtime:read and project:read; Runtime reauthorizes the exact Project and
creation-time Session owner. Filtering occurs before totals and pagination, so
foreign owners cannot affect the count or hide rows. Default limit is 10,
normalized to 1–20; titles are redacted and capped at 240 Unicode characters and
the JSON output at 32 KiB. Offset pagination is not a frozen snapshot. Short refs
remain principal-scoped selectors. The caller must explicitly choose a returned
identity; discovery never starts, resumes, or selects recent work. Closed history
can be read, while mutation still requires an Active Session. A retention
tombstone is not a retained Session: list_sessions omits it from rows and
totals, does not mint a session_ref for it, and has no expired lifecycle
filter. Exact expired identity is visible only to a later lookup that already
holds that canonical id or the matching principal-scoped ref.
Built-in workflow guidance instructs models to save agreed decisions and current
progress/remaining work through explicit post_session_message calls at recovery
boundaries. These retained reports supplement native tool/validation evidence.
They do not capture unsent Host conversation or private model reasoning. See the
continuity guide for the user and operator flow.
read_session_handoff and finish_coding_task return the same version-1
handoff_brief, built by one shared pure projection. It is the compact,
model-friendly view for a new window, a new Agent, or a human receiver;
continuation_feedback remains the more detailed evidence available from the
explicit diagnostic handoff view and closeout tools. The
brief is not Session replay, does not reconstruct chat or hidden model
context, and does not decide that implementation work is complete.
The builder consumes only the bounded Session summary, discussion, continuation feedback,
workspace, validation, Job, guidance, exploration, external-report, and suggested-action
snapshots that its caller already obtained. It performs no shell, Git, file,
search, LSP, Agent, or Runner request; does not refresh activity, consume
guidance, append a ledger event, or call an LLM; and stores no new Session
data. work_on_project intentionally does not return handoff_brief, so the
standard startup core's worst-case size does not grow.
A direct internal read_session_handoff(...) call does not add business
events beyond those snapshots. Calls through MCP, REST, or runtime dispatch
remain subject to the uniform recorder and normally append exactly
tool_call_started and tool_call_finished. This telemetry is not guidance
consumption or a handoff-builder mutation, and read_session_handoff must
not receive a recorder bypass.
The projection has these stable bounds and semantics:
- root and latest task instruction excerpts reuse the existing Session
credential redaction (including token, Bearer, and client-secret styles) and
are capped at 600 Unicode characters. Unix,
Windows-drive, UNC, and
file://locations; shell commands and parameters; fenced and inline code; and ordinary task prose remain available as useful handoff context. The latest excerpt is the latest retainedtask_instruction; when it equals the root, the same excerpt is returned. It is null when no such retained event exists. Itstruncatedflag is true only when credential redaction, the 600-character bound, or final byte-budget reduction changed the returned excerpt. - changed paths reuse continuation changes (12 maximum); recent files reuse
the attempt exploration workset (8 maximum, deduplicated newest-first);
unresolved failure identities are capped at 5; deterministic next actions
are capped at 5. Each bounded evidence list preserves
total/returned/truncated. Recent files are only continuity hints, not complete history. task.decisionsandtask.recent_progressreturn at most five newest retained notes per kind, including message identity, status, supersession and excerpt. The discussion snapshot already caps message bodies at 240 characters plus its truncation suffix; the brief additionally applies credential redaction and a 600-character ceiling. Source truncation, redaction and byte-budget removal are explicit.totalcounts retained notes, not unlimited history; unavailable discussion produces null totals anddiscussion_unavailablein the basis. These notes remain reports, never instructions, new authority, execution proof or completion judgments. The shared 8 KiB budget may omit older notes.- external reports are an independent read-only claim section capped at five
identities. A byte-budget reduction updates
returnedandtruncatedas it removes reports;unknown_countstill counts all retained reports. Store unavailability is local to this section and does not change native closeout. progress.stateis selected in order: a non-mutable lifecycle isclosed; a workspace conflict, blocking/recovering Job, unresolved validation failure, or open risk isblocked; workspace changes without a proven latest validation pass areneeds_validation; missing critical evidence isinsufficient_evidence; otherwise an active Session isready_to_continue. A dirty worktree alone is not a blocker, questions and todos are counts rather than blockers, and terminal-pending Jobs are nonblocking.- workspace is
available,not_requested, orunavailable.include_workspace=falsenever causes an implicit Git query. Validation ispassed,failed,not_run,not_requested, orunavailable;include_validation=falsenever masquerades asnot_run. basis.completeis false whenever a sorted fixedreason_codesentry identifies omitted, unavailable, or raced recovery evidence, including an evicted attempt boundary or an external-report change during snapshot assembly. Internal error text is never a reason code.
The complete object is checked against its actual serialized JSON size and hard-capped at 8192 bytes. Stable reduction removes recent files, changed paths, failure identities, next actions, and then instruction excerpt characters while retaining lifecycle/mode, progress and validation status, attention counts, basis, and deterministic/LLM flags.
A new window can start a new Session normally and then explicitly read the old
Session with read_session_handoff(session_id=...). Explicit
resume_session_id remains available when the caller truly intends to resume
the same active Session and continues to obey the existing identity,
lifecycle, project, guard, and binding rules.
For deliberate coordinator/worker delegation across separate windows, keep the Sessions independent and use the existing handoff plus message-board primitives; see Manual Multi-Window Collaboration.
HTTP Action call auditing and operator-facing grouping of external API requests. It answers “what HTTP/API actions happened in this audit window?” — not “what is the coding task ledger for this repo work?”.
- Group HTTP Action / REST audit events under one audit session
- Persist action audit records (endpoints, status, durations, redacted summaries)
- Snapshot non-secret caller attribution at event write time: canonical credential kind, optional stable user id, and OAuth-only client id
- Idle open-session reuse and explicit close for operator audit views
- Aggregate stats for read-only audit APIs, with explicit bounded-scan coverage
Model-visible runtime tool calls reuse the existing Action Audit event as the
durable/queryable sink for low-cardinality ergonomics telemetry. No second
telemetry table or recorder is created. The shared ToolRuntime kernel owns the
normal timer for a registered model-visible tool; the transport that already owns
the outer Action Audit row then finalizes one summary.model_ergonomics object
from the final model-facing ToolResult projection, retaining the canonical
closed execution state captured before late model compaction. Execution
ActionAudit receipts retain definition-authorized bounded lifecycle, validation
counts/assertion verdicts, and source classifications, never raw command/script/stdout/stderr,
diagnostic bodies, or source fence identity.
Workflow Session evidence is consumed before projection. A transport may use a bounded
fallback timer only after a runtime tool identity is established when MCP-only
validation rejects the call before kernel entry or the MCP hard dispatch timeout
prevents kernel completion. Batch items do not create generic invocation records,
and hidden/internal helpers do not start this telemetry.
The current durable generic record uses schema_version = 12. Older telemetry rows
remain naturally queryable and are not migrated or backfilled. The current
record contains:
- base generic fields:
tool_name,tool_category,success,duration_ms, nullableserialized_result_bytes, nullable structurederror_kind,failure_kind,recovery_kind, and authoritative closedexecution_state; - optional
finish_summary_onlyand boundedwork_on_projectrequest-shape facts; - optional edit measurement fields:
edit_surface,edit_outcome, andedit_conflict_kind. - optional
readinesssummary for successful transient waits:mode,requested_jobs,unique_jobs,waited_ms,wait_state,ready_count,pending_count. It retains no logs, paths, credentials, or observation tokens; sub-millisecond waits are not mislabeled as already-ready. - optional bounded Job convergence counts and salted exact-relation events; see Server-only convergence measurement. Exact serial predecessor links remain outer Action Audit metadata; they do not infer model turns or supply Workflow Session authority.
Invocation behavior is recorded in invocation: presence of record/ack/ack_ref/
reply/resolve/context/control, bounded ACK/request/unknown-key counts, a boolean
map for the seven known context keys, and closed before/after-success control
kinds. MCP captures the supplied envelope before stripping it, including explicit
empty arrays; other kernel adapters project their typed invocation metadata.
bootstrap on work_on_project records final material availability, instruction
content inclusion/truncation/source count, observed instruction/workspace/semantic
status and Skills/Plugins availability/count/truncation. Null means unobserved,
not false. Counts saturate at 1024. instruction_read on read_files records only
AGENTS.md/CLAUDE.md basename booleans, enabling later sequence analysis without
persisting paths. These are observations, not a Runtime duplicate-call detector.
Metadata trace/ActionAudit retains these facts without full trace. Full trace continues to own raw/effective debugging payloads. No Session/message/Goal/task IDs, selectors, opaque refs, reply/resolution/control text, unknown key strings, paths, branches, hashes, fingerprints, revision or idempotency tokens enter these new facts. Request presence never proves successful authorization or delivery; use final result/material facts separately. Older rows are not backfilled. Generic final-result byte size and latency remain available without retaining revision values or recovery bodies.
edit_surface is the closed label canonical | advanced. edit_outcome is a
bounded structured classification derived from authoritative edit result fields.
edit_conflict_kind is limited to the closed allowlist multiple_matches,
match_not_found, occurrence_out_of_range, overlapping_edits, and
sha256_mismatch; unknown values are omitted and arbitrary error prose is never
promoted into the durable record.
serialized_result_bytes is the UTF-8 byte length of the exact final
model-facing ToolResult JSON object (success, output, and error only when
present), serialized with the normal serde JSON representation. It is not a
character count, Rust memory size, stdout/stderr estimate, database-row size, or
HTTP/JSON-RPC/MCP framing size. MCP finalizes the count from the final
structuredContent ToolResult after MCP-only image/resource framing, so bytes
removed from the model-facing ToolResult are not charged. If a registered
model-visible tool identity has already been established but a deterministic
pre-result rejection occurs (for example invalid arguments, insufficient scope,
or MCP wrapper validation), the invocation is still counted with
serialized_result_bytes = null; canonical edit tools classify those definite
rejections as edit_outcome = rejected.
The MCP outer hard dispatch timeout is different: dispatch may already have
started and the terminal edit result is unavailable. It is recorded with
error_kind = dispatch_hard_timeout, and a canonical edit remains
edit_outcome = uncertain rather than being interpreted as a definite rejection.
No retry authority, state-change claim, or rollback claim is derived from that
measurement. There is currently no authoritative generic final-result truncation
fact, so generic result_truncated is deliberately not recorded;
tool-specific truncation fields keep their existing meanings.
Classification consumes structured ToolResult fields only. It never derives a
kind from arbitrary English error prose. The generic record stores no tool
arguments, commands/argv/scripts/stdin/stdout/stderr, ToolResult body or error
prose, paths/cwd/project ids, query/file/clipboard/Computer contents, Session
message/prompt/answer bodies, credentials, native identities, or arbitrary user
text. Existing Action Audit correlation/attribution fields remain separate
pre-existing audit data; P1a does not copy them into model_ergonomics.
edit_tool_telemetry remains the edit-specific structured tracing enrichment.
An edit invocation therefore has one generic Action Audit invocation record plus
its existing edit-specific enrichment, not two generic counts. Workflow Session
tool_call_started / tool_call_finished events remain a separate workflow
ledger and are not the persistence source for this aggregate ergonomics data.
Telemetry is observation-only and failure-isolated. Failure to serialize the bounded generic projection or to persist the Action Audit row is dropped/warned without changing the tool's success, output, error, permission, execution state, retry safety, Job lifecycle, or Computer authority.
The real MCP protocol method tools/list has a separate durable measurement;
it is not stored under summary.model_ergonomics and must not be confused with
the runtime list_tools tool. Each executed JSON-RPC request with an id
creates one metadata-only ActionAudit row:
endpoint = /mcp
action_name = toolsList
operation = mcp_tools_list
A JSON-RPC notification has no executed ActionAudit row. On success the durable summary shape is:
{
"transport": "mcp",
"tool_surface": {
"schema_version": 1,
"protocol_era": "legacy | stateless_2026",
"compact_schemas": false,
"tool_count": 0,
"serialized_tools_bytes": 0,
"serialized_result_bytes": 0,
"gateway_tool_included": false
}
}tool_count comes from the final caller-visible tools array.
serialized_tools_bytes is the exact JSON UTF-8 byte length of final
result["tools"]; serialized_result_bytes is the exact JSON UTF-8 byte length
of the final MCP result object, after stateless result metadata has been added
when applicable but excluding the JSON-RPC envelope. gateway_tool_included
reflects the actual final response rather than theoretical authorization. The
serialized fields are transport telemetry, not a model-token/context measurement: an
Apps-enabled response may include ui.visibility=["app"] descriptors and Host-only
_meta presentation data that ordinary model tool selection does not consume. Size
regression tests therefore track model-visible compact discovery separately from raw
MCP transport overhead.
durable summary never stores the tool-schema body, tool-name array,
descriptions, arguments, or scopes. ActionAudit persistence failure remains
non-blocking and cannot change the MCP protocol result.
For dogfood analysis, read bounded Action Audit events through
/api/audit/session or query SQLite action_events.summary_json, select rows
with summary.model_ergonomics, and aggregate by tool_name. The raw bounded
records are sufficient for invocation/success counts, duration p50/p95,
serialized-result mean/high percentiles, and structured error/recovery-kind
distributions in later SQL/Python analysis; no dashboard or analytics service is
part of this contract.
| Aspect | Contract |
|---|---|
| ID form | UUID string (or client-supplied id via headers/query), not wc_sess_* |
| Request affinity | Headers x-action-session-id / x-webcodex-session-id, or query action_session_id |
| Default creation | Server may create a new UUID when no open recent session is reused |
| Durable caller attribution | principal_kind, optional principal_user_id, OAuth-only oauth_client_id; legacy rows remain NULL and are never inferred from target project or session |
| Stats exposure | /api/audit/stats aggregates credential kinds and OAuth client usage; ordinary /api/audit/session event views do not expose principal/user/client attribution fields |
| Aspect | Contract |
|---|---|
| Internal module | action_audit_sessions (crate-private; formerly the module path action_sessions) |
| HTTP handlers | audit_http under /api/audit/* |
| Persistence | SQLite tables action_sessions and action_events |
| Related types | ActionSessionRecord, ActionEventRecord, DB helpers in db/audit.rs |
- An audited HTTP request arrives; optional explicit audit session id is read from headers/query.
get_or_create_active_sessionattaches the event to an existing open session (explicit id, or recent idle-open session) or creates a new one.- Events are written to SQLite; session aggregate counters update. Caller attribution is snapshotted from the authenticated request context before the write and never reconstructed later from execution targets.
- Operator APIs list sessions, fetch one session with events, or compute stats. Stats report scanned/available event coverage and fail on database read errors instead of silently presenting a partial aggregate as complete.
- Sessions may be closed (
status = closed); idle open sessions time out for reuse purposes (ACTION_SESSION_IDLE_TIMEOUT_SECS).
This lifecycle is orthogonal to Workflow Session start/finish tools.
- Not a coding / workflow session
- Not a substitute for canonical coding-startup /
work_on_projectevidence - Not an input to
read_session_summary, message board, orfinish_coding_task - Not automatically correlated to any
wc_sess_*
The two systems:
- Use different ID namespaces
- Use different storage backends
- Expose different APIs (runtime tools / MCP vs
/api/audit/*) - Define different open/close and failure semantics
There is no shared session state machine, no shared store, and no
requirement that a request participate in both. A single HTTP call may
incidentally touch both only when a tool invocation both (a) records workflow
ledger evidence via session_id / recording_session_id and (b) is wrapped by
HTTP action audit middleware — those are still two separate writes.
Do not:
- Fold Action Audit Sessions into
tool_runtime::sessions - Store workflow ledger events in SQLite
action_*tables - Reuse
wc_sess_*as SQLiteaction_sessions.session_idby convention - Drive workflow guards from audit session status, or audit close from
finish_coding_task - “Simplify” by making one ID type serve both products
Merge would couple coding-task continuity to HTTP transport audit, break identity rules, and blur security/guard boundaries. Keep two implementations.
The standing optional-correlation contract is:
Until that design is implemented, code must treat the systems as unlinked.
| Forbidden | Why |
|---|---|
Infer wc_sess_* from current HTTP Action Audit Session |
Wrong namespace; audit ids are not workflow ids |
| Fall back to Action Audit Session when Workflow Session is missing | Breaks unknown_session_id and explicit-wins |
Treat /api/audit/session payload as coding-task summary |
Different evidence model and redaction rules |
Pass audit UUID as tool session_id expecting ledger semantics |
Unknown or wrong session; not a supported bridge |
The following names are part of storage, HTTP, or external API contracts. Internal Rust module renames for clarity are allowed; these surfaces are not renamed without an explicit compatibility migration:
- Table:
action_sessions - Table:
action_events - Index:
idx_action_sessions_status_last_event - Column names and migration history in
db/schema.rs/db/audit.rs
POST /api/audit/sessionsPOST /api/audit/sessionPOST /api/audit/stats- Request affinity:
x-action-session-id,x-webcodex-session-id, queryaction_session_id
- Audit session records (
session_id,status, counters, timestamps, …) - Audit event views (without principal attribution fields) and stats aggregates, including coverage plus credential-kind/OAuth-client usage summaries
- Workflow tool fields:
session_id,recording_session_id, session mode valuesnormal/read_only. A pre-0.4 persisted v2 row containingmode = "inspect"is malformed under the current enum and is discarded row-closed without reinterpreting it asnormal. - Error kinds such as
unknown_session_idandsession_retention_expired
- Canonical tool schemas and Runtime API recorder metadata distinguish
business
session_idfromrecording_session_id - MCP tool input schemas for session tools
- Runtime tool names (
start_session,work_on_project,read_session_summary, …); retired wire names are not part of this external surface
| Layer | Current clarity practice |
|---|---|
| Docs / design | Prefer Workflow Session and Action Audit Session |
| Rust module path | tool_runtime::sessions vs action_audit_sessions |
| SQLite / HTTP / JSON | Keep existing action_sessions / session_id names for compatibility |
Renaming a crate-private module path does not change wire contracts. Renaming tables, routes, or serialized field names does.
AGENTS.md— executable Session invariantsarchitecture-decisions.md— dual-model summaryruntime-api-guidelines.md—session_idvsrecording_session_idon Runtime HTTP../ARCHITECTURE.md— module map and Workflow Session overview