Skip to content

The Reasoning Degradation Ledger: devague lapse (0.22.0, #97) - #101

Merged
OriNachum merged 17 commits into
mainfrom
reasoning-degradation-ledger
Jul 29, 2026
Merged

The Reasoning Degradation Ledger: devague lapse (0.22.0, #97)#101
OriNachum merged 17 commits into
mainfrom
reasoning-degradation-ledger

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Closes #97.

The seven-leg method run end to end on itself — /scope/think
/challenge/spec-to-plan/assign-to-workforce/deviate
/summarize-delivery — shipping the move issue #97 asked for. The delivery
summary below is the accountability artifact, committed at
docs/deliveries/2026-07-29-reasoning-degradation-ledger.md.

The new surface:

$ devague lapse "<what>" --code <code> [--skipped "<check>"] [--ref REF ...] [--origin user|llm]
$ devague lapse --list [--json]
$ devague lapse --confirm <lN> | --reject <lN>

Three design points worth a reviewer's attention, each a /challenge finding
the human confirmed:

  1. Codes validate fail-closed at filing, not in __post_init__. Every
    other kind vocabulary here validates in __post_init__, which means it
    re-validates on load — so retiring a dead code would have made every frame
    that ever filed it unloadable. That directly contradicts the spec's own
    commitment to remove codes with zero filings (h11).
  2. SCHEMA_VERSION 4→5. Without it an older binary loads a lapse-bearing
    frame tolerantly and its next save silently deletes every filed lapse.
  3. The ledger never gates, in any status, on either engine — 20 tests pin
    it, and the exported spec-md is byte-identical before and after filing.

Filed during the run: #98, #99, #100. Two lapses (l1, l2) are filed
against this run's own reasoning and are still proposed — adjudicating
them with devague lapse --confirm/--reject is the first real exercise of
that path.


Delivery Summary — reasoning-degradation ledger

plan: reasoning-degradation-ledger · run: complete · date: 2026-07-29
baseline: devague summary skeleton

Intent

Ship issue #97 — a
deterministic move that records degradations of the reasoning process
(moments where an assumption was silently substituted for a check) as
first-class, append-only ledger entries filed when they happen. The
reasoning-side twin of deviate, filed friction-free and never gating
convergence. Six tasks across three dependency waves, fanned out by
/assign-to-workforce after the /scope/think/challenge
/spec-to-plan legs converged and exported the spec and plan.

Planned Work

Quoted verbatim from the devague summary skeleton:

  • t1 — Lapse domain model on Frame: LapseRecord, lapse codes, schema v5
  • t2 — CLI verb lapse: file, list, adjudicate
  • t3 — Render the ledger: show and summary consume, spec stays untouched
  • t4 — Gate inertness pinned by tests
  • t5 — Skills sweep: producer, consumer, and the subagent boundary
  • t6 — Docs, contract, changelog, version

Actual Delivery

Plan task Status What actually landed
t1 delivered LapseRecord + LAPSE_CODES + LAPSE_STATUSES and Frame.lapses / add_lapse / find_lapse / set_lapse_status in devague/frame.py; SCHEMA_VERSION 4→5; 31 new tests in tests/test_frame_lapse.py. Merged 4a47f74
t2 delivered devague/cli/_commands/lapse.py (file / --list / --confirm / --reject), registered in cli/__init__.py, MOVES row added in learn.py; 34 new tests in tests/test_cli_lapse.py. Merged 2bc7620
t3 delivered _lapse_lines in render/frame_md.py, _lapse_evidence_lines + lapse_evidence JSON in render/summary_md.py; spec_md.py unchanged by design, pinned by a byte-identity regression test; 17 new tests. Merged 0f67775
t4 delivered 20 gate-inertness tests across tests/test_convergence.py and tests/test_plan_convergence.py; zero production code, as the task specified. Merged 5259c33
t5 delivered challenge routing row + hard rule, summarize-delivery moves table + Delivery Claims step, assign-to-workforce worktree prohibition generalized, docs/skills.md enumerations swept. Merged e95b7af
t6 delivered docs/spec-contract.md LapseRecord entity + Moves rows + schema v5, README.md, CLAUDE.md, CHANGELOG.md, version 0.21.0→0.22.0. Merged b92f982

All six tasks delivered; none partial, dropped, or blocked.

Mid-work Decisions

  • d1 — the split-plan --write path escapes verbatim task text before
    writing markdown — the committed gate-2 artifact failed the repo's own
    markdownlint: cli/__init__.py in t2's instruction rendered as
    strong-emphasis (MD050 ×2, MD037 ×1). No plan task covers the split-plan
    script, and CI does not lint markdown, so nothing would have caught it
    before review. Approved by the gate-2 owner mid-run, recorded before the fix
    landed.
  • The /challenge pass, run before /spec-to-plan, itself broke the spec
    export: a scope surface carrying its own code span was blind-wrapped in a
    second one (MD038). Fixed in spec_md.py / frame_md.py with three new
    tests, and folded into the baseline commit rather than a plan task — the
    breakage predated the plan, so no task could have covered it. No deviation
    record covers this; captured here directly.
  • Three coverage targets (h8, c13, h11) were deliberately deferred at
    plan time, not dropped mid-run: all three measure the shipped verb in a real
    embodiment dogfood cycle and cannot be tested inside this PR. They render in
    the plan's ## Deferred targets section with their reason.

Drift From Plan

Plan item Reason for divergence Classification
t5 (d1) the committed gate-2 artifact failed the repo's own markdownlint: cli/__init__.py in t2's instruction rendered as strong-emphasis (MD050 ×2, MD037 ×1). No plan task covers the split-plan script, and CI does not lint markdown, so nothing would have caught it before review. Approved by the user mid-run. acceptable

No other task diverged from its confirmed contract. Two additions beyond the
letter of the acceptance criteria are noted rather than classified as drift,
because each stays inside its task's file scope and criteria: t3 added a
lapse_evidence key to summary_data()'s JSON for parity with every other
section, and t1 moved a second SCHEMA_VERSION pin in tests/test_frame.py
that the plan's instruction did not know existed.

Evidence

  • tests: full suite uv run pytest -n auto -q1071 passed, 0 failed
    (970 before the run; +101)
  • tests: tests/test_frame_lapse.py, tests/test_cli_lapse.py — 65 passed
  • tests: tests/test_convergence.py, tests/test_plan_convergence.py — 67 passed
  • lint: uv run flake8 --config=.flake8 devague/ tests/ — clean
  • lint: uv run black --check devague/ tests/ — 101 files unchanged
  • lint: markdownlint-cli2 "README.md" "CHANGELOG.md" "CLAUDE.md" "docs/**/*.md"
    — 0 errors
  • version: uv run devague --versiondevague 0.22.0
  • commits: e5047a4..8856938 (14 commits)
  • issues: #97 (delivered),
    #98,
    #99,
    #100 (filed during the run)

Delivery Claims

Claim Confidence Evidence
devague lapse files, lists, and adjudicates lapse records end to end high 34 tests in tests/test_cli_lapse.py · commit 2bc7620 · exercised for real on this run (l1, l2 filed)
lapse codes validate fail-closed at filing but load tolerantly, so a retired code never bricks a frame high tests/test_frame_lapse.py file→retire→reload regression · commit 4a47f74
the ledger never gates — no convergence blocker, warning, or parked item names a lapse in any status high 20 tests in tests/test_convergence.py + tests/test_plan_convergence.py · commit 5259c33
the exported spec-md is byte-identical before and after filing lapses high byte-identity regression test in tests/test_render_sharper.py · commit 0f67775
SCHEMA_VERSION 5 protects filed lapses from an older binary silently dropping them on save high reject-newer store test in tests/test_frame_lapse.py · commit 4a47f74
the ledger surfaces as confidence evidence in devague summary high _lapse_evidence_lines · tests/test_summary.py · this artifact's own skeleton rendered l1/l2 as pending
filing costs the operator under a minute (honesty condition h8) unverified deferred to the embodiment dogfood cycle — not claimed done
every shipped code has a reachable producer, not merely a definition (c13/h11) unverified deferred to the embodiment dogfood cycle — no code has been filed against four of the six

Lapse ledger evidence: l1 (provenance-missing) and l2
(grader-unverified) are filed but still proposed — pending the gate
owner's devague lapse --confirm/--reject, so neither is yet evidence and
neither caps a claim above. Both are self-reports about this run's reasoning:
l1 records that the /challenge pass concluded markdown-safety was handled
after reading only the three CLI renderers, never assign-to-workforce.sh;
l2 records that the per-task TDD merge gate ran pytest and flake8 and was
read as proving "the artifacts are clean" when it never ran markdownlint at
all. d1 is the consequence both describe.

Remaining Work / Follow-up

  • Adjudicate l1 and l2devague lapse --confirm l1 l2 (or reject).
    Until then they are pending, not evidence. This is the first real exercise of
    the adjudication path.

  • #98learn.py's
    MOVES dict is missing five verbs, so devague explain deviate|summary|plan
    all fail. Pre-existing; this run added only the lapse row deliberately, to
    keep the fix reviewable on its own (plan risk r1).

  • #99 — the d1 deviation
    filed as its own issue: split-plan --write wrote unescaped markdown because
    safe_body never ported md_safe_text despite a comment claiming exact
    parity. Fixed here; the issue proposes adding markdownlint to CI and
    addressing the duplicated-helper drift risk.

  • #100deviate and
    summarize-delivery SKILL.md still draw the six-leg flow, omitting
    /challenge. Pre-existing since 0.19.0 and outbound to the whole mesh.

  • Deferred coverage targets h8, c13, h11 — the embodiment dogfood
    cycle. Issue Ledger the reasoning, not just the subject: a deterministic move for recording where the work degraded #97 closes with embodiment offering to run it and report real
    entry counts and which codes turned out dead. h11 commits to removing a
    dead code, not documenting it, so this is a real decision waiting on data
    (plan risk r2; parked items v1, v2).

  • Hard question q4 — how the spec distinguishes "covered" from
    "reachable" per code — remains open on the frame, non-blocking. Four of the
    six shipped codes have no filing yet, which is precisely the condition
    embodiment#18 warned about.

  • devague (Claude)

OriNachum and others added 15 commits July 29, 2026 20:33
The /scope -> /think -> /challenge -> /spec-to-plan legs for issue #97, plus
the renderer fix the challenge pass itself uncovered.

The frame converged with 16 confirmed claims, 16 honesty conditions, three
resolved hard questions, and 14 scope entries citing what was actually read.
The plan converged into three waves (t1 -> t2/t3/t4 -> t5/t6) with three
targets deliberately deferred to the post-ship dogfood milestone.

The renderer change is dogfood fallout: a scope surface carrying its own
code span was blind-wrapped in another, breaking the export under MD038.
spec_md and frame_md now pass such a surface through untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
Adds the Reasoning Degradation Ledger's domain model (issue #97): Frame.lapses,
LapseRecord (id, code, what, skipped_check, refs, origin, status), and
Frame.add_lapse / find_lapse / set_lapse_status. Mirrors DeviationRecord's
chassis (devague/delivery.py) -- prefix-generic _next id minting,
origin-driven initial status, fail-closed __post_init__ validation,
append-only with no amend/delete -- but deliberately refines it in one place
(c21): `code` is validated at the filing path (add_lapse), not in
__post_init__, so retiring a code after a dogfood cycle never bricks a frame
that already filed it; `status`/`origin` still validate in __post_init__
since they never retire.

Bumps SCHEMA_VERSION 4 -> 5 and moves the pin in
tests/test_frame_schema_v2.py and tests/test_frame.py (the latter also pins
the version; permitted single-line update per task scope). New tests in
tests/test_frame_lapse.py cover id minting/origin-driven status/round-trip
(AC1), filing-time-only code validation incl. a file-retire-reload regression
test (AC2, h16), the v5 bump incl. fail-closed-before-parsing-malformed-data
and v4-frame-loads-clean-and-resaves-as-v5 (AC3), and no amend/delete API
plus unvalidated free-text refs (AC4, c20).

Covers targets: c2, h2, c17, h12, c20, c21, h16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
Both convergence gates (devague/convergence.py, devague/plan_convergence.py)
iterate hand-written allowlists over named fields — frame.claims and
frame.open_vagueness on the frame side, plan.tasks and plan.risks on the plan
side — so Frame.lapses (the Reasoning Degradation Ledger, t1) is invisible to
them by default, the same scope_entries precedent. These are pure test
additions pinning that invisibility as a property: converge output is
byte-identical before/after filing a lapse in every status (proposed,
approved, rejected), and neither gate's blockers/warnings/parked_items/
required_next_moves ever names a lapse id, code, or filed text — checked on
both converged and unconverged frames/plans, and on a plan derived from a
lapse-carrying source frame via targets_from_frame. The property holds today;
no production code changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
Clones deviate.py's argument surface and confirm/reject/list shape onto
the Frame-level LapseRecord ledger from t1: `devague lapse "<what>"
--code <code>` files against the current frame (--origin llm lands
proposed; --skipped/--ref stored verbatim, refs never id-validated
since a lapse's refs are deliberately free text); --list [--json]
renders id/code/status; --confirm/--reject transition only proposed
records. Drops --task (no plan link) and id-ref validation relative to
deviate.py, per the plan instruction. Adds the lapse row to learn.py's
MOVES dict so `devague explain lapse` and bare `devague learn` both
surface it.

New tests/test_cli_lapse.py (34 tests) pins the argument surface has no
amend/delete/task/affects flags, the confirm/reject transition rules,
JSON shapes, and deterministic no-subprocess recording.
…touched

frame_md.py gains a "## Lapse ledger" section (id, code, status, what),
omitted entirely when Frame.lapses is empty — the working-state view, so
every lapse renders regardless of status (unlike summary_md's discipline
below). summary_md.py's Delivery Claims section now cites approved lapses
as evidence via a small table (escaped through _escape_table_cell, same as
_drift_lines), proposed lapses render as visibly pending, rejected lapses
are omitted, and a lapse-free (or missing) frame keeps the existing
hardcoded placeholder row untouched — no new failure mode. summary_data's
JSON gains a parallel "lapse_evidence" key. spec_md.py is untouched (no
code change) per the plan's load-bearing design point — a regression test
pins render_spec's byte-identity across every lapse status.

Covers targets c4, h3, c10, c19, h14, h13 (issue #97 t3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
…tays untouched

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
Document the shipped Reasoning Degradation Ledger (Frame.lapses /
LapseRecord, devague lapse, schema v5) in docs/spec-contract.md — a new
LapseRecord entity alongside ScopeEntry, a Moves contract row shaped
identically to the deviate row, a v5 Versioning entry, a convergence-gate
inertness note, and an anti-fabrication-guarantee extension. README.md
names lapse in the flat-verb inventory and the agent-driving flow.
CLAUDE.md's Status section leads with the new release and the stack
expectations verb inventory gains lapse. CHANGELOG gets a 0.22.0 entry
citing issue #97's embodiment evidence verbatim: a 21-task, 7-wave fan-out
whose corrections record was reconstructed from memory, four failed
graders (three inside one task) all found by reading data afterwards and
none by a test failing, one near-miss on a false safety claim, and a
recovery that worked only because raw data happened to be committed.
Version bumped 0.21.0 -> 0.22.0 (minor, new feature) so CI's
version-check passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
…ague#97)

Wire the reasoning-degradation lapse ledger (t2-t4) into the three skills
that touch it, without inventing a new gate or workflow owner:

- challenge/SKILL.md: the routing table gains a seventh row — a reasoning
  degradation the pass itself made while sweeping routes to `devague lapse
  --origin llm`, not the six issue-73 spec-finding categories, since it is
  the agent's self-report about its own reasoning rather than proposed spec
  content. The "nothing else" hard rule now names `devague lapse` alongside
  the existing moves.
- summarize-delivery/SKILL.md: the read-only moves table and hard rule gain
  `devague lapse --list`; the Delivery Claims method step and row-contract
  section now read the ledger (or the `Lapse ledger evidence:` block
  `devague summary` already renders) to ground each confidence level —
  approved lapses cap confidence honestly, proposed ones are not yet
  evidence.
- assign-to-workforce/SKILL.md: the worktree prohibition generalizes from
  "never run `devague plan` inside a task worktree" to "no devague move runs
  inside a task worktree", naming `devague lapse` explicitly — a task agent
  reports a degradation in its transcript, the main agent files the record
  after reconciling the worktree, mirroring the `/scope` subagent boundary
  (#79/#91).
- docs/skills.md: the operator-skills table and the challenge /
  summarize-delivery / assign-to-workforce per-skill sections are swept to
  match every table touched above.

Adjudication is always named as `devague lapse --confirm`/`--reject`,
exercised by the human who already owns the relevant gate — no new gate,
no new role. deviate/SKILL.md is untouched: its only moves table is scoped
to `devague deviate` itself, not a whole-method enumeration, so it needed
no change.

Covers: c5, h4, c6, h5, c9, h7.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
…ry (devague#97)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
The committed gate-2 artifact failed the repo's own markdownlint: a task
instruction naming cli/__init__.py rendered as strong-emphasis (MD050 x2,
MD037 x1). The script's safe_body() claimed to mirror render/_md_safety.py
"exactly" but had only ported autolink_urls plus its own angle-bracket
helper — md_safe_text, the half that wraps underscore-bearing identifiers in
code spans, was never carried across.

Ports md_safe_text faithfully (identifier wrapping, stray control chars,
stray backticks, leading hash, code-span and URL carve-outs) and composes it
into safe_body/safe_heading. Verified idempotent: a second --write is
byte-identical.

Recorded as deviation d1 against t5; no plan task covered the split-plan
script, and CI does not lint markdown, so nothing would have caught this
before review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
The accountability artifact for the six-task, three-wave fan-out: all six
tasks delivered, one approved deviation (d1), two lapses filed against this
run's own reasoning and still awaiting adjudication.

Two delivery claims stay unverified on purpose — h8 (filing costs under a
minute) and c13/h11 (every shipped code has a reachable producer) both need
the embodiment dogfood cycle, and four of the six codes have no filing yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
@OriNachum

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add reasoning-degradation lapse ledger (Frame.lapses) + devague lapse CLI

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add append-only lapse ledger on Frame (schema v5) with filing-time code validation.
• Introduce devague lapse to file/list/adjudicate lapses deterministically.
• Render lapses in show/summary without gating; pin behavior via tests and docs.
Diagram

graph TD
  A["Operator / agent"] --> B["devague CLI"] --> C["lapse command"] --> D["Frame (lapses)"] --> E[("Store (save/load)")]
  D --> F["Renderers"] --> G["summary_md evidence"]
  F --> H["frame_md show"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Store lapses in the Delivery store (like deviations)
  • ➕ Keeps all append-only ledgers in one place
  • ➕ Would reuse existing delivery serialization/versioning
  • ➖ Not reachable before a plan exists (keyed by plan slug)
  • ➖ Breaks the intent to file degradations during frame/spec work
2. Validate lapse `code` in `LapseRecord.__post_init__` (like other enums)
  • ➕ Uniform validation style across Frame entities
  • ➕ Catches invalid codes on any construction path
  • ➖ Retiring/renaming codes would brick loading historical frames
  • ➖ Directly conflicts with planned dead-code removal after dogfooding
3. Render lapses into exported spec-md
  • ➕ Spec artifact would contain end-to-end provenance
  • ➕ Simplifies discovery (one exported file)
  • export overwrites the same dated spec file; would rewrite the what-to-build artifact
  • ➖ Violates the contract that process history should not mutate the spec output

Recommendation: Current approach is the right trade: attach lapses to the Frame (available throughout the run), bump schema to prevent silent loss on older binaries, validate codes fail-closed only on the filing path (so historical frames remain loadable after vocabulary changes), and explicitly keep the exported spec free of execution-time ledger content. The added tests correctly pin the invariant that lapses never gate convergence.

Files changed (34) +3548 / -49

Enhancement (6) +410 / -5
__init__.pyRegister new 'lapse' subcommand +2/-0

Register new 'lapse' subcommand

• Wires the new 'devague lapse' command module into the CLI parser build and command registration sequence.

devague/cli/init.py

lapse.pyImplement 'devague lapse' file/list/confirm/reject command +176/-0

Implement 'devague lapse' file/list/confirm/reject command

• Adds a new deterministic CLI verb to file lapses onto the current frame, list them (text/JSON), and adjudicate proposed entries via confirm/reject. Mirrors the deviate command’s shape while intentionally omitting plan linking and ref validation.

devague/cli/_commands/lapse.py

learn.pyExpose 'lapse' in 'devague learn' move table +6/-0

Expose 'lapse' in 'devague learn' move table

• Adds the lapse move description to the MOVES dict so 'devague learn' lists it and 'devague explain lapse' is supported.

devague/cli/_commands/learn.py

frame.pyAdd Frame.lapses + LapseRecord and bump schema to v5 +138/-1

Add Frame.lapses + LapseRecord and bump schema to v5

• Introduces LapseRecord, lapse codes/statuses, and Frame APIs to add/find/adjudicate lapses. Bumps SCHEMA_VERSION to 5 and loads lapses tolerantly from older frames while validating codes only at filing time to allow vocabulary retirement.

devague/frame.py

frame_md.pyRender lapse ledger in 'devague show' and fix scope surface backticks +27/-1

Render lapse ledger in 'devague show' and fix scope surface backticks

• Prevents double-wrapping scope surfaces that already contain backticks. Adds a new “Lapse ledger” section to frame rendering listing all lapses regardless of status, omitted when empty.

devague/render/frame_md.py

summary_md.pyCite approved lapses as Delivery Claims evidence (with JSON parity) +61/-3

Cite approved lapses as Delivery Claims evidence (with JSON parity)

• Adds lapse evidence rendering: approved lapses become a small evidence table, proposed lapses show as pending ids, and rejected lapses are omitted. Extends 'summary_data()' with structured 'lapse_evidence' output.

devague/render/summary_md.py

Bug fix (2) +53 / -3
assign-to-workforce.shHarden markdown safety for generated task text (md_safe_text) +40/-2

Harden markdown safety for generated task text (md_safe_text)

• Ports 'md_safe_text'-style escaping into the split-plan script to wrap underscore/dunder identifiers and escape stray markdown control characters. Updates 'safe_body'/'safe_heading' to apply this, preventing markdownlint failures from unescaped task instructions.

.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh

spec_md.pyFix scope surface wrapping to avoid nested code spans (MD038) +13/-1

Fix scope surface wrapping to avoid nested code spans (MD038)

• Adds a helper to wrap scope surfaces in a single code span unless they already contain backticks, routing those through the safe renderer. Prevents broken markdown exports when surfaces embed their own code spans.

devague/render/spec_md.py

Tests (8) +1311 / -9
test_cli_lapse.pyAdd end-to-end tests for 'devague lapse' CLI +448/-0

Add end-to-end tests for 'devague lapse' CLI

• Adds comprehensive CLI tests covering record/list/json/adjudication behavior, argument-surface constraints, determinism, and learn/explain visibility. Pins failure modes for invalid codes, invalid transitions, and mutual exclusion with recording.

tests/test_cli_lapse.py

test_convergence.pyPin frame convergence as lapse-inert (never gating) +132/-1

Pin frame convergence as lapse-inert (never gating)

• Adds tests asserting convergence output is byte-identical before/after filing lapses across statuses and that no lapse text leaks into blockers/warnings/parked/required moves. Ensures the new Frame field remains non-gating by contract.

tests/test_convergence.py

test_frame.pyUpdate schema version pin to v5 +6/-4

Update schema version pin to v5

• Updates the schema version assertion to reflect the v5 bump introduced for Frame.lapses.

tests/test_frame.py

test_frame_lapse.pyAdd domain-model tests for Frame.lapses and LapseRecord semantics +301/-0

Add domain-model tests for Frame.lapses and LapseRecord semantics

• Adds tests for default empty ledger, id minting, origin-driven initial status, round-tripping through dict/store, filing-time code validation, tolerant load of retired codes, and status mutation rules.

tests/test_frame_lapse.py

test_frame_schema_v2.pyUpdate schema bump tracking to v5 +3/-3

Update schema bump tracking to v5

• Moves the historical schema-version pin forward to include the v5 bump for Frame.lapses.

tests/test_frame_schema_v2.py

test_plan_convergence.pyPin plan convergence/targets derivation as lapse-inert +150/-1

Pin plan convergence/targets derivation as lapse-inert

• Adds tests ensuring lapses do not perturb 'targets_from_frame()' or plan convergence output, and that lapse identifiers/text never appear in plan gate reporting across lapse statuses.

tests/test_plan_convergence.py

test_render_sharper.pyAdd renderer regressions: backtick surfaces and spec byte-identity vs lapses +108/-0

Add renderer regressions: backtick surfaces and spec byte-identity vs lapses

• Adds tests preventing nested code-span wrapping for scope surfaces and ensuring markdownlint cleanliness. Pins that 'render_spec' output is unchanged (byte-identical) after filing lapses, while frame rendering includes a lapse ledger section.

tests/test_render_sharper.py

test_summary.pyTest summary lapse evidence rendering and degraded-load behavior +163/-0

Test summary lapse evidence rendering and degraded-load behavior

• Adds tests for approved/pending/rejected lapse evidence behavior in the Delivery Claims section, table escaping safety, JSON 'lapse_evidence' shape, and the “no frame available” degraded-load path remaining unchanged.

tests/test_summary.py

Documentation (12) +782 / -29
SKILL.mdDocument lapse reporting and broaden worktree devague prohibition +23/-3

Document lapse reporting and broaden worktree devague prohibition

• Extends task-agent instructions to report (not file) reasoning-degradation lapses. Generalizes the hard rule from “no 'devague plan' in worktrees” to “no devague moves in worktrees,” with explicit 'devague lapse' guidance.

.claude/skills/assign-to-workforce/SKILL.md

SKILL.mdAdd routing for reasoning degradations via 'devague lapse' +26/-6

Add routing for reasoning degradations via 'devague lapse'

• Updates the challenge pass rules and routing table to treat the pass’s own reasoning degradations as a self-report filed immediately via 'devague lapse --origin llm'. Expands the “nothing else” deterministic-moves rule to include 'lapse'.

.claude/skills/challenge/SKILL.md

SKILL.mdGround Delivery Claims confidence in lapse ledger evidence +30/-10

Ground Delivery Claims confidence in lapse ledger evidence

• Extends the delivery-claims guidance to read 'devague lapse --list' / the summary’s lapse-evidence block. Adds 'lapse --list' to the read-only moves table and clarifies proposed vs approved vs rejected handling.

.claude/skills/summarize-delivery/SKILL.md

CHANGELOG.mdRelease notes for 0.22.0 (Reasoning Degradation Ledger) +58/-0

Release notes for 0.22.0 (Reasoning Degradation Ledger)

• Documents the new lapse ledger feature, CLI surface, schema v5 bump rationale, and non-gating guarantees. Summarizes rendering behavior and key design constraints (filing-time validation, spec export stability).

CHANGELOG.md

CLAUDE.mdUpdate project guidance to include lapse ledger semantics and CLI +43/-4

Update project guidance to include lapse ledger semantics and CLI

• Adds a prominent status section describing 'devague lapse', schema v5, validation strategy, non-gating invariant, and rendering behavior. Updates the CLI module inventory to include 'lapse'.

CLAUDE.md

README.mdIntroduce 'lapse' as a first-class verb and method concept +10/-1

Introduce 'lapse' as a first-class verb and method concept

• Adds 'lapse' to the flat-verb inventory and explains its role across the seven-leg method. Clarifies that lapses never gate and only approved ones become summary evidence.

README.md

2026-07-29-reasoning-degradation-ledger.mdAdd committed delivery summary for this run +139/-0

Add committed delivery summary for this run

• Adds the delivery accountability artifact documenting intent, plan vs actual, evidence, drift/deviations, and lapse-ledger usage for confidence grounding. Serves as the run’s committed closure document.

docs/deliveries/2026-07-29-reasoning-degradation-ledger.md

2026-07-29-reasoning-degradation-ledger-split.mdAdd generated implementation split plan export +129/-0

Add generated implementation split plan export

• Adds the split-plan (gate-2) artifact with dependency waves and per-task instructions/acceptance, suitable for assignment and regeneration semantics.

docs/plans/2026-07-29-reasoning-degradation-ledger-split.md

2026-07-29-reasoning-degradation-ledger.mdAdd exported build plan document +82/-0

Add exported build plan document

• Adds the plan export describing tasks t1–t6, dependencies, acceptance criteria, and deferred targets/risks for the reasoning-degradation-ledger work.

docs/plans/2026-07-29-reasoning-degradation-ledger.md

skills.mdUpdate skill docs to include lapse consumption/ownership +21/-3

Update skill docs to include lapse consumption/ownership

• Extends the skills overview and method sections to reference 'devague lapse --list' in summarize-delivery and clarifies subagent boundaries around filing. Aligns the skills documentation with the new verb.

docs/skills.md

spec-contract.mdExtend spec contract with LapseRecord entity, schema v5, and move rows +109/-2

Extend spec contract with LapseRecord entity, schema v5, and move rows

• Updates schema version guidance to v5, documents the LapseRecord entity and its validation/append-only rules, and adds CLI contract rows for 'lapse' record/list/adjudication. Reinforces the invariant that lapses never gate convergence.

docs/spec-contract.md

2026-07-29-reasoning-degradation-ledger.mdAdd spec for Reasoning Degradation Ledger (issue #97) +112/-0

Add spec for Reasoning Degradation Ledger (issue #97)

• Introduces the spec describing the lapse ledger’s intent, requirements, decisions, and boundaries (non-gating, append-only, filing-time validation). Captures the before/after motivation and planned dogfood follow-ups.

docs/specs/2026-07-29-reasoning-degradation-ledger.md

Other (6) +992 / -3
current_planPoint current plan to reasoning-degradation-ledger +1/-1

Point current plan to reasoning-degradation-ledger

• Updates the repo-local current plan slug to the new plan used for this delivery run.

.devague/current_plan

reasoning-degradation-ledger.jsonAdd delivery store snapshot for the run (including d1) +21/-0

Add delivery store snapshot for the run (including d1)

• Adds the delivery JSON capturing the run’s recorded deviation(s), used as a committed accountability artifact for the delivery summary.

.devague/deliveries/reasoning-degradation-ledger.json

reasoning-degradation-ledger.jsonAdd exported frame snapshot for reasoning-degradation-ledger +579/-0

Add exported frame snapshot for reasoning-degradation-ledger

• Adds the frame JSON artifact for the run, including the converged frame state used to seed the plan and documentation.

.devague/frames/reasoning-degradation-ledger.json

reasoning-degradation-ledger.jsonAdd exported plan snapshot for reasoning-degradation-ledger +389/-0

Add exported plan snapshot for reasoning-degradation-ledger

• Adds the plan JSON artifact for the run, capturing tasks/waves/targets as the source for split-plan and documentation exports.

.devague/plans/reasoning-degradation-ledger.json

pyproject.tomlBump project version to 0.22.0 +1/-1

Bump project version to 0.22.0

• Updates the package version for the new feature release that introduces the lapse ledger and schema v5.

pyproject.toml

uv.lockSync lockfile version to 0.22.0 +1/-1

Sync lockfile version to 0.22.0

• Updates the editable package entry to match the bumped project version.

uv.lock

@OriNachum

Copy link
Copy Markdown
Contributor Author

Thanks — logging that no change follows from this review, and why.

Qodo's three alternatives are, precisely, the three design forks this PR
adjudicated before any code was written, and it reached the same verdict on
each independently. Worth recording where each decision lives, since that is
the whole point of the method this PR extends:

  1. Store lapses in the Delivery store — raised as blocking hard question
    q1 during /think. Rejected for exactly Qodo's stated con: the delivery
    store is keyed 1:1 by plan slug and every entry point fails closed with
    no plan selected, so provenance-missing and friends would be
    unfileable during /scope, /think, and /challenge — the legs where
    they most often occur. Recorded as decision c14.
  2. Validate code in __post_init__ — raised by the /challenge pass
    (claim c21, honesty condition h16) and settled with a probe rather
    than an argument: constructing a Claim with an unknown kind raises at
    construction, and from_dict constructs at load, so a closed load-time
    enum would make every frame that ever filed a later-retired code
    permanently unloadable. That directly contradicts h11, which commits to
    removing codes with zero filings after the dogfood cycle. status and
    origin still validate in __post_init__ — they never retire.
  3. Render lapses into the exported spec-md/challenge claim c19,
    with h14 pinning byte-identity. export overwrites the same dated file,
    so execution-time process history would rewrite the what-to-build
    artifact. Same philosophy as the contested-marker work in Exported spec never shows that a deviation contested one of its confirmed claims #92: the spec
    points forward to the ledger, it is never rewritten by it.

SonarCloud is green — 0 new issues, 0 accepted issues, 0 security hotspots,
0.0% duplication, and 98.7% coverage on new code. Note for anyone reading the
briefing rather than the gate: agex pr read reports "Project not on
SonarCloud (skipped)"
on this PR, which is a false negative — filed as
agentculture/devex#96.

Three lapses are filed against this run's own reasoning and are
deliberately still proposed, pending adjudication:

  • l1 (provenance-missing) — the /challenge pass concluded markdown
    safety was handled after reading only the three CLI renderers, never
    assign-to-workforce.sh, which also writes committed markdown.
  • l2 (grader-unverified) — the per-task TDD merge gate ran pytest and
    flake8 and green was read as "the artifacts are clean"; it never ran
    markdownlint at all, so it could not have caught what it was trusted for.
  • l3 (grader-unverified) — the SonarCloud false negative above was
    relayed as fact before the second surface was checked.

d1 — the mid-run deviation that fixed the unescaped split artifact — is the
consequence l1 and l2 both describe. That is the ledger doing the job it
was built for, on the PR that ships it.

Follow-ups filed during the run, none blocking: #98, #99, #100, and
agentculture/devex#96.

  • devague (Claude)

@qodo-code-review

qodo-code-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 92 rules
✅ Skills: cicd, communicate

Grey Divider


Remediation recommended

1. Record flags ignored silently ✓ Resolved 🐞 Bug ≡ Correctness
Description
cmd_lapse falls back to listing when the positional what is missing/empty, even if record-only
flags like --code/--skipped/--ref/--origin are provided, so the command exits 0 without
filing anything. This can silently drop intended ledger entries in scripted or manual usage.
Code

devague/cli/_commands/lapse.py[R134-141]

+    frame = resolve(args.frame)
+    if args.confirm:
+        return _resolve_status(args, frame, args.confirm, "approved")
+    if args.reject:
+        return _resolve_status(args, frame, args.reject, "rejected")
+    if args.what:
+        return _record(args, frame)
+    return _list(args, frame)
Relevance

●●● Strong

Team previously accepted failing closed for ambiguous CLI flag/positional combos to avoid silent
no-ops.

PR-#72

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CLI defines what as an optional positional argument and then uses if args.what: to decide
whether to record or list, so providing record-only flags without a non-empty what will route to
listing and skip persistence.

devague/cli/_commands/lapse.py[118-141]
devague/cli/_commands/lapse.py[144-176]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`devague lapse` dispatches to `_list()` whenever `args.what` is falsy. Because `what` is an optional positional (`nargs="?"`), calls like `devague lapse --code grader-unverified` (or `devague lapse "" --code ...`) silently list and return success without persisting a lapse.

## Issue Context
This is introduced by the new lapse command. The behavior is particularly risky because users may interpret the successful exit as a successful filing, but `store.save()` is never reached.

## Fix Focus Areas
- devague/cli/_commands/lapse.py[118-141]
- devague/cli/_commands/lapse.py[144-176]

## Suggested fix
- In `cmd_lapse`, before the final `return _list(...)`, detect whether any **recording-intent** options were provided (e.g., `args.code`, `args.skipped_check`, `args.refs`, or `args.origin != "user"`). If so and `args.what is None` or `not args.what.strip()`, raise `DevagueError(EXIT_USER_ERROR, ...)` with a hint showing the correct record invocation.
- Consider treating empty/whitespace-only `what` as an error even when `--code` is present.
- Add regression tests:
 - `main(["lapse", "--code", "grader-unverified"])` should exit 1 and not create a lapse.
 - `main(["lapse", "", "--code", "grader-unverified"])` should exit 1 and not create a lapse.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread devague/cli/_commands/lapse.py
… review)

Qodo found a real silent no-op: cmd_lapse fell through to listing whenever
the positional `what` was absent, so

    devague lapse --code provenance-missing --skipped "<check>" --origin llm

printed "no lapses filed yet" and exited 0 having filed nothing. For a ledger
whose entire premise is that filing is cheap enough to do mid-flight, that is
the worst failure available: the operator believes the degradation is on
record and it is not.

Fails closed now, naming the flags that were given, matching the
flag/positional-ambiguity precedent from #72. --origin's parser default moves
from "user" to None so an explicit --origin is distinguishable from the
default; _record resolves None back to "user", so filing behaviour is
unchanged.

7 new tests, including the confirm/reject and --list combinations and a
regression pinning that bare `lapse` and `lapse --list` still list.

Filed as lapse l4: I called the review clean after reading one comment, three
minutes before the Code Review carrying this bug arrived.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
The artifact claimed a clean review and said nothing about SonarCloud. Both
were wrong: Qodo found a silent no-op in `devague lapse`, and the Sonar gate
had run and passed all along (98.7% coverage on new code) behind a false
negative in `agex pr read`.

Adds the post-review fix under Mid-work Decisions, the two new lapses (l3,
l4) to the ledger-evidence block, #102 and devex#96 to follow-ups, and the
Sonar result to Evidence. Three of the four lapses now share one root cause:
an instrument read once and its output treated as settled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6Zm7QrhKV4bzW2RikPGNe
@OriNachum

Copy link
Copy Markdown
Contributor Author

Bug confirmed and fixedd6b9326. Thank you, this was a real one, and
the most embarrassing possible place to have it.

FIX — "Record flags ignored silently"

Reproduced exactly as described:

$ devague lapse --code provenance-missing --skipped "the check I skipped" --origin llm
no lapses filed yet
$ echo $?
0

The entry is gone and nothing says so. For a ledger whose entire premise is
that filing is cheap enough to do mid-flight, a silent no-op is the worst
failure available: the operator believes the degradation is on record, moves
on, and the record does not exist. That is precisely the "written late is
written flattering" failure this feature was built to prevent, reproduced
inside the feature.

Your relevance note pointing at #72 is exactly right — failing closed on
ambiguous flag/positional combos is the established call here.

Now:

$ devague lapse --code provenance-missing --skipped "the check I skipped" --origin llm
error: --code, --skipped, --origin given without a positional 'what' to file
hint: record the lapse in one move: devague lapse "<what>" --code <code>, or drop the flags to list
$ echo $?
1

The guard collects whichever record-only flags were supplied and names them.
--origin's parser default moved from "user" to None so an explicit
--origin is distinguishable from the default; _record resolves None back
to "user", so filing behaviour is unchanged. Seven new tests cover each flag
alone, the combined case, the --confirm/--reject and --list
combinations, and a regression pinning that bare lapse and lapse --list
still list. Suite: 1080 passing.

The same bug is in deviate, and it is worse there

lapse was cloned from deviate, so it inherited the dispatch. Verified
read-only against a real plan:

$ devague deviate --task t1 --reason "probe: is this silently dropped?"
d1: the split-plan --write path escapes verbatim task text ... (task t5, approved) [acceptable]
$ echo $?
0

An approved deviation, discarded, exit 0. The /deviate protocol is stop,
get human approval, record, resume
— a silent step 4 means the run resumes
believing an approved departure is recorded, and /summarize-delivery later
reads that same empty ledger as ground truth for Drift From Plan. Filed as
#102 rather than fixed here, so a change to a released verb stays reviewable
on its own.

Recorded against this run

Filed l4 (grader-unverified): I reported "Qodo required no fixes" after
reading one comment — three minutes before your Code Review arrived. My poll's
exit condition matched any Qodo comment that was not the "busy working"
placeholder, so it fired on the PR Summary and I read arrival as completion.
The skipped check: validate the poll's exit condition against what a completed
review actually looks like, not against "a comment appeared."

That is the third of four lapses on this PR sharing one root cause — an
instrument read once, its output treated as settled. l1, l3, and l4 are
all that shape. The delivery summary now records all four and the fix
(2a0555e).

  • devague (Claude)

@sonarqubecloud

Copy link
Copy Markdown

@OriNachum
OriNachum merged commit a6fdd8e into main Jul 29, 2026
7 checks passed
@OriNachum
OriNachum deleted the reasoning-degradation-ledger branch July 29, 2026 19:19
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.

Ledger the reasoning, not just the subject: a deterministic move for recording where the work degraded

1 participant