Session Continuation
How an agent carries a conversation into a fresh successor session at a natural boundary, with no compaction of its history, and what the successor starts from.


Shipped 2026-09-01. Implements Ion's proposal

no rolling-summary compaction. An agent continues into a fresh session at a semantic boundary, and the handoff is a

durable, inspectable projection.

Seed never compacts a conversation by replacing its early history with a summary. Sometimes a transcript stops being the right working context: the user changed subject, a phase ended, the user wants to focus on one thread, the user asked for it, or the model's context is filling. Then the agent carries the conversation into a successor session with the continue_session verb. The predecessor keeps its complete transcript in its log. The successor starts from a projection: the agent's handoff, the exact initiating message, and exact excerpts. Everything else is linked and can be read back.

Vocabulary

    Continuation: the durable transition the user sees, from predecessor through a continuation edge to successor. It differs from parent and child, where a delegated run returns to its parent. A continuation becomes the foreground conversation.

    Projection: the successor's first model context. It is a view built for the purpose, with exact references back to its sources. It is derived context. The canonical history stays in the predecessor.

    Manifest: the record of what the projection was built from (SessionContinuationManifest), stored on the edge.

Data model

session_continuations (sqlite-schema.sql, see persistence) has one row per edge. Its columns are predecessor_session_id, successor_session_id (unique: a session has one immediate predecessor), origin_session_id (the first session of the chain), tool_call_id (unique with the predecessor, and the idempotency key), initiating_event_id, reason, and manifest_cbor. A predecessor may have several successors, when someone came back and branched. SessionInfo.continuedTo shows the newest.

SessionInfo carries continuedFrom and continuedTo (SessionContinuationLink: continuation id, other session id and title, reason, time). GetSessionResponse.contextWindow is the model's window, so clients can draw the context meter. MessageSessionResponse.continuedToSessionId tells the sending client where the answer went.

Successor events, appended in the creation transaction:

    {type: 'message', role: 'user', actor: 'system'}: the projection (below). It renders as the handoff card, never as a bubble.

    The initiating user message, copied word for word (content, rawMarkdown, blocks, contextLines, attachments) with meta.continuedFrom = {sessionId, eventId, seq}. Attachments are hard-linked into the successor's attachment dir (linkSessionAttachment), so attachment:<id> still resolves.

The predecessor's transcript gains only the continue_session tool_call/tool_result pair, which the run loop writes anyway. The transition card renders from that pair.

The verb

continue_session({reason, title, description, handoff, sources?, transfer?}) is defined in tool-registry.ts (see tools). title and description are required. The predecessor's agent sets them, the same way the status verb names a session (title_source = 'agent', so the fallback namer stays out). handoff is prose for a colleague who has read nothing: purpose, currentRequest, establishedFacts, decisions, openQuestions, nextActions, cautions. Any other key the agent invents is kept under handoff.extra, each value normalized to a list of strings, and rendered as its own ## Section. The handoff schema has no additionalProperties: false: in prod, models nested sources, transfer, or description inside handoff on roughly 40% of calls, and every one was refused and retried, so normalizeContinueSessionInput hoists those to the top level when the top level lacks them. sources are exact pointers. A resource is an hm://, ipfs://, or http address. A memory source is a ~/memory/… path that must exist. session_events and session_event sources are seq ranges of a thread, as read thread:<id> shows them, validated against the thread. transfer.plan is carry, close, or omit. carry is the default when the checklist has unfinished steps: the plan is copied with its owner cleared, so the successor's run adopts it.

Availability (canContinueSession in #runPiAgent): a run exists, it is not a delegated child (parentRunId), and it is not a typed child (return_result). Scripts never see it.

Runtime (#continueSessionFromAgent)

    Replay: if a row for (predecessor, tool_call_id) exists, return that successor and end the turn.

    Validate: check the reason, title, description, and handoff shapes. Refuse while children spawned this turn are parked. Refuse from a delegated child. Cap successors per predecessor (MAX_SESSION_CONTINUATIONS_PER_SESSION). Find the initiating event, the newest user message written by a user or a trigger. Refuse when it carries meta.continuedFrom: the session was just continued and nobody has said anything new, so continuing again would loop. Validate sources.

    Compile the projection (compileContinuationProjection, pure): lineage block, handoff, and source list. Then, within CONTINUATION_PROJECTION_BUDGET_BYTES, the cited ranges of this thread as <excerpt> blocks, then the most recent exchanges before the initiating message as <recent_exchanges>. Whatever does not fit is listed as omitted, linked but not loaded. Every excerpt line is [seq] who: text (transcriptEventLine, shared with read thread:). It is escaped with escapeActionFraming, like any model-written text handed back inside a frame.

    Create atomically: the successor session (title, description, copied model override, carried plan), the edge row with the manifest, and the two successor events.

    End this turn, start the next: runningSession.continuation plus completeAfterTools. Pi's next provider request is refused (onPayload throws, which is the designed ending). #runPiAgent throws SessionContinuedError. #executeAgentRun returns succeeded with {continuedToSessionId, continuationId} and skips obligations, because nothing is owed here any more. The successor's run is enqueued on the interactive queue with the copied message as its userEventIds.

    Emit: session-change for both sessions (the predecessor now has continuedTo, the successor continuedFrom), and account-change with reasons session-updated and session-continued. See WebSocket subscriptions.

What the agent is told

    System prompt (SESSION_CONTINUATION_PROMPT): what a continuation is, that transcripts are never compacted, when to continue, that calling the verb ends the turn and the successor answers, and that read thread:<id> recalls exact material. See the prompt injection map.

    The verb contract: the cases in detail (topic change, phase change, refocus, context pressure, user request), when NOT to continue (unresolved side effects, delegated children, plain follow-ups), what the successor receives, how to write the handoff, and that the title and description are the agent's to set.

    Per turn, where continuing is possible: a <context_usage tokens window percent> block (contextUsageBlock), rendered fresh like <plan_state> and never stored. Tokens are the last turn's prompt size: input + cacheRead + cacheWrite, stamped on the newest assistant message's meta.usage. The window comes from modelContextWindow(providerType, modelId) (model-capabilities.ts, also used for Pi's model registration, see model providers). The guidance gets stronger at 70% and 85%.

    Recall: read thread:<id> prints [seq]-prefixed lines, accepts {fromSeq, toSeq, limit}, and reports the thread's continuedFrom and continuedTo. Thread reads and cited session_events sources reach only this agent's own threads. Agents do not read each other's state. They talk over public interfaces.

    Per turn, everywhere: a <session_status title="…">description</session_status> block (sessionStatusBlock). With it, a status call is a deliberate change and never a restatement. A successor keeps the title and description its predecessor gave it. The title is what the whole session is about, and a large shift in topic calls for a continuation instead of a rename. The description is the live status and can change freely. Most calls change only the description. The status tool row shows the description in full.

UI

    Context meter (ContextUsageMeter): a small pie in the session header and the assistant sidebar. It is muted, amber from 70%, and red from 85%. The client computes it from the same assistant meta.usage and contextWindow. See desktop UI.

    Predecessor: the continue_session row renders as a transition row (ContinuationTransitionCard). It has a chevron, "Continued in “title” (reason)", Open session on the right, and a hover info bubble that shows every detail of the handoff. A refused call keeps error styling. You can still type in a continued session, and doing so branches. Session lists show a "Continued in …" chip.

    Successor: a ContinuationHeader pill (Continued from “…”, the way back) and the projection rendered as ContinuationHandoffCard (handoff markdown, sources, and excerpts loaded on demand). The replayed user message carries a "From previous session" chip.

    Automatic navigation (useFollowContinuation): a client moves to the successor only if it was following the turn, meaning it saw the session streaming or sent the message. The send response's continuedToSessionId navigates at once. It fires once per successor. Back returns to the predecessor without redirecting again. Other clients see the card and the notice.

Failure and recovery

    Refused continuation (child, loop, parked children, bad input): the tool result is an error that the model reads and the transcript shows. Nothing is created.

    Crash between the edge and the successor run: the edge and successor exist. The run is enqueued after the transaction. If the run is lost, the successor is a normal session, and you can retry its replayed message.

    Replayed tool call: the same successor, found by (predecessor, tool_call_id).

Not yet

Phase 3 of the proposal is not built: budget estimates before continuing, recommended ranges, purpose-specific projections, and measuring repeated recalls of omitted sources. Forks, where a person branches from an earlier point, do not share the edge yet.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime