Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sessions

A session is a first-class VM resource that owns three things for a given conversational agent run:

  1. Its transcript history (messages, events, summary, …).
  2. The closure subscribers registered against it via agent_subscribe(session_id, cb).
  3. Its lifecycle — create, reset, fork, trim, compact, close.

Sessions replace the old transcript_policy config pattern. Lifecycle used to be a side effect of dict fields (mode: "reset", mode: "fork" quietly surgerying state on stage entry); it is now expressed by explicit, imperative builtins. Unknown inputs are hard errors.

Quick tour

pipeline main(task) {
  // Open (or resume) a session. `nil` mints a UUIDv7.
  let s = agent_session_open()

  // Seed the conversation.
  agent_session_inject(s, {role: "user", content: "Hello!"})

  // Run an agent loop against the session — prior messages are
  // automatically loaded as prefix, the final transcript is persisted
  // back under `s`.
  let first = agent_loop("continue the greeting", nil, {
    session_id: s,
    provider: "mock",
  })

  // A second call sees `first`'s assistant reply as prior history.
  let second = agent_loop("what do you remember?", nil, {
    session_id: s,
    provider: "mock",
  })

  // Fork to explore a counterfactual without touching `s`.
  let branch = agent_session_fork(s)
  agent_session_inject(branch, {role: "user", content: "what if …"})

  // Release a session immediately.
  agent_session_close(branch)
}

If you don’t pass session_id to agent_loop, the loop mints an anonymous id internally and does NOT persist anything. That preserves the “one-shot” call shape.

Builtins

FunctionReturnsNotes
agent_session_open(id?: string)stringIdempotent. nil mints a UUIDv7.
agent_session_exists(id)boolSafe on unknown ids.
agent_session_length(id)intMessage count. Errors if id doesn’t exist.
agent_session_snapshot(id)dict or nilRead-only deep copy of the transcript.
agent_session_reset(id)nilWipes history; preserves id and subscribers.
agent_session_fork(src, dst?)stringCopies transcript; subscribers are NOT copied.
agent_session_trim(id, keep_last)intRetains last keep_last messages. Returns kept count.
agent_session_compact(id, opts)intRuns the LLM/truncate/observation-mask compactor. Unknown keys in opts error.
agent_session_inject(id, message)nilAppends a {role, content, …} message. Missing role errors.
agent_session_close(id)nilEvicts immediately.

agent_session_compact options

Accepts any subset of these keys; anything else is a hard error:

  • keep_last (int, default 12)
  • token_threshold (int)
  • tool_output_max_chars (int)
  • compact_strategy ("llm" | "truncate" | "observation_mask" | "custom")
  • hard_limit_tokens (int)
  • hard_limit_strategy (same values as above)
  • custom_compactor (closure)
  • mask_callback (closure)
  • compress_callback (closure)

Storage model

Sessions live in a per-thread HashMap<String, SessionState> in crate::agent_sessions. Thread-local is correct because VmValue wraps Rc and the agent loop runs on a pinned tokio LocalSet task.

An LRU cap (default 128 sessions per VM) evicts the least-recently accessed session when a new one is opened over the cap. agent_session_close evicts immediately regardless of the cap.

Subscribers

agent_subscribe(id, closure) appends closure to the session’s subscribers list. The agent loop fires turn_end (and other) events through every subscriber for that session id. Subscribers are not copied by agent_session_fork — a fork is a conversation branch, not an event fanout.

Interaction with workflows

Workflow stages pick up a session id from model_policy.session_id on the node; if unset, each stage mints a stable stage-scoped id. Two stages sharing a session_id share their transcript automatically through the session store — no explicit threading or policy dict required.

To branch a stage’s conversation before running it, call agent_session_fork in the pipeline before workflow_execute and wire the fork id into the relevant node’s model_policy.session_id.

Fail-loud

Unknown option keys on agent_session_compact, a missing role on agent_session_inject, a negative keep_last, and any of the lifecycle verbs (reset, fork, close, trim, inject, length, compact) called against an unknown id all raise a VmError::Thrown(string). exists, open, and snapshot are the only calls that tolerate unknown ids by design.