Skip to content

Enforce data quality at the recording choke point, and fix the Codex session-link defect - #211

Merged
FZ2000 merged 7 commits into
mikehasa:mainfrom
FZ2000:data-quality
Sep 19, 2026
Merged

FZ2000 merged 7 commits into
mikehasa:mainfrom
FZ2000:data-quality

Conversation

@FZ2000

@FZ2000 FZ2000 commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

76 files, 4 commits, no stack

main now contains the branch this builds on (#190 merged), so this is a standalone PR: no stacked diff, no 28k-line file list. The eleven product files are 1,127 insertions; the rest is tests, docs and tooling.

git log --oneline main..HEAD
  7375094 Add a runnable demo of the recording loop, and a falsification run
  0cdc17a Rewrite the review guide, and finish the receipt copy
  dabb93c Add the design record and the measurement tooling
  0a03525 Enforce the recording rules once, for every lane

The two code commits pass the full suite on their own — 4,348 tests each, the first verified in a clean worktree. The head runs 4,351.

Watch it work, then watch it fail

.venv/bin/python design-plans/data-quality/tools/demo-data-quality.py

Parts 1-3 walk the loop an agent and a user are actually in, on temporary stores, printing the exact text each side sees: the Codex SessionStart context; the refusal for a completed section with no summary (verbatim, with the corrected call inside it); the two-word retry refused; process prose accepted; the outcome sentence accepted and joined to its session; then what the card and the receipt render. The evidence lane follows, including files: ["."] (which stores nothing and cannot count), blank before/after summaries (not evidence), and the run-scoped outcome lane saying no runs found until a run exists. Part 3 is adversarial: all three lanes refuse the same record with the same message, and control characters, ANSI escapes, an RTL override, emoji and a 4-byte character never reach the store — while a legitimate unicode, multiline record still lands. tests/test_data_quality_demo.py runs these in CI, so the demo cannot rot into a document.

Part 4 is a falsification run, not a certificate. It goes looking for what is still broken and reports what it finds, live, with counts — and says the design record is stale if one stops reproducing. On the installed ledger today it confirms all five:

Confirmed live
Counting copy a one-check receipt renders 1 checks; every count prints 3 file(s) / 1 command(s)
Card budget 28 of 542 real work-item titles exceed the card's 54 characters
Summary quality most stored summaries still describe process, not outcome
The join 61.3% of work items carry no usage record; 76.0% carry no evidence at all
Untouched history the 5 stored terminal sections with no summary are still there — the rules bind new writes only

The demo's own summary ends "Part 4 is why this is not perfect", and the review guide says to read Part 4 before believing Parts 1-3.

The bug, stated in one line

A Codex session could record 1,161 sections and not one would carry a client_session_id — so 61% of recorded work never joined to the usage that paid for it.

Cause: two defects stacked, neither the agent's fault.

  1. The installer wired no Codex SessionStart hook — the event that carries session identity. Codex exposes eight hook events; only two were used.
  2. The context validator hardcoded client == "claude-code", so a captured Codex context would have been discarded anyway. The MCP inheritance gate and the slot loader repeated the same check.

Before → after, on the real code path

Codex-shaped SessionStart, then a section recorded with no ids of its own:

Before This PR
client_session_id None "codex-session-verify"
client None "codex"
client_context_source claude_code_hook (hook-derived, not claimed exact)

Reproduce it rather than trusting this description

python3 design-plans/data-quality/tools/verify-fixes.py

Drives the real code paths a client drives, prints every value observed, exits non-zero if any claim fails. Throwaway stores only; touches no installed app, live store or client config. Output committed at design-plans/data-quality/evidence/VERIFICATION.txt.

30 of 30 checks passed

Covers: the Codex hook installing and its context being inherited; the upgrade path for a machine with the old config (wrapper replaced, not duplicated — a duplicate double-records tool activity); all three lanes refusing the same incomplete records while accepting complete ones; the read-back loop; the contract line budget; display budgets; and a replay of every stored record (9 of 1,541 refused, 0.58%, all genuinely incomplete, zero false positives).

Where to spend review attention

Full map in design-plans/data-quality/REVIEW_GUIDE.md. The short version:

  1. semantic_rules.py (new, 367 lines) — the whole change in one file, each rule carrying the measurement that justifies it. Decide here whether the rules are right.
  2. service.py (+48) — one function called from record_event, which is why no lane can bypass the rules. The guards are the interesting part: what is deliberately out of scope.
  3. hooks.py (+94/−26) — where the headline defect lived.

Skim: most of mcp.py (+377/−28) is schema text; the two substantive blocks are the client-context validator and the agentacct_work_status implementation. Skip: the 40 existing test files (+290/−86, about seven mechanical fixture lines each) — complete-test-fixtures.py did that work and is worth reading once instead. The four new suites are where the test weight is: 1,218 lines, 1,467 tests.

The rules

Enforced once for every lane. Each is a refusal an agent can fix in one retry:

Rule Measured cost
R1 A title must contain readable text 0 of 534 real sections omit one
R2 Control characters collapsed, and measured, before the length cap 0 real titles affected
R4 A terminal section carries its outcome 5 of 536 terminal sections lacked one
R5 A check names a command, files or an artifact — measured on what is stored, so files: ["."] (which stores nothing) cannot count 64 checks had no command
R6 A check's name identifies it, because supersession keys on it 0 generic names today

Measured cost across all rules: 9 of 1,541 replayed records refused (0.58%), all genuinely incomplete. Zero legitimate reports refused.

What else is in here

Extraction. Codex usage rows carried no revision watermark (0 of 372 vs 2005 of 2005 for claude), so whole-second ties parked as conflicts that could never clear. Claude identity budget 256 KiB → 2 MiB: 94.87% → 100% of 6,274 real transcripts resolve, at no measurable cost (0.8 s for all 6,595 files either way).

agentacct_work_status. next_step and blocker were write-only — asked for, stored, and returned by no tool, so an agent could not recover a previous session's continuation or notice its own unfinished work. Also: client_session_id, client_transcript_id and project_dir had no description at all despite deciding whether usage ever joins.

Display alignment. Field caps and the space each field renders into were chosen independently, so a summary was handed to a card that renders a two-line label. Budgets are now shared in one module and disclosed in the schema.

One unfilled placeholder. The evidence-coverage definition printed by the CLI, the TUI and every exported Markdown receipt read X of Y checkable steps carry a passing check — a formula written for a reader of the source, shipped to a reader of the receipt, and sitting in both published worked examples where it reads as a renderer bug. It now states the rule in words, the examples are regenerated from the receipt engine, and a test fails on any N of M-shaped token in a rendered receipt.

Honest limits

  • Zero of 538 work items took the summary-as-card-title path — that fix removed a latent defect, not a live one.
  • 28 of 538 real titles (5.2%) exceed the card's 54 characters and clip there, surviving in the inspector and on hover.
  • The 54-character card budget is derived from geometry, not measured on screen (±10%).
  • Most rules affect only data written from now on; the 538 existing items are unchanged.
  • The fix needs the Codex hook reinstalled on an existing machine, with the CLI updated first.

Still open, ranked

  1. No checked revision is captured anywhere — a check cannot say which commit passed. Needs capture from the hook/CLI environment, not a self-reported field.
  2. Summary quality: ~41% lead with process, ~20% restate status, only ~3–15% state what changed.
  3. The attention queue raises 2,087 per-turn notices against 405 actionable items.
  4. Turn-boundary hooks the adapters already claim are still unwired.
  5. Counting copy: receipts print 1 checks · 1 passed · 0 failed, touched 3 file(s), 1 step(s) ran in subagents. Eight Python sites and four in the app change together or not at all, and the reference images that draw them can only be re-recorded on macOS 26.6 — filed as item 6 in ASSESSMENT.md §7 rather than half-done.

🤖 Generated with Claude Code

agentacct added 2 commits September 13, 2026 16:16
The rules, the single enforcement point, the MCP surface that carries them to
agents, and the fix this PR is named for.

**The rules** (semantic_rules.py — read this file first). Each rule carries the
measurement that justifies it: a title must render (R1); control characters are
collapsed before the length cap (R2); a terminal section carries its outcome
(R4); a check names something a reviewer could re-run or is objectively anchored
(R5); a check's name identifies it, because supersession keys on it (R6).

**One enforcement point** (service.py). _enforce_semantic_rules is called from
record_event, the single writer every lane funnels through, so no surface can
store what another refuses. Machine-recorded events — imported usage, session
observations, finding dispositions — are deliberately out of scope: no agent
authored them, so a refusal would drop a fact instead of correcting a report.

**The fix** (hooks.py). Codex had no SessionStart hook and the context validator
hardcoded client == "claude-code", so a captured Codex context was discarded
anyway. Measured consequence: 1,161 sections with ZERO carrying a
client_session_id, and 61% of work never joining to the usage that paid for it.
Contexts are now per-client, so one client cannot overwrite another's ids.

**The MCP surface** (mcp.py). Descriptors for client_session_id,
client_transcript_id and project_dir, which had no description at all despite
deciding whether imported usage ever joins. And agentacct_work_status, because
next_step and blocker were write-only — accepted, stored, returned by no tool.

**Extraction** (client_usage.py). Codex usage rows carried no revision watermark
(0 of 372 against 2005 of 2005 for claude), so whole-second ties parked as
conflicts that could never clear. Claude identity budget 256 KiB -> 2 MiB:
94.87% -> 100% of 6,274 real transcripts resolve, at no measurable cost.

**Display alignment** (display_budget.py and three call sites). Field caps and the
space each field renders into were chosen independently, so a summary was handed
to a card that renders a two-line label.

Measured cost, replayed against the real ledger: 9 of 1,521 records refused
(0.59%), every one genuinely incomplete — no outcome, no evidence pointer and no
exit code. Zero legitimate reports refused.

Most test files here are mechanical fixture completion: the rules require fields
most fixtures never carried. 1,503 added lines against 85 removed, and worth
skipping in review.
design-plans/data-quality/ holds the reasoning behind the rules rather than just
the rules: the findings with each measurement, the rule catalogue where every rule
states what justifies it and what false-positive budget it must meet, the text and
display analyses, an assessment of what the app does and does not deliver, the
hook and MCP audit, and a review guide.

Three tools, all read-only against the live store:

* audit-agent-data.py — aggregate audit of the real ledger, plus a --replay mode
  that sends every stored record back through the live write path so a rule that
  refuses legitimate work shows up as a number rather than an opinion
* complete-test-fixtures.py — the AST-guided fixture completion used across 44
  test files in the previous commit. It refuses to write a file that does not
  parse, and building it surfaced four bugs in itself, each documented in the file
* verify-fixes.py — 30 checks that drive the real code paths a client drives and
  exit non-zero if any claim fails. Its output is committed at
  design-plans/data-quality/evidence/VERIFICATION.txt
Two things a reviewer and a reader both hit.

The guide still described a 168-file stacked diff. This PR is 74 files and
three commits, so the reading order, the skip list and the numbers are now
stated against the current base, every figure measured on this head: 74 files;
eleven product files at 1,127 insertions; 40 modified test files at +305/-86
against the four new suites at 1,218 lines and 1,467 tests; 4,348 tests green
at each code commit (0a03525 verified in a clean worktree) and 4,349 at the
head; 30/30 verify-fixes checks; 9 replay refusals of 1,541.

It also gains a "what this PR does not prove" section: no live Codex session
has run through the new hook, the rules bind only new writes, the 54-character
card budget is geometric rather than screen-measured, summary quality is still
the largest gap, and no human has reviewed the app since these changes.

Its commands were checked by running them: both tools import agentacct, so the
guide, the verify-fixes docstring and the audit docstring now name
.venv/bin/python instead of a bare python3 (which stops at "import fastapi" on
a system interpreter). The fixture tool is stdlib-only and says so.

The receipt fix: the evidence-coverage definition printed by the CLI, the TUI
and every exported Markdown receipt read "X of Y checkable steps carry a
passing check". That is a formula written for a reader of the source, shipped
to a reader of the receipt, and it sits in both published worked examples,
where it reads as a renderer bug. It now states the rule in words, the worked
examples are regenerated from the receipt engine rather than hand-edited, and
test_receipt_markdown.py fails on any "N of M"-shaped token in a rendered
receipt.

The counting copy that is still wrong ("1 checks", "touched 3 file(s)",
"1 step(s) ran in subagents") is filed as item 6 in ASSESSMENT.md section 7
with the exact lines: it spans Python and Swift, and the reference images that
draw it can only be re-recorded on macOS 26.6.
FZ2000 and others added 2 commits September 13, 2026 17:07
`design-plans/data-quality/tools/demo-data-quality.py` shows the loop a user and
an agent are actually in, then goes looking for what is still wrong.

Parts 1-3 drive the real code paths on temporary stores and print the exact text
each side sees: the Codex SessionStart context, the refusal an agent gets for a
completed section with no summary (verbatim, with the corrected call in it), the
two-word retry refused, process prose accepted, the outcome sentence accepted and
joined to its session, and then what the card and the receipt render. The
evidence lane follows the same shape, including the two cases that keep it
honest: `files: ["."]` stores nothing and cannot satisfy reproducibility, blank
before/after summaries are not evidence, and the run-scoped outcome lane says
"no runs found" until a run exists. Part 3 is adversarial: all three write lanes
refuse the same record with the same message, control characters, an ANSI
escape, a right-to-left override, emoji and a 4-byte character never reach the
store, a 200-character title is refused rather than truncated, and the
false-positive guard shows a legitimate unicode, multiline record still landing.

Part 4 is not a certificate. It runs the opposite way round: five known defects
are reported live, with counts, and a line says the design record is stale if one
stops reproducing. Today it confirms all five - a one-check receipt renders as
"1 checks" and every count prints as "3 file(s)"/"1 command(s)", 28 of 542 real
work-item titles exceed the card's 54 characters, 61.3% of work items carry no
usage record and 76.0% no evidence, most stored summaries still describe process
rather than outcome, and the 5 stored terminal sections with no summary are still
there because the rules bind new writes only.

`tests/test_data_quality_demo.py` runs Parts 1-3 in CI, so the demo cannot rot
into a document, and asserts the demo keeps naming its own limits. The demo's own
summary ends "Part 4 is why this is not perfect" - the artifact makes no
perfection claim, and the review guide says to read Part 4 before believing
Parts 1-3.
…e wrong code

audit-agent-data.py lacked the src/ path prelude its sibling tools use, so
--replay resolved `agentacct` to whatever was installed. Against a checkout
predating the rules (the editable install points at design/setup-work-overhaul,
which has no semantic_rules), `agentacct.mcp` still imports, the write path has
no rules, every record is accepted, and the tool prints "refused: 0" while
`choke_point_replay` quietly reports "skipped" — a green against the wrong code,
the exact failure mode this tool exists to catch.

- Put this checkout's src/ first, like verify-fixes.py and demo-data-quality.py,
  so --replay tests THIS tree by default.
- Add an `agentacct_under_test` provenance block naming the imported package and
  whether `agentacct.semantic_rules` is present.
- When the rules cannot be imported, emit an explicit `error` in both replay
  blocks and exit non-zero (3) instead of reporting a false `refused: 0`.

Verified: with no PYTHONPATH the tool now reports 9/1548 refused (previously a
false 0); with the rules module blocked it exits 3 with a clear error. Sibling
tools still run clean and nothing imports this script.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013w9bhGCPP2g37g4xiehwDV
FZ2000 and others added 2 commits September 19, 2026 02:30
# Conflicts:
#	src/agentacct/install_guide.py
#	src/agentacct/service.py
- client_usage: main landed the same per-session revision watermark for Codex
  usage rows (`rollout_revision_at` / `source_revision_basis`), so the merged
  constructor passed `source_revision_at` twice and no longer parsed. Keep
  main's watermark and drop this branch's `_codex_session_revision_at` copy.
- install_guide: keep main's whole-job-done capstone as its own bullet (its
  test forbids evidence words in that line) and fold the "record it when
  unsure" rule into the opening bullet, so the shared block stays within the
  ten-line budget the guide tests enforce.
- test_text_hygiene: main's Diagnostics verifiers added a third recorded
  `state` fallback in tui.py (`_source_lozenge`); inventory it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@FZ2000

FZ2000 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main into this branch to clear the conflict. Beyond the textual merges (install guide bullets, service.py gate order: strip workset provenance, then _enforce_semantic_rules):

  • Codex revision watermark: main landed the same fix (rollout_revision_at / source_revision_basis), so the merged constructor passed source_revision_at twice and did not parse. Kept main's watermark and dropped this branch's _codex_session_revision_at copy.
  • Guide line budget: main's whole-job-done capstone bullet stays standalone (its test forbids evidence words in that line); the "record it when unsure" rule now rides the opening bullet so the shared block stays within ten lines.
  • test_text_hygiene inventory: main's Diagnostics verifiers added a third recorded-state fallback in tui.py; inventoried it.

Locally: 4483 passed. The one local failure, test_real_titles_are_worth_showing_in_full, reads this machine's installed ledger and skips on CI.

🤖 Generated with Claude Code

@FZ2000
FZ2000 merged commit ef6f7eb into mikehasa:main Sep 19, 2026
4 checks passed
@mikehasa mikehasa mentioned this pull request Sep 22, 2026
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.

1 participant