{"slug": "session-continuity-protocol", "title": "Session Continuity Protocol", "summary": "Signet is building a Session Continuity Protocol to prevent AI coding assistants from losing context when their window limits are hit. The system uses passive checkpoints, agent-initiated digests, and pre-compaction hooks to preserve session state, with recovery injection on new sessions. A new session_checkpoints table stores ephemeral state separately from long-term memories.", "body_md": "# Session Continuity Protocol\n\n## Context\n\nWhen AI coding assistants hit their context window limit, they compact/summarize the conversation and lose nuance — decisions, state, reasoning. The agent “forgets” mid-task. Signet already sits as an external memory layer. This feature makes it catch what the compactor drops, so agents can survive their own context window dying.\n\nThree components: **rolling session digest**, **context offload hook**, and\n**session recovery**.\n\n## Architecture Overview\n\nTwo data channels feed checkpoints:\n\n**Passive accumulation**(all platforms) — daemon observes search queries from user-prompt-submit and /remember calls, writes structural checkpoints every N prompts**Agent-initiated digest**(all platforms via MCP, Phase 2) —`session_digest`\n\nMCP tool lets the agent write a rich narrative checkpoint with decisions, state, blockers\n\n```\nPassive channel (automatic):\n  user-prompt-submit fires →\n    daemon accumulates queries + /remember calls in continuity-state →\n    every N prompts: buffer checkpoint write (batched, not per-prompt)\n\nActive channel (agent-initiated via MCP, Phase 2):\n  agent calls session_digest tool →\n    daemon writes rich narrative checkpoint with agent-provided summary\n\nPre-compaction (Phase 3):\n  pre-compaction hook fires →\n    daemon writes emergency checkpoint with sessionContext\n\nRecovery (automatic):\n  session-start fires →\n    daemon checks for recent checkpoints matching this project →\n    if found: inject latest checkpoint in pre-reserved budget section\n```\n\n## Phased Rollout\n\n**Phase 1**: schema + sessionKey plumbing + passive checkpoints + recovery injection + API\n**Phase 2**: MCP session_digest tool + agent instruction updates\n**Phase 3**: pre-compaction enrichment + pruning policy tuning + scorer integration\n\n## Data Model\n\nNew migration `016-session-checkpoints.ts`\n\n:\n\n```\nCREATE TABLE IF NOT EXISTS session_checkpoints (\n    id TEXT PRIMARY KEY,\n    session_key TEXT NOT NULL,\n    harness TEXT NOT NULL,\n    project TEXT,\n    project_normalized TEXT,           -- realpath-resolved for matching\n    trigger TEXT NOT NULL,             -- 'periodic' | 'pre_compaction' | 'agent' | 'explicit'\n    digest TEXT NOT NULL,\n    prompt_count INTEGER NOT NULL,\n    memory_queries TEXT,               -- JSON: recent search terms\n    recent_remembers TEXT,             -- JSON: recent /remember content\n    created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_checkpoints_session\n    ON session_checkpoints(session_key, created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_checkpoints_project\n    ON session_checkpoints(project_normalized, created_at DESC);\n```\n\nChanges from original:\n\n**Dropped**— avoids race condition on concurrent writes. Use`sequence`\n\ncolumn and UNIQUE constraint`created_at DESC`\n\nordering instead. Each checkpoint is uniquely identified by`id`\n\n(UUID). No sequence math needed.**Added**—`project_normalized`\n\n`realpath()`\n\n-resolved project path for reliable matching across symlinks/aliases. Raw`project`\n\nkept for display.\n\nWhy a new table instead of `memories`\n\n: checkpoints are ephemeral session state with\na different lifecycle (hours not weeks), different query patterns (lookup by\nsession/project not FTS/vector), different retention. Mixing them would pollute\nthe scoring/decay pipeline.\n\n## Implementation\n\n### 0. SessionKey plumbing fix: CLI hooks + daemon\n\n**This is the prerequisite.** Currently, the CLI hook commands (`signet hook session-start`\n\n, `user-prompt-submit`\n\n, `session-end`\n\n, `pre-compaction`\n\n) don’t\nparse or forward `session_id`\n\nfrom stdin. Claude Code sends it as a common field\non all hook events (ref: [Claude Code hooks docs](https://code.claude.com/docs/en/hooks)).\n\n**CLI changes** (`surfaces/cli/src/cli.ts`\n\n):\n\n- In each hook command’s stdin parser, extract\n`session_id`\n\n(or`sessionId`\n\n) - Forward as\n`sessionKey`\n\nin the POST body to the daemon - All hooks already accept\n`sessionKey`\n\non the daemon side — this just connects the pipe\n\n**Stdin JSON from Claude Code** (common fields on all hooks):\n\n```\n{\n    \"session_id\": \"abc123\",\n    \"transcript_path\": \"/path/to/transcript.jsonl\",\n    \"cwd\": \"/path/to/project\",\n    \"permission_mode\": \"default\",\n    \"hook_event_name\": \"UserPromptSubmit\"\n}\n```\n\n### 1. Migration: `platform/core/src/migrations/016-session-checkpoints.ts`\n\nCreate the table + indexes above. Register in `migrations/index.ts`\n\n.\n\n### 2. Continuity state module: `platform/daemon/src/continuity-state.ts`\n\n**New file.** Separate from session-tracker.ts (which stays focused on runtime\nclaim mutex). This module tracks per-session accumulation state for checkpointing.\n\n```\ninterface ContinuityState {\n    readonly sessionKey: string;\n    readonly harness: string;\n    readonly project: string | undefined;\n    readonly projectNormalized: string | undefined;\n    promptCount: number;\n    lastCheckpointAt: number;\n    pendingQueries: string[];      // capped at 20\n    pendingRemembers: string[];    // capped at 10\n    startedAt: number;\n}\n\nconst state = new Map<string, ContinuityState>();\n```\n\nExports:\n\n`initContinuity(sessionKey, harness, project)`\n\n— called from session-start`recordPrompt(sessionKey, queryTerms)`\n\n— increment count, push query`recordRemember(sessionKey, content)`\n\n— push to pendingRemembers`shouldCheckpoint(sessionKey, config)`\n\n— check prompt count + time threshold`consumeState(sessionKey)`\n\n— returns accumulated state, resets pending arrays`clearContinuity(sessionKey)`\n\n— called from session-end`getState(sessionKey)`\n\n— read-only for diagnostics\n\nPath normalization: `projectNormalized`\n\nis set via `fs.realpathSync()`\n\non init,\nfalling back to raw path if realpath fails. All checkpoint queries use the\nnormalized path.\n\n### 3. Checkpoint module: `platform/daemon/src/session-checkpoints.ts`\n\nNew file with core checkpoint operations:\n\n`writeCheckpoint(db, params)`\n\n- Params: sessionKey, harness, project, projectNormalized, trigger, digest, promptCount, memoryQueries, recentRemembers\n- Generates UUID for\n`id`\n\n- Single INSERT — no sequence math, no race conditions\n- Enforces maxCheckpointsPerSession by counting existing rows and deleting oldest if over limit\n\n`getLatestCheckpoint(db, projectNormalized, withinMs)`\n\n- Query:\n`WHERE project_normalized = ? AND created_at > ? ORDER BY created_at DESC LIMIT 1`\n\n- Also supports lookup by sessionKey directly (for explicit linking)\n- Returns checkpoint row or null\n\n`getCheckpointsBySession(db, sessionKey)`\n\n- Returns all checkpoints for a session, ordered by created_at\n- Used by API endpoint\n\n`pruneCheckpoints(db, retentionDays)`\n\n- Delete checkpoints older than retentionDays\n- Keep the most recent checkpoint per session_key within the retention window\n**Called from daemon scheduler/maintenance loop, NOT session-tracker**\n\n### 4. Buffered checkpoint writes\n\nTo avoid blocking the user-prompt-submit hot path with synchronous SQLite writes on every checkpoint trigger:\n\n`shouldCheckpoint()`\n\nreturns true but doesn’t write immediately- The daemon accumulates the checkpoint data and flushes on a short timer (2-3 second debounce, similar to the existing file watcher debounce pattern)\n- If multiple checkpoints are pending for the same session, merge them (latest state wins)\n- Flush on session-end to ensure no data loss\n\nImplementation: a simple `setTimeout`\n\n-based flush queue in the checkpoint\nmodule. Not a full async worker — just delayed writes.\n\n### 5. Checkpoint digest format (passive channel)\n\nFor daemon-accumulated checkpoints (trigger `\"periodic\"`\n\n):\n\n```\n## Session Checkpoint\nProject: {project}\nPrompts: {count} | Duration: {elapsed}\n\n### Memory Activity Since Last Checkpoint\nQueries: {recent search terms from user-prompt-submit}\nRemembered: {recent /remember contents}\nTop memories accessed: {most-hit memory IDs from session_memories FTS tracking}\n```\n\nFor agent-initiated checkpoints (trigger `\"agent\"`\n\n, Phase 2): the agent’s\nsummary verbatim.\n\nFor pre-compaction (trigger `\"pre_compaction\"`\n\n, Phase 3): daemon accumulated\nstate + runtime-provided sessionContext.\n\n### 6. Hook integration: `platform/daemon/src/hooks.ts`\n\n**handleSessionStart** (modify):\n\n- Call\n`initContinuity(sessionKey, harness, project)`\n\nto set up accumulator - After loading memories, before building inject: call\n`getLatestCheckpoint(projectNormalized, 4hrs)`\n\n- Recovery matching priority: sessionKey lineage (via\n`previousSessionKey`\n\nfield)exact normalized project path > skip\n\n- If checkpoint found: inject as\n`## Session Recovery Context`\n\nsection within a**pre-reserved 2000-char budget**(deducted from total budget upfront, not truncated at the end)\n\n**handleUserPromptSubmit** (modify):\n\n- After FTS query: call\n`recordPrompt(sessionKey, queryTerms)`\n\n- Check\n`shouldCheckpoint(sessionKey)`\n\n— if true, queue a buffered write\n\n**handleRemember** (modify):\n\n- After memory saved: call\n`recordRemember(sessionKey, content)`\n\n**handleSessionEnd** (modify):\n\n- Flush any pending checkpoint writes\n- Call\n`clearContinuity(sessionKey)`\n\nto free memory\n\n**handlePreCompaction** (Phase 3, modify):\n\n- Write a checkpoint with trigger\n`\"pre_compaction\"`\n\n- Include\n`req.sessionContext`\n\nin the digest if provided\n\n### 7. SessionKey plumbing in CLI: `surfaces/cli/src/cli.ts`\n\nFor each hook command (`session-start`\n\n, `user-prompt-submit`\n\n, `session-end`\n\n,\n`pre-compaction`\n\n), update the stdin parser to extract `session_id`\n\nand forward\nit as `sessionKey`\n\nin the POST body. Example for user-prompt-submit:\n\n``` js\nconst parsed = JSON.parse(input);\nuserPrompt = parsed.user_prompt || parsed.userPrompt || \"\";\nsessionKey = parsed.session_id || parsed.sessionId || \"\";\n// ... forward sessionKey in body\n```\n\n### 8. Redaction: `platform/daemon/src/session-checkpoints.ts`\n\nAgent-initiated digests (Phase 2) may contain secrets/tokens. Before storing:\n\n- Apply a denylist pattern scan (common patterns: Bearer tokens, API keys,\nbase64-encoded credentials, env var patterns like\n`$SECRET_NAME`\n\n) - Redact matches with\n`[REDACTED]`\n\n- Same redaction applied before serving via\n`/api/checkpoints`\n\nresponses - Reuse existing content normalization from\n`platform/daemon/src/content-normalization.ts`\n\nif applicable\n\n### 9. Configuration: `platform/core/src/types.ts`\n\nAdd under `PipelineV2Config`\n\n(nested, not top-level):\n\n```\nreadonly continuity?: {\n    readonly enabled: boolean;          // default true\n    readonly promptInterval: number;    // default 10\n    readonly timeIntervalMs: number;    // default 900000 (15 min)\n    readonly maxCheckpointsPerSession: number;  // default 50\n    readonly retentionDays: number;     // default 7\n    readonly recoveryBudgetChars: number; // default 2000\n};\n```\n\nWire defaults in `platform/daemon/src/memory-config.ts`\n\n.\n\n### 10. API endpoints: `platform/daemon/src/daemon.ts`\n\nRead-only endpoints behind auth middleware:\n\n`GET /api/checkpoints?project=...&limit=10`\n\n— recent checkpoints for a project`GET /api/checkpoints/:sessionKey`\n\n— all checkpoints for a specific session- Apply auth scope + rate limiting consistent with other\n`/api/*`\n\nroutes\n\n### 11. Checkpoint pruning: daemon scheduler\n\nWire `pruneCheckpoints()`\n\ninto the existing daemon maintenance/scheduler loop\n(see `platform/daemon/src/scheduler/`\n\n), NOT into session-tracker cleanup.\nRun on the same cadence as other maintenance tasks.\n\n## Platform Support Matrix\n\n| Capability | Claude Code | OpenCode | OpenClaw Plugin | OpenClaw Legacy |\n|---|---|---|---|---|\n| Passive checkpoints (Phase 1) | yes | yes | yes | yes* |\n| Session recovery (Phase 1) | yes | yes | yes | yes |\n| MCP session_digest (Phase 2) | yes | yes | yes | yes |\n| Pre-compaction offload (Phase 3) | yes (new) | yes | yes | no |\n\n*OpenClaw legacy requires /recall or /context commands to trigger user-prompt-submit equivalent\n\n## Predictive Memory Scorer Integration (Phase 3)\n\nDeferred to Phase 3 per rollout plan. Connection points documented in\n`docs/specs/planning/predictive-memory-scorer.md`\n\n:\n\n- Recovery sessions vs cold starts create natural A/B for scorer training\n- Checkpoint\n`memory_queries`\n\nfeed FTS behavioral signal pipeline `session_scores.novel_context_count`\n\nmeasures recovery effectiveness- Agent digests capture what the agent was\n*actually doing*for tighter labels\n\n## Key Design Decisions\n\n**Two data channels, not one.** Passive works everywhere automatically. Active\n(MCP) provides rich narrative data. Both feed the same table. Useful on day one,\ndramatically better as agents learn to call session_digest.\n\n**Separate continuity-state.ts from session-tracker.ts.** Session tracker is\npure in-memory mutex logic. Continuity state is accumulation/buffering. Different\nconcerns, different modules.\n\n**No sequence column.** UUID primary key + `created_at DESC`\n\nordering avoids the\nrace condition on concurrent writes. Simpler, no retry logic needed.\n\n**Buffered writes, not per-prompt.** Debounced 2-3s timer prevents blocking the\nhot path. Merged if multiple triggers fire close together.\n\n**Path normalization for recovery matching.** `realpath()`\n\nresolves symlinks and\naliases. Prevents false matches and missed matches from path variations.\n\n**Pre-reserved recovery budget.** 2000 chars deducted from total inject budget\nupfront, not truncated at the end. Guarantees space for recovery context.\n\n**Redaction before storage.** Agent-authored digests may contain sensitive data.\nDenylist scan catches common secret patterns before write and before API serve.\n\n**SQLite-only storage.** Machine-readable recovery artifacts. Dashboard surfaces\nthem via API.\n\n## Files to Create/Modify\n\n| File | Action | Description |\n|---|---|---|\n`platform/core/src/migrations/016-session-checkpoints.ts` | create | New migration |\n`platform/core/src/migrations/index.ts` | modify | Register migration 016 |\n`platform/core/src/types.ts` | modify | Add continuity config under PipelineV2Config |\n`platform/daemon/src/continuity-state.ts` | create | Per-session accumulation state |\n`platform/daemon/src/session-checkpoints.ts` | create | Checkpoint read/write/prune/redact |\n`platform/daemon/src/hooks.ts` | modify | Wire checkpoint triggers + recovery injection |\n`platform/daemon/src/daemon.ts` | modify | Add /api/checkpoints routes with auth |\n`platform/daemon/src/memory-config.ts` | modify | Wire continuity config defaults |\n`surfaces/cli/src/cli.ts` | modify | Parse session_id from stdin in all hook commands |\n`platform/daemon/src/mcp/tools.ts` | modify | Add session_digest MCP tool (Phase 2) |\n`integrations/claude-code/connector/src/index.ts` | modify | Add PreCompaction hook (Phase 3) |\n\n## Verification\n\n`bun run build`\n\n— confirm no type errors across workspace`bun test`\n\n— run existing tests, confirm nothing breaks- Unit test: continuity-state.ts accumulation, shouldCheckpoint logic, buffer flush\n- Unit test: session-checkpoints.ts write/read/prune, path normalization\n- Integration: start session via CLI hook, send 10+ prompts, verify checkpoints in DB\n- Integration: kill session, start new in same project — verify recovery context injected\n- Integration: verify sessionKey flows through CLI hooks to daemon\n`GET /api/checkpoints?project=...`\n\n— verify API returns data with auth- Verify pruning: create old checkpoints, trigger maintenance, confirm retention", "url": "https://wpnews.pro/news/session-continuity-protocol", "canonical_source": "https://signetai.sh/docs/specs/complete/session-continuity-protocol/", "published_at": "2026-06-18 07:37:48+00:00", "updated_at": "2026-06-18 23:38:42.383697+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models", "ai-infrastructure"], "entities": ["Signet", "Claude Code", "MCP"], "alternates": {"html": "https://wpnews.pro/news/session-continuity-protocol", "markdown": "https://wpnews.pro/news/session-continuity-protocol.md", "text": "https://wpnews.pro/news/session-continuity-protocol.txt", "jsonld": "https://wpnews.pro/news/session-continuity-protocol.jsonld"}}