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

Session steering is available in wippy/session 0.6.3. The built-in chat and chat web components support it in Web Host 1.0.59, with public npm packages at 0.0.59 and facade 0.6.41 pointing to that Host release. No frontend feature flag is required.

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.3
wippy install

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:

  1. Temporary override for the active turn.
  2. Persistent session override in config.input_policy.
  3. The active agent's default.
  4. 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. The current provider operation or a tool batch that is already running may finish. The session suppresses new tools and any further continuation 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, recovery, or handoff. Recovery and handoff clear the temporary policy, retain the persistent policy and pending input, and recompute the active agent default. They do not start a turn. The pending messages are included when the user starts the next turn.

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. Immediate cancellation of an in-flight provider request is also outside this contract.

  • Agents describes agent definitions and general trait behavior.
  • Chat Web Components describes composer behavior, acknowledgements, pending messages, and reconnects.