Skip to content

fix(state): keep a named root memory file's evidence and proposal in its own state dir - #214

Open
Pantoria wants to merge 5 commits into
kunchenguid:mainfrom
Pantoria:fix/189-per-target-evidence-cache
Open

Pantoria wants to merge 5 commits into
kunchenguid:mainfrom
Pantoria:fix/189-per-target-evidence-cache

Conversation

@Pantoria

@Pantoria Pantoria commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Fix issue #189: the evidence cache held one record per transcript (evidence/.json) and one proposal.json, so alternating --memory-file / --target between different root memory files (A, then B, then A) overwrote evidence and re-analyzed every transcript though nothing changed, and B's run wiped A's pending proposal. The user (issue author, Pantoria) asked to implement their own ready-for-pr backpass issues, each in its own worktree and PR. Follow AGENTS.md invariants and VISION.md. No live model calls were made while implementing.
Deliberate decisions: per-target state directory (mirroring nestedStateDir) rather than keying the evidence path on identity+hash, because only that also fixes the single proposal.json. Runs whose resolved primary root file differs from the default run's file keep evidence/, evidence-summary.json, proposal.json, prompts/, synthesis/, raw/, apply/ under /targets/-/; --memory-file X and --target X share one dir. A default run (and --memory-file/--target naming the default file, and skill --target) keeps .backpass/ exactly as today - the maintainer required that. Rejections, gap ledger, scan cache, and probe cache stay shared via the root State (maintainer asked rejections be shared; the ledger is already scoped by memoryPath). User scope target dirs live under the user state root with mode 0700, never in the repo. apply/status/analyze with --memory-file X read X's state; bare apply and status list pending target proposals with the exact command. AGENTS.md got a one-clause update under 'Scopes and targets' to record the new layout. Known, accepted limitations: one-time re-analysis on upgrade for users who already used non-default --memory-file (no migration); skill --target still shares the default proposal.json (out of scope). Maintainer wants a test that fails if the per-surface path is removed (A,B,A with no re-analysis of A) - included in test/target-state.test.js - and live evidence of a real A,B,A --memory-file run showing reuse.

What Changed

  • A run that names a root memory file other than the default one (--memory-file X or a memory --target X) now keeps its evidence, evidence summary, proposal, prompts, synthesis staging, raw, and apply surface under <state root>/targets/<file>-<sha8>/ (surfaceState in src/target.js, targetStateDir in src/state.js). Training A, then B, then A no longer re-analyzes unchanged transcripts or overwrites the other file's pending proposal. Default runs, runs that name the default file, and skill targets still use .backpass/ exactly as before. User-scope target dirs sit under the user state root with mode 0700.
  • Target states share the scope root's scan cache, rejections, gap ledger, and probe cache. Raw-file reclamation and the host-cache prune now run against the scope root (state.scopeRoot), so they also cover targets/.
  • apply, status, propose, analyze, and full-run hints now show the exact command for the state being read, e.g. backpass apply --memory-file X (with --scope user when needed). Bare apply and status also list pending target proposals that are unapplied and passed their gates. README and AGENTS.md describe the new layout, and test/target-state.test.js covers it, including an A, B, A run where A's evidence is reused without re-analysis.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The fix round fully addresses both earlier findings: every apply/status hint is now built from the state being read, so a target state gives --memory-file <file>, and bare apply also lists waiting target proposals when the default proposal is already applied or failed its gates. I traced the default, target, skill-target and user-scope cases and found no wrong output or behavior change for default runs.

Testing

Ran the real CLI in throwaway repos with an isolated HOME and a stand-in acpx/pi harness, so no real model was called. Covered: the A,B,A --memory-file runs, compared with the same runs on the base commit; the default layout; --target and --memory-file sharing one state; the status and apply hints; review-2's bare-apply listing; the fixed synthesis-failure hint in both the propose and full-run paths; and user-scope permissions. Every live-driven scenario passed. The targeted regression test file was also run, but it is not a live run, so its scenario is recorded as untested. The gate-failed and bootstrap-applied states for review-2 were seeded by editing the disposable proposal.json, because the stand-in harness can't produce them reliably. This is a CLI-only change, so the evidence is CLI transcripts rather than screenshots.

  • Live validation: ✅ go - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Training AGENTS.md, then CLAUDE.md, then AGENTS.md (and CLAUDE.md) with --memory-file reuses each file's evidence: the 3rd/4th runs show '0 new · 3 cached' and make zero analysis calls ✅ pass live r2-aba-memory-file.txt
Before/after: the same A,B,A sequence on base commit e6a030a re-analyzes all 3 transcripts every run ✅ pass live r2-aba-base-commit.txt
B's run no longer wipes A's pending proposal: .backpass/proposal.json (AGENTS.md) and .backpass/targets/CLAUDE.md-6ebdb617/proposal.json (CLAUDE.md) both exist ✅ pass live r2-aba-memory-file.txt (state layout section)
A default bare run keeps the .backpass/ layout unchanged and creates no targets/ directory ✅ pass live r2-default-layout-and-failure-hints.txt
--target CLAUDE.md shares --memory-file CLAUDE.md's state: analyze reports 0 new · 3 cached ✅ pass live r2-status-apply-hints.txt
Bare status lists the waiting CLAUDE.md proposal with backpass apply --memory-file CLAUDE.md; status --memory-file CLAUDE.md reads CLAUDE.md's state ✅ pass live r2-status-apply-hints.txt
review-1: apply --target CLAUDE.md on a --memory-file proposal suggests backpass apply --memory-file CLAUDE.md; apply --memory-file writes only CLAUDE.md; bare apply writes only AGENTS.md ✅ pass live r2-status-apply-hints.txt
review-2: bare apply with a gate-failed, bootstrap-applied, or missing default proposal names the waiting backpass apply --memory-file CLAUDE.md (gate-failed and applied states seeded in proposal.js… ✅ pass live r2-bare-apply-lists-targets.txt
Adversarial: synthesis fails in CLAUDE.md's state (both propose --memory-file and the full --target run) and the hint says backpass propose --memory-file CLAUDE.md; in the default state it stays… ✅ pass live r2-default-layout-and-failure-hints.txt
User scope: A,B,A on .claude/CLAUDE.md and .codex/AGENTS.md reuses evidence; target dirs are mode 700 under ~/.config/backpass/user; repo gets no .backpass ✅ pass live r2-user-scope-aba.txt
Maintainer-requested regression test (A,B,A with no re-analysis, plus the hint tests) passes ⏸️ untested no The earlier payload ran this only as a node --test test/target-state.test.js unit/integration test, not live against the running product, so no live result was recorded. The same A,B,A behavior was…
Evidence: A,B,A,B --memory-file runs on fixed source (reuse + two proposals)
=== A (AGENTS.md), B (CLAUDE.md), A, B via --memory-file (src: ~/.no-mistakes/worktrees/8ce6865cb38e/01M503TV5DA5S5E2XH135BV7XX) ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file AGENTS.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· AGENTS.md: [................................] 15 / 5,000 tok · 1 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· CLAUDE.md: [................................] 21 / 5,000 tok · 2 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply --memory-file CLAUDE.md` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file AGENTS.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· AGENTS.md: [................................] 15 / 5,000 tok · 1 instructions
· evidence: 0 new · 3 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 2 ( 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· CLAUDE.md: [................................] 21 / 5,000 tok · 2 instructions
· evidence: 0 new · 3 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-2 tokens: not reported by pi

Review and apply with `backpass apply --memory-file CLAUDE.md` (nothing has been written).
[exit 0]
  model calls: 2 ( 1 "turn":"annotate"; 1 "turn":"edit";)

=== state layout ===
.backpass/evidence
.backpass/proposal.json
.backpass/targets
.backpass/targets/CLAUDE.md-6ebdb617
.backpass/targets/CLAUDE.md-6ebdb617/evidence
.backpass/targets/CLAUDE.md-6ebdb617/proposal.json
.backpass/proposal.json -> {"files":["AGENTS.md"],"appliedAt":null}
.backpass/targets/CLAUDE.md-6ebdb617/proposal.json -> {"files":["CLAUDE.md"],"appliedAt":null}
Evidence: Same A,B,A,B on base commit (bug reproduced: always re-analyzes, one proposal)
=== A (AGENTS.md), B (CLAUDE.md), A, B via --memory-file (src: /tmp/bp-live2/base) ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file AGENTS.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· AGENTS.md: [................................] 15 / 5,000 tok · 1 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· 3 transcript(s) have evidence from a previous memory surface (sha256:3f8e98e4730414f6 -> sha256:55cb399b3f0f7de7); that evidence is stale, not missing, and reuse resumes once this pass re-judges it against the current memory file and skill descriptions
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· CLAUDE.md: [................................] 21 / 5,000 tok · 2 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file AGENTS.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· 3 transcript(s) have evidence from a previous memory surface (sha256:55cb399b3f0f7de7 -> sha256:3f8e98e4730414f6); that evidence is stale, not missing, and reuse resumes once this pass re-judges it against the current memory file and skill descriptions
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· AGENTS.md: [................................] 15 / 5,000 tok · 1 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md
backpass v0.1.34 · repo · budget 5,000 tok · since all
· 3 transcript(s) have evidence from a previous memory surface (sha256:3f8e98e4730414f6 -> sha256:55cb399b3f0f7de7); that evidence is stale, not missing, and reuse resumes once this pass re-judges it against the current memory file and skill descriptions
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed
· CLAUDE.md: [................................] 21 / 5,000 tok · 2 instructions
· evidence: 3 new · 0 cached · 0 too short · 0 failed
· synthesizing with pi effort=high

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)

=== state layout ===
.backpass/evidence
.backpass/proposal.json
.backpass/synthesis/CLAUDE.md
.backpass/proposal.json -> {"files":["CLAUDE.md"],"appliedAt":null}
Evidence: status/apply hints and --target sharing
=== --target CLAUDE.md shares --memory-file CLAUDE.md's evidence ===

$ backpass analyze --harness pi --since all --analysis-agent pi --jobs 1 --target CLAUDE.md
targeting CLAUDE.md; existing skills are read-only this run

analyzed against CLAUDE.md (2 instructions, 21 tok)
  0 newly analyzed · 3 cached · 0 skipped (too short) · 0 failed
[exit 0]
  model calls: 0 ()
=== bare status lists the waiting CLAUDE.md proposal ===

$ backpass status
repo /tmp/bp-live2/w1/repo

BUDGET (always-loaded)
  AGENTS.md      [................................] 15 / 5,000 tok · 1 instructions
  CLAUDE.md      [................................] 21 / 5,000 tok · 2 instructions separate - not optimized

CACHE
  scan cache      3 file(s) fingerprinted
  evidence        3 ok · 0 skipped · 0 failed
  gradients       3 session(s) · 0+ 0- · 1 gap cluster(s)
  rejections      0 remembered

PROPOSAL
  generated       2026-10-15T15:54:43.460Z
  edits           1
  review with `backpass apply`
  CLAUDE.md       1 edit(s) · review with `backpass apply --memory-file CLAUDE.md`

MODELS
  analysis        auto - 7 candidates, none probed yet
  synthesis       auto - 7 candidates, none probed yet
[exit 0]
  model calls: 0 ()
=== status --memory-file CLAUDE.md ===

$ backpass status --memory-file CLAUDE.md
repo /tmp/bp-live2/w1/repo

BUDGET (always-loaded)
  CLAUDE.md      [................................] 21 / 5,000 tok · 2 instructions

CACHE
  scan cache      3 file(s) fingerprinted
  evidence        3 ok · 0 skipped · 0 failed
  gradients       3 session(s) · 0+ 0- · 1 gap cluster(s)
  rejections      0 remembered

PROPOSAL
  generated       2026-10-15T15:54:43.755Z
  edits           1
  review with `backpass apply --memory-file CLAUDE.md`

MODELS
  analysis        auto - 7 candidates, none probed yet
  synthesis       auto - 7 candidates, none probed yet
[exit 0]
  model calls: 0 ()
=== review-1: apply --target CLAUDE.md on a --memory-file proposal: hint names --memory-file ===

$ backpass apply --target CLAUDE.md --dry-run --decisions e1=accepted
targeting CLAUDE.md; existing skills are read-only this run
error this proposal targets the whole surface, not CLAUDE.md
  apply it with `backpass apply --memory-file CLAUDE.md`, or run backpass again with the target you want
[exit 1]
  model calls: 0 ()
=== apply --memory-file CLAUDE.md writes only CLAUDE.md ===

$ backpass apply --memory-file CLAUDE.md --decisions e1=accepted

1 accepted · 0 rejected
  corpus interactive 3 · non-interactive 0
  wrote CLAUDE.md (e1)
    budget [................................] 21 -> 30 / 5,000 tok
[exit 0]
  model calls: 0 ()
 M CLAUDE.md
=== re-apply CLAUDE.md: hint names its own state ===

$ backpass apply --memory-file CLAUDE.md --decisions e1=accepted

1 accepted · 0 rejected
  corpus interactive 3 · non-interactive 0
  failed CLAUDE.md: CLAUDE.md changed after this proposal was made (sha256:acbc7a8b5380049b -> sha256:61112bc169652f6c), so its edits no longer describe the file on disk; nothing was written. Run `backpass` to re-propose against the current CLAUDE.md - that pass reanalyzes transcripts against the new file, it does not reuse the stale judgments behind this proposal.
  nothing written
[exit 1]
  model calls: 0 ()
=== bare apply: applies AGENTS.md default proposal ===

$ backpass apply --decisions e1=accepted

1 accepted · 0 rejected
  corpus interactive 3 · non-interactive 0
  wrote AGENTS.md (e1)
    budget [................................] 15 -> 24 / 5,000 tok
[exit 0]
  model calls: 0 ()
 M AGENTS.md
 M CLAUDE.md
=== retrain CLAUDE.md so a target proposal is pending, then bare apply on applied default (review-2) ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md -q
warn synthesis violated 1 gate(s); re-prompting with the exact violations
warn synthesis violated 1 gate(s); re-prompting with the exact violations
error synthesis could not produce a valid proposal after 2 re-prompt(s) (1 violation(s))
  the gates above are what the next synthesis must satisfy; run `backpass propose --memory-file CLAUDE.md` again
[exit 1]
  model calls: 7 ( 3 "turn":"analysis"; 3 "turn":"annotate"; 1 "turn":"edit";)

$ backpass apply --decisions e1=accepted

1 accepted · 0 rejected
  corpus interactive 3 · non-interactive 0
  failed AGENTS.md: AGENTS.md changed after this proposal was made (sha256:0473d7272a6a8f38 -> sha256:700c2659c1c39829), so its edits no longer describe the file on disk; nothing was written. Run `backpass` to re-propose against the current AGENTS.md - that pass reanalyzes transcripts against the new file, it does not reuse the stale judgments behind this proposal.
  nothing written
[exit 1]
  model calls: 0 ()
Evidence: Default layout + synthesis-failure hints (target vs default)
=== fresh repo: default (bare) run keeps .backpass layout, no targets/ ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 -q
warn CLAUDE.md is a separate memory file and will NOT be updated - only AGENTS.md is optimized. To cover both, consolidate: move its content into AGENTS.md and make CLAUDE.md a pointer (a single line: @AGENTS.md).

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)
.backpass
.backpass/apply
.backpass/evidence
.backpass/evidence-summary.json
.backpass/gap-ledger.json
.backpass/prompts
.backpass/proposal.json
.backpass/scan-cache.json
.backpass/synthesis
no targets/ dir
=== --memory-file AGENTS.md (default file named) reuses .backpass evidence ===

$ backpass analyze --harness pi --since all --analysis-agent pi --jobs 1 --memory-file AGENTS.md
· 3 transcript(s) have evidence from a previous memory surface (sha256:65d9990c3ffbfdc9 -> sha256:3f8e98e4730414f6); that evidence is stale, not missing, and reuse resumes once this pass re-judges it against the current memory file and skill descriptions
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed

analyzed against AGENTS.md (1 instructions, 15 tok)
  3 newly analyzed · 0 cached · 0 skipped (too short) · 0 failed

  tier-1 tokens: not reported by pi
[exit 0]
  model calls: 3 ( 3 "turn":"analysis";)
=== train CLAUDE.md with --target ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --target CLAUDE.md -q

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-1 tokens: not reported by pi
  tier-2 tokens: not reported by pi

Review and apply with `backpass apply --memory-file CLAUDE.md` (nothing has been written).
[exit 0]
  model calls: 5 ( 3 "turn":"analysis"; 1 "turn":"annotate"; 1 "turn":"edit";)
=== adversarial: synthesis returns no text in CLAUDE.md's state (propose subcommand) ===

$ backpass propose --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md
· synthesizing with pi effort=high

  x the synthesis edit turn left the staging copy byte-identical to the original, so its first annotate turn had nothing to describe

  no proposal was saved: no annotation turn produced one
error synthesis made no changes to the staging copy during the edit turn, so its first annotate turn had nothing to describe
  the edit turn made no changes to the staging copy, so there was nothing for the annotation turn to describe; run `backpass propose --memory-file CLAUDE.md` again, or pin a different harness with --synthesis-agent
[exit 1]
  model calls: 2 ( 2 "turn":"synth-empty";)
=== adversarial: same via the full run with --target ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --target CLAUDE.md -q
error synthesis made no changes to the staging copy during the edit turn, so its first annotate turn had nothing to describe
  the edit turn made no changes to the staging copy, so there was nothing for the annotation turn to describe; run `backpass propose --memory-file CLAUDE.md` again, or pin a different harness with --synthesis-agent
[exit 1]
  model calls: 2 ( 2 "turn":"synth-empty";)
=== control: default-state failure keeps bare 'backpass propose' ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 -q
warn CLAUDE.md is a separate memory file and will NOT be updated - only AGENTS.md is optimized. To cover both, consolidate: move its content into AGENTS.md and make CLAUDE.md a pointer (a single line: @AGENTS.md).
error synthesis made no changes to the staging copy during the edit turn, so its first annotate turn had nothing to describe
  the edit turn made no changes to the staging copy, so there was nothing for the annotation turn to describe; run `backpass propose` again, or pin a different harness with --synthesis-agent
[exit 1]
  model calls: 5 ( 3 "turn":"analysis"; 2 "turn":"synth-empty";)
=== retrain CLAUDE.md OK, then make default proposal fail gates; bare apply (review-2) ===

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 --memory-file CLAUDE.md -q

proposal · repo · CLAUDE.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 21 -> 30 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (CLAUDE.md) (+9 tok, 3 transcript(s))

  tier-2 tokens: not reported by pi

Review and apply with `backpass apply --memory-file CLAUDE.md` (nothing has been written).
[exit 0]
  model calls: 2 ( 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass --harness pi --since all --analysis-agent pi --synthesis-agent pi --jobs 1 -q
warn CLAUDE.md is a separate memory file and will NOT be updated - only AGENTS.md is optimized. To cover both, consolidate: move its content into AGENTS.md and make CLAUDE.md a pointer (a single line: @AGENTS.md).

proposal · repo · AGENTS.md · 1 edit(s) from 3 session(s) · interactive 3 · non-interactive 0
  budget [................................] 15 -> 24 / 5,000 tok
  evidence: 0 positive · 0 negative · 1 gap clusters

  e1 ADD      Add: Always run lint before pushing. (AGENTS.md) (+9 tok, 3 transcript(s))

  tier-2 tokens: not reported by pi

Review and apply with `backpass apply` (nothing has been written).
[exit 0]
  model calls: 2 ( 1 "turn":"annotate"; 1 "turn":"edit";)

$ backpass apply --decisions e1=accepted

1 accepted · 0 rejected
  corpus interactive 3 · non-interactive 0
  wrote AGENTS.md (e1)
    budget [................................] 15 -> 24 / 5,000 tok
[exit 0]
  model calls: 0 ()
Evidence: Bare apply lists waiting target proposals
=== seeded: default proposal.json carries violations (as a gate-failed synthesis saves it); CLAUDE.md target proposal pending ===

$ backpass apply --decisions e1=accepted
error the saved proposal failed its mechanical gates and was never approved for apply
  run `backpass propose` again; proposals for other memory files are waiting: `backpass apply --memory-file CLAUDE.md`
[exit 1]
  model calls: 0 ()
=== seeded: default proposal.json has appliedAt (as bootstrap records it) ===

$ backpass apply --decisions e1=accepted
error the last proposal was already applied by bootstrap (2026-10-15T00:00:00Z)
  run `backpass` again to produce a fresh one; proposals for other memory files are waiting: `backpass apply --memory-file CLAUDE.md`
[exit 1]
  model calls: 0 ()
=== no default proposal at all ===

$ backpass apply --decisions e1=accepted
error no proposal to apply
  proposals for other memory files are waiting: `backpass apply --memory-file CLAUDE.md`
[exit 1]
  model calls: 0 ()
Evidence: User scope A,B,A and 0700 target dirs
=== user scope A, B, A ===

$ backpass analyze --scope user --harness pi --since all --analysis-agent pi --jobs 1 --memory-file .claude/CLAUDE.md
user scope: this checkout is not a write target; edits go to the user-level memory file and skills
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed

analyzed against .claude/CLAUDE.md (1 instructions, 10 tok)
  3 newly analyzed · 0 cached · 0 skipped (too short) · 0 failed

  tier-1 tokens: not reported by pi
[exit 0]
  model calls: 3 ( 3 "turn":"analysis";)

$ backpass analyze --scope user --harness pi --since all --analysis-agent pi --jobs 1 --memory-file .codex/AGENTS.md
user scope: this checkout is not a write target; edits go to the user-level memory file and skills
· analyzing 3 transcript(s) with pi effort=medium at jobs=1
  3/3 analyzed

analyzed against .codex/AGENTS.md (2 instructions, 14 tok)
  3 newly analyzed · 0 cached · 0 skipped (too short) · 0 failed

  tier-1 tokens: not reported by pi
[exit 0]
  model calls: 3 ( 3 "turn":"analysis";)

$ backpass analyze --scope user --harness pi --since all --analysis-agent pi --jobs 1 --memory-file .claude/CLAUDE.md
user scope: this checkout is not a write target; edits go to the user-level memory file and skills

analyzed against .claude/CLAUDE.md (1 instructions, 10 tok)
  0 newly analyzed · 3 cached · 0 skipped (too short) · 0 failed
[exit 0]
  model calls: 0 ()

$ backpass analyze --scope user --harness pi --since all --analysis-agent pi --jobs 1 --memory-file .codex/AGENTS.md
user scope: this checkout is not a write target; edits go to the user-level memory file and skills

analyzed against .codex/AGENTS.md (2 instructions, 14 tok)
  0 newly analyzed · 3 cached · 0 skipped (too short) · 0 failed
[exit 0]
  model calls: 0 ()
=== user state targets (mode) ===
700 /tmp/bp-live2/w5/home/.config/backpass/user/targets
700 /tmp/bp-live2/w5/home/.config/backpass/user/targets/.codex_AGENTS.md-ee83867a
=== repo has no .backpass: ===
.git
AGENTS.md
CLAUDE.md
Evidence: target-state.test.js run
✔ training two root files in turn reuses each one's evidence: A, then B, then A analyzes nothing again (1322.730952ms)
✔ a default run keeps .backpass/ exactly as before, and naming the default file changes nothing (637.149773ms)
✔ apply reads the named file's proposal, and a bare apply names the proposals it is not reading (559.14991ms)
✔ a target state shares the scope-level files and lives under the scope's own state root (7.423003ms)
✔ a synthesis that fails in a target state says to re-run the target's own command (691.017704ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3311.393942
  • Evidence: Driver scripts and stand-in acpx (local file: ~/.no-mistakes/evidence/01M503TV5DA5S5E2XH135BV7XX/r2-driver)
Evidence: Synthesis-failure hint in target state
$ backpass propose ... --memory-file CLAUDE.md
error synthesis made no changes to the staging copy during the edit turn, ...
...; run `backpass propose --memory-file CLAUDE.md` again, or pin a different harness with --synthesis-agent
- Outcome: 🔧 3 issues found → auto-fixed ✅ across 2 runs (7m46s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ src/commands/apply.js:93 - --memory-file X and --target X share one state directory, but apply's hints still assume a single shared proposal. Example: a user runs backpass --memory-file CLAUDE.md (saved target is surface) and then backpass apply --target CLAUDE.md. That opens CLAUDE.md's target state, fails the sameTarget check, and suggests "apply it without --target". A bare backpass apply then reads the default AGENTS.md proposal, not the CLAUDE.md one. Before this change both commands read the same proposal.json, so the hint was correct. Fix: build the hint from applyCommand(config.state, ctx.scope). Other hints in a target state still point at the default state and should get the same treatment: apply.js:~107 "run backpass again to produce a fresh one", apply.js:~101 "run backpass propose again", and status.js:181 "none yet - run backpass". Each should name --memory-file &lt;surface&gt; when state.surface is set.
  • ℹ️ src/commands/apply.js:75 - Bare backpass apply lists pending target proposals only when no default proposal.json exists. Once the default proposal has been applied (appliedAt set) or failed its gates, apply throws the 'already applied' or 'failed its mechanical gates' error and does not mention a waiting targets/ proposal. The intent says bare apply lists pending target proposals with the exact command. Consider adding the listTargetProposals() hint to those two errors as well. status already lists them unconditionally.

🔧 Fix applied.
✅ Re-checked - no issues remain.

🔧 **Test** - 3 issues found → auto-fixed ✅
  • ⚠️ src/commands/propose.js:334 - In a target state, synthesis-failure hints still say to run bare backpass propose again (propose.js:316, 319, 322, 325, 334). Repro: backpass --target CLAUDE.md (or --memory-file CLAUDE.md) where synthesis fails its gates prints "run backpass propose again". Running that command proposes for the default AGENTS.md, not CLAUDE.md. This is the same kind of misleading hint review-1 asked to fix in apply/status. Fix: build these hints with stateCommand(&#34;propose&#34;, config.state, ctx.scope).
  • ℹ️ src/target.js:126 - In a repo with a separate CLAUDE.md, a bare default run and --memory-file AGENTS.md share .backpass/ (as designed) but have different memory-set hashes, so switching between them still re-analyzes every transcript. This matches base behavior and the stated design (naming the default file keeps the default state); noted for awareness only.
  • 🚨 live validation verdict: no-go (11 of 11 scenarios were driven live against the product); failed: Adversarial: synthesis fails in a target state and the hint should name the target's command
  • Live validation: ❌ no-go - 11 of 11 scenarios driven live against the product
Scenario Result Live Evidence
A,B,A with --memory-file: training AGENTS.md, then CLAUDE.md, then AGENTS.md again re-analyzes nothing on the third run ✅ pass live aba-full-run.txt (runs 3 and 4: '0 new · 3 cached', zero analysis model calls); node --test test/target-state.test.js
Regression baseline: the same A,B,A sequence on the base commit re-analyzes every transcript ✅ pass live aba-base-commit-repro.txt (every run: 3 newly analyzed, with a 'stale evidence' notice)
B's run keeps A's pending proposal: .backpass/proposal.json (AGENTS.md) and targets/CLAUDE.md-<sha8>/proposal.json coexist ✅ pass live aba-full-run.txt 'proposals on disk' section
--target CLAUDE.md and --memory-file CLAUDE.md share one state directory (one reuses the other's evidence) ✅ pass live target-and-gated-default.txt (analyze --memory-file CLAUDE.md after a --target CLAUDE.md run: 0 newly analyzed · 3 cached)
status lists pending target proposals with the exact command, and status --memory-file X reads X's state ✅ pass live status-apply.txt
apply --memory-file CLAUDE.md writes only CLAUDE.md, and bare apply writes only AGENTS.md ✅ pass live status-apply.txt (file contents after each apply)
Bare apply with a gate-failed default proposal still names the waiting CLAUDE.md proposal (review-2 fix) ✅ pass live target-and-gated-default.txt: "run backpass propose again; proposals for other memory files are waiting: backpass apply --memory-file CLAUDE.md"
apply --target CLAUDE.md on a proposal made with --memory-file points to backpass apply --memory-file CLAUDE.md (review-1 fix) ✅ pass live status-apply.txt; apply-hints.txt ('run backpass propose --memory-file CLAUDE.md again')
Adversarial: synthesis fails in a target state and the hint should name the target's command ❌ fail live apply-hints.txt: backpass --target CLAUDE.md synthesis failure prints "run backpass propose again" without --memory-file CLAUDE.md
User scope: a non-default user memory file keeps its state under ~/.config/backpass/user/targets with mode 0700, never in the repo, and A,B,A reuses evidence ✅ pass live user-scope-aba.txt
A default run, and --memory-file/--target naming the default file, keep .backpass/ exactly as before (no targets/) ✅ pass live default-layout.txt; target-state.test.js test 2
  • node --test test/target-state.test.js (4/4 pass, including the A,B,A no-re-analysis regression test)
  • Full backpass --memory-file AGENTS.md / --memory-file CLAUDE.md / AGENTS.md / CLAUDE.md runs (scan→analyze→propose) in a disposable git repo with an isolated HOME and pi sessions, using a stand-in acpx at the model boundary
  • Same A,B,A backpass analyze sequence on base commit e6a030a (extracted with git archive) to reproduce the original bug
  • backpass status and backpass status --memory-file CLAUDE.md with both proposals pending
  • backpass apply --memory-file CLAUDE.md --decisions e1=accepted and bare backpass apply --decisions e1=accepted: each wrote only its own file
  • backpass --target CLAUDE.md, then backpass analyze --memory-file CLAUDE.md, to check the two flags share one state directory
  • Bare backpass apply with a gate-failed default proposal and a pending CLAUDE.md target proposal
  • backpass apply --target CLAUDE.md against a proposal made with --memory-file, plus apply --target CLAUDE.md against a --target proposal
  • User scope: backpass analyze --scope user --memory-file .claude/CLAUDE.md / .codex/AGENTS.md / .claude/CLAUDE.md, then inspected the state-dir layout and modes
  • Default-layout check: backpass analyze, then --memory-file AGENTS.md and --target AGENTS.md, then listed .backpass/

🔧 Fix applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Training AGENTS.md, then CLAUDE.md, then AGENTS.md (and CLAUDE.md) with --memory-file reuses each file's evidence: the 3rd/4th runs show '0 new · 3 cached' and make zero analysis calls ✅ pass live r2-aba-memory-file.txt
Before/after: the same A,B,A sequence on base commit e6a030a re-analyzes all 3 transcripts every run ✅ pass live r2-aba-base-commit.txt
B's run no longer wipes A's pending proposal: .backpass/proposal.json (AGENTS.md) and .backpass/targets/CLAUDE.md-6ebdb617/proposal.json (CLAUDE.md) both exist ✅ pass live r2-aba-memory-file.txt (state layout section)
A default bare run keeps the .backpass/ layout unchanged and creates no targets/ directory ✅ pass live r2-default-layout-and-failure-hints.txt
--target CLAUDE.md shares --memory-file CLAUDE.md's state: analyze reports 0 new · 3 cached ✅ pass live r2-status-apply-hints.txt
Bare status lists the waiting CLAUDE.md proposal with backpass apply --memory-file CLAUDE.md; status --memory-file CLAUDE.md reads CLAUDE.md's state ✅ pass live r2-status-apply-hints.txt
review-1: apply --target CLAUDE.md on a --memory-file proposal suggests backpass apply --memory-file CLAUDE.md; apply --memory-file writes only CLAUDE.md; bare apply writes only AGENTS.md ✅ pass live r2-status-apply-hints.txt
review-2: bare apply with a gate-failed, bootstrap-applied, or missing default proposal names the waiting backpass apply --memory-file CLAUDE.md (gate-failed and applied states seeded in proposal.js… ✅ pass live r2-bare-apply-lists-targets.txt
Adversarial: synthesis fails in CLAUDE.md's state (both propose --memory-file and the full --target run) and the hint says backpass propose --memory-file CLAUDE.md; in the default state it stays… ✅ pass live r2-default-layout-and-failure-hints.txt
User scope: A,B,A on .claude/CLAUDE.md and .codex/AGENTS.md reuses evidence; target dirs are mode 700 under ~/.config/backpass/user; repo gets no .backpass ✅ pass live r2-user-scope-aba.txt
Maintainer-requested regression test (A,B,A with no re-analysis, plus the hint tests) passes ⏸️ untested no The earlier payload ran this only as a node --test test/target-state.test.js unit/integration test, not live against the running product, so no live result was recorded. The same A,B,A behavior was…
  • Live A,B,A,B backpass --memory-file AGENTS.md|CLAUDE.md full runs on the fixed source (r2-driver/s1.sh)
  • Same A,B,A,B sequence on base commit e6a030a extracted via git archive (before/after reproduction)
  • backpass analyze --target CLAUDE.md after --memory-file CLAUDE.md runs (shared state)
  • backpass status and backpass status --memory-file CLAUDE.md
  • backpass apply --target CLAUDE.md on a --memory-file proposal, apply --memory-file CLAUDE.md, bare backpass apply (r2-driver/s2.sh)
  • Fresh repo bare run: inspect .backpass layout for absence of targets/ (r2-driver/s3.sh)
  • Adversarial: synthesis returns no text in CLAUDE.md state via backpass propose --memory-file CLAUDE.md and backpass --target CLAUDE.md; control in default state
  • Bare backpass apply with seeded gate-failed, seeded bootstrap-applied, and missing default proposal while a CLAUDE.md proposal is pending (r2-driver/s4.sh)
  • User scope backpass analyze --scope user --memory-file .claude/CLAUDE.md|.codex/AGENTS.md A,B,A,B plus mode/location check (r2-driver/s5.sh)
  • node --test test/target-state.test.js
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Zexuan Liu and others added 3 commits October 15, 2026 11:27
…its own state dir

Evidence was cached one record per transcript in `.backpass/evidence/`, so training two
root memory files in turn with `--memory-file` (or a memory `--target`) overwrote one
file's evidence and proposal with the other's, and switching back re-analyzed every
transcript though nothing had changed.

A run whose trained root file is not the one a default run would train now keeps its
per-surface files (evidence/, evidence-summary.json, proposal.json, prompts/, synthesis/,
raw/, apply/) in `.backpass/targets/<file>-<hash>/`, keyed on the resolved primary path so
`--memory-file X` and `--target X` share one directory. The scan cache, rejections, gap
ledger (already keyed by memory path), probe cache, and fetched host transcripts stay
shared in the scope root. A default run's layout is unchanged, and user-scope target
state stays inside the user state directory.

`backpass apply --memory-file X` reads X's proposal; the propose/run hint names that
command, and a bare `apply` or `status` lists target proposals still waiting.

Fixes kunchenguid#189

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with a non-blocking issue in how overlapping reviews update the waiting list.

Reviews (3) · Last reviewed commit: "no-mistakes(ci): I fixed ci-1 the way yo..." · Reviewed by Greptile

Comment thread src/state.js Outdated
Comment thread src/target.js Outdated
Comment thread src/commands/status.js Outdated
…d, with a regression test for each. All three tests fail without the fix and pass with it. `pnpm run check` passes: lint, format, typecheck and all 936 tests. - **ci-1 (fully reviewed proposals stayed listed):** A proposal waiting for review must be one that is neither applied nor fully reviewed. `cmdApply` in `src/commands/apply.js` now saves a `reviewedAt` timestamp on the proposal when every edit was decided (accepted or rejected), nothing failed, and it was not a dry run. `listTargetProposals()` in `src/state.js` now skips proposals with `reviewedAt`, so `status`, `status --json` and the bare-`apply` hints all stop listing them. A proposal with any undecided edit stays listed. Test: a dry run and a partial `--decisions e1=rejected` leave the proposal listed; deciding both edits removes it from `status --json` and from bare `apply`'s hint. - **ci-2 (suggested commands could break on unusual filenames):** Every suggested command must give the shell the exact filename as one argument. All the apply, propose and re-run hints are built by `stateCommand` in `src/target.js`. It now leaves plainly safe paths unquoted (so `CLAUDE.md` still prints as before) and wraps anything else in POSIX single quotes, escaping any `'` inside. Test: a file named ``O'Reilly $HOME `id` notes.md`` gives a command that `sh` splits back into the exact filename, and bare `apply` prints the same command. - **ci-3 (`status --json` missed target proposals):** The JSON output must show the same waiting proposals as the text output. `status --json` now includes `targetProposals: [{ path, edits, apply }]`, and the text output prints from the same list. Test: with only a `CLAUDE.md` target proposal saved, `status --json` shows `proposal: null` alongside that entry and its apply command. The default `.backpass/` layout is unchanged, and I didn't edit `AGENTS.md`
Comment thread src/commands/apply.js Outdated
… writes `proposal.json`, so a newer proposal saved while you're reviewing can't be overwritten. `pnpm run check` passes: lint, format, typecheck and all 937 tests. **The rule:** marking a proposal as reviewed must hide only the exact proposal that was reviewed, and must never change the saved proposal. Two places have to agree on this: the code that records a finished review (`cmdApply`) and the code that decides what is still waiting (`listTargetProposals`). The only users of that list are `status`, `status --json` and the hints printed by bare `apply`, so they all follow it. **What changed:** - `src/state.js`: a new `proposalHash(proposal)` hashes the proposal as it was read. A new `State.markReviewed(hash)` writes a small `reviewed.json` (`{ proposal: <hash> }`) in the same state folder as the proposal. `listTargetProposals()` hides a target proposal only when that hash matches the current `proposal.json`. The `reviewedAt` field from last round is gone. - `src/commands/apply.js`: the hash is taken when the proposal is first read, before the review starts. Once every edit is decided, nothing failed, and it wasn't a dry run, apply writes the marker instead of rewriting the proposal. Proposals with undecided edits stay listed, as before. - One choice of mine: the marker is written only for named-file proposals under `targets/`. Only those are ever listed, so writing it for the default proposal would add an unused file to `.backpass/`, whose layout you said must not change. **Tests** (in `test/target-state.test.js`): - New: "a newer proposal saved during the review stays intact and waiting". A fake `lavish-axi` saves a newer 3-edit proposal for `CLAUDE.md` while the review is open, then sends the decision for the 1-edit proposal that was shown. The newer proposal survives byte-for-byte and is still listed in `status --json` with 3 edits. - Updated the existing completed-review test to check that `proposal.json` is left exactly as it was. - Both fail on the previous commit and pass now. `AGENTS.md` was not edited
Comment thread src/state.js
Comment on lines +217 to +218
markReviewed(hash) {
this.writeJsonFile(this.reviewedPath, { proposal: hash });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Completed proposals reappear

markReviewed() replaces the only saved review hash. If an old review stays open while a newer proposal is saved and fully reviewed, finishing the old review last replaces the newer completion record. Both reviews can reject every edit, so file freshness checks do not prevent this.

Bare status and apply then advertise the completed newer proposal as waiting, prompting users to review the same rejected edits again. Keep completion records per proposal hash, or prevent an older review from replacing the current proposal's completion record.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

Thanks @Pantoria, this is a thorough fix. I reviewed the diff against main. CI is green on both OSes and the no-mistakes attestation is bound to the current head (41ef9a6). It also still merges cleanly onto today's main (f24dbab, after #213 and #210), and the full suite passes on that merge when I run it locally.

The only open item is the one you already mentioned on #189: live evidence from a real A, B, A --memory-file run with a real analysis harness, not the stand-in acpx/pi. It should show that the third run (A again) reports A's transcripts as cached, with no new analysis calls. Pasting the relevant lines of the run output here is plenty. Once that's up, this is ready.

contract-class: restore. I checked the unconfigured path: cli.js only swaps in surfaceState when --memory-file or a memory --target names a file that differs from the default. A plain run, and a run that names the default file or a skill, still uses rootState, so .backpass/ is untouched. For named non-default files, this fixes the cache contract evidenceKey already promised (it's keyed by memory hash). Side effect worth knowing: bare backpass apply no longer picks up a named file's proposal. It lists it and prints the exact backpass apply --memory-file <file> command. That's loud and named, so I'm fine with it.

VISION (per rule):

  1. Evidence is the only currency: aligns (each named file keeps its own evidence; rejections and the gap ledger stay shared, so a session still counts once per gap).
  2. The human owns the weights: aligns (apply reads exactly the named file's proposal; B's run no longer wipes A's pending proposal).
  3. Nothing the model says is taken on faith: aligns (state layout only).
  4. The budget is the constraint, not a setting: aligns (no re-analysis on every switch).
  5. It reads what you already have and owns nothing: aligns (local state only; user-scope target dirs stay 0700 under the user state dir).
  6. Failure is loud and named: aligns (hints name the exact command for the state being read).
  7. Scope / align-resist: aligns (doesn't widen write scope or touch the two-session bar).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants