plugins/os-vault/eval/ — held-out unprompted vault-write discrimination eval: ambiguity ladder L1 explicit -> L3 conceptual, paired positives/negatives, 6 run-set scenarios on a new reportgen Ruby fixture + 6 frozen reserve twins, isolated sandbox vault, headless-only runner, deterministic-first Ruby checker (narrow judge fallback stubbable via OS_VAULT_EVAL_JUDGE_CMD), model-free self-test 21/21. Scenario Task blocks are held-out; reserve never read. Isolation seam: OS_VAULT_PATH overrides vault_path via config.load_config(); OS_VAULT_SKIP_REBUILD suppresses the SessionStart graphify rebuild. Write SKILL.md contract fix (defers to vault-conventions.md) + Vault location section. Baseline grid (run-set x sonnet/haiku x 3 reps + 1 counted canary, 37 reps): positives 1/19, negatives 18/18. L1 persists every rep but routes to built-in auto-memory instead of the vault; L2/L3 mostly don't persist; zero over-triggering. Wording loop (step 4, not started) targets: trigger at L2/L3, route to vault at L1. Loop-input candidates: vault note os-vault-write-eval-baseline-grid-results. CLAUDE.md pointers updated for this and os-status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| bin | ||
| fixture | ||
| results | ||
| scenarios | ||
| scenarios-reserve | ||
| README.md | ||
| judge-rubric.md | ||
README.md
os-vault write-behavior eval — unprompted vault-write discrimination (held-out)
Last updated: 2026-07-06 — harness built (WS2); baseline grid NOT yet run.
Measures whether the model unprompted (a) writes evergreen cross-project knowledge to
the SecondBrain vault when it surfaces during normal work, and (b) writes it with
contract-conforming frontmatter — and equally, whether it correctly does NOT write
ephemera, project-local detail, or repo-recorded decisions to the vault. Applies the os-adr
Eval B/C playbook (ambiguity ladder, paired positives/negatives, run-set + frozen reserve).
Plan: docs/plans/ws2-os-vault-write-eval.md.
HELD-OUT — read this first. The
## Taskblocks inscenarios/*.mdandscenarios-reserve/*.mdmust never be pasted into an interactive session or informally "tried out". The only permitted non-grid execution isbin/self-test, which fabricates transcripts and never uses the Task blocks. The reserve set is additionally never read informally — it becomes the measurement set the moment wording is tuned against the run-set.
What the baseline measures
At baseline the shipped os-vault wording has no trigger-conditioned "when to write" phrasing anywhere (WS2 defect 2, deliberately left unfixed until after the baseline). Expected shape: positives likely FAIL axis (a) at both tiers (that gap IS the measurement); negatives should PASS (nothing currently pushes the model to write). The post-baseline wording loop then tunes trigger wording (skill description/body, hook note, optionally a CLAUDE.md section template mirroring os-adr's) against the run-set only.
Design: ambiguity ladder with paired positive/negative
| Level | Positive (should write/offer) | Negative (should NOT write) |
|---|---|---|
| L1 explicit | P1: durable Finch API fact + explicit "worth remembering" cue | N1: explicit "make a note" cue, but in-progress refactor status (ephemeral) |
| L2 moderate | P2: durable Finch OAuth fact stated as debugging context, no cue | N2: same framing, but the quirk is this repo's own legacy config (project-local) |
| L3 conceptual | P3: client-standing Acme convention mentioned in passing | N3: convention-shaped knowledge already recorded in the repo's ADR (docs/adr/0002) |
Negatives are equally tempting by construction (explicit note cue / discovery framing /
convention shape) — an always-write model aces positives and fails all negatives; a
never-write model does the reverse. Reserve set (P4–P6/N4–N6, scenarios-reserve/) mirrors
the ladder in a different knowledge domain: chartkit tool behavior + Globex delivery
conventions + the SFTP ADR (docs/adr/0003).
Fixture
fixture/project/—reportgen, a small Ruby CLI that pulls data from the fictional Finch API and renders/delivers client reports. Modules are rigged so every scenario's premise is visible in code (fetcher without pagination, expires_in-trusting OAuth client, legacy alias block in config/schema.yml, raw-UTC renderer, non-streaming chartkit usage, emailing delivery, hardcoded staging bucket, half-done ExportWriter refactor). Has a 3-ADRdocs/adr/history generated with os-adr's own CLIs (0002 UTC-storage and 0003 SFTP-delivery are the N3/N6 targets).CLAUDE.mdis deliberately plain — no memory or vault trigger wording (that is the tuning surface under test, absent at baseline).fixture/vault/— the isolated SecondBrain stand-in: a verbatim frozen copy of the realvault-conventions.mdplus four seeded notes (different client, different project, unrelated tools) so the vault looks lived-in, and an emptyjournal/. The checker treats the fixture vault as the manifest: anything new or content-changed in a sandbox vault (outsidejournal/andgraphify-out/) is a vault write.
Isolation (do not skip)
The runner sets, for every rep:
OS_VAULT_PATH=<sandbox>/vault— honored by the write skill ("Vault location") and by every os-vault hook viaconfig.load_config(), so the session-end journal, vault sync (no-ops: sandbox vault has no.git) and any vault reads all hit the sandbox vault.OS_VAULT_SKIP_REBUILD=1— suppresses the SessionStart detached graphify/ollama rebuild (pointless and slow against a throwaway vault).
The checker additionally scans every transcript for tool writes under
~/Documents/SecondBrain and fails the rep loudly (real-vault-breach) if the model wrote
to the real vault despite the override — check for and revert any such write immediately
(the vault_sync hook auto-commits the real vault at SessionEnd).
Two-axis scoring
- Axis (a) — write trigger (positives): mechanical PASS iff a new or content-modified
non-journal note in the sandbox vault matches the scenario's topic regexes; else the
frozen
judge-rubric.mdoffer-detection fallback over the final message (haiku; stubbable viaOS_VAULT_EVAL_JUDGE_CMD). TSV recordsA-via:written|offered. - Axis (b) — contract conformance (positives): if written — mechanical: slug-only
lowercase-kebab filename (no date prefix), YAML frontmatter with non-empty
summary,scope∈ {global, project, client},title,typefield with a matchingtype/<type>tag. Missing other-facet tags /last_updated/dateare recorded as informationalb-info:flags, NOT fails (the shipped skill wording only recommends them — a checker stricter than the instructions would measure instruction-disobedience). If offer-only:B-via:offered(nothing written to check). - Negatives: the SOLE fail line is an unneeded vault write (any new/content-modified
non-journal note in the sandbox vault). Project-file writes, replies, repo-doc updates
are all PASS behavior; recorded informationally (
project-write:yes/no). - Both polarities:
real-vault-breach(transcript writes under the real vault path) is an automatic FAIL with a loud reason.
Overall PASS — positives: both axes; negatives: no vault write (+ no breach).
TSV: scenario model A:... B:... PASS|FAIL reasons
Running
# one cell (canary first — mandatory for the first-ever live run; count its result)
bin/run P1-L1-reporting sonnet /tmp/vault-eval --reps 1
cat /tmp/vault-eval/results.tsv
# full baseline grid: run-set × {sonnet, haiku} × 3 reps, parallel
for s in P1-L1-reporting N1-L1-reporting P2-L2-reporting N2-L2-reporting P3-L3-reporting N3-L3-reporting; do
for m in sonnet haiku; do
bin/run "$s" "$m" /tmp/vault-eval-grid --reps 3 --results /tmp/vault-eval-grid/results.tsv &
done
done
wait
Each rep: fresh sandbox (project git-inited + pristine vault copy) → claude -p --model <tier> --output-format stream-json --verbose --dangerously-skip-permissions with
cwd = sandbox project and the isolation env set → transcript.jsonl → bin/check appends
one TSV row (guarded with || true; check row counts == expected reps before reading
results — the Eval C pipefail lesson).
Before the first grid (mandatory discipline gates, from the WS2 plan)
- Conformance dry-run on paper (done at design time; recorded in scenario files): a perfectly write-skill-compliant model passes every negative; an always-write model passes positives and fails negatives.
bin/self-testgreen — model-free, fabricates transcripts/sandboxes both directions, including a shipped-instruction-compliant positive (offer-only with stubbed judge) and a journal-only negative (must PASS — journal writes are hook-automatic).- Canary cell first; hand-verify its TSV row against the raw transcript; count it.
bin/refresh-pluginsbefore every grid; verify caches carry the OS_VAULT_PATH-aware skill wording.
Layout
| Path | What |
|---|---|
fixture/project/ |
reportgen Ruby CLI + 3-ADR history + plain CLAUDE.md |
fixture/vault/ |
isolated vault: frozen vault-conventions.md + 4 seeded notes + journal/ |
scenarios/ / scenarios-reserve/ |
run-set / frozen reserve (6 + 6, held-out Task blocks) |
bin/sandbox <scenario> <dest> |
fresh sandbox: git-inited project copy + pristine vault copy |
bin/run <scenario> <model> <workdir> [--reps N] [--results F] |
headless runner (the ONLY valid execution mode) |
bin/check <scenario> <sandbox> [--tsv <model>] |
two-axis checker; exit 0/1; TSV mode |
bin/self-test [workdir] |
model-free both-directions harness validation |
judge-rubric.md |
frozen offer-detection rubric (positive axis (a) fallback only) |
Why headless-only
This eval measures unprompted behavior: each rep needs a fresh session where the real
SessionStart hooks fire and the os-vault skill descriptions are loaded exactly as in
production. In-session Agent-tool subagents inherit the parent session and are invalid here
(same rule as os-adr Eval B/C; see
~/Documents/SecondBrain/howto/running-autoresearch-skill-evals.md).
After the baseline
Results go to a vault eval-results note (template _templates/eval-results.md) and the
cc-os CLAUDE.md pointer list. If the gap is confirmed, the /autoresearch wording loop runs
per the howto note: checker/fixtures/scenarios/rubric frozen, wording only, reduced inner
grid, full grid to confirm — and the run-set is thereafter contaminated; measurement moves
to the reserve set.