Sessions¶
A session is the agentsdk identifier that scopes conversation history. Maven manages two artifacts per session:
- The agentsdk session ID — a string passed to the runtime so the SDK loads the right history file.
- The stable route key — a
channel:chatidentifier the gateway uses to map a real-world chat to whichever session is current for it.
The router (internal/kernel/session.Router) is the bridge.
Session ID grammar¶
Defined in internal/kernel/sessionid.
| Kind | Format | When |
|---|---|---|
chat |
telegram-12345, web-uuid, … |
Default for an active chat; the route key's natural session. |
rotated |
telegram-12345:rotated:1740000000000000000 |
After /new or /compact rotation. |
isolated |
telegram-12345:isolated:1740000000000000000 |
SessionModeIsolated (Telegram slash defs that set session: isolated). |
cron |
cron:{jobID}:{uuid} |
Each cron job run gets a fresh ID; jobs do not share history. |
heartbeat |
heartbeat:{uuid} |
Each heartbeat tick gets a fresh ID. |
task |
task:{uuid} |
In-process subagent run via the Task tool. |
Parsing back is symmetric: sessionid.Parse("cron:job-1:abc-uuid") returns {Kind: cron, Owner: "job-1", Token: "abc-uuid"}.
Stable route key¶
bus.InboundMessage.StableRouteKey() returns "{Channel}:{ChatID}". This is not a session ID — it's the routing key the router uses to look up which session ID is current.
flowchart LR
A[Inbound: channel + chat] --> B[StableRouteKey<br/>e.g. telegram:42]
B --> C{Session router}
C -->|active mapping| D[Rotated session ID]
C -->|no mapping| E[Default chat session ID]
Resolution¶
SessionResolver.ResolveSDKSessionID(channel, chat, routeKey, mode):
mode |
Result |
|---|---|
current |
Router lookup; falls back to sessionid.ChatSessionID(channel, chat). |
isolated |
sessionid.New(KindIsolated, chatSessionID).String() — never persisted to the router. |
current is the default for chat inbound; isolated is opt-in per Telegram slash command via the session: isolated frontmatter.
Rotation¶
Two flows rotate a chat's session:
/newbuiltin. Channels emitbus.RoutingHints.BuiltinCommand = "new". The pipeline callsRouter.Rotate(routeKey), replies "Started a fresh session.", and never invokes the model./compactslash command. The model produces a continuation summary, the pre-compact flush hook fires (rememberimportant context), then the router rotates and the new session's history is seeded with the summary as a system message.
Both produce rotated: session IDs. The router persists mappings to <workspace>/.maven/session-router.json.
Persistence layout¶
~/.maven/
config.json # app config
sessions/ # agentsdk per-session history JSONL
telegram-42.jsonl
telegram-42:rotated:1740…000.jsonl
workspace/
.maven/
session-router.json # routeKey → current session ID
history/ # compact seed files keyed by sessionID
The agentsdk's SessionStore (a session.Store writing JSONL under ~/.maven/sessions) holds turn-by-turn message history. The router stores which session ID a chat currently uses.
Trigger isolation¶
Triggers do not use the router:
- Cron generates a fresh
cron:{jobID}:{uuid}per run. Two runs of the same job never share history. - Heartbeat generates a fresh
heartbeat:{uuid}per tick. - Memory consolidation uses
isolated:mem-consolidate:{nanos}.
This isolation is deliberate: backgrounds should not contaminate the user's chat history (and vice-versa).
Task subagents¶
The in-process Task tool derives task:{uuid} for each invocation. Nested task delegation is rejected — the tool inspects the parent session ID via turnctx metadata and refuses to run if it is already a task: session.
See Guides: Subagents.
Web UI sessions¶
The Web UI generates a UUID per browser session and includes it as the Maven-Session-Id header (or ?session= query param for the voice WebSocket). The wsession.ResolveMavenSessionID resolver:
- Accepts a
Maven-Session-Idheader for new conversations. - Accepts a
previous_response_idbody field for/v1/responsesfollow-ups and binds it back to its prior session ID. - Errors with
invalid previous_response_idfor unknown or malformed IDs.