mishkan-harness 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +75 -24
  2. package/bin/mishkan.js +145 -22
  3. package/docs/design/MISHKAN_decisions.md +400 -0
  4. package/docs/design/MISHKAN_observability.md +1023 -0
  5. package/docs/usage/01-installation.md +40 -12
  6. package/docs/usage/10-observability.md +149 -0
  7. package/docs/usage/README.md +1 -0
  8. package/package.json +1 -1
  9. package/payload/install/settings.hooks.json +9 -0
  10. package/payload/mishkan/hooks/model-route.py +32 -0
  11. package/payload/mishkan/hooks/post-tool-observe.sh +222 -14
  12. package/payload/mishkan/hooks/pre-tool-security.sh +16 -0
  13. package/payload/mishkan/hooks/pre-tool-trace.sh +43 -0
  14. package/payload/mishkan/observability/README.md +100 -0
  15. package/payload/mishkan/observability/bus.py +89 -0
  16. package/payload/mishkan/observability/bus.sh +117 -0
  17. package/payload/mishkan/observability/schema.json +209 -0
  18. package/payload/mishkan/observability/usage_parser.py +211 -0
  19. package/payload/mishkan/observability/watch/README.md +62 -0
  20. package/payload/mishkan/observability/watch/pyproject.toml +22 -0
  21. package/payload/mishkan/observability/watch/src/mishkan_watch/__init__.py +3 -0
  22. package/payload/mishkan/observability/watch/src/mishkan_watch/__main__.py +29 -0
  23. package/payload/mishkan/observability/watch/src/mishkan_watch/app.py +235 -0
  24. package/payload/mishkan/observability/watch/src/mishkan_watch/client.py +134 -0
  25. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/__init__.py +1 -0
  26. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/activity.py +100 -0
  27. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/agents.py +278 -0
  28. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/knowledge.py +188 -0
  29. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/live.py +307 -0
  30. package/payload/mishkan/observability/watch/src/mishkan_watch/tabs/workflows.py +189 -0
  31. package/payload/mishkan/observability/watch/src/mishkan_watch/theme.tcss +254 -0
  32. package/payload/mishkan/observability/watchd/README.md +59 -0
  33. package/payload/mishkan/observability/watchd/pyproject.toml +23 -0
  34. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__init__.py +3 -0
  35. package/payload/mishkan/observability/watchd/src/mishkan_watchd/__main__.py +230 -0
  36. package/payload/mishkan/observability/watchd/src/mishkan_watchd/lifecycle.py +68 -0
  37. package/payload/mishkan/observability/watchd/src/mishkan_watchd/server.py +134 -0
  38. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/__init__.py +6 -0
  39. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/bus_tail.py +97 -0
  40. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/cognee_poll.py +228 -0
  41. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/mcp_probe.py +230 -0
  42. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_discover.py +74 -0
  43. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/session_tail.py +161 -0
  44. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/subagent_tail.py +195 -0
  45. package/payload/mishkan/observability/watchd/src/mishkan_watchd/sources/worktree_poll.py +107 -0
  46. package/payload/mishkan/observability/watchd/src/mishkan_watchd/state.py +288 -0
  47. package/payload/mishkan/observability/watchd/tests/test_state.py +77 -0
@@ -167,6 +167,406 @@ small extra Neo4j. The curated library is small and regenerable
167
167
  bulk seeding bursts embedding calls and cloud free-tier embeddings 429
168
168
  (RESOURCE_EXHAUSTED).
169
169
 
170
+ ## D-008 — Three-layer memory epistemology: structure / project semantics / curated cross-project (added 2026-06-05)
171
+
172
+ **Decision:** MISHKAN's knowledge surface is split into **three physically
173
+ separate stores**, each owning one epistemic question, with no overlap of write
174
+ authority:
175
+
176
+ | Store | Question it answers | Source of truth | Write authority |
177
+ |---|---|---|---|
178
+ | **Graphify** (per-project, local artifacts) | *How is the code structured?* — call graphs, dependents, god nodes, schema-to-code edges, file-to-symbol provenance | tree-sitter AST + optional LLM enrichment, deterministic, re-derivable from the repo | the build (`graphify` CLI), no agent writes by hand |
179
+ | **Cognee work** (`:7777`, `cognee` MCP) | *Why does this code exist? what did we decide? what did we learn on this project?* | curated project artifacts (PRD, SRS, ADRs, sprint reports, agent learnings) ingested via `mishkan-ingest` | agents, gated by `mishkan: ingest` frontmatter |
180
+ | **Cognee curated** (`:7730`, `cognee-curated` MCP, read-only from projects) | *What have we learned across all projects?* | promoted cross-harness knowledge, reference library | `/sprint-close` + `seed-curated-library.sh`, only |
181
+
182
+ Graphify v0.8.31 (MIT, [github.com/safishamsi/graphify](https://github.com/safishamsi/graphify))
183
+ joins the stack as a **third store**, not as a Cognee feeder, not as a Cognee
184
+ replacement.
185
+
186
+ ### Force-tension
187
+
188
+ **What pushes toward a third layer.** Structural questions ("who calls
189
+ `apply_overlay`?", "what depends on `models.User`?", "where are the god nodes?",
190
+ "what tables does this service read?") are answered today by repeated grep and
191
+ file reads, which is exactly the failure mode the engineer profile names: token
192
+ waste, context bloat, and answers that drift because they reconstruct structure
193
+ from prose instead of reading it from the AST. Graphify gives a deterministic
194
+ graph, locally extracted, re-derivable from the repo at any time — the *opposite*
195
+ epistemic shape from Cognee work, which is a curated, lossy, LLM-summarised
196
+ narrative of decisions. Conflating the two in one store has already been rejected
197
+ once (D-007 separated curated from work for the same reason: different write
198
+ discipline, different trust shape).
199
+
200
+ **What pushes back.** Three stores is one more runtime, one more failure mode,
201
+ one more place for an agent to look in the wrong order. Graphify is a young
202
+ project (v0.8.x, ~10 weeks old at this writing) with a credible breaking-change
203
+ risk. Cognee already has a code-extraction notion (`codify`); adding Graphify
204
+ risks duplicating capability the harness already paid for. The boundary between
205
+ "structure" and "semantics" is not always crisp ("why does this function call
206
+ `Y`?" is both).
207
+
208
+ The tension resolves toward the split: the *write discipline* is what matters,
209
+ and the three stores have three different write disciplines (deterministic build
210
+ output / curated agent ingestion / cross-harness promotion). Collapsing any two
211
+ of them collapses one discipline into another and loses the property D-007 was
212
+ introduced to protect.
213
+
214
+ ### Alternatives considered
215
+
216
+ 1. **Keep only the two Cognee stores (status quo).** *Bad.* Structural questions
217
+ stay grep-shaped — high token cost per question, answers that miss
218
+ transitive edges and god-node patterns by construction. Cognee `codify`
219
+ produces an LLM-summarised view of code, not a deterministic call graph: it
220
+ is the wrong tool for "who depends on X" and was never meant to be that tool.
221
+ Leaves the engineer's documented complaint about token waste unaddressed.
222
+
223
+ 2. **Add Graphify as a third store with a hard write-discipline boundary.**
224
+ *Chosen.* Each store answers one question, each has one writer, agents
225
+ consult them in a documented order (Graphify first for structure, Cognee
226
+ work for rationale, Cognee curated for cross-project precedent). Mirrors
227
+ D-007's logic: physical separation when write discipline differs.
228
+
229
+ 3. **Make Graphify a pre-processor / feeder into Cognee work.** Rejected.
230
+ Graphify's value is that the graph is *deterministic and re-derivable* —
231
+ pushing it through Cognee's LLM extraction layer destroys both properties.
232
+ The output would be a lossy paraphrase of a graph that was exact, ingested
233
+ into a store optimised for semantics not topology. It would also entangle
234
+ Graphify's update cycle (per-commit) with Cognee's ingestion cycle (curated,
235
+ sparse), forcing one to the cadence of the other.
236
+
237
+ 4. **Substitute Cognee with Graphify entirely.** *Bad — door explicitly closed.*
238
+ Graphify does not hold decisions, rationale, sprint reports, agent learnings,
239
+ or the curated cross-project library. Replacing Cognee with Graphify would
240
+ delete the "why" layer to gain a "how" layer. The two are orthogonal, not
241
+ competing.
242
+
243
+ 5. **Defer Graphify and re-evaluate after a measurement POC.** Rejected as the
244
+ primary path because the write-discipline argument is independent of any
245
+ token-saving number: even if Graphify saved zero tokens, the deterministic
246
+ structural graph still belongs in its own store. A measurement POC is still
247
+ useful but is out of scope here (see below).
248
+
249
+ ### Invariants of boundary (the routing matrix agents follow)
250
+
251
+ - **Structure → Graphify.** "Who calls X", "what depends on Y", "god nodes",
252
+ "files touching table Z", "transitive dependents of module M", "what does
253
+ this file import / export". Deterministic; re-derivable; cite the graph node
254
+ id.
255
+ - **Project rationale / decisions / learnings → Cognee work.** "Why did we
256
+ choose X over Y", "what did sprint S3 conclude on auth", "what did Hizkiah
257
+ learn about the embedding 429 issue". Curated; cite the ingested artifact.
258
+ - **Cross-project reference → Cognee curated.** "How have we handled rate
259
+ limiting elsewhere", "what does the engineer profile say about commit
260
+ format". Read-only from projects; cite the curated node id.
261
+ - **Ambiguous questions ("why does this function call Y?")** decompose into a
262
+ structure half (Graphify: it calls Y at file:line, via path P, in branch B)
263
+ and a semantics half (Cognee work: ADR D-00x decided that B owns Y because
264
+ reasons). Agents answer both halves explicitly; they never fuse them into
265
+ one store.
266
+ - **Write authority is exclusive.** No agent writes to Graphify (only the
267
+ `graphify` CLI does, on its update trigger). No build process writes to
268
+ Cognee work (only agents do, gated by `mishkan: ingest`). No project writes
269
+ to Cognee curated (only `/sprint-close` and the seed script do).
270
+
271
+ ### Integration
272
+
273
+ - **Runtime placement.** Graphify runs **per-project**, artifacts live under
274
+ `.graphify/` in the project (`graph.html`, `GRAPH_REPORT.md`, `graph.json`),
275
+ gitignored by default. No shared service, no port. The Neo4j export
276
+ (`--neo4j-push`) targets a **dedicated Graphify Neo4j container** when used —
277
+ it does **not** share the Cognee work store's Neo4j (port 7687 in that
278
+ container is Cognee's; collision would commingle a deterministic AST graph
279
+ with an LLM-summarised semantic graph, the exact conflation D-008 forbids).
280
+ Most agent queries hit `graph.json` directly; Neo4j is opt-in for cross-repo
281
+ graph queries and not part of the default install.
282
+ - **Re-extraction trigger.** Incremental refresh via `graphify --update` runs:
283
+ (a) on a **post-commit hook** when files in `src/`, `lib/`, or
284
+ language-specific source roots change, and (b) on `/sprint-close` as a
285
+ belt-and-braces full re-extract. No cron — the engineer's stateful-operation
286
+ rule applies: AI prepares the command, Y4NN runs it (the post-commit hook is
287
+ local to his machine, run by his shell, not by an agent).
288
+ - **Agent consult order (PreToolUse hook).** Graphify ships a PreToolUse hook
289
+ that nudges Claude Code toward graph-first queries. The MISHKAN integration
290
+ is **deferred to a follow-up ADR** (see Out of Scope) — landing it requires
291
+ threading through the existing Bash PreToolUse validator chain (D-004) and
292
+ per-agent opt-in for the five code-writing specialists (Hizkiah, Salma,
293
+ Oholiab, Nathan, Zadok). The hook is *available* but *not enabled by default*
294
+ in this decision.
295
+ - **Citation discipline.** When an agent answers from Graphify, it cites the
296
+ graph node id and the source `file:line`. When it answers from Cognee work,
297
+ it cites the ingested artifact. No "according to the graph" without an id.
298
+
299
+ ### Out of scope (explicitly not decided here)
300
+
301
+ 1. **Token-saving measurement POC.** Whether Graphify saves Nx tokens on a
302
+ representative agent workload is a separate, instrumented experiment with
303
+ its own success criteria. The 71.5× figure circulating in third-party write-ups
304
+ is **not** verified by this ADR and must not be cited as a MISHKAN claim
305
+ until measured.
306
+ 2. **Refactor of the Explore agent / Hiram's exploration playbook.** Whether
307
+ Hiram should consult Graphify before grep is a downstream agent change,
308
+ not decided here.
309
+ 3. **Unified Graph Explorer UI** combining Graphify's `graph.html` and
310
+ Cognee's Neo4j browser. Not decided; each store keeps its own UI.
311
+ 4. **Graphify PreToolUse hook enablement and routing.** Whether and how the
312
+ hook fires for the five code-writing specialists, and how it composes with
313
+ the existing Bash PreToolUse chain, is deferred to a follow-up ADR.
314
+ 5. **Cognee `codify` deprecation.** Whether Cognee's own code-extraction
315
+ feature is now redundant in MISHKAN given Graphify is not decided here —
316
+ it stays available; `mishkan-ingest` continues to gate its use.
317
+ 6. **Cross-project Graphify federation** (one graph across all projects in
318
+ `projects.yaml`). Not in scope; Graphify is per-project for now.
319
+
320
+ ### Consequences
321
+
322
+ **Positive.**
323
+ - Code-writing specialists (Hizkiah, Salma, Oholiab, Nathan, Zadok) get a
324
+ deterministic structural answer for "who calls X" / "what depends on Y"
325
+ without grep-shaped token spend.
326
+ - Write discipline stays sharp: three stores, three writers, three citation
327
+ shapes. No agent has to guess where to write.
328
+ - Cognee work stays small and curated — it does not get polluted with
329
+ AST-derived nodes that change on every commit.
330
+ - Graphify artifacts are re-derivable from the repo, so the third store has
331
+ effectively zero backup obligation: delete `.graphify/` and rebuild.
332
+ - The boundary is testable: a CI check can refuse a commit that ingests
333
+ AST-shaped content into Cognee work.
334
+
335
+ **Negative.**
336
+ - One more runtime to install, document, and teach 45 agents to route to.
337
+ - Graphify v0.8.x is young; a breaking change in node schema or CLI flags
338
+ would touch every agent that cites a graph node id. Mitigation: cite by
339
+ `file:line` alongside node id so the answer survives a schema change.
340
+ - The "structure vs semantics" line is not always crisp; some questions
341
+ require both stores and a careful answer. Documented in the routing
342
+ matrix above, but it adds cognitive load.
343
+ - The Neo4j-push path introduces a second Neo4j container if enabled —
344
+ more memory, more secrets to manage. Mitigation: keep it opt-in; default
345
+ is `graph.json` only.
346
+ - The deferred PreToolUse hook means agents must be *told* to consult
347
+ Graphify first; the harness does not enforce it until the follow-up ADR
348
+ lands.
349
+
350
+ **Supersedes / amends:** none. Extends D-001 (Cognee local Docker) and D-007
351
+ (curated vs work split) by adding a third epistemic layer on the same
352
+ discipline.
353
+
354
+ ## D-009 — Graph-first PreToolUse gate for the five code-writing specialists (added 2026-06-05)
355
+
356
+ **Decision:** introduce a MISHKAN PreToolUse hook — `pre-tool-graphify-nudge.py` —
357
+ that, for **exactly the five code-writing specialist agents** (Hizkiah, Salma,
358
+ Oholiab, Nathan, Zadok), runs alongside the existing security (D-004) and
359
+ model-routing hooks and **advises** — does not block — when a `Read` or `Grep`
360
+ call on source code looks like a structural query that Graphify (D-008) could
361
+ have answered deterministically. The hook is **advisory** (soft gate), not
362
+ hard-deny: it injects a permissionDecisionReason nudge plus, where supported, a
363
+ concrete `graphify search` command into the tool input metadata, but always
364
+ returns `allow`. Conformance is measured via the existing PostToolUse observer
365
+ chain; the gate **never** refuses the underlying tool call.
366
+
367
+ ### Force-tension
368
+
369
+ **What pushes toward enforcement.** The five named agents are the harness's
370
+ heaviest token consumers — Hizkiah and Salma in particular routinely Read 6–10
371
+ files to answer "who calls X" or "what depends on Y", which D-008 just declared
372
+ the wrong store for that question. Without a runtime mechanism, the D-008
373
+ routing matrix is doctrine on paper that drifts the moment an agent is mid-task
374
+ and reaches for the familiar tool. The whole reason D-008 exists is to make
375
+ structural queries deterministic; leaving the enforcement at "we told them to"
376
+ collapses the discipline into a code-review aspiration.
377
+
378
+ **What pushes against a hard gate.** Three failure modes make hard-deny costly:
379
+ (a) the project may not yet be scanned (first run, fresh clone) — denying Read
380
+ would brick the agent until Y4NN runs `graphify`, violating the stateful-
381
+ operation contract that says AI prepares but does not execute scans; (b) the
382
+ graph can be stale (HEAD has moved since the last `--update`), so the agent's
383
+ correct move is to Read the source of truth, not the cached graph; (c) the
384
+ heuristic for "this query is structural" is necessarily fuzzy — a query like
385
+ "how does this function handle errors" is semantic and Graphify is the wrong
386
+ store for it. A hard gate would generate false-positive blocks on cases (a),
387
+ (b), and (c), creating the exact friction the engineer profile names as a
388
+ top complaint with AI tooling.
389
+
390
+ The tension resolves toward **soft gate + telemetry**: the hook nudges and
391
+ records, never blocks. If telemetry shows ≥80% of code-writing Reads now
392
+ preceded by `graphify search` after one sprint, the doctrine is working without
393
+ enforcement. If it does not, a future ADR can revisit hardening.
394
+
395
+ ### Alternatives considered
396
+
397
+ 1. **Hard gate — deny Read/Grep when no `graphify search` was observed in the
398
+ agent's session within the last N tool calls.** *Bad.* Brittle on first-scan,
399
+ stale-graph, and semantic-query cases; turns a coordination problem into a
400
+ runtime block; violates the asymmetric-delegation contract by effectively
401
+ forcing the agent to ask Y4NN to run a scan mid-task.
402
+
403
+ 2. **Soft gate — advisory injection on suspected structural queries; always
404
+ allow.** *Chosen.* Surfaces the doctrine at the exact moment it would
405
+ otherwise be skipped, names the concrete command (`graphify search <symbol>`),
406
+ and degrades gracefully when the graph is absent or stale. Composes cleanly
407
+ with the existing Bash PreToolUse chain (D-004) and the Python model-routing
408
+ hook because all three already follow the fail-open-on-error contract.
409
+
410
+ 3. **Silent telemetry only — count the conformance ratio in the PostToolUse
411
+ bus, no advisory in the prompt.** *Useful, but insufficient alone.* Without
412
+ the in-prompt nudge, the agent has no feedback signal mid-task; the ratio
413
+ would document drift rather than reduce it. Adopted as **phase 1**: ship the
414
+ telemetry first (single sprint), then layer the advisory nudge on top once
415
+ the baseline is measured. The full soft-gate behaviour is the **phase 2**
416
+ target documented here.
417
+
418
+ 4. **Skill-only doctrine — encode "graphify first on structural queries" in the
419
+ five craft skills (`hizkiah-backend-impl-craft.md` etc.) and rely on agents
420
+ to follow it.** *Insufficient.* The harness already has the precedent
421
+ (D-004 PreToolUse security) that doctrine without a mechanism drifts;
422
+ skills inform behaviour, hooks enforce shape. Adopted **alongside** the
423
+ hook, not as a substitute.
424
+
425
+ 5. **Expand the gate to all 45 agents.** Rejected as scope creep. The QA,
426
+ reporter, research, and orchestration agents do not write code and rarely
427
+ ask structural questions; gating them adds noise without changing behaviour.
428
+ Expansion to Hiram (Explore) is the obvious next candidate but is deferred
429
+ to the Explore-refactor ADR (D-008 Out of Scope #2).
430
+
431
+ ### Invariants of the gate
432
+
433
+ - **Scope — exactly five agents.** The hook activates only when the invoking
434
+ subagent is one of: **Hizkiah, Salma, Oholiab, Nathan, Zadok**. For any
435
+ other agent (including Hiram, Caleb, all QA/reporters/orchestrators), the
436
+ hook is a no-op. Adding an agent requires a new ADR.
437
+ - **Trigger condition (precise).**
438
+ - **`Read`** on a file whose extension matches the Graphify-supported set:
439
+ `.py .ts .tsx .js .jsx .mjs .cjs .go .rs .java .php .rb`. Configs,
440
+ Markdown, YAML, lockfiles, and dotfiles are **not** triggers.
441
+ - **`Grep`** when the `pattern` is a bare identifier (matches
442
+ `^[A-Za-z_][A-Za-z0-9_]*$`) — i.e. clearly a symbol lookup, not a
443
+ semantic regex. Patterns with `.*`, alternation, multiline, or non-word
444
+ characters do **not** trigger.
445
+ - **Explore** tool calls are out of scope (deferred ADR).
446
+ - **Fallback behaviour (graceful degradation).**
447
+ - *No `.graphify/graph.json` in the project* → emit a single advisory line
448
+ "Graphify not yet scanned for this project; ask Y4NN to run `graphify`
449
+ (stateful op). Falling back to Read is correct for now." and `allow`.
450
+ - *Graph stale* (`graph.json` mtime older than the most recent commit on
451
+ HEAD) → emit "Graphify graph is older than HEAD; structural answer may
452
+ be stale, prefer Read+cite for changes after `<sha>`." and `allow`.
453
+ - *Trigger heuristic likely wrong* (the agent has already issued a
454
+ `graphify search` in this session for a related symbol) → no nudge,
455
+ silent allow. The PostToolUse counter still records the read.
456
+ - **Opt-out path.** Two mechanisms, in order of normalcy:
457
+ 1. Per-tool-call: a `tool_input.metadata.skip_graphify_nudge: true` field
458
+ suppresses the advisory for that single call. Used when an agent has
459
+ explicitly decided the query is semantic.
460
+ 2. Session-wide: env `MISHKAN_GRAPHIFY_NUDGE=off` disables the hook
461
+ entirely (Y4NN debug escape). Recorded in the session-start observer
462
+ so disablement is visible in sprint reports.
463
+ - **Performance budget.** The hook must add **≤ 50 ms p95** to PreToolUse
464
+ latency. Implementation must avoid invoking `graphify search` itself
465
+ inside the hook — it inspects `graph.json` metadata (mtime, presence)
466
+ and the tool input shape only. If the hook exceeds 200 ms on any call,
467
+ it self-disables for the remainder of the session and logs to the
468
+ PostToolUse observer ("graphify nudge self-disabled: budget exceeded").
469
+ - **Fail-open contract.** Identical to D-004 and to `model-route.py`: any
470
+ parse, IO, or format error → emit nothing, exit 0, never block.
471
+ - **Conformance metric.** Two numbers, recorded by `post-tool-observe.sh`
472
+ per session and aggregated at `/sprint-close`:
473
+ 1. **Nudge-respect ratio** — of the Read/Grep calls that triggered an
474
+ advisory, the fraction followed by a `graphify search` within the
475
+ next 3 tool calls of the same agent.
476
+ 2. **Pre-Read graph consultation rate** — of all triggering Read/Grep
477
+ calls by the five agents, the fraction preceded by **any**
478
+ `graphify search` in the same session. Target ≥ 80% after one
479
+ sprint of phase-2 operation.
480
+ Both metrics are reported, neither is a gate.
481
+
482
+ ### Integration
483
+
484
+ - **Hook file.** `payload/mishkan/hooks/pre-tool-graphify-nudge.py`,
485
+ registered alongside `pre-tool-security.sh` and `model-route.py` in the
486
+ PreToolUse chain. Order: security (deny on violation) → model-route
487
+ (inject model) → graphify-nudge (advise). Each is independent and
488
+ fail-open; chain order is for clarity, not correctness.
489
+ - **Subagent detection.** Reuses the same `subagent_type` field
490
+ `model-route.py` already reads from `tool_input`. When the field is
491
+ absent (top-level Claude Code session, not a subagent), the hook is a
492
+ no-op — Y4NN is not in scope of the gate.
493
+ - **Advisory shape.** The hook returns
494
+ `hookSpecificOutput.permissionDecision = "allow"` with a populated
495
+ `permissionDecisionReason` quoting the exact `graphify search` command
496
+ to try first. The agent sees the reason; nothing is enforced.
497
+ - **Phase 1 (this sprint).** Ship the hook in **telemetry-only mode**:
498
+ trigger detection runs, metrics are recorded, no advisory text emitted.
499
+ Establishes the baseline number for "how often do the five agents
500
+ already consult Graphify before Read?".
501
+ - **Phase 2 (next sprint, conditional on phase-1 baseline).** Enable the
502
+ advisory text. Re-measure. If nudge-respect ratio < 50% after one sprint,
503
+ open a follow-up ADR — do not unilaterally promote to hard gate.
504
+
505
+ ### Out of scope (explicitly not decided here)
506
+
507
+ 1. **Token-saving measurement POC** (the 71.5× figure) — still separate, as
508
+ in D-008 Out of Scope #1. The conformance metric here is behavioural
509
+ (did the agent consult the graph?), not economic (how many tokens did
510
+ it save?).
511
+ 2. **Refactor of the Explore agent / Hiram's exploration playbook** —
512
+ deferred per D-008 Out of Scope #2. The gate explicitly excludes
513
+ Explore tool calls.
514
+ 3. **Unified Graph Explorer UI** — out of scope, per D-008.
515
+ 4. **Adding further agents to the gate** (e.g. Hiram once refactored, or
516
+ any future code-writing specialist) — requires a new ADR amendment.
517
+ 5. **Cognee-work consultation gate** — a symmetric soft-gate for semantic
518
+ queries ("have we decided this before?") that would nudge toward
519
+ Cognee MCP before a freeform reasoning answer. Plausible and consistent
520
+ with the D-008 routing matrix, but not decided here. If pursued, it
521
+ would follow this ADR's shape (advisory, fail-open, telemetry-first).
522
+ 6. **Incremental re-extract on commit** — D-008 Out of Scope. The stale-
523
+ graph fallback above accommodates the absence of incremental refresh;
524
+ it does not commit the harness to provide one.
525
+ 7. **Hard-deny mode** — explicitly deferred. The phase-2 advisory is the
526
+ strongest enforcement this ADR sanctions. Any future hardening is a
527
+ separate, dated decision.
528
+
529
+ ### Consequences
530
+
531
+ **Positive.**
532
+ - The D-008 routing matrix gains a runtime mechanism for its highest-
533
+ traffic edge (structural queries by code-writing specialists), without
534
+ introducing a friction failure mode the engineer profile rejects.
535
+ - Telemetry-first phasing means the harness measures before it constrains;
536
+ the phase-2 decision will be grounded in a baseline, not a guess.
537
+ - Fail-open contract preserves the property all MISHKAN hooks share: a
538
+ broken hook never bricks delegation.
539
+ - Composable with the existing PreToolUse chain (D-004 security,
540
+ model-route) without rewriting any of them; each hook stays small and
541
+ inspectable.
542
+
543
+ **Negative.**
544
+ - One more hook to maintain, with its own heuristic surface (extension
545
+ list, regex for "bare identifier", stale-graph detection). Each is a
546
+ small calibration debt — false-positive nudges on semantic queries
547
+ will accumulate small annoyance until the advisory wording is tuned.
548
+ - The stale-graph fallback depends on file mtime vs. HEAD commit time,
549
+ which is approximate; an agent could pull a graph from a sibling
550
+ checkout and have a wrong mtime [UNKNOWN — to verify in implementation
551
+ whether this corner case warrants a content-hash check].
552
+ - Phase-2 enablement creates a sprint-boundary coordination point: the
553
+ decision to flip the advisory on depends on phase-1 telemetry being
554
+ reviewed, which is a `/sprint-close` agenda item that did not previously
555
+ exist.
556
+ - The 50 ms p95 budget is tight for Python startup on cold cache;
557
+ implementation may need to be a small Bash inspector instead, mirroring
558
+ `pre-tool-security.sh`'s shape. [UNKNOWN — measure cold-start cost of
559
+ the Python interpreter on Y4NN's machine before committing to language.]
560
+ - Documents an opt-out (`MISHKAN_GRAPHIFY_NUDGE=off`) which, like every
561
+ opt-out, can become a habit that erodes the discipline. The session-
562
+ start observer logging is the partial mitigation; review at
563
+ `/sprint-close` is the rest.
564
+
565
+ **Supersedes / amends:** none. Extends D-008 (Graphify as third store) by
566
+ providing the runtime mechanism D-008 explicitly deferred (D-008 Out of
567
+ Scope #4). Composes with D-004 (existing PreToolUse Bash chain) without
568
+ modifying it.
569
+
170
570
  ---
171
571
 
172
572
  *Decisions locked May 2026. Revisit only with a dated amendment below.*