Companion to
ARCHITECTURE.md(the why). This file is the how: every component mapped to exact paths, schemas, wiring, formulas, and acceptance gates. A fresh session should be able to build trident from this file alone. Execute wave by wave; run the acceptance gate before moving on.Before any wave: run
~/.claude/scripts/budget-posture.shand honor it. Building the governor must not blow the budget the governor exists to protect. Each wave is sized to be finishable inside one session.
| Decision | Resolution |
|---|---|
| Pin semantics | Bypass valves. L_eff = pins[sid] if sid in pins else global. A pinned terminal routes independently (pin may exceed global); global rules every unpinned + every new terminal. |
| Anti-theater | Pins human-set ONLY (no script/hook may write pins). 📌 N/M shown in posture + statusline. MINT sets theater_warning=true when pinned sessions ≥50% of active sessions. Dead-session pins reaped when their rate-limits-<sid>.json is gone/stale >24h. throttle unpin-all exists. |
| Segment count | Exactly 3: MINT / SHAPER / LEDGER (+ proxy as SHAPER's deep floor). |
| Lever curve | M = (L_eff/100)^1.5. L=100 → free lane (no lever damping). L=0 → minimum viable (Haiku/inline/no-thinking), never a halt. |
| Bright line | Routing never cuts correctness: never strip tracebacks/test output/diffs; never drop the active-handoff chunk; never block first-reads; verification floor = 1. |
| Fail direction | Everything fails OPEN (work > governance). Failures must be visible (posture line + contract_ok=false), never silent AND consequential. |
| Honesty labels | Mechanical: spawn model tier, injection bytes, compact threshold, ROI admission, proxy ceiling. Advisory: fan-out width (flock-counted), thinking budget, voter count. Never claim otherwise. |
~/Documents/Dev/trident/
mint/mint.py # Segment 1 daemon
mint/formulas.py # single source of all curves (imported by mint only)
shaper/shaper-prompt.sh # UserPromptSubmit (thin: jq reads + emit)
shaper/shaper-tools.py # PreToolUse rewrite/deny (thin: read wallet, no math)
shaper/env-stamp.sh # headless spawn wrapper
ledger/ledger-settle.py # Stop + SubagentStop
ledger/ledger-filter.sh # PostToolUse noise strip (bright-line bounded)
ledger/antibodies.py # pattern store + graduation (library, called by settle)
proxy/trident-proxy.py # ANTHROPIC_BASE_URL SSE-transparent model-ceiling proxy
bin/trident # CLI: status | posture | canary | counters | pause
launchd/com.casterly.trident-mint.plist
launchd/com.casterly.trident-proxy.plist
tests/ # per-component smoke tests (see §7)
~/.claude/state/
wallet.json # MINT's atomic output (schema §2)
trident-counters.json # flock'd in-flight counters
antibodies.json # LEDGER pattern memory
trident-canary.json # contract check result per CC version
~/.claude/settings.json # hook rewiring (§5)
~/.claude/scripts/throttle # v3: stays the lever UI; resolution logic REMOVED (MINT owns it) ⚠ coordinate w/ sibling workstream
~/.claude/scripts/budget-posture.sh# becomes thin `trident posture` shim reading wallet.json
~/.claude/CLAUDE.md # Prime Directive 3: 4-band table → "read the [TRIDENT …] envelope"
workflow-watcher + conductor launchers # call env-stamp.sh before claude -p
~/.claude/skills/token-gate/SKILL.md # keeps consent role; cost math now read from wallet
capture-rate-limits.sh · token_watch.py · shed compact-guard.sh · shed inject.sh (sizing half — selection stays in shed) · claudeignore-guard.sh · filter-tool-output.sh · token-warn.sh · shed governor.py fanout logic · log-session-stats.sh · standalone guard-thresholds.json
Written atomically (tmp + rename) by mint.py ONLY. Consumers do zero math — every value pre-baked.
trident-counters.json (separate file — hot path, flock'd):
{ "spawns_inflight": 2, "by_session": { "<sid>": 1 }, "updated_at": "..." }Lock protocol: flock(LOCK_EX) on trident-counters.lock, read-modify-write, release. Increment in
shaper-tools on Agent/Workflow allow; decrement in ledger-settle on SubagentStop; mint.py reconciles
drift every tick (counter > live subagent transcripts → reset to observed).
- Trigger: launchd
com.casterly.trident-mint, KeepAlive, tick every 30s; ALSO receives statusline pushes (statusline command pipes its stdin JSON tomint.py --ingest -then prints the status string — absorbscapture-rate-limits.sh, keep its NULL-guard + monotonic stale-value guard logic verbatim). - Algorithm per tick:
- Ingest newest telemetry (statusline push or per-session files); apply both guards.
- Read
burn-throttle.json(throttle CLI remains the writer of lever intent). - Reap dead pins + stale
rate-limits-*.json(>24h), cap 50 (keep capture-rate-limits.sh behavior). H_raw = min(5h_left, 7d_left, p_pool_left if active) + imminence_bonus(+40pp if reset<30m, +20pp if <60m, cap 100).- For
default(L_eff=global) and each pin:M=(L_eff/100)^1.5;H = min(H_raw, 100)/sqrt(active_sessions)— except at L_eff=100: skip lever damping entirely (free lane; only H_raw matters). - Compute every derived value via
formulas.py(§6). Render envelope strings. theater_warning = pinned_active ≥ 0.5 × active_sessions.- Atomic write wallet.json. Reconcile counters.
- Failure mode: daemon down → wallet stale. Every consumer checks
updated_at; if >120s old, behave ascontract_ok=false(advisory-only, log one posture warning). Never block work. - Acceptance gate:
trident statusshows live wallet <35s old across 2 terminals; pin one terminal → itsper_pinblock appears within one tick; kill daemon → consumers visibly degrade, nothing breaks.
- Keeps:
throttle [N|up|down|full|pin|unpin|pins|unpin-all|show], writesburn-throttle.jsononly. - Removes: any resolution/posture logic (MINT owns it). Gauge display now also prints
📌 pinned N/Mandtheater_warningif set. No code path anywhere else may writepins.
- Hard latency budget: <100ms. jq reads of wallet.json only; NO python on this path.
- Steps: resolve own sid → pick
derived.per_pin[sid] // derived.default→- emit envelope line (always, one line);
- compact-guard: compare session tokens (
rate-limits-<sid>.jsoncontext_window) against shedguard-thresholds.json × compact_scale; if crossed, emit the[COMPACT-GUARD: …]block (keep shed's existing wording — the ratchet behavior is learned downstream); - injection sizing: export
SHED_MAX_INJECT=<inject_cap_tokens>consumed by shed's inject (shed keeps selection, trident owns size). Rank-0 active-handoff chunk is exempt from the cap (bright line).
- Acceptance gate:
timethe hook <100ms; envelope visible in transcript; drop lever → envelope updates next turn; heavy session triggers guard earlier at low lever.
- Read wallet block (own sid) ONCE. Determine depth: hook payload
transcript_pathcontains/subagents/→ this session IS a subagent → ceiling =tier_ceiling_child; elsetier_ceiling_spawn. - For
Agent: if requestedmodeltier > ceiling → returnupdatedInputwithmodellowered to ceiling. Never raise. Missing model → leave (inherits parent; ceiling still binds the child's own spawns). - For
Workflow: readmeta.phases[].modelhints? NO — out of contract. Instead: DENY with reason whenspeculative=falseAND prompt/args look speculative (TC3) or whenspawns_inflight ≥ fanout_max × 2(runaway guard). Reason string must tell the model the cheaper shape:"trident: over width budget (W≤3). Split into ≤3 agents or run inline." - Width: flock-increment counter; if
spawns_inflight > fanout_max→ still ALLOW (advisory dimension) but append warning toupdatedInput.prompt? NO — do not mutate prompts (correctness risk). Log to ledger + envelope shows the breach next turn. Only the runaway guard (2×) hard-denies. - Absorb claudeignore-guard: port its big-read/repeat-read deny rules verbatim (they're mechanical + safe).
- Output JSON shape: verify exact
hookSpecificOutput/permissionDecision/updatedInputfield names against the installed CC version AT BUILD TIME (canary §3.9 does this continuously after). - Acceptance gate: in a live session ask for 5 Opus subagents at lever 40 → transcript shows spawns ran Sonnet/Haiku; pinned-100 terminal spawns Opus untouched; subagent session's own spawns forced to Haiku.
- Usage:
env-stamp.sh claude -p "..."in workflow-watcher + conductor launchers. - Injects
TRIDENT_ENVELOPEenv +--model $(wallet headless.model)onto the command line (the ONE mechanical main-loop lever that exists: model choice at session start). Refuses to add--bareitself; if caller insists on--bare, stamp still applies (env + --model survive; hooks don't — proxy floor covers). - Acceptance gate: workflow-watcher build runs Sonnet at lever 50, Opus at lever 100 + GREEN.
- On SubagentStop: decrement flock counter; append spawn record (model, tokens if available) to
session-stats.jsonl(absorbs log-session-stats.sh format — keep fields identical for evolve-v2 compat). - On Stop: write session settlement (planned-vs-actual: envelope values vs measured burn via
usage-ledger.py); feed shed's
update-guard-thresholds.py(COMPOSE — call it, don't reimplement); runantibodies.observe()(§3.7); token-warn behavior folds in (same thresholds source). Handoff-writer stays separate (it's shed's, already works). - Stop blocking: only the existing approver/shed semantics — trident itself NEVER blocks Stop (it's a
meter, not a cage). JSON
decision:blockis approver's tool, not ours. - Acceptance gate: after a fan-out session,
session-stats.jsonlshows N spawn records + 1 settlement; counters return to 0; guard thresholds file updated.
- Schema per antibody:
{pattern_key, kind: fanout_waste|speculative_rerun|tier_overkill|reread_loop, evidence: [run_ids], confirmations, grader_score, state: observe|advise|block, surfaces_seen: [...]}. - Keyed by pattern, not surface (cross-surface transfer). Minted/strengthened in settle when planned-vs-actual shows waste (spawned 8, used 1; Opus spawn returned <500 tokens; same file read 5×…).
- Graduation:
confirmations ≥ 3 AND grader_score ≥ 0.7→ state=advise (envelope mentions it);≥5 AND ≥0.8→ state=block (shaper-tools enforces as deny/downgrade). Inverted activation: whenM < 0.3, blocking requires≥0.9— never trigger-happy when mistakes are costliest. Tolerance list:~/.claude/state/antibody-tolerance.json, human-edited, bypasses blocks. - First month: everything stays
observe(hard-coded until date in config), then graduation enables. - Acceptance gate: seeded synthetic waste run mints an antibody; 3 repeats graduate it; tolerance entry suppresses it.
- aiohttp/raw-asyncio reverse proxy on
127.0.0.1:8742→api.anthropic.com. SSE-transparent: stream chunks through untouched (no buffering — this is the make-or-break requirement; test with a streamed completion before wiring anything). - Only two powers: (1) rewrite
"model"in request body when tier > wallet ceiling for the requesting class (classify by env header the stamp adds, else default class); (2) append{ts, model, usage}toproxy-ledger.jsonlfrom response (non-streaming fields or final SSEmessage_delta). - Wired via
ANTHROPIC_BASE_URL=http://127.0.0.1:8742in: launchd env for headless daemons FIRST (Wave 4a); shell profile for interactive LAST and only after a week of headless soak (Wave 4b — Tarun decision pending). Health: launchd KeepAlive; if port dead, env var is harmless? NO — a dead proxy with the env set breaks all API calls. Mitigation:trident proxy offflips a profile include + launchd env removal in one command; proxy also self-checks upstream on start and exits non-zero (so KeepAlive loops) rather than half-working. Document this as the known SPOF; default posture = headless-only. - Acceptance gate: streamed Opus request at lever 30 arrives at API as Sonnet, streams back smoothly;
--bareclaude -p through proxy gets ceilinged;trident proxy offrestores direct in <5s.
- Runs on mint startup + when CC version string changes (
claude --versioncached in canary file). - Checks: statusline JSON still has
rate_limits.five_hour.used_percentage; hook payload fixture still parses;AgentInputschema still has writablemodelenum (probe viaclaude --help/sdk-tools.d.ts if present); settings.json hooks still registered. - Any failure →
contract_ok=falsein wallet + one PushNotification + posture line shows⚠ trident degraded (CC vX.Y drift). Consumers all fail to advisory-only. Acceptance gate: fake a renamed field in a fixture → degrade fires end-to-end, work continues.
| Surface | Path through trident |
|---|---|
| Terminal / VS Code turn | mint(wallet) → shaper-prompt(envelope+guard+inject-cap) → model works → shaper-tools on each spawn (tier rewrite/deny + counter) → ledger-filter on outputs → ledger-settle on Stop |
| Subagent | parent's shaper-tools shaped its model at spawn → child session detected via transcript_path → child's own spawns ceilinged at tier_ceiling_child → SubagentStop settles + decrements |
Headless claude -p |
env-stamp (--model + envelope env) → same hooks as terminal (unless --bare) → settle on Stop |
--bare / raw SDK / MCP |
proxy floor only: model ceiling + metering. Honest: that is all anyone can do. |
"statusLine": { "command": "mint.py --ingest-statusline" }, // absorbs capture-rate-limits.sh
"hooks": {
"UserPromptSubmit": [ shed inject.sh (selection only), trident shaper-prompt.sh ], // compact-guard.sh REMOVED
"PreToolUse": [ shed permit_observe.sh, approver_pretooluse.py, trident shaper-tools.py ], // claudeignore-guard REMOVED (rules ported)
"PostToolUse": [ shed observe.sh, trident ledger-filter.sh ], // filter-tool-output REMOVED (absorbed)
"Stop": [ shed handoff-writer.sh, shed reflect.sh, approver_stop.py, trident ledger-settle.py ], // token-warn + log-session-stats REMOVED (absorbed)
"SubagentStop": [ trident ledger-settle.py ], // NEW — without this, fan-out accounting dies
"SessionStart": [ shed brief.sh, constitution-lint.sh ] // unchanged
}Order matters: approver before shaper-tools (approver may approve, must never see post-rewrite input it can't reason about → actually REVERSED: shaper-tools LAST so approver approves intent, trident shapes it).
M(L) = (L/100)^1.5 # L=100 → bypass all lever damping
H = min(5h, 7d, p_pool) + bonus(reset) # bonus: +40 if <30m, +20 if <60m
H_eff = min(H,100) / sqrt(active_sessions)
tier: opus if complexity>max(0.6·M, .95-gate) … per ARCHITECTURE table; ceilings clamp to enum
W = max(1, floor(12 · M · (H_eff/100)^1.2))
think = floor(32000 · M · (H_eff/100)^2) # ADVISORY
inject = floor(4000 · M^0.6) # MECHANICAL; rank-0 handoff chunk exempt
V = max(1, round(5 · M · H_eff/100)) # floor 1 ALWAYS
speculative = (M · H_eff) > 15
compact = 0.5 + M/2 # threshold multiplier
roi_min = min(1.2/M, 6) # capped — learning never locks out
- Wave 1 — MINT: mint.py + wallet + counters + throttle-v3 coordination + posture shim. Soak: 1 week parallel with capture-rate-limits.sh, then delete it + token_watch.py.
- Wave 2 — SHAPER probes: shaper-prompt + shaper-tools + env-stamp + CLAUDE.md PD-3 rewrite (4-band table → envelope line) + token-gate skill update. Soak: 3 days, then delete compact-guard.sh, claudeignore-guard.sh, governor.py fanout logic, inject sizing env hack.
- Wave 3 — LEDGER: ledger-settle (Stop+SubagentStop) + ledger-filter + antibodies (observe-only) + evolve-v2 ROI wiring + claude-meter/glyph read wallet.json. Then delete token-warn.sh, filter-tool-output.sh, log-session-stats.sh.
- Wave 4 — Floor: 4a proxy for headless launchd only + canary. 4b (separate Tarun decision): proxy for interactive shells. Then final deletion pass + update memory + ship per casterly-ship if publishing.
- Exact PreToolUse output JSON for
updatedInput(field names/casing) on the installed version. AgentInput.modelaccepted values (enum strings) right now.- SubagentStop payload fields (need session id + transcript path of the CHILD).
- Statusline stdin schema (
rate_limits,context_window,transcript_pathfields). - Whether VS Code sessions carry any distinguishing field (treat identically if not).
- June-15
-ppool: how it appears in telemetry once live (placeholder in schema until observed). - Hook timeout defaults per event on installed version (budget shaper-prompt accordingly).
{ "schema_version": 1, "contract_ok": true, // canary result; false → all consumers degrade to advisory-only "updated_at": "2026-06-05T02:10:00Z", "telemetry": { "five_hour": { "left_pct": 52.0, "resets_at": 1781234567 }, "seven_day": { "left_pct": 71.0, "resets_at": 1781834567 }, "p_pool": { "left_pct": null, "active_from": "2026-06-15" }, // headless Agent-SDK pool, post-split "active_sessions": 3, "stale_s": 12 // age of newest telemetry; >120 → degrade flag in posture }, "lever": { "global": 50, "pins": { "<sid>": 100 }, // mirrored from burn-throttle.json (throttle CLI still writes it) "pinned_count": 1, "theater_warning": false }, "derived": { "default": { // L_eff = global → for every unpinned session "L_eff": 50, "M": 0.354, "H": 36.7, "tier_ceiling_spawn": "sonnet", // depth-0 session spawning children "tier_ceiling_child": "haiku", // session that is itself a subagent "fanout_max": 3, "thinking_budget": 4000, // ADVISORY (envelope only) "inject_cap_tokens": 2150, // MECHANICAL (shaper-prompt emits the bytes) "verify_min": 1, "speculative": false, "compact_scale": 0.68, // multiplier on shed guard thresholds "roi_min": 3.4, "envelope": "[TRIDENT L=50 H=37 tier≤sonnet W≤3 think≤4k inject≤2.1k V≥1 spec=off]" }, "per_pin": { "<sid>": { /* same shape, computed at that pin's L_eff */ } } }, "headless": { // values env-stamp.sh injects (computed for TC2 at L_eff=global) "model": "claude-sonnet-4-6", "envelope": "[TRIDENT TC2 ...]" } }