{"slug": "sub-agent-context-continuity", "title": "Sub-Agent Context Continuity", "summary": "Signet has implemented live transcript persistence and sub-agent context inheritance to solve a gap where sub-agents spawned from parent sessions started with no memory or entity context. The engine now writes session transcripts on every user prompt and at session end, making active session content queryable, and automatically threads parent context into child sub-agents at spawn time.", "body_md": "# Sub-Agent Context Continuity\n\n*Engine-managed context threading from parent to sub-agent.*\n\n“The engine manages memory deterministically. The agent doesn’t have to think about memory management — the engine just does the right thing.” — LCM paper, adapted\n\n## Background\n\nIssue #315 surfaced a gap: when a parent agent spawns a sub-agent, the sub-agent starts cold. No memories. No entity context. No awareness of what the parent session has accumulated. The parent must manually summarize relevant context in the task description to compensate, which is fragile and burdens every sub-agent invocation.\n\nThere is a deeper root cause underneath this: session transcripts are only persisted at session end. An active session is dark — its content is unavailable to any other session until it terminates. This makes the parent→child context problem unsolvable even if we wanted to solve it, because the parent’s transcript doesn’t exist yet at the time the sub-agent spawns.\n\nThis spec addresses both: fix the persistence gap first, then build context inheritance on top of it.\n\n## Phase 1: Live Transcript Persistence\n\n**Status:** complete.\n\n### The Problem\n\n`session_transcripts`\n\n(migration 040, hardened by migration 047) stores\nthe complete transcript and keeps it agent-scoped. Prompt-time upserts\nalready keep active sessions visible before session end. If a session\nis interrupted or still running when a sub-agent spawns, the latest\nprompt-time snapshot remains queryable.\n\n### The Fix\n\nKeep writing to `session_transcripts`\n\non every `UserPromptSubmit`\n\nhook\ncall and at `SessionEnd`\n\n. The shared `upsertSessionTranscript`\n\nhelper\npreserves `created_at`\n\n, refreshes `updated_at`\n\n, and updates the FTS\nsurface through the table triggers.\n\nThe active schema supports this:\n\n```\nCREATE TABLE session_transcripts (\n    session_key TEXT NOT NULL,\n    content     TEXT NOT NULL,\n    harness     TEXT,\n    project     TEXT,\n    agent_id    TEXT NOT NULL DEFAULT 'default',\n    created_at  TEXT NOT NULL,\n    updated_at  TEXT,\n    PRIMARY KEY (agent_id, session_key)\n);\n```\n\nThe upsert on each `UserPromptSubmit`\n\nkeeps `content`\n\ncurrent as the\nsession grows. The `created_at`\n\ncolumn preserves the original write\ntime and `updated_at`\n\nrecords the latest active snapshot. `session_end`\n\nretains its own write as a final snapshot to ensure the terminal state\nis captured even if the last few turns missed a hook.\n\n### What This Enables Immediately\n\nAny session — sub-agent or otherwise — can now query a running parent\nsession’s transcript via the existing `session_transcripts`\n\ntable. A\nnew `session_search`\n\nMCP tool and `/api/sessions/search`\n\nendpoint\nexpose this. Cross-session transcript availability also improves\ndebugging, diagnostics, and the session summary DAG (LCM Pattern 4)\nwhich currently can only process completed sessions.\n\n### Implementation\n\n`platform/daemon/src/hooks.ts`\n\n:`handleUserPromptSubmit`\n\nreads the transcript path or inline transcript, normalizes it, and calls`upsertSessionTranscript`\n\n.`platform/daemon/src/hooks.ts`\n\n:`handleSessionEnd`\n\ncalls the same helper for the terminal write.`platform/daemon/src/session-transcripts.ts`\n\n: owns the scoped upsert, read, and FTS-backed search helpers.\n\n## Phase 2: Sub-Agent Context Inheritance\n\n### The Principle\n\nWhen a sub-agent spawns, the daemon should thread the parent’s context to it automatically. No prepare/claim tokens. No explicit tool calls. The engine observes the parent-child relationship through harness-native signals and injects at session-start.\n\nThis is the LCM scope-reduction invariant applied to Signet: the parent delegates a scoped task and the engine ensures the sub-agent has the context it needs to perform it.\n\n### Harness Detection Matrix\n\nEach harness surfaces parent-child relationships differently. Signet handles each natively:\n\n**Claude Code** — `agent_id`\n\nis present on all hook payloads inside a\nsub-agent session (added CC v2.1.69, March 5 2026). No `SubagentStart`\n\nhook needed. Detection:\n\n- Sub-agent’s\n`SessionStart`\n\narrives with`agent_id`\n\npresent. - Daemon queries\n`session_transcripts`\n\nfor the most recently updated row matching the same`project`\n\n,`harness`\n\n, and Signet`agent_id`\n\nwhere`session_key != current`\n\n:`SELECT * FROM session_transcripts WHERE agent_id = ? AND project = ? AND harness = ? ORDER BY updated_at DESC LIMIT 1`\n\n- That row is the parent. No TTL, no pending-spawn slot, no extra agent action. If two parent sessions exist for the same project, the most recently active one is the least surprising choice.\n\n**OpenClaw** — session keys self-describe lineage:\n`agent:{id}:subagent:{uuid}`\n\nvs `agent:{id}:main`\n\n. The `resolveAgentId`\n\nfunction already parses this format (Multi-Agent Phase 8). Detection:\n\n- In\n`handleSessionStart`\n\n, if`isSubagentSessionKey(sessionKey)`\n\nis true, extract the parent session key from the format and query`session_transcripts`\n\ndirectly. No pending-spawn slot needed.\n\n**OpenCode** — `session.created`\n\nSSE event includes `parentID`\n\non\nchild sessions. Detection:\n\n- The plugin records the child session’s\n`parentID`\n\nand passes it through the next session-start hook body as`parentSessionKey`\n\n. Daemon uses it for direct lookup.\n\n**Codex** — sub-agent sessions are indistinguishable from external\nhook payloads. No parent info is available. Graceful degradation:\nno context block injected. The agent can still call `session_search`\n\nmanually if the parent session key is known.\n\n### The Context Block\n\nThe inherited context block is assembled deterministically from data\nalready in the DB. No LLM call. This keeps the session-start latency\nbudget intact and avoids the cost-at-spawn problem LCM’s\n`lcm_expand_query`\n\nsub-agent isolates.\n\nContent (ordered by usefulness, subject to token budget):\n\n**Checkpoint summary**— content from the most recent`session_checkpoints`\n\nrow for the parent session key, if one exists. Checkpoints are already budgeted and formatted for injection; no re-processing needed.**Transcript tail**— last 3000 characters of`session_transcripts.content`\n\nfor turns since the last checkpoint (or from the start if no checkpoint exists). Character budget, no turn parsing, no harness-specific format logic.**Active constraints**—`entity_attributes`\n\nwhere`kind = 'constraint'`\n\nfor focal entities in the checkpoint. Always included per Invariant 5.**Focal entity names**— entity names from the checkpoint’s structural snapshot, if present.\n\nIf the parent has neither a checkpoint nor a transcript yet, the block is omitted rather than injected empty.\n\nThe block is formatted as a named section in the inject:\n\n```\n## Inherited from Parent Session\n\n[session title or \"active session\"]\nRecent context:\n  [last N turns, truncated to budget]\nFocal entities: [entity names]\nActive constraints: [constraint text]\n```\n\nIf the parent session has no transcript yet (e.g., it just started), the block is omitted rather than injected empty.\n\n### Sub-Agent Session Search\n\nWith Phase 1 in place, sub-agents can query any session’s transcript, including the parent’s active session, via a new MCP tool:\n\n```\nmcp__signet__session_search(query, sessionKey?)\n```\n\n`query`\n\n— natural language or keyword search applied to`session_transcripts.content`\n\nvia FTS5`sessionKey`\n\n— optional; defaults to the parent session key if the current session is a sub-agent\n\nThis gives sub-agents pull access to parent context on demand, complementing the push at session-start. Follows LCM’s On-Demand Expansion pattern (Pattern 5) applied across session boundaries.\n\nThe `session_search`\n\nMCP tool is also useful for the parent agent to\nretrospectively search its own prior sessions, independent of the\nsub-agent use case.\n\n### Configuration\n\n```\nmemory:\n  pipelineV2:\n    subagents:\n      inheritContext: true    # default: true when parent is detected\n      tailChars: 3000         # chars from transcript tail since last checkpoint\n```\n\n`includeEntities`\n\nand `includeConstraints`\n\nare always on — constraints\nsurface unconditionally per Invariant 5. `inheritContext: false`\n\ndisables\nthe inject block entirely while leaving `session_search`\n\navailable.\n\nVisibility scoping is not configurable: sub-agents always inherit the\nparent’s `agent_id`\n\nfor all queries. An `isolated`\n\nparent’s sub-agents\nsee only the parent’s data; a `shared`\n\nparent’s sub-agents see all\n`visibility=global`\n\ndata. The existing agent scoping invariant handles\nthis with no special-casing.\n\n### Implementation\n\n`platform/daemon/src/hooks.ts`\n\n: add parent-lookup logic to`handleSessionStart()`\n\n. For CC: if`agent_id`\n\nis present in the payload, query`session_transcripts WHERE project = ? AND session_key != ? ORDER BY updated_at DESC LIMIT 1`\n\n. For OpenClaw: if`isSubagentSessionKey(sessionKey)`\n\n, extract parent key from session key format and query directly. For OpenCode: if`parentSessionKey`\n\nis present in the hook body, use it directly. If parent found, call`assembleInheritBlock()`\n\n.`platform/daemon/src/hooks.ts`\n\n: add`assembleInheritBlock(parentKey, cfg)`\n\n— fetches latest`session_checkpoints`\n\nrow for parent, takes`cfg.tailChars`\n\nfrom tail of`session_transcripts.content`\n\n, fetches active constraints for checkpoint’s focal entities. Pure DB reads, no LLM. Returns formatted block or`null`\n\nif no data exists yet.`platform/daemon/src/mcp.ts`\n\n: register`session_search`\n\ntool. Queries`session_transcripts`\n\nvia FTS5;`sessionKey`\n\nparam defaults to parent key when current session is a sub-agent.`platform/daemon/src/daemon.ts`\n\n: add`GET /api/sessions/{key}/transcript`\n\nand`POST /api/sessions/search`\n\nendpoints.`platform/core/src/types.ts`\n\n: extend`AgentManifest`\n\nwith`memory.pipelineV2.subagents`\n\nconfig block.- No connector changes needed for CC — no\n`SubagentStart`\n\nhook required.\n\n## Relationship to LCM Patterns\n\nThis spec is a direct application of two LCM patterns from\n`docs/specs/planning/LCM-PATTERNS.md`\n\n:\n\n**Pattern 1 (Lossless Retention)** — Phase 1 applies lossless retention\nat the turn level. Active sessions are no longer dark. The immutable\nstore (LCM’s term) is the `session_transcripts`\n\ntable; every turn is\npersisted verbatim as it arrives.\n\n**Pattern 5 (On-Demand Expansion)** — the `session_search`\n\ntool is the\npull path. The inherited context block is the push path. Together they\ncover both the predictable (what the engine infers the sub-agent needs)\nand the unpredictable (what the sub-agent discovers it needs mid-task).\n\nThe key deviation from LCM’s `lcm_expand_query`\n\n(which spawns a\nsub-agent for expansion) is that Signet’s expansion runs in-process.\nNo sub-agent needed for expansion because the knowledge is structured in\nSQLite — a targeted query is cheaper and faster than a spawned agent.\n\n## Decisions\n\n-\n**Transcript slice format**— character budget (3000 chars from tail), not discrete turn parsing. The inherit block is for orientation, not perfect reproduction.`session_search`\n\nis the pull path for anything more specific. No harness-specific parsing needed. -\n**CC parent detection**— project-keyed recency query on`session_transcripts`\n\n, no`SubagentStart`\n\nhook and no pending-spawn slot.`ORDER BY updated_at DESC LIMIT 1`\n\nwhere project, harness, and Signet agent scope match and session key differs. Simpler, no extra prepare step, and it follows the most recently active parent. -\n**Visibility scoping**— sub-agents inherit parent’s`agent_id`\n\nautomatically. Not configurable. The existing agent scoping invariant (cross-cutting invariant 1) handles this with no special-casing. -\n**Harness sub-agent IDs are not Signet agent IDs**— Claude Code’s`agent_id`\n\nhook field is treated as harness lineage metadata only. It must not be used as Signet’s persistence`agent_id`\n\nor sub-agent sessions would silently fork user memory scope.\n\n*Written by Nicholai and Ant. March 24, 2026. Informed by issue #315\nand harness hook research (Claude Code v2.1.69+, OpenClaw plugin\nhooks, OpenCode session.created parentID, Codex graceful degradation).*", "url": "https://wpnews.pro/news/sub-agent-context-continuity", "canonical_source": "https://signetai.sh/docs/specs/approved/sub-agent-context-continuity/", "published_at": "2026-06-20 17:49:17+00:00", "updated_at": "2026-06-20 18:11:59.898350+00:00", "lang": "en", "topics": ["ai-agents", "ai-infrastructure", "developer-tools"], "entities": ["Signet", "Claude Code", "LCM"], "alternates": {"html": "https://wpnews.pro/news/sub-agent-context-continuity", "markdown": "https://wpnews.pro/news/sub-agent-context-continuity.md", "text": "https://wpnews.pro/news/sub-agent-context-continuity.txt", "jsonld": "https://wpnews.pro/news/sub-agent-context-continuity.jsonld"}}