Sessions
The wippy/session module owns persistent chat sessions and the policy for
accepting user input. A session blocks new messages during an active turn by
default. Steering lets an enabled session accept input during generation and
apply it before the next model step in the same turn.
Availability
This page describes wippy/session 0.6.19 with Agent 0.5.11 and LLM 0.5.14.
Steering was introduced in Session 0.6.3. See Chat Web
Components for client support.
A new client connected to an older Session server keeps the legacy status-based
sending behavior when interaction is absent. An older client connected to a
new Session server can keep using normal chat and Stop, but it does not gain
steering controls or pending-message presentation.
Setup
Add the module to the project:
wippy add wippy/session@0.6.19
wippy install
Stable history
Each normal continuation extends the preceding request. The session stores context where it was sent and replays it unchanged, preserving the provider prefix for prompt caching and thinking blocks. This requires Agent 0.5.10 and LLM 0.5.13 or later.
- Time context: an agent with
agent_options.turn_contextgets one stored time note per turn. The session renders it throughwippy.agent:turn_contextand stores it after activation, lifecycle validation and the Stop gate, before the model call. The system row carriesmetadata.preserve_position = trueandmetadata.system_action = "turn_context". It follows the user message and any activation notes; tool continuations add none. A handoff to a time-aware agent adds it only if that turn has none. Retries and reopened sessions replay the stored minute. A rendering error fails the turn. - Recall:
response.context_notesare stored in order as developer rows after lifecycle notes and before the step output, withpreserve_positionandmemory_ids. Notes and output commit together. Kept truncated or cancelled text keeps its notes; discarded output discards them. Assistant rows no longer carry recall IDs or counts. - Lifecycle:
activateandbefore_stepmessages must bedeveloperorsystem. They are positional rows withmetadata.lifecycle_phaseandmetadata.system_action = "lifecycle_note". Activation notes persist when activation runs; before-step notes commit with the output. A binding may contributememory_ids, but cannot replace these host metadata fields. - Thinking: assistant rows store
prefix_fingerprint,thinking_blocks,content_positionswhen present, and the immutablegeneration_checkpoint_id. Each step usesdrop_stale_thinkingandthinking_is_currentto compare both the fingerprint and generation checkpoint. Missing generation metadata is stale; changing an anchor's checkpoint does not change its generation. The current tool round keeps its blocks for provider handling. - Tool rounds: all calls from a response follow its assistant row, then all results follow in the model's call order. Adjacent rows remain separate. Developer rows and positional system rows replay in place; other system rows, such as agent-change notices, stay out of the prompt.
Model text and lifecycle notes are stored only when they contain a non-space
character. A blank assistant row can hold a tool round's thinking without
creating blank text on the wire. User input with no visible text, files or
attachments is rejected with empty_message; attachments can stand alone.
Deliberate prefix edits
Checkpoints replace earlier history, and agent, tool or trait changes can replace the system prompt or tool list. Other edits include withdrawing stale tool results or expired Attention attachments, changing an attached file's rendered name or size, and adding, deleting or clearing session memory in the system prompt. Client-injected system/developer rows can arrive before an in-flight response. Stop can return applied steering to pending; a discarded step loses its before-step and recall notes. A restored request excerpt and its time note leave the prompt when the next user turn starts.
These edits restart the cache at the changed prefix and invalidate later thinking. Rows from older releases may render differently once after upgrading; missing fingerprints count as stale. Cache reuse remains provider-dependent.
Checkpoints
With a checkpoint provider configured, the session compares the response's
tokens.context_tokens with the effective token_threshold using strict >.
This is the full current input, including cached input, not cumulative usage
or only uncached prompt_tokens. Tool continuations are checked too, before
the next round and model step. LLM context admission can also request compaction
before dispatch; an unsettled tool round cannot be cut.
Options resolve from lowest to highest precedence:
- Session-wide
token_checkpoint_threshold(default 50000) andcheckpoint_function_id. - Agent
agent_options.checkpoint: trait behavior defaults, then explicit agent values. - Persisted session
config.checkpoint.
config:
checkpoint:
token_threshold: 20000
model: class:fast
max_tokens: 3000
This is persisted host configuration; start tokens and _control.config do not
implicitly accept the map. enabled: false disables checkpointing; a nonpositive
threshold disables automatic scheduling. Maps merge recursively and lists replace,
including empty lists. Each queued checkpoint captures its effective options.
A trait checkpoint binding runs before the configured function fallback; both
receive those options. A strict binding failure prevents fallback and state writes.
See per-agent compaction for trait entry shape.
The default summarizer uses its resolved model's context window, reserves the
output budget and a safety margin, and caps max_tokens at the model's output
limit. It reads the previous summary and new conversation, including positional
notes and arguments/results of public, private and delegation calls. Per-value
max_tool_result_chars clamps still apply (default 2000).
The conversation uses available input space after fixed sections and output
reservation. An explicit max_conversation_chars caps it; the 500000-byte default
applies only when no model window resolves. If fitting this conversation requires
dropping bytes, the checkpoint fails before generation. The duplicate KEY TOOL
RESULTS section keeps the newest max_tool_results (default 100), bounded by
max_tool_results_chars (default 100000) and remaining space. Dropping these
duplicates alone does not fail a checkpoint: the results remain in the conversation.
The default output budget is 3000 tokens; the summarizer asks for less than that
budget. A summary cut off at max_tokens fails. Any checkpoint failure leaves
the previous summary and history anchor unchanged. If the full prompt still fits
the agent's model window with its output reservation, the session sends it and
tries compaction again on the next step. Otherwise the turn fails visibly. Use
a summarizer with enough input capacity or checkpoint earlier.
A checkpoint within a turn retains the originating request at user authority, followed by the resume note and later messages. The restored request is capped at 6000 UTF-8 bytes, keeping both ends, and its time note is restored with it. Attachments are covered by the summary rather than reloaded. A later ordinary user message starts a new turn; steering cannot replace the active task.
Input policy
A session may store one persistent override:
config:
input_policy:
while_running: steer
while_running accepts block or steer. The effective policy is resolved
in this order:
- Temporary override for the active turn.
- Persistent session override in
config.input_policy. - The active agent's default.
block.
The wippy.session.traits:steering trait sets the active agent default to
steer:
traits:
- wippy.session.traits:steering
Steering remains opt-in. Agents without this trait and sessions without an override keep the blocking behavior.
Agent management tool
The wippy.session.traits:input_control trait grants the
set_session_input_policy tool:
traits:
- wippy.session.traits:input_control
This trait grants control but does not enable steering by itself. The tool has two arguments:
| Argument | Values | Default | Meaning |
|---|---|---|---|
mode |
block, steer, inherit |
Required | Set or remove the override at the selected scope. |
scope |
turn, session |
turn |
Apply the change to this turn or persist it on the session. |
The tool accepts no session ID. It is bound to the calling session and checks that the calling agent still owns that session. The session validates and persists the change before the tool reports success. A failed write does not change the in-memory policy.
Temporary turn overrides clear on completion, Stop, failure, recovery, and
agent handoff. A persistent session override remains until it is changed or
removed with inherit at session scope.
Public interaction state
REST session responses and WebSocket session updates may include:
interaction?: {
can_send: boolean
revision: number
}
can_send is the committed effective sending state. revision increases only
when committed can_send changes. Clients should keep explicit false values
and ignore lower revisions.
A full REST response without interaction means the server uses the older
contract. The client should clear its cached interaction state and use the
legacy session-status behavior. A partial WebSocket update that omits
interaction leaves the current value unchanged. A supplied malformed object
also leaves the last valid state unchanged.
Stop is separate from can_send. Its availability comes from the existing
session status.
Steering lifecycle
The server assigns the canonical message ID and persists a steering message before acknowledging it. Steering messages alone receive input metadata:
input?: {
state: "pending" | "applied"
after_message_id?: string
}
Normal idle messages keep their existing metadata. Pending steering is ordered
by persisted date, then by server message_id. After the current response
and its running tool batch finish, the session builds the next prompt and runs
its pre-step lifecycle work while every steering row is still pending. At the
model dispatch boundary, it changes the complete batch to applied in one
transaction and places those messages after the completed response and tool
results. A failed batch update leaves every row pending and publishes no
applied event.
If the recorded prompt anchor was pruned or is missing, the session inserts the steering message at the first valid boundary after the checkpoint context. It does not omit the message. Malformed steering metadata leaves the durable row in place and fails the apply operation visibly.
If prompt construction, agent loading, or lifecycle work fails before model dispatch, steering stays pending and the session does not continue by itself. If the provider fails after dispatch, the steering stays applied because it already entered the prompt. Later prompt history keeps it.
An event publication failure after a database commit does not roll back the committed state. REST reads and reconnect refreshes reconstruct that state.
Stop, recovery, and handoff
Stop is committed before it is acknowledged. If persistence fails, the active turn continues and the command returns an error. Repeated Stop commands are idempotent.
If a steering message commits before Stop, it remains pending. If Stop commits
first, a later send is rejected. Stop closes the turn's cancel channel to cancel
in-flight model and tool calls. Streamed text already received is stored with
metadata.stopped = true; cancelled calls are stored with status cancelled,
and the turn ends idle. Completed tool effects are not undone. With an older
Agent that does not use the cancel channel, Stop takes effect at the next
operation boundary.
The existing Stop supervisor also bounds a stuck operation. If the session has not stopped after 10 seconds, it requests cancellation of the session process. After another 10 seconds, it requests termination if the process is still running. These are process-level safeguards, not a guarantee that an external provider request or a tool's side effects are cancelled immediately.
Unused steering remains pending after Stop or recovery, ready for the next
user-started turn. A committed tool handoff through _control.config.agent
continues the active turn with the new agent after the current tool results
settle, and applies pending steering at its next dispatch. Handoff clears the
temporary policy and recomputes the agent default; the persistent policy remains.
Stop suppresses continuation while preserving a committed agent switch.
Stop also commits a positional developer boundary marking the preceding turn cancelled. Recovery stores an interruption boundary before accepting input if the prior turn did not finish. Reopening does not duplicate it. Disconnect, close, inactivity and capacity retirement let an accepted turn finish; reconnection cancels pending retirement.
Delivery confirmation
The client uses the existing WebSocket request_id correlation mechanism.
After persisting a message, the server publishes its existing received event
on session:<session_id>:message:<message_id> with the original request_id,
server message ID, text, attachments, and optional input metadata. Only a
valid receipt matching the send request confirms delivery and lets the sender
clear its draft and attachments. Receipts without that request ID update
history without clearing the draft.
Stop returns a session update with the original request_id after committing
its state. Rejections use the existing error event with the request ID and
error details. Opening or recovering a session while handling a send must not
confirm that send before the message is persisted.
When steering enters the model prompt, the server publishes type: "update"
on the message topic with message_id and the changed input metadata.
Clients merge this patch into the message and preserve applied state when a
delayed pending receipt arrives. A patch received before its message must not
create an empty chat row.
Older fire and forget transports may retain a random message ID. That legacy ID has no retry or deduplication meaning.
Admission, validation, and definite database failures return a request-matched error and write no message. If the database commit succeeds but the acknowledgement is lost, the row remains durable and appears after refresh. The client keeps the draft and reports that delivery was not confirmed.
There is no automatic retry, client message ID, fingerprint, or deduplication scan. Manual resend after uncertain delivery may create a duplicate.
Related pages
- Agents describes agent definitions and general trait behavior.
- Chat Web Components describes composer behavior, acknowledgements, pending messages, and reconnects.