Capabilities · 4. Continuity
Continuity is the set of capabilities that let a piece of work survive the thing that was doing it. A process exits, a context window fills, a run is picked up by a different agent tomorrow, and the question in every case is the same: what did the last attempt leave behind that this attempt can use?
This chapter is Explanation. It says why each of these eight capabilities exists, what changes once it is on, and what it deliberately does not do. It does not restate request shapes, parameter lists or route tables; those live in Sessions, cases and handoffs and the sessions API plane, and each explainer links to the right place at the end.
Read it in order if you are designing how your agents hand work to each other. Read a single section if you already know which capability you are evaluating.
| Capability | What it carries forward | Status | |
|---|---|---|---|
| 4.1 | Sessions | An arbitrary JSON working state, per agent, across restarts | SHIPPED |
| 4.2 | Session checkpoints | A bounded resumption point written mid-run | SHIPPED |
| 4.3 | Cases | Past attempts at similar tasks, and whether they worked | SHIPPED |
| 4.4 | Handoffs | A whole run's state, facts and work ids, in one envelope | SHIPPED |
| 4.5 | Decision records | A choice and its rationale, where the next session looks | SHIPPED |
| 4.6 | Constraints and action checks | The rules a run must respect, checkable before acting | SHIPPED |
| 4.7 | Autonomy contract | A typed statement of how far this caller may act alone | FLAG, default off |
| 4.8 | Artefact parking | A large output, out of context, behind a reference | FLAG, default off |
The pattern across all eight is worth naming once. Continuity in this daemon is something an agent writes deliberately, not something the system captures for it. Nothing here observes your run and infers what mattered. That is a design position, not an omission, and each explainer below states the price it pays for it.
4.1 Sessions that survive a restart
Status · SHIPPED · Reached through · MCP
get_session,save_session,list_sessions,delete_session,archive_session,unarchive_session·GET/PUT /v1/sessions/{id}/state· Console → Work › Sessions · Who it is for · both
What it does. A session is one arbitrary JSON document of working state, held under a session id and namespaced per agent. You write it with save_session and read it back with get_session, including after the daemon process has restarted, because every write is appended to a journal that is replayed at boot. Archiving hides a session reversibly; a TTL reaper clears the rest.
Why it works this way. The daemon deliberately does not model what is inside a session. The rejected alternative was a typed session schema with fields for plan, progress and open questions, which would have made the store legible at the cost of forcing every harness into one shape and every schema change into a migration. An opaque JSON blob keeps the daemon out of the business of knowing how your agent thinks. The price is that the daemon cannot help you when the blob is wrong: it will not validate it, will not merge two writes, and will not tell you that the shape changed.
What changes for you.
- As an agent: resumption state is something you write, not something you hope the harness kept. The first call of a run is a read of your own last state, and the last call is a write of what the next run needs.
- As an operator: live and archived sessions are visible in the console, so "what is that agent in the middle of" is answerable without reading a transcript.
What it does not do.
- It does not fsync. A
save_sessionthat returned success can be lost to a power failure, and there is no torn-tail repair on the session journal and no size cap on the document. - It is not shared state. Session ids are scoped per agent by
scoped_session_id(scope.rs:123), so two agents using the same logical id get two different documents, not one contended one. - It is not a lease. Sessions do not coordinate concurrent work; path leases do, and they are chapter 5 of this set.
Where the detail lives. Daemon §12.1 for the record, the lifecycle and the durability boundary; session_store.rs:29 for the stored shape.
4.2 Budgeted session checkpoints
Status · SHIPPED · Reached through · MCP
session_checkpoint· Who it is for · agent
What it does. A checkpoint is a bounded resumption point written part-way through a run: enough to pick the work back up, sized so that picking it back up does not cost the whole context window. session_checkpoint (sessions.rs:77) is one of only six tools where token_budget is hard-required, and the call fails without it.
Why it works this way. A checkpoint exists to be read back into a context window, so an unbounded checkpoint is a checkpoint that cannot be used. The rejected alternative was to store everything and truncate at read time, which pushes the decision about what matters to the moment you have the least information about it: mid-recovery, in a fresh process, with no memory of why any of it was written. Making the budget mandatory at write time forces that judgement to happen while the agent still holds the run in its head. The trade-off is real friction: the tool refuses a call rather than guessing a default.
What changes for you.
- As an agent: you decide what is worth resuming from while you still know, and you can checkpoint often, because each one is cheap by construction.
- As an operator: a run that dies half-way leaves behind something bounded and readable rather than a partial transcript.
What it does not do.
- It does not snapshot the conversation. It stores what you pass and nothing else; there is no capture of tool calls, model output or context.
- It does not clamp what you write. The budget is a declared bound, and budget handling is not uniform across the MCP surface; the per-tool rules matter and are documented, not inferred.
Where the detail lives. MCP overview §11.11 for which tools require, default or ignore a budget; MCP tool reference §12.14 for the parameters.
4.3 Procedural memory: cases
Status · SHIPPED · Reached through ·
POST /v1/cases,POST /v1/cases/retrieve· Who it is for · agent
What it does. A case is a record of one attempt: the task, the action taken, the outcome, whether it succeeded and a reward score. Later, an agent facing a similar task retrieves the closest previous cases, optionally only the ones that worked. This is learning from experience without touching model weights.
Why it works this way. The problem is that an agent fleet re-derives the same approach, including the same mistakes, every time it meets a familiar task. The obvious alternative is fine-tuning, which is expensive, slow to iterate, opaque about which example changed which behaviour, and impossible to undo for a single bad lesson. A case store keeps the lesson as data: you can read it, delete it, and see exactly which precedent influenced a decision. The trade-off accepted was that retrieval quality is now a retrieval problem rather than a training problem, and today that retrieval is deliberately simple.
What changes for you.
- As an agent: before starting a task, you can ask what happened the last five times something like it was attempted, and weight your plan accordingly.
- As an operator: a recurring failure becomes visible as a cluster of unsuccessful cases rather than as a mood.
What it does not do.
- Retrieval is not semantic.
similarity(case_store.rs:289) is lexical token overlap, so a precedent phrased differently from your query will not be found. - There is no similarity floor. A retrieval always returns up to
top_kcases, however unlike your task they are; the caller has to judge relevance, because the store will not. - There is no MCP tool for cases. An agent reaches them over HTTP.
Where the detail lives. Daemon §12.2 for the record and the retrieval maths; retrieve_similar for the implementation.
4.4 Agent-to-agent handoff packages
Status · SHIPPED · handoff telemetry behind
CORECRUXD_HANDOFF_OBSERVATIONS, default off · Reached through · MCPcreate_handoff,accept_handoff·POST /v1/workbench/handoff-v2· Who it is for · agent
What it does. create_handoff bundles a session's working state, the facts it referenced, the work items it touched and a structured task record into a single content-hashed envelope. accept_handoff takes that envelope on the other side and unpacks it into the receiving agent's context. The intent is that a run changing hands loses nothing that was written down.
Why it works this way. Handoffs between agents normally happen in prose: one agent writes a summary, the next agent reads it, and everything the summary omitted is gone. That failure is not a model quality problem, it is a data structure problem, so the fix is a structured envelope rather than a better prompt. Two alternatives were rejected. A shared mutable workspace, where both agents read and write the same live state, removes the handoff but replaces it with contention and makes "what did the outgoing agent actually believe" unanswerable. Passing raw session ids, so the receiver reads the sender's state directly, breaks the moment the two agents are on different daemons or different tenants. A self-contained envelope travels, and the content hash makes it checkable that what arrived is what left.
The accepted trade-off is the one you have to read carefully: this envelope is authenticated, not signed. It is protected by a symmetric MAC (handoff.rs:282), which is a genuine integrity control against a party that does not hold the key, and is not evidence of authorship, because anyone who can verify a handoff can also mint one.
What changes for you.
- As an agent: handing over is one call, and the receiving agent starts with the same facts, the same work ids and the same task record you had, rather than with your summary of them.
- As an operator: a multi-agent run has explicit seams. You can point at the envelope that passed between two agents instead of reconstructing the boundary from two transcripts.
What it does not do.
- The signature is a symmetric MAC, not a signature. It does not prove which agent created the envelope. Treat it as tamper-evidence in transit between parties that already trust each other, never as attestation of origin.
- There is no expiry and no replay protection. An envelope that was valid once stays valid, and an accepted handoff can be accepted again.
- With the handoff secret unset, the key is regenerated on every daemon restart, so envelopes do not survive a restart of the daemon that minted them.
- The Pro
handoff-v2HTTP surface is a separate, unsigned surface and is entitlement-gated; it is not the MCP path described here.
Turn it on. Nothing is required for the core path. Handoff telemetry is behind CORECRUXD_HANDOFF_OBSERVATIONS, default off.
Where the detail lives. Daemon §12.3 for the package format, the accept-path checks and what is lost on accept; MCP tool reference §12.20 for the two calls.
4.5 Decision records
Status · SHIPPED · Reached through · MCP
record_decision· Who it is for · both
What it does. record_decision (decision.rs:19) writes a decision and its rationale into a session-scoped namespace, which is to say: into the place the next session will actually look, rather than into a chat log nobody re-reads.
Why it works this way. The expensive thing about a lost decision is not the decision, it is the re-litigation. A fresh session with no record of why an approach was abandoned will propose it again, confidently. Storing decisions as ordinary facts under a predictable namespace was chosen over a dedicated decision-log subsystem precisely because it inherits everything the fact store already does: versioning, recall, privacy scoping and export. The trade-off is that a decision record has no special standing. It is a fact that happens to be about a choice, and the daemon will not stop you contradicting it.
What changes for you.
- As an agent: the rationale you had at the moment of choosing is recoverable later, by you or by a sibling session, without re-reading a transcript.
- As an operator: "why did it do that" has a written answer that predates the outcome, which is a materially different artefact from an explanation generated afterwards.
What it does not do.
- It does not enforce anything. Recording a decision does not constrain a later action; that is 4.6, and even that is advisory.
- It is not a receipt. A decision record is a self-report by the agent that wrote it, with no signature and no independent corroboration.
- There is no schema for the rationale. Whether it is useful in three weeks depends entirely on what the agent chose to write.
Where the detail lives. MCP tool reference §12.21 for the parameters and the namespace.
4.6 Declared constraints, pre-flight checks and action enrichment
Status · SHIPPED · Reached through · MCP
declare_constraint,get_constraints,check_constraints,enrich_action·POST /v1/actions/enrich· Who it is for · agent
What it does. You declare the rules a run must respect, read them back, and check a proposed action against them before taking it. check_constraints (constraint.rs:186) returns a verdict of block, warn or pass, taken from the highest severity among the constraints that matched, and lists each match with a percentage. enrich_action fills a raw action out with the context needed to judge it, so the check has something to match against.
Why it works this way. The usual home for these rules is the system prompt, where they are invisible to everything except the model: you cannot list them, cannot audit them, cannot tell whether a given action was checked, and cannot change them without editing a prompt. Pulling them into the substrate makes them data. They can be enumerated, versioned, exported and reviewed by a person who is not reading the agent's context. The trade-off, chosen deliberately, is that matching is lexical rather than semantic for most constraint types, with a regular-expression path for shell-command patterns. That makes the check explainable, cheap and deterministic, and it makes it miss paraphrases.
What changes for you.
- As an agent: there is a call you can make before a risky action that answers "is this allowed here", and the answer names the constraint it matched and how strongly.
- As an operator: the rules your fleet is meant to follow are a list you can read, not an assumption about what is in a prompt.
What it does not do.
- It does not enforce. A
blockverdict is a returned string; nothing in the daemon refuses the action. The controls that do refuse are the human approval gate (chapter 5 of this set) and route authorisation (chapter 7), and a co-operating client is what turns ablockinto a stop. - It does not understand your action. Matching is term overlap against the constraint's assertion, so a constraint about "deleting production data" will not match an action phrased as "drop the live table".
- It does not scale without bound. The check loads the active constraint set with a fixed cap, so this is a working rulebook, not a policy engine.
Where the detail lives. MCP tool reference §12.22 for the four tools; API §7.7 for the HTTP enrichment route.
4.7 Autonomy contract
Status · FLAG
CORECRUXD_FEATURE_AUTONOMY_CONTRACT, default off · Reached through · MCPautonomy_contract· Who it is for · agent
What it does. Behind CORECRUXD_FEATURE_AUTONOMY_CONTRACT, default off, autonomy_contract (autonomy.rs:244) returns a typed statement of how far this caller may act without a human: a per-capability matrix of what is allowed, what is denied and why, derived from the caller's capability token. With the flag off, the tool answers with a disabled notice rather than an empty contract, so the two states are distinguishable.
Why it works this way. An agent that does not know its own limits either stops too often, asking permission it already has, or discovers a boundary by hitting a denial mid-run. Both are expensive. Returning the boundary as a structured contract lets the harness plan around it before acting rather than recovering after a refusal. The design decision worth noticing is that the contract is derived from the same capability token that gates the calls, not written separately, so it cannot drift from what would actually happen. The trade-off is that the contract is only as meaningful as the token behind it: with no capability router on the context, the tool says so plainly rather than returning a permissive answer.
What changes for you.
- As an agent: you can read your own ceiling at the start of a run and route the parts you cannot do to an approval gate instead of to a failure.
- As an operator: what an agent believes it may do is inspectable, and it is derived from the same token you issued rather than from a second configuration you would have to keep in sync.
What it does not do.
- It does not grant or change anything. It reports the boundary; it does not move it.
- It is not enforcement. Denial happens at the capability check on the call itself, and reading the contract is optional.
- Without a capability token on the context, there is no meaningful contract to return, and the tool reports that condition rather than inventing one.
Turn it on. CORECRUXD_FEATURE_AUTONOMY_CONTRACT=1. Default off.
Where the detail lives. MCP tool reference §12.2 for the response shape and the flag behaviour.
4.8 Artefact parking
Status · FLAG
CORECRUXD_FEATURE_ARTEFACTS, default off · Reached through · MCPartefact_put,artefact_get,artefact_list· Who it is for · agent
What it does. Behind CORECRUXD_FEATURE_ARTEFACTS, default off, an agent can park a large output outside the conversation and carry only a reference to it. artefact_put (artefacts.rs:141) takes bytes and returns an id of the form art_<blake3_hex>; artefact_get fetches them back; artefact_list shows what this caller has parked.
Why it works this way. The forcing problem is that a long result has to go somewhere, and in a naive harness that somewhere is the context window, where it stays for the rest of the run and is paid for on every subsequent turn. Parking it converts a recurring cost into a one-off write plus a short id. Two design choices follow from that. The id is the content hash, so two writers of identical bytes coalesce onto one artefact rather than duplicating it. And the TTL is enforced at the tool layer rather than the store, defaulting to seven days with a ninety-day cap, because how long a parked output should live is a policy decision and policy belongs where an operator can see it.
What changes for you.
- As an agent: a large intermediate result stops competing with your working context, and you can hand the id onward instead of the payload.
- As an operator: parked outputs are attributed to a passport and are listable, so an agent cannot quietly accumulate storage you cannot see.
What it does not do.
- It is not durable archival storage. Artefacts expire, defaulting to seven days.
- It is not shared. Cross-passport reads are refused with
CAPABILITY_DENIED, so parking is not a way to pass data between agents; that is 4.4. - It does not compress or transform anything. What you put is what you get back.
Turn it on. CORECRUXD_FEATURE_ARTEFACTS=1. Default off, so the surface ships dark until an operator opts in.
Where the detail lives. MCP tool reference §12.13 for the three tools and their parameters; artefacts.rs:6 for the id, TTL and scoping rules.
Sources
- Sessions: session_store.rs:29, set_archived, reap_expired, scope.rs:123
- Checkpoints: sessions.rs:77
- Cases: case_store.rs:242, case_store.rs:289, cases.rs:61
- Handoffs: handoff.rs:61, handoff.rs:205, handoff.rs:282, workbench.rs:551
- Decisions: decision.rs:19
- Constraints: constraint.rs:48, constraint.rs:186, constraint.rs:270, action.rs:17, actions.rs:35
- Autonomy contract: autonomy.rs:244
- Artefacts: artefacts.rs:141, artefacts.rs:196, artefacts.rs:246

