Writ Architecture.
Chapter 3 of 4

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.

This chapter answers

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).

Figure 3.1 Per-prompt composition: /prompt-bundle Click to enlarge
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).

Figure 3.2 Always-on selection: union, mode strip, applicability Click to enlarge
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
A governance invariant, encoded as one predicate

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).

Figure 3.3 Trigger-index algebra: floor, push, pull Click to enlarge
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

EndpointChannel / purposeAnchor
POST /queryChannel 1: ranked domain rules. Runs the pipeline off the event loop so concurrent hook requests are not serialized.routes/query.py:42
GET /always-onChannel 2: standing obligations, mode-scoped and applicability-filtered. 5,000-token cap reported.routes/query.py:522
POST /methodology-companionChannel 3: deterministic workflow-state bundle.routes/query.py:120
POST /prompt-bundleComposition of all three plus session-cache updates.routes/query.py:165
GET /rule/:id, POST /propose, POST /feedback, POST /conflicts, GET /healthSingle-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 /exploreGraph explorer JSON and the interactive view served from writ/static/explore.html.routes/explorer.py