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
explicit `model: "sonnet"` pins on general-purpose agents (env override unset — distinct
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);
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
import json
import os
import re
from dataclasses import dataclass
from datetime import date
from pathlib import Path
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)
class Check:
name: str
@ -171,4 +206,5 @@ REGISTRY = [
Check("subagent-model-env-override", subagent_model_env_override, project_scoped=False),
Check("adr-system-present", adr_system_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)
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):
"""run() contract: routing, aggregation, snooze/suppress, isolation."""