Sessions and hooks API
Harness hook and session lifecycle endpoints.
Hook endpoints integrate with AI harness session lifecycle events. They are used by connector packages to inject memory context and extract new memories.
The x-signet-runtime-path request header (or runtimePath body field)
declares whether the caller is the plugin or legacy runtime path. The
daemon enforces that only one path can be active per session — subsequent
calls from the other path return 409.
POST /api/hooks/session-start
Section titled “POST /api/hooks/session-start”Called at the beginning of a session. Returns context and relevant memories
for injection into the harness system prompt. Requires remember permission
(via hook routing).
Request body
{ "harness": "claude-code", "project": "/workspace/repo", "agentId": "optional-signet-agent-id", "harnessAgentId": "optional-harness-subagent-id", "parentSessionKey": "optional-parent-session-key", "sessionKey": "session-uuid", "runtimePath": "plugin", "claimOnly": false}harness is required. agentId is the Signet persistence scope. First-seen
named agent IDs are registered in the agents table with read_policy set to
shared as the initial policy; existing agent policy rows are preserved.
Harness native sub-agent identifiers, such as Claude Code’s agent_id, must be
sent as harnessAgentId; they are lineage hints and are not used for Signet data
scoping. parentSessionKey may be provided when the harness exposes explicit
lineage. If it is absent, Signet infers parent context where possible from
harness-native signals such as OpenClaw lineage session keys or recent Claude
Code parent activity in the same project.
Set claimOnly: true only when recovering an already-running harness session
after a daemon restart. It requires a plugin or legacy runtime path in
x-signet-runtime-path or runtimePath; requests without one return 400.
The daemon renews the runtime-path claim and returns { "sessionKnown": true }
without rebuilding or returning startup context. The caller must not inject
startup memory or identity into an existing conversation: its original
session-start context is already in the system prompt, and changing it
mid-conversation invalidates prompt caching.
Response — ordinary starts return the implementation-defined context object
from handleSessionStart; claim-only recovery returns only
{ "sessionKnown": true }.
POST /api/hooks/user-prompt-submit
Section titled “POST /api/hooks/user-prompt-submit”Called on each user message. Returns compact entity current-view context only when the prompt mentions a known ontology entity or active alias and at least one current attribute clears the confidence gate. The entity mention scopes the search; attribute relevance chooses which aspect context to inject.
Request body
{ "harness": "claude-code", "userMessage": "How do I set up dark mode?", "userPrompt": "How do I set up dark mode?", "lastAssistantMessage": "Earlier we discussed using CSS variables for theme tokens.", "sessionKey": "session-uuid", "transcriptPath": "/tmp/signet/session-transcript.txt", "runtimePath": "plugin"}harness is required.
userMessage is preferred when the harness can provide a cleaned user turn.
userPrompt, lastAssistantMessage, transcriptPath, and inline transcript
are optional.
Prompt-submit does not run generic memory recall and has no fallback injection.
Low-signal prompts, unknown entities, ambiguous entity mentions, and prompts
where no attribute clears hooks.userPromptSubmit.minScore return inject: "".
Explicit recall is still available through /api/memory/recall and MCP/CLI
recall tools. Raw transcript search is not injected on prompt-submit; use the
dedicated session_search MCP/API surface when a caller needs transcript
evidence.
Under sustained concurrency (many harnesses submitting at once), the daemon
caps in-flight prompt-submit work: once more than 8 submissions are processing
concurrently, the hook returns 503 with a Retry shortly error instead of
queueing indefinitely. Callers should treat 503 as backpressure and retry
with backoff.
POST /api/hooks/session-end
Section titled “POST /api/hooks/session-end”Called at session end. Captures immutable episodic transcript evidence for later summary and Dreaming work; it does not save the raw transcript as a retrieval memory. Releases the session’s runtime path claim.
Request body
{ "harness": "claude-code", "sessionKey": "session-uuid", "sessionId": "session-uuid", "transcriptPath": "/tmp/signet/session-transcript.txt", "capturedAt": "2026-08-03T20:00:00.000Z", "runtimePath": "plugin"}harness is required.
transcriptPath or inline transcript may be provided for transcript
capture. capturedAt is optional for live hooks; importers should supply the
original ISO-8601 event time so temporal reasoning retains source chronology.
Signet stores a cleaned conversation-only transcript as episodic evidence and
may retain raw auditable traces separately in daemon logs.
When transcript text is available, the daemon queues a capture receipt and
then writes the canonical conversation transcript as JSONL at
$SIGNET_WORKSPACE/memory/{harness}/transcripts/transcript.jsonl and records
lineage through the session manifest. Existing markdown transcript artifacts
remain readable for backward compatibility and are backfilled into the JSONL
history.
The session-end marker completes the canonical transcript row. Dreaming reads
that completed row through a sanitized, read-time projection: tool calls remain
as markers, tool outputs are excluded, and the retained transcript is not
rewritten. There is no summary-worker job or generated session-summary artifact
in this path. The response includes transcriptCaptureJobId when transcript
capture was queued. Poll GET /api/hooks/transcript-capture/:jobId?agentId=<agent>
until the status is completed; the receipt never exposes transcript content.
POST /api/hooks/remember
Section titled “POST /api/hooks/remember”Explicit memory save from within a session. Requires remember permission.
Request body
{ "harness": "claude-code", "content": "User wants dark mode by default", "sessionKey": "session-uuid", "runtimePath": "plugin"}harness and content are required.
POST /api/hooks/recall
Section titled “POST /api/hooks/recall”Explicit memory query from within a session. Requires recall permission.
Request body
{ "harness": "claude-code", "query": "user UI preferences", "keywordQuery": "\"dark mode\" OR theme", "project": "/workspace/repo", "limit": 5, "type": "preference", "tags": "ui,editor", "who": "claude-code", "since": "2026-01-01T00:00:00Z", "until": "2026-04-01T00:00:00Z", "aggregate": true, "aggregateBudget": "small", "saveAggregate": true, "sessionKey": "session-uuid", "agentId": "alice", "includeRecalled": false, "runtimePath": "plugin"}harness and query are required.
This route is a hook-oriented wrapper around POST /api/memory/recall. It
accepts a narrower request surface, applies hook/session policy checks, and
then forwards the supported recall filters and explicit aggregate recall flags
into the shared recall path.
When sessionKey is present, it participates in the same context-epoch dedupe
ledger as POST /api/memory/recall.
project on this route is forwarded as the memory project filter. It is not
remapped to recall scope.
Response
Same recall-family shape as POST /api/memory/recall, plus legacy
compatibility fields during the transition period:
{ "results": [], "memories": [], "count": 0, "query": "user UI preferences", "method": "hybrid", "meta": { "totalReturned": 0, "hasSupplementary": false, "noHits": true }, "message": "No matching memories found."}Special no-op cases preserve the same shape and add a flag:
{ ..., "bypassed": true }when the session is bypassed{ ..., "internal": true }for internal no-hook calls
memories and count are legacy compatibility aliases for older hook
consumers and will mirror results and results.length during the
transition period. message is the canonical formatted recall brief used by
thin harness hooks so connectors do not reimplement ranking or presentation
rules.
POST /api/hooks/pre-compaction
Section titled “POST /api/hooks/pre-compaction”Called before context window compaction. Returns summary instructions for
the compaction prompt.
This endpoint does not advance the recall context epoch; only
/api/hooks/compaction-complete does.
Request body
{ "harness": "claude-code", "sessionKey": "session-uuid", "runtimePath": "plugin"}harness is required.
POST /api/hooks/compaction-complete
Section titled “POST /api/hooks/compaction-complete”Save a compaction summary as a memory row, as a temporal DAG artifact, and as a canonical immutable markdown compaction artifact linked back through the session manifest.
Request body
{ "harness": "claude-code", "summary": "Session covered dark mode setup and vim configuration...", "sessionKey": "session-uuid", "project": "/workspace/repo", "runtimePath": "plugin"}harness and summary are required.
If sessionKey is present, the daemon uses it to preserve lineage:
- the memory row is agent-scoped
source_idpoints back to the session lineage- the temporal node keeps
session_key - the artifact can later be expanded through the temporal drill-down API
- transcript and temporal summary persistence are keyed by
agentId + sessionKey, so identical session keys from different agents do not collide - the canonical compaction file is written to
memory/{captured_at}--{session_token}--compaction.md - the mutable manifest for that session is backfilled with
compaction_path - the recall context epoch advances, so memories recalled before compaction are eligible again in the fresh context
If compaction fires before transcript persistence lands, callers should also
send project. The daemon uses that explicit project as the fallback lineage
scope until transcript storage catches up.
Response
{ "success": true, "memoryId": "uuid", "contextEpoch": 1 }POST /api/hooks/session-checkpoint-extract
Section titled “POST /api/hooks/session-checkpoint-extract”Record a mid-session checkpoint for long-lived sessions (Discord bots,
persistent agents) that never call session-end. Computes a delta since the
last extraction cursor, retains it in the canonical transcript/checkpoint
records, and does not release the session claim. Checkpoints are not delivered
to Dreaming until the transcript receives its completion marker.
Request body
{ "harness": "openclaw", "sessionKey": "session-uuid", "agentId": "agent-id", "project": "/workspace/repo", "transcriptPath": "/tmp/signet/session.jsonl", "runtimePath": "plugin"}harness and sessionKey are required. transcript (inline string) takes
precedence over transcriptPath; both fall back to the stored session
transcript from a prior session-end or user-prompt-submit call. Native
daemon file-backed transcript reads require transcriptPath to resolve under
the connector staging root /tmp/signet, point to a regular file, and fit
within the transcript size limit.
The endpoint skips silently when:
- The delta since the last extraction cursor is < 500 characters
- No transcript is available
- The session is bypassed
On a checkpoint with transcript content, the daemon retains a full snapshot
when it is at least as complete as the stored canonical row, writes the
continuity checkpoint, and resets the live continuity window. The endpoint
intentionally returns { "skipped": true } for every request after this
bookkeeping: the retired summary worker is no longer a production path and no
jobId is created. It also returns that response when no transcript is
available, the pipeline is disabled, or the session is bypassed.
GET /api/hooks/synthesis/config
Section titled “GET /api/hooks/synthesis/config”Return the current synthesis configuration (thresholds, model, schedule).
POST /api/hooks/synthesis
Section titled “POST /api/hooks/synthesis”Request a MEMORY.md synthesis run. Implementation-defined request body
and response from handleSynthesisRequest.
Current MEMORY.md generation is a deterministic projection, not a free-form
LLM rewrite:
- scored durable memories come from the memory database
- rolling session-ledger rows come from canonical artifact frontmatter in
memory_artifacts - temporal context comes from
session_summariesDAG artifacts - the response keeps the rendered markdown in
promptfor backward compatibility, withmodel: "projection" indexBlockcontains the exact## Temporal Indexblock already included in the rendered projection
The rendered file contains these required sections:
## Global Head (Tier 1)## Thread Heads (Tier 2)## Session Ledger (Last 30 Days)## Open Threads## Durable Notes & Constraints## Temporal Index
Optional agentId / sessionKey inputs may be provided so synthesis resolves
the correct agent-scoped head.
POST /api/hooks/synthesis/complete
Section titled “POST /api/hooks/synthesis/complete”Write a newly synthesized MEMORY.md. Backs up the existing file before
overwriting and records DB-backed head metadata used for same-agent
merge protection.
Request body
{ "content": "# Memory\n\n...", "agentId": "optional-agent-id", "sessionKey": "optional-session-key"}content is required.
If another writer currently holds the active MEMORY.md lease for the same
agent head, this route returns 409.
Response
{ "success": true }Sessions
Section titled “Sessions”The sessions API exposes active session state, including per-session bypass
toggles. When bypass is enabled for a session, all hook endpoints return
empty no-op responses with bypassed: true — but MCP tools (memory_search,
memory_store, etc.) continue to work normally.
GET /api/sessions
Section titled “GET /api/sessions”List active sessions for the requesting agent with their bypass status.
The response merges live tracker claims with live cross-agent presence so
sessions do not disappear just because one surface has not claimed the
session yet. Results are scoped to the authenticated agent; for
cross-agent visibility use GET /api/cross-agent/presence.
Response
{ "sessions": [ { "key": "session-uuid", "runtimePath": "plugin", "claimedAt": "2026-03-08T10:00:00.000Z", "expiresAt": "2026-03-08T14:00:00.000Z", "bypassed": false } ], "count": 1}GET /api/sessions/:key
Section titled “GET /api/sessions/:key”Get a single session’s status by its session key.
Both raw keys (abc123) and prefixed keys (session:abc123) are accepted.
Response
{ "key": "session-uuid", "runtimePath": "plugin", "claimedAt": "2026-03-08T10:00:00.000Z", "expiresAt": "2026-03-08T14:00:00.000Z", "bypassed": false}Returns 404 if the session key is not found.
GET /api/sessions/:key/transcript
Section titled “GET /api/sessions/:key/transcript”Return the canonical cleaned transcript for a session. Results are scoped to
the authenticated agent; pass agent_id only when calling with an authorized
agent scope.
Both raw keys (abc123) and prefixed keys (session:abc123) are accepted.
Response
{ "sessionKey": "session-uuid", "agentId": "default", "content": "User: ...\nAssistant: ..."}Returns 404 if no transcript exists for that session and agent scope.
GET /api/sessions/blackbox
Section titled “GET /api/sessions/blackbox”List sessions with replayable Black Box evidence for the requesting agent.
This is a dashboard-oriented flight recorder index assembled from existing
recall telemetry, context injection events, source artifacts, and epistemic
assertions. Results are scoped to the authenticated agent and may be narrowed
with project.
Query parameters
agent_id/agentId— optional scoped agent selector when authorized.project— optional project path filter.limit— optional maximum session count.
Response
{ "agentId": "default", "sessions": [ { "sessionKey": "session-uuid", "agentId": "default", "project": "/workspace/repo", "lastAt": "2026-03-08T10:00:00.000Z", "recallEvents": 3, "artifactEvents": 1 } ], "count": 1}GET /api/sessions/:key/blackbox
Section titled “GET /api/sessions/:key/blackbox”Return a session-scoped Black Box replay for a specific session key. The replay
preserves the raw session key supplied by the caller so persisted session:
prefixes continue to resolve. It explains what evidence was visible to Signet
at each point in the session; it does not claim deterministic model causality.
Query parameters
agent_id/agentId— optional scoped agent selector when authorized.project— optional project path filter. When present, unprojected recall context rows are omitted unless they can be joined through project artifacts.
Response
{ "sessionKey": "session-uuid", "agentId": "default", "generatedAt": "2026-03-08T10:05:00.000Z", "eventCount": 2, "events": [ { "id": "telemetry:abc:query", "kind": "recall.requested", "at": "2026-03-08T10:00:00.000Z", "title": "Recall requested", "detail": "dashboard provenance", "refs": [], "payload": { "route": "/api/memory/recall", "resultCount": 3 } } ], "frame": { "at": "2026-03-08T10:00:00.000Z", "activeRefCount": 0, "activeRefs": [], "likelyInfluences": [], "warnings": [] }}events[].kind is one of recall.requested, recall.result,
context.recalled, artifact.written, or assertion.created.
POST /api/sessions/search
Section titled “POST /api/sessions/search”Search active or completed session transcripts. This route powers the
session_search MCP tool and is intended for sub-agents that need to inspect
the parent session without forcing a large token snapshot into every spawn.
Results are agent-scoped and require recall permission.
Request body
{ "query": "Juniper trunk ports", "sessionKey": "optional-specific-session", "currentSessionKey": "agent:nicholai:subagent:abc123", "agentId": "nicholai", "project": "/workspace/repo", "limit": 5}query is required. limit is clamped to 1..20. If sessionKey is absent
and currentSessionKey encodes OpenClaw sub-agent lineage, Signet defaults the
search to the inferred parent session. Otherwise, Signet searches transcripts
in the requested agent and project scope while excluding currentSessionKey.
Response
{ "query": "Juniper trunk ports", "hits": [ { "sessionKey": "agent:nicholai:main", "project": "/workspace/repo", "updatedAt": "2026-03-25T10:05:00.000Z", "excerpt": "keep the Juniper EX4300 VLAN audit focused on trunk ports", "rank": -1.2 } ], "count": 1}GET /api/sessions/summaries
Section titled “GET /api/sessions/summaries”List temporal manifest nodes used for drill-down and MEMORY.md synthesis.
Completed transcript nodes are written by Dreaming’s content pass; historical
summary nodes remain readable for provenance. Results are agent-scoped.
Response
{ "summaries": [ { "id": "sess-1", "kind": "session", "depth": 0, "source_type": "transcript", "source_ref": "session-uuid", "meta_json": "{\"source\":\"dreaming-content-pass\"}" } ]}POST /api/sessions/summaries/expand
Section titled “POST /api/sessions/summaries/expand”Expand a temporal node by id. Returns lineage, linked memories, and transcript
context for MEMORY.md drill-down and LCM-style expansion. Expansion is
agent-scoped.
Request body
{ "id": "node-id", "includeTranscript": true, "transcriptCharLimit": 2000}Response
{ "node": { "id": "node-id", "kind": "session", "depth": 0, "sourceType": "summary" }, "parents": [], "children": [], "linkedMemories": [], "transcript": { "sessionKey": "session-uuid", "excerpt": "..." }}POST /api/sessions/:key/bypass
Section titled “POST /api/sessions/:key/bypass”Toggle bypass for a session. When enabled, all hook endpoints for this session
return empty no-op responses with bypassed: true. MCP tools are not affected.
Both raw keys and session:<uuid> forms are accepted.
Request body
{ "enabled": true}enabled is required (boolean).
Response
{ "key": "session-uuid", "bypassed": true}Returns 404 if the session key is not found.

