Files
claude-plugin-inference-arb…/skills/offload-audit/SKILL.md
T
oleks dcec9cabfb FR-4.5: evidence-stability gate — refuse to file a candidate whose confidence moved with the window
Phase 7 filed anxious/agent-wip/release-policy-derivation as `measured` at
11.94% of audited spend on a 30-day window. The same candidate over 7 days came
back `thin` at 0.00%. Only the date range differed. A human reading either issue
body alone cannot know the other exists, which is rubric.md §4a's failure mode
reached through the plugin's own headline number.

That was caught by a human running a second skeptical review — luck, not a
control. It is now mechanical, in the same register as FR-4.2's overrule-case
gate:

- bin/stability-classify matches each `file` candidate against the most recent
  prior snapshot recording it (FR-5.2 identity) and downgrades it to a boundary
  question if `measurement_strength` changed or the share crossed the 2% filing
  threshold between windows. Movement within a label is a trend, not an
  instability; no prior window is `unchecked`, not unstable (FR-2.4).
- The downgrade is a finding against THIS repo, not the target's — the target
  did not change, the auditor described it two ways. Carries an `ia-stability`
  marker so a re-run comments rather than duplicating.
- bin/ia_store.py factors the store and identity resolution out of
  audit-snapshot so both scripts hash signatures identically. A divergence there
  would make the gate match nothing and fail open.

Documented as spec FR-4.5 and rubric §5b (shipped verbatim in references/).
Wired into skills/offload-audit as step 4b, before filing-plan; steps 5-6 now
consume its output rather than classified.json.

S4 is retired in its old form — a human triage catching a bad candidate is not a
repeatable test — and re-passed as: the gate catches the real
release-policy-derivation case with no human in the loop. Asserted in
tests/stability.test.sh case (f) against the verbatim two-window Phase 7 output.

Closes kotkan/claude-plugin-inference-arbitrage#11
Suite: 178 assertions, exit 0.
2026-07-29 20:25:04 +03:00

13 KiB
Raw Blame History

name, description, allowed-tools
name description allowed-tools
offload-audit Run a full offload audit on a Claude Code plugin — measure what its skills and agents actually cost, classify each candidate against the boundary rubric, file the well-evidenced ones as issues on the target's own repo, and write the run summary to this plugin's wiki. Trigger on "audit this plugin for token optimization", "can any of this be a script", "offload analysis", "where is this plugin wasting inference", "check <plugin> for scripting opportunities", "what is this plugin paying inference for", "run an offload audit". Read-only with respect to the audited plugin. Bash, Read, Grep, Glob, Skill, Agent

Audit a plugin for work that should be a script

Seven steps, in order. Steps 13 and 4b are scripts, step 4 is the only judgment, steps 56 are delegation. Do not do by hand anything a bin/ tool does — this plugin is subject to its own thesis (FR-8), and an audit that counts things by inference is the finding, not the method.

Full rubric: the boundary-rubric skill. Deeper reporting discipline: agents/offload-analyst.md. Issue-body contract and the marker-search mechanics: ${CLAUDE_PLUGIN_ROOT}/references/issue-template.md.


1. Resolve and measure

IA=${CLAUDE_PLUGIN_ROOT}
$IA/bin/plugin-inventory <path|name> --json > inv.json      # static pass
$IA/bin/offload-scan --plugin <name> --days 30 --json > scan.json  # dynamic pass

plugin-inventory refuses a directory that is not a plugin rather than guessing (FR-1.3). A target with no transcript history is still auditable — the static pass stands alone and every candidate is marked unmeasured (FR-2.4).

2. Read the measurement honestly, before looking for candidates

Open the report with window, absolute evidence counts, the named structural blind spots (hooks, agents, direct commands/), and only then the coverage ratio — stated as dilution, not completeness.

Coverage answers "how much of these sessions was about something else", not "how much of this plugin's work did we see."

A real run against anxious measured 0.021. That is not a failed scan; it is long multi-topic sessions. Reporting it as completeness is the easiest way to mislead the reader. Then give the mechanical-share headline — the number that says whether there is anything here at all.

3. Gather candidates

From plugin-inventory: high verb ratio with no bin/ script behind it (a conjunction — never the ratio alone), duplicated command blocks, rule tables, low script coverage. From offload-scan: high MTR with real spend, recurring n-grams, read amplification, retry density, fan-out. judgment_density is a brake — high judgment density with high spend means the plugin is healthy and must be reported that way.

aggregate.hook_prose_drift entries are not offload candidates. Pass them with "category": "hook-prose-drift" so they come back as drift-note.

4. Judge, then grade mechanically

This is the only step that is inference. Invoke the boundary-rubric skill per candidate; write the five determinism tests, the falsifiability triple, the escalation path and digest schema into a judgments JSON (shape: tests/calibration/*.judgments.json), then:

$IA/bin/boundary-classify judgments.json > classified.json

Never grade by hand and never override the verdict it returns. The filing threshold (high confidence and ≥2% of audited spend and the triple complete including the overrule case) lives in that script.

No overrule case → boundary question, reported, never filed. Hard gate, FR-4.2. When genuinely torn, do not file.

4b. Check the evidence against the previous window

The second hard gate (FR-4.5, rubric §5b). It runs on every audit, before anything is filed, and its output replaces classified.json downstream:

$IA/bin/stability-classify --classified classified.json --target <name> \
    --scan scan.json > stable.json

It matches each file candidate against the most recent prior snapshot that recorded it (FR-5.2 identity) and downgrades any whose measurement_strength changed, or whose share crossed the 2% filing threshold, between the two windows. Use stable.json for steps 5 and 6. Never file from classified.json once this has run.

If the store has no history for this target — a first audit — every candidate comes back stability: unchecked and files normally. That is the FR-2.4 outcome, not a failure. If the store is cold but the wiki has history, seed it first, or the gate has nothing to check against:

$IA/bin/audit-snapshot import --target <name> --snapshots <fetched snapshots.jsonl>

4b-i. A downgrade is a finding against this plugin

Each entry in stability_findings is a defect in the auditor — the tool described the same candidate two different ways depending on the date range. File it, implicitly, on kotkan/claude-plugin-inference-arbitrage, not on the target's repo. Idempotency works the same way as FR-6.3: the finding carries self_report.marker (<!-- ia-stability: <candidate_id> -->); search for that marker first and comment on the existing issue rather than opening a second one.

Agent(subagent_type="anxious:issuer-agent", prompt=...)
  "On kotkan/claude-plugin-inference-arbitrage: search issues (state=all) for the
   literal marker <!-- ia-stability: <candidate_id> -->. If it exists, comment the
   new numbers on that issue. If not, open one — the marker must be the first line
   of the body, verbatim. This is an inference-arbitrage self-audit finding: a
   candidate's evidence label changed across measurement windows, so the gate
   refused to file it. Include the headline, both windows, and both figures."

Do not report the instability as a caveat inside a target-repo issue. A hedge a reader can skip is not a gate.

5. Output path A — file the issues

Filing is implicit. Do not ask permission to file a candidate that cleared the gate — this matches the environment's standing bug-handling convention. What you must not do is file anything the gate rejected.

This plugin holds no Gitea credentials and calls no Gitea MCP tool. The chain is fixed:

offload-audit → Agent(anxious:issuer-agent) → cluster:gitea-agent → Gitea

5a. Resolve the repo and get the searches

$IA/bin/filing-plan queries \
    --classified stable.json --judgments judgments.json \
    --target-path <resolved plugin path> > queries.json

If filing is unavailable (FR-6.4 — a third-party or marketplace plugin with no repo this environment can write to), skip the rest of path A entirely, carry the reason into the report and into the run summary, and go to step 6. Path B still runs. This is a normal outcome, not an error.

5b. Run the marker search — the step that prevents duplicates

Delegate the searches in queries.json. Both queries, merged; the label scan is the authoritative one. Bodies are not in listing responses, so each candidate issue needs an issue_read:

Agent(subagent_type="anxious:issuer-agent", prompt=...)
  "Read-only lookup on <owner/repo>, no writes. For an inference-arbitrage
   audit I need to know whether these candidates already have issues.
   1. list_issues(owner, repo, labels=['token-offload'], state='all')
   2. search_issues(query='ia-candidate: <id>', owner, state='all')  [per id]
   3. For each issue number found: issue_read(method='get')  → body
      and issue_read(method='get_comments') → comment bodies
   Return a JSON array of {number, state, title, body, comments:[{body}]}.
   Do not create, edit, or label anything."

Write the returned array to existing.json. If the search fails or you cannot get bodies, stop path A and report it — do not proceed with an empty array. filing-plan requires --existing precisely so that a failed search cannot silently become a duplicate-filing spree (FR-6.3).

5c. Build the plan

$IA/bin/filing-plan plan \
    --classified stable.json --judgments judgments.json \
    --target-path <path> --existing existing.json \
    --audit-page "Audits/<target>/<YYYY-MM-DD>" \
    --window "<since> -> <until>" --run-date <YYYY-MM-DD> \
    --status <snapshot-diff status, if known> > filing.json

Each action is file, comment, or skip, with the exact body already rendered. Execute the plan verbatim — do not rewrite a body, and never turn a comment into a file. Surface any warnings to the user.

5d. Execute through issuer

One Agent call per action (or one call carrying all of them):

Agent(subagent_type="anxious:issuer-agent", prompt=...)
  # for action == "file"
  "File this on <owner/repo>. It is an inference-arbitrage audit finding.
   Proposed title: <proposed_title>   (yours to rewrite per your taxonomy)
   Body — file VERBATIM, the first line is an idempotency marker that a later
   audit searches for and MUST NOT be altered or reformatted:
   <body>
   Labels: 'token-offload' is required — create it on this repo if absent
   (colour #8b5cf6, 'A step paying for inference where a deterministic script
   would do'). Add the four-axis taxonomy labels and any milestone per your own
   rules; typically kind/chore or kind/capability-gap, area/agent-behavior,
   domain/agents, activity/automate.
   Return the issue reference in full owner/repo#num form."

  # for action == "comment"
  "Add this comment to <issue>, an existing inference-arbitrage finding being
   re-measured. Do not open a new issue. Post the body VERBATIM (the first line
   is an idempotency marker). If the issue is closed, use your judgement about
   reopening; the audit does not require it.
   <body>"

issuer owns repo, final title, labels beyond token-offload, and milestone. You own the body.

Collect the returned refs into issue-map.json{"<candidate_id>": "owner/repo#N"}.

6. Output path B — the run summary

Path B runs after path A so the summary records real issue numbers, and it runs even when path A was skipped.

$IA/bin/audit-snapshot write --target <name> --from inv.json scan.json \
    --classified stable.json --issue-map issue-map.json \
    --repo <owner/repo> \
    --note "<anything the scripts cannot see — see below>"
$IA/bin/audit-snapshot pages --target <name> > pages.json

audit-snapshot renders Audits/<target>/<YYYY-MM-DD>, regenerates Audits/<target>/Latest, and appends the machine-readable record to Data/<target>/snapshots.jsonl. It does not write to Gitea either — delegate each page in pages.json to cluster:gitea-agent for the wiki of kotkan/claude-plugin-inference-arbitrage, respecting append_only on the JSONL.

--note is repeatable and is how a filing outcome reaches the page — the snapshot scripts make no Gitea calls and cannot see one. When filing was unavailable, that note is required (FR-6.4):

--note "filing: unavailable — <the reason from filing-plan, verbatim>"

Use it likewise for a search that failed, or a plan you could not fully execute. Say it in the report either way; the note is what makes it durable.

Publishing gotcha: wiki_write create/update take the page title, but delete takes the URL-decoded sub_url (Data/<target>/snapshots.jsonl.-) — deleting by title 404s.

7. Report to the user

In this order (FR-7.3): coverage and window → mechanical-share headline → ranked candidate table with position, confidence and value → diff against the previous snapshot → boundary questions, as questions → drift notes, with their documentation fix → issues filed or updated, in full owner/repo#num form → the wiki page path.

Rank by measured offload_value and say that you are doing so. Name skills and agents, never UUIDs or session ids. Emit only shapes and counts — no transcript content, file contents, command arguments, or user prose ever enters a report, an issue, or a wiki page (FR-3.5).

"Nothing to offload here" is a complete answer

If nothing clears the bar, say so plainly and stop. Do not pad the report to look thorough. token-budget is expected to produce exactly this result, and a run against it that yields confident candidates is a bug in this plugin, not a finding about token-budget. Report the pure-inference findings by name — they are the evidence that you looked rather than shrugged.

Never

  • Never edit the plugin you audit, and never implement your own recommendation.
  • Never call a Gitea tool directly from this skill.
  • Never file a candidate the gate rejected, or one whose repo is unavailable.
  • Never re-run an audit "to be safe" without the marker search. That is the one mistake that makes this plugin hated.