Injection channels
Knowledge reaches the coding agent through three deliberately different channels: a ranked broad query, an unranked set of standing obligations, and a deterministic methodology match. One endpoint composes all three per prompt.
- Why there are three channels instead of one ranked search.
- How the composition endpoint keeps per-prompt latency down and avoids re-injecting what the agent already has.
- How mandatory rules are guaranteed to arrive, in every mode, on every turn.
- Why methodology matching refuses embeddings on purpose.
- Diagram convention: the dark green rounded block is where the flow starts (START or INPUT); orange blocks are outcomes; numbered steps show reading order.
3.1One warm call composes three channels
POST /prompt-bundle (writ/server/routes/query.py:165) awaits the three channel handlers in-process inside the warm daemon and returns the rendered blocks separately. It replaced a shell hook that spawned roughly 28 cold Python processes per turn; the module header records the measured motivation: about 420ms of a measured 646ms per prompt went to those spawns (writ/retrieval/prompt_bundle.py:1-7).
The session cache drives deduplication: rules already injected in the current phase are excluded from the broad query, the remaining token budget caps it, and the previous turn's injected ids are passed as the preferred order so the sticky tiebreak (chapter 2) keeps output stable for prompt caching (routes/query.py:193-201).
flowchart LR
HOOK(["START: the user sends a prompt<br/>(the per-prompt hook fires)"])
PB["POST /prompt-bundle"]
CACHE[("Session cache<br/>loaded rule ids per phase<br/>remaining token budget<br/>last-injected ids")]
subgraph C1["Channel 1: broad retrieval"]
QRY["POST /query<br/>the 5-stage pipeline, chapter 2"]
end
subgraph C2["Channel 2: standing obligations"]
AO["GET /always-on"]
end
subgraph C3["Channel 3: methodology"]
MC["POST /methodology-companion<br/>trigger-index match"]
end
REND["Rendered blocks<br/>returned separately<br/>so the hook controls final order"]
HOOK -->|"1"| PB
CACHE -->|"context"| PB
PB -->|"2a"| QRY --> REND
PB -->|"2b"| AO --> REND
PB -->|"2c"| MC --> REND
QRY -.->|"adds injected ids, spends budget,<br/>updates last-injected"| CACHE
REND -->|"3: injected into the turn"| HOOK
classDef entry fill:#0B5E55,stroke:#083F39,color:#FFFFFF,font-weight:bold
classDef finish fill:#F6EEDF,stroke:#B4560F,color:#6B3D14,font-weight:bold
classDef hub fill:#DCE9E5,stroke:#0B5E55,stroke-width:2px
class HOOK entry
class REND finish
3.2Channel 2: obligations, not rankings
Some rules must reach the agent regardless of what the prompt says. Selection is one Cypher predicate: mandatory = true OR always_on = true, defined once in writ/graph/predicates.py:16 and used verbatim by the endpoint (routes/query.py:522). There is no ranking and no budget drop on this channel: applicability decides, and a rule with no routing data fails open to universal so nothing silently disappears (writ/retrieval/always_on_filter.py:27-42).
flowchart TD ALLR(["START: every Rule node in the graph"]) UNION["1. Union predicate:<br/>mandatory = true OR always_on = true<br/>(single source: predicates.py)"] FRBN["ForbiddenResponse nodes<br/>always included:<br/>response discipline applies every turn"] COMB["2. Combined bundle"] STRIP["3. Mode strip:<br/>process-domain rules kept only in<br/>work and debug modes.<br/>Mandatory rules are exempt:<br/>they arrive in every mode"] APPF["4. Applicability filter, optional:<br/>injection point = prompt / write / bash / stop<br/>trigger keywords whole-word matched<br/>against the context text"] REND2["5. Summary render: trigger + statement<br/>token estimate reported against a 5,000 cap"] ALLR --> UNION --> COMB FRBN --> COMB COMB --> STRIP --> APPF --> REND2 classDef entry fill:#0B5E55,stroke:#083F39,color:#FFFFFF,font-weight:bold classDef finish fill:#F6EEDF,stroke:#B4560F,color:#6B3D14,font-weight:bold classDef hub fill:#DCE9E5,stroke:#0B5E55,stroke-width:2px class ALLR entry class REND2 finish
Mandatory rules are excluded from the ranked pool (RANKED_INCLUDE_WHERE) and injected here (INJECTION_RULE_WHERE). Both predicates live in one module (writ/graph/predicates.py) imported by the pipeline loader and by two integrity checks (detect_stranded_mandatory, detect_ranked_exclusion_mismatch), so the selection and its validator cannot drift apart. This closed a real incident class: 29 of 32 mandatory rules once reached neither path because two mechanisms keyed on different fields.
3.3Channel 3: deterministic on purpose
Methodology (skills, playbooks, techniques, anti-patterns) is matched by node-declared routing data, never by similarity (writ/retrieval/trigger_index.py). The module header states the reason: reaching for embeddings here would collapse the rule/methodology distinction the hybrid model rests on. The match is a set union with a strict priority order, and obligations are never silently dropped: if floor plus push alone exceed the budget, the response flags it loudly instead (trigger_index.py:143-179).
flowchart LR MODE(["INPUT: the session mode<br/>(work, debug, review...)"]) ACT(["INPUT: an action<br/>(a tool event)"]) PRM(["INPUT: the prompt text"]) FLOOR["floor:<br/>nodes whose floor_modes<br/>contain the current mode"] PUSH["push:<br/>nodes whose action_triggers<br/>contain the action.<br/>Bypasses the already-injected exclusion:<br/>timing is the value"] PULL["pull:<br/>curated trigger keywords<br/>whole-word matched in the prompt,<br/>scored by match count"] ASM["Assemble:<br/>floor first, then push (obligations, never dropped),<br/>pull fills the remaining budget,<br/>lowest match count dropped first"] OUT["Bundle + over_budget flag<br/>(loud when obligations alone exceed the cap)"] MODE --> FLOOR --> ASM ACT --> PUSH --> ASM PRM --> PULL --> ASM ASM --> OUT classDef entry fill:#0B5E55,stroke:#083F39,color:#FFFFFF,font-weight:bold classDef finish fill:#F6EEDF,stroke:#B4560F,color:#6B3D14,font-weight:bold classDef hub fill:#DCE9E5,stroke:#0B5E55,stroke-width:2px class MODE entry class ACT entry class PRM entry class OUT finish
3.4The serving surface
| Endpoint | Channel / purpose | Anchor |
|---|---|---|
POST /query | Channel 1: ranked domain rules. Runs the pipeline off the event loop so concurrent hook requests are not serialized. | routes/query.py:42 |
GET /always-on | Channel 2: standing obligations, mode-scoped and applicability-filtered. 5,000-token cap reported. | routes/query.py:522 |
POST /methodology-companion | Channel 3: deterministic workflow-state bundle. | routes/query.py:120 |
POST /prompt-bundle | Composition of all three plus session-cache updates. | routes/query.py:165 |
GET /rule/:id, POST /propose, POST /feedback, POST /conflicts, GET /health | Single-rule fetch, graph-first rule proposal, frequency feedback, conflict check, liveness with index-warm status. | routes/query.py:310-464 |
GET /graph, GET /node/:id, GET /dashboard, GET /explore | Graph explorer JSON and the interactive view served from writ/static/explore.html. | routes/explorer.py |