From 2ca616a12f83d16aebddede57a1bf42a14d28aed Mon Sep 17 00:00:00 2001 From: mingmxren Date: Sun, 1 Mar 2026 11:37:03 +0800 Subject: [PATCH] docs(plan): add scope-aware session rotation design --- .../2026-03-01-session-rotation-design.md | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 docs/plans/2026-03-01-session-rotation-design.md diff --git a/docs/plans/2026-03-01-session-rotation-design.md b/docs/plans/2026-03-01-session-rotation-design.md new file mode 100644 index 000000000..b99f4f764 --- /dev/null +++ b/docs/plans/2026-03-01-session-rotation-design.md @@ -0,0 +1,178 @@ +# Session Rotation and Scope-Aware Session Control Design + +## Background + +PicoClaw currently derives a single `SessionKey` from routing and uses it directly for message history and summary. There is no first-class session rotation state, and no user commands for listing/resuming historical sessions in a routing scope. + +Current limitations: + +- No `/new` (or `/reset`) command to start a fresh conversation context. +- No `/session list` or `/session resume ` command. +- No backlog cap/cleanup strategy for old sessions. +- `SessionManager` stores session content only; it lacks active-session index semantics. + +## Goals (Phase 1) + +- Add `/new` command (alias `/reset`) to start a new session in current routing scope. +- Add `/session list` and `/session resume ` for scope-local session navigation. +- Enforce configurable backlog limit per scope and remove oldest sessions when exceeded. +- Keep behavior aligned with existing routing/session boundaries (`dm_scope` etc.), with no override that breaks routing semantics. + +## Non-Goals (Phase 1) + +- No automatic extraction of archived sessions into `memory/MEMORY.md`. +- No cross-scope or global session browsing/resume. +- No changes to routing rules or `dm_scope` semantics. + +## Domain Boundaries + +### Existing boundaries (preserve) + +- `routing` computes canonical session boundary from channel/account/peer + `dm_scope`. +- `session` stores conversation history and summary. +- `state` stores runtime notification state (`last_channel`, `last_chat_id`) for heartbeat/devices. + +### Design decision + +Use **SessionManager-owned session index semantics** (B+ approach), not a new `state`-domain session index. + +Reasoning: + +- Session lifecycle belongs to the session domain, not notification state. +- Avoid cross-domain write consistency problems (`state` index + `sessions` content). +- Better long-term extensibility for phase-2 archival queue integration. + +## Terminology + +- `scopeKey`: routing-produced key representing session scope anchor (currently `route.SessionKey`). +- `sessionKey`: concrete key used to read/write messages in current turn. + +Relation: + +- `scopeKey` is not a new entity type; it is naming for `route.SessionKey` usage in rotation logic. +- `sessionKey = ResolveActive(scopeKey)` where active may be `scopeKey` itself or rotated variants. + +## Data Model + +### Session index storage + +Persist under: + +- `/sessions/index.json` + +Model sketch: + +```json +{ + "version": 1, + "scopes": { + "agent:main:telegram:direct:user123": { + "active_session_key": "agent:main:telegram:direct:user123#3", + "ordered_sessions": [ + "agent:main:telegram:direct:user123#3", + "agent:main:telegram:direct:user123#2", + "agent:main:telegram:direct:user123" + ], + "updated_at": "2026-03-01T11:00:00+08:00" + } + } +} +``` + +### Key generation + +- First session for a scope remains exactly `scopeKey`. +- Rotated sessions use monotonic suffix `#` under the same scope (e.g. `scopeKey#2`, `scopeKey#3`). + +## API Design (SessionManager Extension) + +- `ResolveActive(scopeKey string) (sessionKey string, err error)` +- `StartNew(scopeKey string) (newSessionKey string, err error)` +- `List(scopeKey string) ([]SessionMeta, err error)` +- `Resume(scopeKey string, index int) (sessionKey string, err error)` +- `Prune(scopeKey string, limit int) (pruned []string, err error)` +- `DeleteSession(sessionKey string) error` (content + file + index adjustment) + +`SessionMeta` includes: + +- ordinal index (for user-facing stable numbering) +- session key +- updated timestamp +- optional message count +- active flag + +## Runtime Flow + +1. `AgentLoop.processMessage` routes inbound message and gets `scopeKey` (`route.SessionKey`). +2. Before `runAgentLoop`, call `sessionKey := Sessions.ResolveActive(scopeKey)`. +3. `runAgentLoop` uses resolved `sessionKey` for history/summary read/write. +4. Commands: + - `/new` / `/reset`: `StartNew(scopeKey)` + `Prune(scopeKey, backlogLimit)` + - `/session list`: `List(scopeKey)` + - `/session resume `: `Resume(scopeKey, n)` + +## Command Scope Semantics + +- Commands operate within current routing scope only. +- This naturally follows `dm_scope` behavior: + - `main`: one shared scope + - `per-peer` / `per-channel-peer` / `per-account-channel-peer`: corresponding isolated scopes + +## Consistency & Recovery + +### Concurrency + +- SessionManager guards session map and index model with one lock domain. +- No external package writes index structures directly. + +### Persistence order + +- Write/ensure session content, then atomically write `index.json`. +- On startup: + - Remove index entries referencing missing session files. + - Optionally recover orphan session files into scope lists (recommended). + +### Failure behavior + +- User command failures are non-fatal and user-readable. +- File-delete failure during prune logs warning and retries/repair on next startup. + +## Configuration + +Add `session.backlog_limit`: + +- default: `20` +- min: `1` +- apply per `scopeKey` + +Invalid values fallback to default with warning log. + +## Testing Strategy + +### Unit + +- ResolveActive bootstrap behavior. +- StartNew monotonic key generation and active pointer update. +- List stable ordering/index mapping. +- Resume bounds checking and activation. +- Prune oldest deletion + active safety. + +### Integration + +- `/new` creates isolated subsequent conversation context. +- `/session list` and `/session resume` affect only current scope. +- Scope semantics respect `dm_scope`. + +### Recovery + +- Corrupted or stale index references are self-healed. +- Backlog cleanup removes both in-memory and on-disk artifacts. + +## Phase 2 Extension Hook (Planned) + +When phase 2 starts, add archival queue semantics in session domain: + +- Enqueue old sessions on rotate/prune. +- Async worker summarizes/extracts value into memory files. +- Keep this decoupled from phase 1 command behavior. +