Phase 3 — Dynamic pass #3
Notifications
Due Date
No due date set.
Blocks
#4 Phase 4 — Rubric and classification
kotkan/claude-plugin-inference-arbitrage
Reference: kotkan/claude-plugin-inference-arbitrage#3
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Per design/tasks.md Phase 3. Depends on decision 0.1 (closed —
cc-tokensvia PATH + cache-glob fallback).bin/offload-scanskeleton: streaming JSONL reader, attribution filter,(message.id, requestId)de-dup — delegate token/cost arithmetic tocc-tokens, don't reimplement the pricing table or streaming-snapshot maxoffload_waste, read amplification, retry density, judgment density, fan-out multiplier, compositeoffload_valuetests/scan.test.shagainst synthetic fixture transcripts only — never real transcripts, they contain private contentImplemented in
bdb62da. See the comment below for the two spec deviations the real transcripts forced, and the calibration numbers for Methodology/Calibration.md.Phase 3 landed in
bdb62da(3.1–3.9 all checked above). Leaving open for review.What shipped
bin/offload-scan(~600 lines, Python 3 stdlib only, executable bit set) — one streaming pass over~/.claude/projects/**/*.jsonlwith an mtime prefilter, attribution filter,(message.id, requestId)de-dup takingmaxon every usage field, invocation reconstruction, turn classification, signature masking, n-gram mining, all §5.2 metrics, and the FR-3.4 coverage headline.references/signals-catalog.md— four new sections read only byoffload-scan:mechanical-tool-calls,read-tool-calls,read-only-bash,scan-thresholds. Same fenced-block contract as the static pass, extended to accept*globs so MCP tools match on their last__segment.plugin-inventoryignores them; nothing in Phase 2 changed behaviourally.tests/scan.test.sh+tests/fixtures/transcripts/— 30 assertions, all passing, against four hand-built synthetic transcripts (generate.pyregenerates them). No real transcript content is in this repo, and the suite asserts that: it greps the output for five fixture literals and fails if any survives masking.tests/run-all.shpicks it up automatically via its./*.test.shglob; Phase 2's suite still passes; shellcheck clean.Deviations from design/plan.md §4.1 — both forced by what the transcripts actually contain
attributionAgent, notagentName.agentNameexists but on a separatetype: "agent-name"line that maps asessionIdto a human display label ("cluster-grinder", "price-hunter") — it is a Workflow session name, not a subagent type, and it never appears on an assistant line. The real field isattributionAgenton the subagent sidechain transcripts under<session>/subagents/*.jsonl, carrying values likecluster:gitea-agent,anxious:issuer-agent. Those lines also carryattributionPlugin, so agent turns are attributed without needingplugin-inventory --jsonto supply the agent list at all — the plan's step-2 dependency on the static pass turned out to be unnecessary.attributionSkillis sometimes bare, sometimes plugin-qualified (planning-with-filesvsmemory:save). Normalized: an unqualified value is prefixed with the attributed plugin.The
cc-tokensdependency — resolution order held, mechanism adjustedResolution order is exactly as decided in 0.1: PATH →
~/.claude/plugins/cache/*/token-budget/*/bin/cc-tokens(newest semver) → fail loudly with an install hint. Nothing is vendored and no pricing table is reimplemented.But it imports the module instead of shelling out with
--json, and that gap is worth recording.cc-tokens' actual CLI surface isdaily | sessions | projects | models | composition | hogs | context, all of which aggregate by day/session/project/model. None of them can answer "give me weighted tokens for this specific set of turns" — the attribution fields this pass filters on are not on its CLI surface at all, andcontext --json(the only per-turn output) is scoped to one session and omitsmessage.id/requestId, so there is no join key. Shelling out cannot express the query.Importing keeps the property that decision 0.1 actually cares about: every token, cost and weighted number in the output is computed by
cc_tokens.Rec.cost/Rec.weighted, so the pricing table, the cache multipliers, and the tier normalization still have exactly one home. The import is guarded — a missingRec,price_fororparse_dayfails loudly rather than silently degrading.This makes the plan's preferred long-term fix concrete:
cc-tokens attribute --by skill|plugin|agent --jsonupstream intoken-budgetwould letoffload-scandrop the import for a subprocess call. Worth filing againstkotkan/claude-plugin-token-budgetwhen Phase 3 is signed off.Calibration numbers for
Methodology/Calibration.md(3.9)Real-history sanity run,
--plugin anxious --days 30, against the live~/.claude/projects(1.7 GB, ~4800 transcript files):Memory is the headline: 29 MB peak over a 1.7 GB history confirms the O(attributed turns + sessions) bound, well inside the emmett constraint (spec S8). 94 s is acceptable for an on-demand audit.
Coverage of 0.021 is a real finding, not a bug, and it is exactly what FR-3.4 exists to surface. The denominator is "every assistant turn in any session where the target was active at all" — and this environment's sessions are long, multi-topic marathons, so a skill invoked once inside a 5,000-turn session drags the ratio down hard. Two consequences for Phase 4: the analyst must lead with this number and treat a dynamic pass at this coverage as weak evidence; and it may be worth reporting a second, tighter denominator (turns within the invocation windows) alongside it, so "we saw 2% of the sessions" is not confused with "we saw 2% of the plugin's work". Flagging for a decision rather than changing the spec unilaterally.
One more observation for the rubric's calibration: turns attributed to
anxiousshow up under agents that are notanxious' own (repo-worker,cluster:gitea-agent) — an anxious-attributed turn executing inside someone else's subagent. The grouping reports this honestly rather than discarding it, and it is likely to matter for the fan-out analysis in Phase 4.fanout_multiplieris reported unclamped and can come out below 1 (0.2–0.66 on the real run): a subagent spawned with a tight brief genuinely carries less context than a marathon main session. That is fan-out done right, and it is more useful reported than hidden. It is a diagnostic only — it is deliberately not an input tooffload_value.Not done here (by design)
No output from the real-history run is committed anywhere, no snapshot is persisted (Phase 5), and nothing is classified or filed (Phases 4 and 6).