os-orchestration: semi-automate the biweekly IRL audit (Option C)

Deterministic layer: audit/bin/audit-stats (Ruby) composes the existing
extract fact-sheets across all production transcripts since the last
run, writes per-run stats/flags/sheets to
~/.local/state/os-orchestration/audit/, and appends the metrics.tsv
trend ledger (seeded with the 2026-07-10 run: 23 sessions).
Judgment layer: new /os-orchestration:audit-sessions skill — sonnet
auditor fan-out over precomputed flagged regions only, tiered
synthesis with run-over-run trend verdicts, stops before any wording
edit. Nudge: os-status check orchestration-audit-due warns when the
ledger is >=14 days stale (missing ledger = machine not opted in).
Planka recurrence entry deferred: gem offsets are +1y/+6m/+3m/+1m by
design; +2w blocked on parked gem versioning decisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XknQRvihHDpYE47RTmUR4N
This commit is contained in:
jared 2026-07-10 12:00:10 -04:00
parent 242ca84744
commit 3e42aecc13
6 changed files with 458 additions and 0 deletions

View File

@ -269,6 +269,21 @@ into `~/.claude/plugins/os-vault`
settled-design drift 8/19 sessions). Side finding needing separate repro: harness ignored settled-design drift 8/19 sessions). Side finding needing separate repro: harness ignored
explicit `model: "sonnet"` pins on general-purpose agents (env override unset — distinct explicit `model: "sonnet"` pins on general-purpose agents (env override unset — distinct
from WS1 cause); the self-report line caught it. from WS1 cause); the self-report line caught it.
- **IRL audit semi-automated (2026-07-10):** recurring biweekly audit formalized, working
from any cwd. Deterministic layer: `audit/bin/audit-stats` (Ruby, composes the existing
`audit/bin/extract` fact-sheets) enumerates production transcripts since the last run
(excludes `-tmp-orch-*`/`-tmp-claude-*` sandboxes, `*-eval` dirs, active/trivial
sessions), writes `~/.local/state/os-orchestration/audit/run-<date>/`
(stats.tsv/flags.md/sheets) and appends the trend ledger `metrics.tsv` (seeded with the
2026-07-10 run: 23 sessions, 52 spawns, 1 missing model). Judgment layer: new skill
`/os-orchestration:audit-sessions` (rubric in `references/`) — sonnet auditor fan-out
over flagged regions only, tiered synthesis, run-over-run trend verdicts, stops before
any wording edit. Nudge: os-status check `orchestration-audit-due` warns when the
ledger's last row is ≥14 days old (missing ledger = machine not opted in, silent; env
override `OS_ORCH_AUDIT_LEDGER` for tests). Planka recurrence-manifest entry skipped:
the gem's offsets are deliberately `+1y/+6m/+3m/+1m` only (issue 11) — a `+2w` offset is
a gem change blocked on the parked publish/versioning decisions; the os-status nudge is
the trigger for now.
**Global os-status plugin** — `cc-os/plugins/os-status/` (git-tracked, 2026-07-06); **Global os-status plugin** — `cc-os/plugins/os-status/` (git-tracked, 2026-07-06);
symlinked into `~/.claude/plugins/os-status` symlinked into `~/.claude/plugins/os-status`

View File

@ -0,0 +1,252 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# audit-stats — deterministic run-level driver for the orchestration IRL audit.
#
# Enumerates production session transcripts modified since a given date,
# reduces each to a delegation fact-sheet (audit/bin/extract), and writes a
# run directory under the audit state dir (~/.local/state/os-orchestration/audit):
#
# run-<date>/stats.tsv one row per audited session (deterministic columns)
# run-<date>/flags.md heuristic missed-delegation candidates, per session,
# for auditor judgment (mechanical vs judgment-dependent)
# run-<date>/sheets/ full fact-sheet JSON per session
#
# and appends one row to metrics.tsv at the state-dir root — the trend ledger
# that the os-status due-check and run-over-run comparison read. Everything in
# this file is code-only: no model involvement, reproducible from transcripts.
#
# Usage: audit-stats [--since YYYY-MM-DD] [--exclude SESSION_ID] [--state-dir DIR]
# [--projects-dir DIR] [--dry-run]
#
# --since defaults to the date of the last metrics.tsv row; the first run must
# pass it explicitly. Transcripts written to within the last 10 minutes are
# treated as active sessions and deferred to the next run.
require "date"
require "fileutils"
require "json"
require "optparse"
# `extract` is extensionless (a CLI); its __FILE__ guard makes load safe here.
load File.expand_path("extract", __dir__)
module OrchAudit
DEFAULT_STATE_DIR = File.expand_path("~/.local/state/os-orchestration/audit")
DEFAULT_PROJECTS_DIR = File.expand_path("~/.claude/projects")
class TranscriptSet
# Non-production sessions: eval sandboxes (-tmp-orch-*), headless runs
# spawned from session scratchpads (-tmp-claude-*), and sessions whose
# cwd was an eval harness directory (…-eval).
EXCLUDED_PROJECT_DIRS = [/-tmp-orch-/, /-tmp-claude-/, /-eval\z/].freeze
ACTIVE_WINDOW_SECONDS = 600
def initialize(projects_dir:, since:, excluded_sessions: [], now: Time.now)
@projects_dir = projects_dir
@since = since
@excluded_sessions = excluded_sessions
@now = now
end
def paths
Dir.glob(File.join(@projects_dir, "*", "*.jsonl")).sort.select { |p| candidate?(p) }
end
private
def candidate?(path)
return false if EXCLUDED_PROJECT_DIRS.any? { |re| File.dirname(path).match?(re) }
return false if @excluded_sessions.include?(File.basename(path, ".jsonl"))
mtime = File.mtime(path)
mtime.to_date >= @since && (@now - mtime) > ACTIVE_WINDOW_SECONDS
end
end
class SessionStats
MIN_ASSISTANT_TURNS = 8
def initialize(path)
@path = path
@sheet = FactSheet.new(path).data
end
attr_reader :path, :sheet
def session_id = File.basename(@path, ".jsonl")
def project = File.basename(File.dirname(@path))
def trivial? = sheet[:metadata][:assistant_turns] < MIN_ASSISTANT_TURNS
def spawns = sheet[:agent_spawns]
def flagged_runs = sheet[:missed_delegation_candidates]
def spawns_missing_model
spawns.count { |s| !s[:model_explicit] }
end
# A pin mismatch is deterministic evidence of the harness ignoring an
# explicit model param (the 2026-07-10 finding): param present, resolved
# model known, and the resolved id does not contain the requested tier.
def model_pin_mismatches
spawns.count do |s|
s[:model_explicit] && s[:model_param] && s[:resolved_model] &&
!s[:resolved_model].to_s.include?(s[:model_param].to_s)
end
end
def sendmessage_followups
spawns.sum { |s| s[:rounds] - 1 }
end
def row
m = sheet[:metadata]
[
session_id, project, m[:assistant_turns], m[:main_loop_output_tokens],
m[:main_loop_share], spawns.size, spawns_missing_model,
model_pin_mismatches, sendmessage_followups, flagged_runs.size
]
end
end
class AuditRun
STATS_HEADER = %w[
session project assistant_turns main_loop_output_tokens main_loop_share
spawns spawns_missing_model model_pin_mismatches sendmessage_followups
flagged_runs
].freeze
LEDGER_HEADER = %w[
date since sessions trivial_skipped spawns spawns_missing_model
model_pin_mismatches sendmessage_followups flagged_runs
median_main_loop_share
].freeze
def initialize(state_dir:, projects_dir:, since:, excluded_sessions:, today: Date.today)
@state_dir = state_dir
@since = since
@today = today
@transcripts = TranscriptSet.new(
projects_dir: projects_dir, since: since, excluded_sessions: excluded_sessions
)
end
def ledger_path = File.join(@state_dir, "metrics.tsv")
def run_dir = File.join(@state_dir, "run-#{@today.iso8601}")
def plan
@transcripts.paths
end
def execute
audited, trivial = partition_sessions
write_run_dir(audited)
append_ledger(audited, trivial)
{ run_dir: run_dir, audited: audited.size, trivial: trivial.size }
end
private
def partition_sessions
@transcripts.paths.map { |p| SessionStats.new(p) }.partition { |s| !s.trivial? }
end
def write_run_dir(audited)
sheets_dir = File.join(run_dir, "sheets")
FileUtils.mkdir_p(sheets_dir)
File.write(File.join(run_dir, "stats.tsv"), stats_tsv(audited))
File.write(File.join(run_dir, "flags.md"), flags_md(audited))
audited.each do |s|
File.write(File.join(sheets_dir, "#{s.session_id}.json"), JSON.pretty_generate(s.sheet))
end
end
def stats_tsv(audited)
lines = [STATS_HEADER.join("\t")]
audited.each { |s| lines << s.row.join("\t") }
lines.join("\n") + "\n"
end
def flags_md(audited)
md = +"# Flagged regions for auditor judgment — run #{@today.iso8601}\n\n"
md << "Heuristic candidates only; sequential-dependent work is a valid reason not\n"
md << "to delegate. Auditors judge mechanical vs judgment-dependent per region.\n"
audited.each do |s|
md << "\n## #{s.session_id} (#{s.project})\n\n"
if s.flagged_runs.empty?
md << "No flagged runs.\n"
next
end
s.flagged_runs.each do |m|
md << "- **#{m[:tool]} x#{m[:length]}** (jsonl lines #{m[:lines].join('-')}, "
md << "#{m[:distinct_targets]} distinct targets)\n"
m[:targets_sample].each { |t| md << " - `#{t}`\n" }
end
end
md
end
def append_ledger(audited, trivial)
FileUtils.mkdir_p(@state_dir)
File.write(ledger_path, LEDGER_HEADER.join("\t") + "\n") unless File.exist?(ledger_path)
File.open(ledger_path, "a") { |f| f.puts(ledger_row(audited, trivial).join("\t")) }
end
def ledger_row(audited, trivial)
[
@today.iso8601, @since.iso8601, audited.size, trivial.size,
audited.sum { |s| s.spawns.size },
audited.sum(&:spawns_missing_model),
audited.sum(&:model_pin_mismatches),
audited.sum(&:sendmessage_followups),
audited.sum { |s| s.flagged_runs.size },
median(audited.map { |s| s.sheet[:metadata][:main_loop_share] })
]
end
def median(values)
return 0 if values.empty?
sorted = values.sort
mid = sorted.size / 2
(sorted.size.odd? ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0).round(3)
end
end
class Ledger
def self.last_run_date(path)
return nil unless File.exist?(path)
last = File.readlines(path).map(&:strip).reject(&:empty?)[1..]&.last
last && (Date.iso8601(last.split("\t").first) rescue nil)
end
end
end
if $PROGRAM_NAME == __FILE__
options = {
state_dir: OrchAudit::DEFAULT_STATE_DIR,
projects_dir: OrchAudit::DEFAULT_PROJECTS_DIR,
exclude: [],
dry_run: false
}
OptionParser.new do |o|
o.banner = "Usage: audit-stats [--since YYYY-MM-DD] [--exclude SESSION_ID] [options]"
o.on("--since DATE") { |d| options[:since] = Date.iso8601(d) }
o.on("--exclude ID", "Session id to skip (repeatable)") { |id| options[:exclude] << id }
o.on("--state-dir DIR") { |d| options[:state_dir] = File.expand_path(d) }
o.on("--projects-dir DIR") { |d| options[:projects_dir] = File.expand_path(d) }
o.on("--dry-run", "List transcripts that would be audited, then exit") { options[:dry_run] = true }
end.parse!
since = options[:since] ||
OrchAudit::Ledger.last_run_date(File.join(options[:state_dir], "metrics.tsv"))
abort "No --since given and no prior ledger row to infer it from" unless since
run = OrchAudit::AuditRun.new(
state_dir: options[:state_dir], projects_dir: options[:projects_dir],
since: since, excluded_sessions: options[:exclude]
)
if options[:dry_run]
puts run.plan
exit 0
end
result = run.execute
puts "Audited #{result[:audited]} sessions (#{result[:trivial]} trivial skipped)"
puts "Run dir: #{result[:run_dir]}"
puts "Ledger: #{run.ledger_path}"
end

View File

@ -0,0 +1,68 @@
---
description: Run the biweekly orchestration IRL audit - deterministic transcript stats precompute, then auditor fan-out judging flagged regions against ORCHESTRATION.md rules, then a tiered tune-up report. Works from any directory. Trigger via the os-status due nudge or the Planka recurrence card.
---
# Audit production sessions against the ORCHESTRATION.md rules
You are running the recurring (biweekly) IRL audit of the shipped delegation-economics
rules. The signal source is real production session transcripts — NEVER eval grid
scenarios or reserve sets (contaminated for wording tuning since 2026-07-08). This skill
works from any cwd; nothing here requires being inside the cc-os repo.
## Step 1 — Deterministic stats precompute (code, no judgment)
Run the driver at `<skill base dir>/../../audit/bin/audit-stats` (the skill base
directory is `<plugin root>/skills/audit-sessions`, the driver lives at
`<plugin root>/audit/bin/audit-stats`):
```bash
<plugin root>/audit/bin/audit-stats --exclude <CURRENT_SESSION_ID>
```
(If the current session id is unknown, omit `--exclude` — the 10-minute active-window
guard already defers the live session to the next run.)
- `--since` defaults to the last ledger row's date. It refuses to run without a prior row;
a first-ever run needs an explicit `--since YYYY-MM-DD`.
- Output: `~/.local/state/os-orchestration/audit/run-<date>/` containing `stats.tsv`
(per-session deterministic columns), `flags.md` (heuristic missed-delegation regions),
and `sheets/*.json` (full per-session fact-sheets). One row is appended to
`~/.local/state/os-orchestration/audit/metrics.tsv` — the trend ledger. Never edit the
ledger by hand; never re-derive these numbers with a model.
## Step 2 — Auditor fan-out (judgment only where code can't judge)
Read `stats.tsv` and `flags.md` from the run dir. Sessions with zero spawns AND zero
flagged runs AND main_loop_share 1.0 at low turn counts usually need no auditor —
include them only in the aggregate. Batch the remaining sessions into ~3 parallel
auditor agents (58 sessions each), `model: "sonnet"`, background, one round each.
Each auditor prompt must include:
- The rubric: read `references/rubric.md` (sibling to this SKILL.md) and paste it in.
- The session's fact-sheet JSON path and flagged regions — auditors judge
**mechanical vs judgment-dependent** for each flagged region by reading the transcript
selectively around the cited jsonl lines (jq on line ranges, never the raw file).
- The self-report line: "State the exact model ID you are running as in the first line
of your report." Cross-check it; flag any pin mismatch in the report.
- The exact per-session report format from the rubric.
## Step 3 — Synthesize and compare (main loop)
- Aggregate auditor findings into the tiered format: Tier 1 (HIGH, recurring),
Tier 2 (MEDIUM), Tier 3 (solved surfaces — explicitly name what NOT to spend
wording budget on).
- Compare this run's ledger row against prior rows in `metrics.tsv` — the previous
run's Tier 1 items are hypotheses this run tests (e.g. did settled-design drift
drop after the 2026-07-10 wording patch?). State each verdict explicitly.
- Write `report.md` into the run dir.
## Step 4 — Stop for the human
Report the tiers and trend verdicts, then STOP. Do not edit ORCHESTRATION.md without
explicit approval — wording changes are a human decision (injected-token budget; grid
validation is unavailable). If approved later: edit
`~/dev/cc-os/plugins/os-orchestration/ORCHESTRATION.md`, run `~/dev/cc-os/bin/refresh-plugins`,
and record the change in `~/dev/cc-os/docs/implementation-status.md`.
Finally, if a Planka card for this audit exists on the backlog board, move it to
Review (never to Done).

View File

@ -0,0 +1,55 @@
# IRL audit rubric — shipped ORCHESTRATION.md delegation-economics rules
Audit each assigned session against these shipped rules. A "finding" is a concrete
violation, near-miss, or friction point, with evidence (jsonl line number / short quote).
The deterministic fact-sheet (stats + flagged regions) is precomputed — do NOT recount
tool calls or spawns; your job is the judgment layer: mechanical vs judgment-dependent,
severity, and category.
## Rule surfaces (categories)
1. **missed-delegation** — main loop ran a mechanical sequence beyond ~3 tool calls with no
judgment between steps (multi-file edits, lookups, conversions, long log review, wide
grep-and-synthesize) instead of delegating down-tier. Includes: write-N-files fan-out
from a settled design done directly; the operator's own eval/benchmark/extraction loops.
2. **model-param** — an Agent spawn with no explicit `model:` param, or mechanical work
sent to sonnet/opus when haiku would do, or a sonnet/opus spawn whose prompt lacks the
self-report line, or a downgraded/mismatched resolved model treated silently as
judgment-tier.
3. **spawn-batching** — many small spawns where ~58 similar items should have been grouped
into one agent prompt; or a follow-up on an agent's result sent as a fresh spawn instead
of SendMessage to the live agent.
4. **async-usage** — main loop idle-waiting on a synchronous agent when background +
continue was possible; sleep-polling a background job; or conversely
babysitting/polling that the rules say to do directly.
5. **redundant-context** — delegated investigation re-covering ground the main loop already
read, or the main loop reading many files itself before delegating the investigation
anyway.
6. **over-delegation** — delegation where direct work was correct: ≤2 tool-call ops,
judgment-dependent steps, interactive troubleshooting, or a uniform change coverable by
one scripted Bash loop.
7. **drift** — session starts disciplined then drifts into long direct runs mid-session,
especially after a design/decision settles.
## Per-session report format (return exactly this)
```
SESSION: <session id>
PROJECT/TOPIC: <one line what the session was doing>
FINDINGS:
- [<category>] <severity HIGH/MED/LOW><one-sentence finding> | evidence: <jsonl line or short quote>
...or "none"
FLAGGED-REGION VERDICTS: <for each precomputed flagged region: mechanical | judgment-dependent | mixed, one line each>
POSITIVE: <patterns worth keeping, if notable>
```
## Method notes
- The fact-sheet JSON gives spawns (with model params and resolved models), per-segment
tool profiles, and flagged same-tool runs with jsonl line ranges. Read the transcript
ONLY selectively around those lines, e.g.
`sed -n '120,180p' file.jsonl | jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="text") | .text'`
- Judge "mechanical vs judgment" from tool names + inputs + the surrounding
user/assistant text: did each result change what happened next?
- Sequential-dependent work is a valid reason not to delegate; interactive
troubleshooting (user replying every few turns) is a valid reason too.

View File

@ -11,8 +11,10 @@ invariants.md). Do not paraphrase them.
from __future__ import annotations from __future__ import annotations
import json import json
import os
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date
from pathlib import Path from pathlib import Path
from typing import Callable, Optional from typing import Callable, Optional
@ -160,6 +162,39 @@ def vault_hub_note_present(ctx: Ctx) -> CheckResult:
) )
AUDIT_LEDGER_ENV = "OS_ORCH_AUDIT_LEDGER"
DEFAULT_AUDIT_LEDGER = "~/.local/state/os-orchestration/audit/metrics.tsv"
AUDIT_INTERVAL_DAYS = 14
def _last_audit_date(ledger: Path):
try:
rows = [l for l in ledger.read_text().splitlines() if l.strip()][1:]
return date.fromisoformat(rows[-1].split("\t")[0])
except Exception:
return None
def orchestration_audit_due(ctx: Ctx) -> CheckResult:
"""Nudge when the biweekly orchestration IRL audit is overdue. The ledger
(metrics.tsv) is appended deterministically by os-orchestration's
audit/bin/audit-stats; this check only reads its last row's date."""
ledger = Path(
os.path.expanduser(ctx.environ.get(AUDIT_LEDGER_ENV, DEFAULT_AUDIT_LEDGER))
)
last = _last_audit_date(ledger)
if last is None:
return OK # no ledger — this machine hasn't opted into audits yet
days = (date.today() - last).days
if days < AUDIT_INTERVAL_DAYS:
return OK
return warn(
f"orchestration IRL audit due (last run {last.isoformat()}, {days} days"
" ago) — run /os-orchestration:audit-sessions (works from any project;"
" deterministic stats precompute, then auditor fan-out)."
)
@dataclass(frozen=True) @dataclass(frozen=True)
class Check: class Check:
name: str name: str
@ -171,4 +206,5 @@ REGISTRY = [
Check("subagent-model-env-override", subagent_model_env_override, project_scoped=False), Check("subagent-model-env-override", subagent_model_env_override, project_scoped=False),
Check("adr-system-present", adr_system_present, project_scoped=True), Check("adr-system-present", adr_system_present, project_scoped=True),
Check("vault-hub-note-present", vault_hub_note_present, project_scoped=True), Check("vault-hub-note-present", vault_hub_note_present, project_scoped=True),
Check("orchestration-audit-due", orchestration_audit_due, project_scoped=False),
] ]

View File

@ -198,6 +198,38 @@ class HubNoteCheckTest(unittest.TestCase):
self.assertEqual("warn", result.status) self.assertEqual("warn", result.status)
class AuditDueCheckTest(unittest.TestCase):
def ledger_env(self, tmp, rows):
path = Path(tmp) / "metrics.tsv"
header = "date\tsince\tsessions\n"
path.write_text(header + "".join(f"{r}\t2026-01-01\t5\n" for r in rows))
return {checks.AUDIT_LEDGER_ENV: str(path)}
def test_missing_ledger_is_silent_machine_not_opted_in(self):
ctx = make_ctx(environ={checks.AUDIT_LEDGER_ENV: "/nonexistent/metrics.tsv"})
self.assertEqual("ok", checks.orchestration_audit_due(ctx).status)
def test_recent_run_is_ok(self):
with TemporaryDirectory() as tmp:
env = self.ledger_env(tmp, [date.today().isoformat()])
self.assertEqual("ok", checks.orchestration_audit_due(make_ctx(environ=env)).status)
def test_stale_run_warns_with_last_date(self):
with TemporaryDirectory() as tmp:
env = self.ledger_env(tmp, ["2026-01-01", "2026-02-01"])
result = checks.orchestration_audit_due(make_ctx(environ=env))
self.assertEqual("warn", result.status)
self.assertIn("2026-02-01", result.message)
self.assertIn("/os-orchestration:audit-sessions", result.message)
def test_corrupt_ledger_reads_as_missing(self):
with TemporaryDirectory() as tmp:
path = Path(tmp) / "metrics.tsv"
path.write_text("header only\nnot-a-date\tx\n")
env = {checks.AUDIT_LEDGER_ENV: str(path)}
self.assertEqual("ok", checks.orchestration_audit_due(make_ctx(environ=env)).status)
class RunnerTest(unittest.TestCase): class RunnerTest(unittest.TestCase):
"""run() contract: routing, aggregation, snooze/suppress, isolation.""" """run() contract: routing, aggregation, snooze/suppress, isolation."""