Skill router

July 24, 2026 · View on GitHub

Historical / superseded for retrieval. The Python gates this doc describes (scripts/route.py, view.py, etc.) were folded into the binary — recall returns a seed digest from digest.go, drill/SQL/find are rekal commands, and relative dates are agent reasoning + SQL (no when command). What still ships as scripts is the map/wiki gates (scripts/map.sh, scripts/wiki-gate.sh). For the live surface see skills/rekal/SKILL.md and docs/spec/command/. Kept as design history for the three-homes framing.

The Claude Code surface is a single skill (skills/rekal/), redesigned from SOUL.md's "The skill" tenets. It is thin on the route, rich on arrival, and organized around three homes:

  • Function → a command (or a remaining gate script) — deterministic data for the agent's judgment.
  • Knowledge → rich prose, on demand — informs judgment, never makes it.
  • Judgment → the agent's reasoning — never frozen into a script or a rule.

The agent classifies the question, loads one module (or runs one script), and stops. Install copies the whole tree; clean / refresh purge legacy companion dirs. No corpus profiles ship — the route is general.

Layers

flowchart TB
  tip["SKILL.md route<br/>always loaded, thin"]
  tip --> triage{"Substrate?"}
  triage -->|Tree| grep["grep / read HEAD"]
  triage -->|Knowledge / ledger| route["scripts/route.py"]
  triage -->|Map| mapf["scripts/map.sh fresh"]
  triage -->|past reasoning| ref["Read references/ledger.md"]
  route -->|KNOWLEDGE| readk["Read pointer at HEAD"]
  route -->|INJECT| drill["references/ledger.md → drill"]
  route -->|SILENCE| quiet["Stay silent on memory"]
  mapf --> mapr["references/map.md"]
LayerPathLoads when
RouteSKILL.mdAlways (triage + dispatch only; trusts reasoning)
Scriptsscripts/*Route or reference names them — deterministic data
Referencesreferences/*.mdOne Read after triage — then stop

Substrate triage

flowchart TD
  q["Question"] --> tense{"True now, or was?"}
  tense -->|was / only record is a conversation| ledger["Ledger<br/>route.py recall / SQL"]
  tense -->|now| kind{"Code or prose?"}
  kind -->|code| tree["Tree — grep / read<br/>do not recall"]
  kind -->|prose| know["Knowledge — rekal → route.py<br/>Read pointer, stop"]
  q --> shape{"Breadth / structure?"}
  shape -->|yes| map["Map — map.sh fresh first"]

Boundary line (route): grep for code that is · knowledge for prose that is · ledger for the why that was. A fact whose only record is a past conversation is ledger, not knowledge — so a pure-dialogue corpus (no code, no HEAD prose, no structure) routes to the ledger by degeneration, with no chat profile or separate build.

Recall route (knowledge vs episode vs silence)

Bars live in route.py — not route prose. Ranking still uses max-normalized score; the gate uses absolute confidence. Mass is a signal, not a veto.

flowchart LR
  r["rekal JSON"] --> rt["route.py"]
  rt -->|confidence≥0.25 (soft 0.20, gap≥0.02)| i["INJECT top=/gap= + top-20<br/>sid conf=· t·n· snippet<br/>+ KNOWLEDGE line if present"]
  rt -->|else + knowledge present| k["KNOWLEDGE path=score<br/>agent judges the distribution"]
  rt -->|else| s["SILENCE"]

confidence = max(saturate(bm25), cosine) + 0.15·saturate(facet) — never divided by the candidate-set max (junk queries also normalize score ≈ 1.0). Super-low episode floor 0.25; soft path 0.20 with gap ≥ 0.02. Matching knowledge report floor 0.25 — omit junk marker scores. Labels are recommendations — biased toward more data than decision. Grey-band hits inject with conf= for the agent to weigh. Session/SQL drills pipe through view.py (raw turns / TSV), never raw JSON.

Substrates are inclusive: an episode and a knowledge hit can both report (mixed convention + why questions). Line 1 stays the primary verdict; a trailing KNOWLEDGE line accompanies INJECT when prose also matched. Knowledge alone is the report when the episode gate fails. SILENCE is machine-only when neither substrate has signal.

The knowledge score has no corpus-invariant floor — it blends semantic cosine, whose junk baseline drifts — so route.py reports the per-file distribution and the agent judges. mass stays inside the script (never a veto, never emitted). confidence is emitted on the INJECT header (top=/gap=) and each seed row so the agent can weigh or drill selectively.

On INJECT the digest is seed coverage: top-20 as sid conf=… t<turn> "snippet", roughly constant in -n. Beyond 20: reformulate / multi-search.

Other gates

ScriptMachine event
map.sh freshFRESH / STALE / MISSING vs HEAD watermark
map.sh watermarkWrite line-1 watermark (+ stub if missing)
wiki-gate.shRefuse wiki writes on the default branch

Dispatch map (route → module)

flowchart LR
  subgraph tip_dispatch ["Route dispatch"]
    A["present prose"] --> R["route.py"]
    B["pointed past episode"] --> R
    C["temporal / analytical / why / provenance"] --> L["references/ledger.md"]
    F["breadth"] --> MF["map.sh fresh → map.md"]
    H["docs/wiki PR"] --> WG["wiki-gate.sh → wiki.md"]
    I["flags / SQL / schema"] --> RF["references/reference.md"]
  end

ledger.md is the one rich page on reasoning over the past — recall, widen, depth-as-judgment, time-axis, enumeration, whose-fact/premise, analytical SQL, decision arcs, provenance. One question, one substrate. The route returns data; the agent decides the move. Cite session / turn / commit with every memory claim.