Hooks Integration
knowing pushes graph-ranked context into an agent's context window at lifecycle events (session start, before an edit, before spawning a subagent), so the agent gets relevant symbols without having to decide to pull them.
There are two layers:
knowing hook <event>(harness-agnostic core). A single command with a documented, transport-neutral stdin/stdout contract: event + payload in, context out. This is the recommended integration path for any harness (Hermes, OpenCode, Codex, custom loops).- Claude Code hook scripts (reference adapter). The five shell scripts in
hooks/that predate the neutral command. They remain for backward compatibility, but new Claude Code setups can point directly atknowing hook <event> --emit claude-code, which folds the Claude Code payload in and emits its native envelope with no wrapper script.
Harness-Agnostic Contract (A1)
knowing hook <event> reads an optional JSON payload on stdin and writes a result on stdout. The event is the positional argument (or --event, or a stdin event field, in that precedence).
Events: session-start, pre-edit, pre-task, pre-files, pre-compact, post-task.
Input (stdin, JSON; all fields optional):
{
"event": "pre-edit",
"file": "path/to/file.go",
"content": "<code being edited>",
"task": "<task or prompt text>",
"files": ["a.go", "b.go"]
}
The parser is liberal: it also accepts the common Claude Code keys (hook_event_name, tool_name, tool_input.{file_path,old_string,prompt,description}, prompt) and folds them into the neutral fields, so a raw Claude Code payload works unmodified.
Query derivation (ported from the benchmarked pre-edit logic, so behaviour matches):
- pre-edit: extract symbol names from content (func/type/var declarations, method and package calls, CamelCase fallback), filter generic-noise identifiers, take the top 5. Falls back to the file's basename when no symbols are found.
- pre-task: the task text verbatim.
- pre-files: ForFiles blast radius over files.
- session-start / pre-compact: an ambient capability blurb with graph node/edge counts.
- post-task: no injectable context (feedback attribution lives in the MCP feedback layer).
Output with --emit neutral (default):
{
"event": "pre-edit",
"query": "QuerySet",
"context": "GCF profile=graph tool=context_for_task ...",
"symbols": 57,
"tokens": 397,
"latency_ms": 42,
"source": "knowing"
}
--emit claude-code instead produces Claude Code's native hookSpecificOutput.additionalContext envelope (with permissionDecision: allow for PreToolUse). Empty context yields empty output, which every harness treats as a no-op.
Flags: --db, --budget (default 400), --format (default gcf), --emit (neutral|claude-code), --repo, --event. A missing database is not an error: the hook emits empty context so the harness proceeds cleanly on an unindexed repo.
Wiring examples
Claude Code (.claude/settings.json), no wrapper script:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Edit|Write", "command": "knowing hook pre-edit --emit claude-code" }
],
"SessionStart": [
{ "command": "knowing hook session-start --emit claude-code" }
]
}
}
Generic harness (pseudocode):
out = run("knowing hook pre-task", stdin={"task": prompt})
if out.context: inject(out.context)
More adapter examples live in hooks/adapters/.
Keeping the graph current (git auto-sync, A3)
Context injection is only useful if the graph reflects the current code. knowing
sync keeps a tracked repo's graph fresh with zero agent action:
knowing sync # reindex the repo at the cwd
knowing sync --roster # reindex every repo in the roster
knowing sync install # write git post-merge + post-checkout hooks
knowing sync uninstall # remove them
sync reindexes the repo (content-addressing keeps unchanged files cheap) and
scopes LSP enrichment to the files that changed since the last indexed commit:
it reads the previous commit from the latest snapshot and diffs it against HEAD,
so a routine git pull refreshes in seconds rather than re-enriching the whole
tree. With no prior snapshot it does a full index + enrich; with no changes it
skips enrichment entirely.
sync install registers the repo in the roster (so sync can resolve its DB)
and writes two hooks non-destructively (marker-delimited, appended after any
pre-existing hook body):
- post-merge — fires after
git pull/git merge. - post-checkout — fires on branch switch only (
$3 == 1), not file checkouts.
Both invoke knowing sync --quiet in the background and never fail the git
operation (they no-op if knowing is not on PATH). This replaces the
long-lived mcp --watch fs-watcher for the common "reindex on pull" case with a
turnkey per-repo hook.
How the Claude Code Shell Hooks Work
Claude Code supports lifecycle hooks: shell commands triggered at defined moments (session start, before a tool runs, before compaction, on stop). Each hook receives JSON on stdin describing the event, and may output a JSON object with a message field. Claude Code prepends that message to the agent's context for the current operation.
knowing's hooks call knowing context (or knowing query) against the local SQLite graph database. The context engine runs Random Walk with Restart (RWR) seeded by task-relevant symbols, ranks results using HITS hub/authority scores, and packs the top symbols into a token budget. The hook formats this as a one-line JSON message and exits.
All hooks share the same structure:
- Check the kill switch (
KNOWING_HOOKS=offdisables everything). - Verify the database file exists.
- Locate the
knowingbinary (PATH or./knowing). - Query the graph for context relevant to the hook's event.
- Output
{"message": "..."}or exit silently (exit 0 with no output means "no injection").
If any step fails (no database, no binary, empty results), the hook exits cleanly with no output. Hooks never block the agent or produce errors.
Hook Suite
| Hook | Claude Code Event | Matcher | Fires When | Token Cost |
|---|---|---|---|---|
knowing-session-start |
SessionStart | (none) | Session begins | ~50 (once) |
knowing-pre-edit |
PreToolUse | Edit\|Write |
Before any file edit | ~130 per edit |
knowing-pre-compact |
PreCompact | (none) | Before context compaction | ~100 (rare) |
knowing-post-task |
Stop | (none) | Agent completes a task | ~80 (once) |
knowing-subagent |
PreToolUse | Agent\|Task |
Before spawning a subagent | ~130 per spawn |
Hook Details
knowing-session-start (SessionStart)
When it fires: Once, at the start of a new Claude Code session.
What it does: Queries the graph for a node count, then injects a brief orientation message listing available MCP tools (context_for_task, context_for_pr, context_for_files, blast_radius, cross_repo_callers) and recommending format=gcf for token savings.
What context it injects: A static orientation block (~50 tokens) telling the agent the graph exists, how many symbols are indexed, and which tools to call.
Why it matters: Without this, the agent does not know knowing's MCP tools are available until it discovers them through other means. This is a near-zero-cost nudge that fires once.
File: hooks/knowing-session-start
knowing-pre-edit (PreToolUse: Edit/Write)
When it fires: Before every Edit or Write tool invocation. Claude Code passes the tool input (including file_path and old_string) on stdin.
What it does:
- Parses the tool input JSON to extract the file path and
old_string. - Runs edit-aware symbol extraction on
old_string: regex patterns identify Go function names, type declarations, variable/const assignments, method calls, and package-qualified calls. - Queries the context engine with extracted symbols (or falls back to the filename if no symbols are found).
- Injects graph-ranked context: callers, dependents, related types for the symbols being edited.
What context it injects: A GCF/KWF-formatted block of graph-ranked symbols related to the file and specific code being modified. Default budget is 800 tokens, which compresses to ~130 tokens in the injected message.
Edit-aware seeding: The hook does not just query by filename. It extracts the primary symbols from the code being changed (old_string) and uses those as the query. This produces sharply differentiated RWR scores instead of broad package-level matches. The tiered seed matching in the context engine (exact > prefix > substring) means the top results are structurally relevant to the specific edit.
Why it matters: In 9 out of 10 benchmark tasks, this injection provides enough context that the agent skips a separate context_for_task call (which would cost ~4000 tokens). Net savings: +305 tokens across 10 tasks.
File: hooks/knowing-pre-edit
knowing-pre-compact (PreCompact)
When it fires: Before Claude Code compacts the conversation to free context window space.
What it does: Queries the context engine for "most important symbols" at a 600-token budget in GCF format. Injects a compact orientation snapshot (top symbols by blast radius and authority score).
What context it injects: A GCF block of the highest-impact symbols in the codebase (~100 tokens), prefixed with [knowing orientation snapshot - carry through compaction].
Why it matters: This is arguably the highest-value hook. After compaction, the agent loses accumulated context about the codebase. This snapshot survives compaction and gives the agent a structural map to resume from, preventing the "agent amnesia" problem where it re-explores files it already understood.
File: hooks/knowing-pre-compact
knowing-post-task (Stop)
When it fires: When the agent completes a task (the Stop event).
What it does:
- Runs
git diff --name-only HEADto find files modified in the current session. - Filters to source files (
.go,.ts,.py,.rs,.java,.cs), limited to 10 files. - Queries the context engine with
context -filesfor those modified files at a 400-token budget. - Injects a diagnostic listing symbols that may be affected by the changes.
What context it injects: A GCF block listing callers, dependents, and related types for the modified files (~80 tokens), prefixed with [knowing post-task diagnostic] and the list of modified files.
Why it matters: Acts as a self-correction prompt. If the agent modified server.go but forgot to update a caller in handler.go, this surfaces the relationship. The agent can then decide whether follow-up changes are needed.
File: hooks/knowing-post-task
knowing-subagent (PreToolUse: Agent/Task)
When it fires: Before the agent spawns a subagent via the Agent or Task tool.
What it does:
- Parses the tool input JSON to extract the subagent's task description (from
prompt,description, ortaskfields, truncated to 200 characters). - Queries the context engine with that task description at the configured budget.
- Injects graph-ranked context scoped to the subagent's specific task.
What context it injects: A GCF block of symbols relevant to the subagent's task (~130 tokens), prefixed with [knowing context for subagent task].
Why it matters: Spawned subagents start with no codebase knowledge. This hook gives them a focused graph context for their specific task, so they begin informed instead of exploring from scratch.
File: hooks/knowing-subagent
Configuration
Enabling Hooks
Add hook entries to .claude/settings.local.json:
{
"hooks": {
"SessionStart": [
{"command": "./hooks/knowing-session-start"}
],
"PreToolUse": [
{"matcher": "Edit|Write", "command": "./hooks/knowing-pre-edit"},
{"matcher": "Agent|Task", "command": "./hooks/knowing-subagent"}
],
"PreCompact": [
{"command": "./hooks/knowing-pre-compact"}
],
"Stop": [
{"command": "./hooks/knowing-post-task"}
]
}
}
Recommended Minimum
If you want the lowest-risk starting point, enable only SessionStart and PreCompact:
{
"hooks": {
"SessionStart": [
{"command": "./hooks/knowing-session-start"}
],
"PreCompact": [
{"command": "./hooks/knowing-pre-compact"}
]
}
}
These two fire rarely (once at session start, once before each compaction) and solve concrete problems (agent does not know tools exist; agent forgets after compaction) at near-zero token cost.
Disabling Hooks
Disable all hooks at once with an environment variable:
export KNOWING_HOOKS=off
Or remove individual hook entries from .claude/settings.local.json.
Environment Variables
All hooks share these configuration variables:
| Variable | Default | Description |
|---|---|---|
KNOWING_HOOKS |
on |
Set to off to disable all hooks |
KNOWING_HOOKS_DB |
knowing.db |
Path to the SQLite graph database |
KNOWING_HOOKS_BUDGET |
800 |
Token budget for context injection |
KNOWING_HOOKS_FORMAT |
gcf |
Output format (gcf for minimal tokens, kwf, or xml) |
KNOWING_HOOKS_LOG |
.knowing-hooks.jsonl |
Path for the metrics log |
KNOWING_HOOKS_VERBOSE |
0 |
Set to 1 to print injection details to stderr |
Prerequisites
Hooks require:
- A
knowing.dbSQLite database (runknowing index -db knowing.db .to create one). - The
knowingbinary on PATH or as./knowingin the project root.
If either is missing, hooks exit silently with no effect.
Keeping the graph fresh: For best results, run the MCP server with
--watch enabled (knowing mcp --watch -db knowing.db) or run
knowing watch ./repo in a separate terminal. This re-indexes changed files
automatically on save, so hooks always query up-to-date graph data.
How Hooks Interact with the Context Engine
The hooks are thin shell wrappers around knowing's context engine (internal/context). The query flow is:
-
Seed selection: The hook provides a query string (extracted symbols or filename). The context engine matches this against graph nodes using three-tier matching: exact symbol name match (tier 1), prefix match on the last path component (tier 2), substring fallback with a cap of 5 candidates (tier 3). This tiered approach produces 5-30 high-quality seed nodes instead of 100 broad ones.
-
Random Walk with Restart (RWR): Starting from the seed nodes, the engine runs RWR across the graph edges (
calls,implements,references, etc.) to score every reachable node by structural proximity to the seeds. -
HITS reranking: Hub/authority scores separate structurally important symbols (heavily called authorities) from leaf functions that happen to be graph-adjacent.
-
Token packing: The engine selects the top-ranked symbols that fit within the token budget using a knapsack-style packer. GCF encoding compresses the output (84% fewer tokens than JSON).
-
Hook output: The shell script wraps the engine's output in a JSON
{"message": "..."}envelope that Claude Code consumes.
The edit-aware seeding in knowing-pre-edit is particularly important: by extracting symbols from the old_string of an Edit operation, the hook queries the graph for exactly what is being changed rather than the broader file neighborhood. This is what made hooks net-positive after the initial experiments showed them to be net-negative with filename-only queries.
Performance and Benchmarks
Measured Results (10 realistic edit tasks)
| Metric | Value |
|---|---|
| Tasks where hook eliminates need for manual context call | 9/10 (90%) |
| Net token savings across all tasks | +305 tokens saved |
| Precision (injected symbols that are task-relevant) | 30.5% |
| Recall (needed symbols provided by the hook) | 52.3% |
| Latency p95 | 347ms |
What the Numbers Mean
- Net-positive: The hook spends ~130 tokens per edit but eliminates a ~4000-token manual
context_for_taskcall in 90% of cases. Across 10 tasks, this nets +305 tokens saved. - Precision of 30.5% means roughly a third of injected symbols are directly task-relevant. The rest are structurally related (same package, callers) but not critical. For automatic injection this is acceptable because the cost is low.
- Recall of 52.3% means the hook provides just over half the symbols the task actually needs. This is enough for the agent to skip the manual call in most cases.
- Latency under 350ms at p95 means the hook adds imperceptible delay to edit operations.
Running Benchmarks
# Index the repo first
knowing index -db knowing.db .
# Precision/recall benchmark
KNOWING_DB=knowing.db go test -tags hookbench ./hooks/benchmark/ -v -run TestHookBenchmark
# Cost comparison benchmark
KNOWING_DB=knowing.db go test -tags hookbench ./hooks/benchmark/ -v -run TestHookCostComparison
# Try a different budget
KNOWING_DB=knowing.db KNOWING_HOOKS_BUDGET=1200 go test -tags hookbench ./hooks/benchmark/ -v
Experimental History
The hooks went through two phases. The initial implementation used filename-only queries and produced net-negative results (-910 tokens across 10 tasks, only 20% coverage). The fix was two-fold:
- Edit-aware seeding: extracting symbols from
old_stringinstead of querying by filename. - Tiered seed matching: the context engine's seed selection was changed from flat substring matching to a three-tier system (exact > prefix > substring with a cap). This produced sharply differentiated RWR scores instead of flat distributions.
After these changes, coverage jumped from 20% to 90% and the token impact flipped from -910 (cost) to +305 (savings). The full experimental history is in hooks/FINDINGS.md.
Metrics and Analysis
All hooks log events to .knowing-hooks.jsonl:
{"ts":1716000000,"file":"internal/mcp/server.go","event":"inject","latency_ms":280,"tokens":145,"format":"gcf","budget":800}
{"ts":1716000001,"file":"internal/mcp/server.go","event":"miss","latency_ms":12,"tokens":0}
{"ts":1716000002,"event":"pre_compact","tokens":95}
{"ts":1716000003,"event":"subagent_inject","latency_ms":280,"tokens":145}
{"ts":1716000004,"event":"post_task","files":"internal/mcp/server.go","tokens":82}
Events include:
- inject: context was found and injected (with latency and token count).
- miss: query returned no results (file not in graph or no matching symbols).
- pre_compact: orientation snapshot injected before compaction.
- subagent_inject / subagent_miss: context for a spawned subagent.
- post_task: diagnostic context for modified files.
Run the analysis tool to get a summary report:
./hooks/analyze-hooks
This produces hit rate, token overhead (total/mean/p50/p95), latency distribution, per-file breakdown, and A/B evaluation guidance.
File Reference
| File | Purpose |
|---|---|
hooks/knowing-session-start |
SessionStart hook |
hooks/knowing-pre-edit |
PreToolUse hook for Edit/Write |
hooks/knowing-pre-compact |
PreCompact hook |
hooks/knowing-post-task |
Stop hook |
hooks/knowing-subagent |
PreToolUse hook for Agent/Task |
hooks/analyze-hooks |
Metrics analysis script |
hooks/benchmark/bench_test.go |
Precision/recall benchmark |
hooks/benchmark/cost_test.go |
Cost comparison benchmark |
hooks/README.md |
Hook suite overview |
hooks/FINDINGS.md |
Full experimental history |