Hooks
Signet’s hook system lets Harnesses integrate with session lifecycle events — injecting Memory at session start, capturing summaries at compaction, and triggering MEMORY.md synthesis.
Overview
Section titled “Overview”Hooks are HTTP endpoints exposed by the Signet Daemon. Harnesses call them at specific lifecycle points:
| Hook | When | Purpose |
|---|---|---|
session-start |
New session begins | Inject memories, identity, and the Memory Check Loop into context |
user-prompt-submit |
Before each user turn | Inject compact current-view context only when the prompt mentions a known entity or active entity alias |
session-end |
Session finishes | Persist transcript lineage and queue session summary |
pre-compaction |
Before context compaction | Get summary guidelines |
compaction-complete |
After compaction | Save a first-class compaction artifact into the temporal DAG |
synthesis |
Scheduled or manual | Get prompt to regenerate MEMORY.md |
synthesis/complete |
After synthesis | Save the merge-safe temporal head |
Session-end Boundary Reasons
Section titled “Session-end Boundary Reasons”POST /api/hooks/session-end accepts an optional reason field. Harnesses
must send an explicit lifecycle reason when the request represents a real
session boundary:
| Reason | Boundary |
|---|---|
clear |
The caller discards the current session context |
session.deleted |
OpenCode deleted the session |
session_branch |
Oh My Pi forked the current session |
session_fork |
pi forked the current session |
session_shutdown |
pi shut down the current session |
session_switch |
pi switched to another session |
These reasons emit one anonymous session.end telemetry event per session
lifetime. Repeated end requests for the same session are deduplicated.
Requests without a recognized boundary reason, including ordinary
session.idle calls, emit session.turn telemetry instead. They still persist
the transcript and queue normal session-end processing.
{ "harness": "opencode", "sessionKey": "session-identifier", "reason": "session.deleted"}Per-Session Bypass
Section titled “Per-Session Bypass”Bypass silences all Signet hooks for a single session without stopping the
daemon. This is useful when you want to work without automatic memory
extraction but still have access to MCP tools like memory_search and
memory_store.
Activation paths
Section titled “Activation paths”-
Environment variable — Set
SIGNET_BYPASS=1before starting a session. The CLI hook process exits immediately with code 0; the daemon is never contacted. -
Daemon API / MCP tool / Dashboard — The session is tracked normally, but the bypass flag is flipped. All hook endpoints return empty no-op responses with
bypassed: truein the response body.
Behavior when bypassed
Section titled “Behavior when bypassed”When bypass is active for a session, all seven hook endpoints return empty
no-op responses with bypassed: true:
session-start— no memories or identity injecteduser-prompt-submit— no per-prompt context loadedsession-end— no memory extraction (but the session claim is still released so future sessions are not blocked)pre-compaction— no summary guidelinescompaction-complete— summary is discardedremember— memory is not savedrecall— no search results returned
The synthesis and synthesis/complete hooks are not affected by bypass.
They are scheduler-driven and have no session context.
The SIGNET_BYPASS=1 environment variable causes the CLI hook process to
exit immediately — the daemon is never contacted, so no session is created
and no network request is made.
Session Start Hook
Section titled “Session Start Hook”POST /api/hooks/session-start
Called when a new agent session begins. Returns memories and context formatted for injection into the system prompt.
Request
Section titled “Request”{ "harness": "openclaw", "agentId": "optional-agent-id", "harnessAgentId": "optional-harness-native-subagent-id", "parentSessionKey": "optional-parent-session-key", "context": "optional context string", "sessionKey": "optional-session-identifier"}harness is required. Everything else is optional. agentId is the Signet
persistence scope. Harness-native sub-agent identifiers should be sent as
harnessAgentId; they are used only for parent-session inference.
When a daemon restart is detected during an already-running session, a harness
may send claimOnly: true with the same sessionKey and a plugin or legacy
runtime path in x-signet-runtime-path or runtimePath. Signet renews the
runtime claim and returns { "sessionKnown": true } without generating
memories, identity, or an inject block. This mode rejects requests without a
runtime path. Do not use it for a new session: it deliberately preserves the
existing conversation’s stable system prompt rather than constructing startup
context.
Response
Section titled “Response”{ "identity": { "name": "Mr. Claude", "description": "Personal AI assistant" }, "memories": [ { "id": 42, "content": "nicholai prefers bun over npm", "type": "preference", "importance": 0.8, "created_at": "2025-02-15T10:00:00Z" } ], "recentContext": "<!-- MEMORY.md contents -->", "inject": "You are Mr. Claude...\n\n## Relevant Memories\n- ..."}The inject field is ready-to-use text for prepending to the system prompt. It includes memories, recent context, the Memory Check Loop, and identity only when Signet-managed identity is enabled.
The Memory Check Loop tells agents when prior context may matter, how to run 1-3 targeted recalls, what pitfalls to avoid, and how to verify they are grounded before acting. It is intentionally behavioral prompt shaping, not a new hook schema or recall algorithm.
Configuration
Section titled “Configuration”In agent.yaml (see Configuration):
capabilities: identity: mode: managed # managed | passthrough | off
hooks: sessionStart: recallLimit: 10 # How many memories to include includeIdentity: true # Only applies when identity.mode is managed includeRecentContext: true # Include MEMORY.md content recencyBias: 0.7 # 0=importance-only, 1=recency-only
contextProfiles: coding: sessionStart: recallLimit: 5 maxInjectTokens: 5000 identity: files: - path: context-profiles/coding/AGENTS.md maxChars: 2200 rich: sessionStart: recallLimit: 50 maxInjectTokens: 20000 harnessProfiles: pi: coding hermes-agent: richContext profiles override hook budgets and startup identity/context files
per harness. Use them to keep coding harnesses lean while preserving richer
cold-start identity in operator or character-forward harnesses. For a compact
coding prompt, run signet context compile --profile coding --max-chars 2200;
that ACPX/inference-backed compiler reads the canonical identity files and
writes context-profiles/coding/AGENTS.md. Session-start hooks only read the
compiled artifact, so model synthesis is never performed in the hot hook path.
Memory scoring uses: score = importance × (1 - recencyBias) + recency × recencyBias
where recency is 1 / (1 + age_in_days).
User Prompt Submit Hook
Section titled “User Prompt Submit Hook”POST /api/hooks/user-prompt-submit
Called before each user turn is handed to the model. Prompt-submit does not run generic memory recall and does not inject fallback guidance when it cannot find a confident match.
The hook listens for known ontology entities and active entity aliases. When a
prompt names one, the entity match scopes the search, then Signet scores that
entity’s current attributes against the remaining prompt. The highest-scoring
attributes choose which aspects to inject into a compact
## Relevant Entity Context block. hooks.userPromptSubmit.minScore gates
attribute-driven aspect selection; maxInjectChars caps the block.
When the prompt is low-signal, mentions no known entity or alias, is ambiguous,
or no attribute clears the confidence gate, the hook returns an empty inject
string. Literal aspect names alone do not select context. This keeps the active
agent loop trustable: absence of injected context means Signet chose not to
inject, not that the broader source substrate has no relevant evidence.
Explicit recall remains available through /api/memory/recall, signet_recall,
memory_search, and related MCP/CLI surfaces. Raw transcript search is
deliberately not injected through this hook; use session_search when a caller
needs transcript evidence.
Pre-Compaction Hook
Section titled “Pre-Compaction Hook”POST /api/hooks/pre-compaction
Called before the harness compresses/summarizes the conversation context. Returns a prompt and guidelines for generating a durable session summary.
Request
Section titled “Request”{ "harness": "openclaw", "sessionContext": "optional current session summary", "messageCount": 150, "sessionKey": "optional-session-id"}Response
Section titled “Response”{ "summaryPrompt": "Pre-compaction memory flush. Store durable memories now.\n\nSummarize...", "guidelines": "Summarize this session focusing on:\n- Key decisions made\n..."}The harness should use summaryPrompt as the instruction to the model for generating a session summary.
Configuration
Section titled “Configuration”hooks: preCompaction: includeRecentMemories: true # Include recent memories in prompt memoryLimit: 5 # How many recent memories summaryGuidelines: | # Custom summary instructions Focus on: - Decisions made - Code patterns discovered - User preferencesCompaction Complete Hook
Section titled “Compaction Complete Hook”POST /api/hooks/compaction-complete
Called after compaction with the generated summary. Saves the summary as a
session_summary memory row and as a first-class temporal DAG artifact used
by MEMORY.md.
Temporal lineage remains agent-scoped. Same sessionKey values from
different agents do not share transcript or summary storage.
Request
Section titled “Request”{ "harness": "openclaw", "summary": "Session summary text...", "sessionKey": "optional-session-id", "project": "/workspace/repo"}If compaction arrives before transcript persistence, project is the required
fallback lineage key. When both exist, transcript lineage wins and the request
project is only used as a fallback.
Response
Section titled “Response”{ "success": true, "memoryId": 123}MEMORY.md Synthesis
Section titled “MEMORY.md Synthesis”Synthesis regenerates the MEMORY.md file by asking an AI model to write a
coherent summary of scored memory and temporal state.
The daemon synthesis worker is the primary runtime path. Harness-scheduled calls are still supported, but they now write through the same DB-backed, lease-protected head record. A busy head lease is a deferred write, not a terminal failure.
Step 1: Request synthesis
Section titled “Step 1: Request synthesis”POST /api/hooks/synthesis
{ "trigger": "scheduled"}Response:
{ "harness": "openclaw", "model": "sonnet", "prompt": "You are regenerating MEMORY.md...\n\n## Memories to Synthesize\n...", "memories": [...]}Step 2: Run the model
Section titled “Step 2: Run the model”The harness runs the prompt through the specified model.
Step 3: Save the result
Section titled “Step 3: Save the result”POST /api/hooks/synthesis/complete
{ "content": "# Memory\n\n## Active Projects\n..."}The daemon:
- Backs up the existing MEMORY.md to
memory/MEMORY.backup-<timestamp>.md - Writes the new content with a generation timestamp header
- Returns
{ "success": true }
Configuration
Section titled “Configuration”memory: synthesis: harness: openclaw # which harness runs synthesis model: sonnet # model identifier schedule: daily # daily | weekly | on-demand max_tokens: 4000Get synthesis config
Section titled “Get synthesis config”GET /api/hooks/synthesis/config
Returns the current synthesis configuration. Harnesses can poll this to know when to trigger synthesis.
OpenClaw Integration
Section titled “OpenClaw Integration”The @signetai/adapter-openclaw package provides a ready-made plugin:
import createPlugin from '@signetai/adapter-openclaw';
const signet = createPlugin({ enabled: true, daemonUrl: 'http://localhost:3850'});
// In your OpenClaw configuration:export default { plugins: [signet],};The plugin automatically calls the appropriate hook endpoints at the right lifecycle moments:
// Session start — inject memoriesconst context = await signet.onSessionStart({ harness: 'openclaw', sessionKey: session.id});// context.inject → prepend to system prompt
// Pre-compaction — get summary instructionsconst guide = await signet.onPreCompaction({ harness: 'openclaw', messageCount: messages.length});// Use guide.summaryPrompt as the compaction instruction
// Compaction complete — save summaryawait signet.onCompactionComplete({ harness: 'openclaw', summary: generatedSummary});
// Manual memory operationsawait signet.remember('nicholai prefers bun', { who: 'openclaw' });const results = await signet.recall('coding preferences');In the current OpenClaw plugin runtime, post-compaction persistence may read
the latest compaction summary back from sessionFile when the hook payload
only exposes metadata. That keeps compaction artifacts in the same temporal
body as ordinary session-end summaries instead of discarding them.
Claude Code Integration
Section titled “Claude Code Integration”Claude Code uses file-based hooks in ~/.claude/settings.json. The hooks call the Signet CLI, which routes requests through the daemon HTTP API:
{ "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "signet hook session-start -H claude-code --project \"$(pwd)\"", "timeout": 3000 }] }], "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "signet hook user-prompt-submit -H claude-code --project \"$(pwd)\"", "timeout": 7000 }] }], "SessionEnd": [{ "hooks": [{ "type": "command", "command": "signet hook session-end -H claude-code", "timeout": 15000 }] }] }}Prompt-submit timeout note: SIGNET_PROMPT_SUBMIT_TIMEOUT defaults to
5000 (daemon wait budget). Claude Code hook config adds a +2000ms
grace buffer when written to settings.json, so the installed
UserPromptSubmit timeout default is 7000.
Upgrade note: Claude Code hook timeouts are persisted in
~/.claude/settings.json during connector install/update. Existing
installs keep old timeout values until you rerun signet connect claude-code (or signet setup) to refresh hook config.
The CLI calls the daemon’s hook endpoints and outputs context that Claude Code injects into the session.
OpenCode Integration
Section titled “OpenCode Integration”OpenCode uses a bundled plugin installed by @signetai/connector-opencode
at ~/.config/opencode/plugins/signet.mjs. The plugin calls the daemon
API at session lifecycle events (session-start, user-prompt-submit,
session-end) and exposes /remember and /recall as native tools.
Install is handled automatically by signet setup or signet connect opencode.
Set SIGNET_ENABLED=false or SIGNET_NO_HOOKS=1 to prevent the plugin from
registering hooks for a sterile background process.
Legacy: Earlier installations placed a fetch-based
memory.mjsat~/.config/opencode/memory.mjs. This path is deprecated. Runningsignet connect opencodemigrates the installation to the current bundled plugin at~/.config/opencode/plugins/signet.mjs.
pi Integration
Section titled “pi Integration”pi uses a bundled extension installed by @signetai/connector-pi at
~/.pi/agent/extensions/signet-pi.js (or $PI_CODING_AGENT_DIR/extensions/signet-pi.js).
The extension calls the daemon API at session lifecycle events (session-start,
user-prompt-submit, session-end, compaction) and exposes /recall, /remember,
and /signet-status commands plus signet_recall, signet_source_search,
signet_session_search, and signet_remember LLM-callable tools.
Install is handled automatically by signet setup or signet connector install pi.
For a remote daemon, pass the daemon URL and API key during install:
signet api-key create --name "work laptop pi" --connector pi --agent-id pi-work-laptopsignet connector install pi \ --url https://signet-home.tailnet:3850 \ --api-key sig_sk_... \ --agent-id pi-work-laptopConfiguration is optional via ~/.pi/agent/extensions/signet.json. Set
SIGNET_ENABLED=false to disable for a single session.
Implementing a Custom Hook Client
Section titled “Implementing a Custom Hook Client”If you’re building a new harness integration, call the hooks directly:
# Session startcurl -X POST http://localhost:3850/api/hooks/session-start \ -H 'Content-Type: application/json' \ -d '{"harness": "my-tool"}'
# Pre-compactioncurl -X POST http://localhost:3850/api/hooks/pre-compaction \ -H 'Content-Type: application/json' \ -d '{"harness": "my-tool", "messageCount": 200}'
# Save compaction summarycurl -X POST http://localhost:3850/api/hooks/compaction-complete \ -H 'Content-Type: application/json' \ -d '{"harness": "my-tool", "summary": "..."}'The daemon returns JSON at each step. Check /health first to verify the daemon is running.
Logs API (Bonus)
Section titled “Logs API (Bonus)”The daemon also exposes a real-time log stream via Server-Sent Events:
GET /api/logs/streamUseful for harnesses that want to monitor Signet activity without polling:
const evtSource = new EventSource('http://localhost:3850/api/logs/stream');evtSource.onmessage = (e) => { const entry = JSON.parse(e.data); console.log(entry.level, entry.message);};Or fetch recent logs:
curl "http://localhost:3850/api/logs?limit=50&level=warn"