Conversation
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.
`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
…at were not true A ground-truth audit against a real repo found that a reviewer could learn THAT something was recorded and whether the agent claimed it was checked, but almost never what happened. Of 55 questions a reviewer naturally asks across four task shapes, 1 was fully answerable and 40 were not. Three statements the product made were false: - "The agent's command argument was not stored" when it was stored. The projection hard-nulled it and kept only a boolean, conflating an agent-supplied command with a hook digest that genuinely has no text. These are now two named states. - actions_synopsis.state said "exact" while the same payload showed 4 captured record_section calls against 5 recorded sections. "exact" is now earned by comparing capture against the ledger, and degrades to "partial" with a sentence naming the shortfall. - revision_label asserted "at <sha>" when the server reads HEAD at record time and agents record before committing, so every stamp named the commit BEFORE the work. Proven: a passing tests/test_subtract.py check was stamped with a commit containing neither subtract() nor that file. The label now leads with its basis, and a check whose declared files are absent at the stamped revision carries a named contradiction. The contract was manufacturing unreadable records. `name` doubled as the supersession join key, so a compliant agent used the command string and produced two byte-identical cards; `summary` was advertised as safely omittable, so failures arrived with no description. Supersession now keys on a check_key (or command + evidence_type + section_id), `name` is a human label beside its command, and a failed or errored check must carry the assertion. check_identity stays stable across commits, and legacy rows whose name IS the command keep superseding byte for byte. Superseded runs stay in the receipt so a fail->pass recovery can be rendered at all; checkpoint prose becomes its own ordered beat instead of being overwritten by the next write; gaps rank by what they prevent rather than by which ingestion source was silent. Measured cost: replaying 1,900 stored records, 116 are refused (6.11%), nothing stored is altered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
The record page had no check list at all: `grep "included:" WorkPane.swift` returned two call sites, [.actions, .cost] and [.task, .agents], so dimensions.evidence.checks[] -- result labels, exit codes, revisions, files -- was shown on no surface. The task whose entire evidence was a fail at one commit and a pass at the next rendered that as a card reading "2 records". Now: a Checks block lists every run with its result, exit code, revision and files, superseded runs greyed rather than hidden; Activity defaults to an ordered list (the canvas becomes its zoom control) because three events inside 0.33s is the worst possible case for a proportional time axis; and the header always states how many records are loaded instead of gating that count on an active filter. Fields that were decoded and then dropped are now rendered: lane/lane_label (absent from CodingKeys entirely, so vertical position on the canvas carried no meaning), section_kind, next_step, and noteText -- the reducer's own result/exit-code disagreement, which the app detected and never said. A passing task used to show strictly LESS evidence than a failing one, because the only surface naming a check rendered `if let attention`. That slot now carries a positive evidence callout when there is no attention item, and a finding marked reviewed keeps its failure summary, exit code and next step instead of having them deleted. Data marks no longer wear the interactive accent: chartBar and chartCatRead move to indigo, and the shared-scale tool-call bars in ReceiptsPane -- pure cobalt in both themes -- move with them. AccentReservationTests pins the rule with an OKLab floor, proves the floor is real by requiring accentPressed to fail it, and source-scans for a data mark painted with Theme.accent so the next one cannot land silently. The six visual-regression suites skip on this host: the baselines were recorded on macOS 26.6 and this is 26.5.1. They are stale after this change and must be re-recorded on a 26.6 host before they mean anything. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
main advanced 18 commits while this branch was in flight, including two independent redesigns of the same surfaces: mikehasa#260 (readable Session and Work detail views) and mikehasa#256 (folder-anchored session groupings). Eleven files conflicted. Resolution notes, where the two sides genuinely disagreed: - STEP ROW: kept ours (StepCard), deleted main's StepSpineRow and StepDetailBody. StepCard takes its tally from the reducer where main's composed the wording in Swift, and main's row put .accessibilityElement(children: .ignore) on a Button, which strips the button role and its press action. Kept main's SessionStepSpine container -- the numbered rail is a real improvement -- and made it render StepCard, so both surfaces now use one step-list component. - CODEX REVISION WATERMARK: kept main's, deleted ours. Both sides fixed the same bug; ours returned microseconds with basis rollout_file_mtime_us while the sibling observation on the same session carries nanoseconds. Mixing units on an ordering field is a real defect, and ours would have failed main's own test. - RECORD OUTCOME BARS: deleted main's. They were unreferenced once the hero resolution dropped the call, and they carried an accent-as-data-mark and a green-tier disagreement. - ACCENT RESERVATION: main's Theme.sourceColor returned the bare interactive cobalt for claude-code and painted legend swatches and timeline lane bars with it. Renamed to chartSource* so AccentReservationTests' OKLab floor applies automatically rather than by hand. swift build clean, 679 Swift tests / 6 skipped (the host-gated visual baselines), 4,735 Python tests, surface parity 69. The audit fix this branch exists for was re-verified on rendered pixels after the merge: a task whose evidence is a test failing at one commit and passing at the next still lists both runs without clicking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
… change The app was consuming ~19% of the main thread while nobody touched it. Measured on a mounted window with a forced layout flush: 581 ms of main-thread CPU every 3 seconds on a 44-step record, against an idle control of 3.7 ms. Cause, and it was not where I guessed. There is no event-log poll involved -- GlanceState polls at 30 s and the window refresh at 60 s. The cost was a 3-second per-task receipt poll in the Work pane (WorkPane.swift) whose result was assigned unconditionally (DashboardStore `receipt = payload`), so Observation rebuilt the whole record page 20 times a minute. Three consecutive /v1/receipt responses for the same task were byte-identical (156,247 bytes, `cmp` clean): the page was being rebuilt to draw the same pixels. Nor was the expensive surface the one this branch added. Ablation measured the Checks block at 3370 vs 3424 ms (noise) and the ordered Activity list and the canvas at no measurable cost. Essentially all of it was the numbered step spine. Fixes, each with its measured number: - The receipt poll now compares a fingerprint of the raw response bytes and skips republishing when they match. 1893.7 ms -> 0.0 ms per idle poll tick on the heaviest task, reproduced independently against a 0.0-0.2 ms control. - Three repeated collections moved onto the project's own lazy container (ScrollContentStack): record-page build -58% / -55% / -41%. - Column widths in the Work table are memoised on (text, size, bold, tracking): a real but small 7-20% of the back-to-table build, reported small rather than dressed up. - The timeline poll no longer writes three unconditional @State fields per tick. Behaviour-preserving; NOT measured, and not claimed as a speedup. The republish gate is the risky half: a gate that is too eager wastes a rebuild, but one that is too clever shows a reviewer stale evidence and says nothing. It is now a pure `receiptIsAlreadyOnScreen` whose every condition fails OPEN, with six tests pinning the direction that would otherwise fail quietly -- that a receipt whose bytes changed always reaches the screen. 690 Swift tests, 6 skipped (the host-gated visual baselines). Python 4,735 and surface parity 69 unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
… page, make focus visible Three interaction defects, all introduced by the record-page rebuild. THE TIME CANVAS. A user reported that "the timeline window increase and decrease scroll feature is gone". Nothing was deleted: `listSuitsData` decided between the ordered list and the time canvas with `recordCount <= 4 || span < 5`, and most tasks have four records or fewer, so the canvas -- and with it scroll-to-zoom, pinch, +/- and the window scroller -- stopped appearing. Count was a proxy for the wrong question. The two surfaces differ on whether the time axis SEPARATES the records: a list orders, a canvas positions, and the canvas earns its place exactly when position carries information order does not. The predicate now reads the records' own stamps and picks the list only when they cannot be told apart on an axis at all. Three records at 8:13:12/:14/:17 are a shape; twenty records sharing one stamp are a list at any N. Three things compounded it, all fixed here. The choice was stored per task, so a reviewer who wanted the canvas had to re-choose it on every task forever; it is now an app-wide default that a per-task override still beats. The picker called hold(), so changing how records are DRAWN silently dropped live follow -- drawing is not an investigation. And every control in the Activity heading rendered as chrome-less prose, which is why a changed default read as a deleted feature; the picker and "Show all time" now carry a resting hairline. THE CHECKS CLICK. Pressing a row asked the timeline to select the matching event, and that path scrolled the outer document to reveal the timeline's selection -- throwing the reader ~2,628 pt past the row they had just expanded, which then sat 1,400 pt above the viewport. The canvas now reframes internally and the document moves only far enough to reveal the row's own detail, and only when it is not already on screen. Measured after: 0 pt of movement on four presses where the row was already visible. FOCUS. Three separate causes for one symptom. The canvas stroked its ring in the parent NSView underneath an opaque hosting view, so AX reported FOCUSED and nothing was drawn. The task table takes one keyboard stop for the whole table by design, but its rows read the ambient isFocused that the container's focus sets, so every visible row ringed at once. And the record document was not in the key loop at all: with Full Keyboard Access off, Tab yielded ten stops and not one of them was on the page. Tab now walks 26 record-page stops, breadcrumb through Copy task ID, including every check row and step-spine row. A focused control is visibly focused: work.breadcrumb.back goes from 95 to 434 accent pixels, 423 differing pixels, max channel delta 210. 702 Swift tests, 6 skipped (the host-gated visual baselines). Python 4,735 and surface parity 69 unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
A reviewer reported the timeline's zoom and window-resize "gone" three times. Twice the cause was mine and already fixed. The third time they were right in a way no amount of re-enabling could address: on a task whose whole recorded span is 0.30 s, WorkTimeCanvasLayout clamps the visible window to min(minimumVisibleSpan, full.span), so the window ALREADY equals the full span and no zoom, pinch, key or handle drag can change it. The scroller nonetheless kept its Tab stop, its AXIncrement/AXDecrement, its help text and its resize/openHand cursor. It looked and announced itself as a live control and was inert. The floor stays absolute at 5 s, and the reasoning is now written down beside it: a proportional floor would zoom a 0.3 s task to the same ratio as a two-day one, and proportional zoom is not legible zoom because the axis does not scale with the task. The consequence -- a task under the floor cannot be narrowed at all -- is not a bug to hide but a state to NAME. So `canNarrow` is computed once, and when it is false the surface stops advertising: no handle buttons, no adjustable actions, no Tab stop, no resize cursor, and the slot carries the named state from the Python vocabulary instead of a dead control. Gestures that cannot change the window no longer swallow the event. This is the honesty rule the rest of the product already follows: absence is always a named state, never a dash, a zero, or a control that does nothing. 46 canvas tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
…dget A reviewer looking at the running app said the page was unreadable and the information useless. Measured on a real render, they were right: the Usage+Recording tail was a near-constant ~1,250px on every record regardless of how much work was done, and on one task that tail was 42.8% named absence, 25.5% restatement, 9.9% a raw task id, and 9.7% new fact -- twelve statements, of which seven said "we don't know" and two repeated the title. The cause was structural. The page was organised around the receipt's OWN data model -- Actions, Cost, Evidence, Gaps, Recording are `dimensions` keys -- so it faithfully reported what the recorder knew about its own recording, and almost nothing about the work. THE ASK. There was no goal field anywhere, so "completed" was unjudgeable: `objectives` is the list of SECTION TITLES, which are steps, and on most records objectives[0] is the Task title again. `task_goal` is now recorded once on a task's first section and projected to dimensions.task.goal. It is FIRST-write- wins, unlike `summary`, which is deliberately last-write-wins: a later section restating the goal must not redefine what the task was opened for, and a later section omitting it must not erase it. A task with none says so, and never borrows a step title -- answering "what was this for" with "what someone did next" is the confusion the field exists to end. That chain broke in the middle once already and nothing noticed: the MCP accepted task_goal and the receipt read it off the work item, but WorkEvent dropped it in between, so the page said no goal was recorded for tasks whose agent had recorded one. It is pinned end to end now, because only the whole chain is the behaviour. THE SUMMARY. Its description asked agents to "lead with the result, not the process", which produced changelog entries: mechanism and numbers, no purpose and no consequence. It now asks for the CONSEQUENCE first -- what a reader should believe or do -- then the mechanism, with a worked weak-vs-strong contrast in the description itself. Descriptions do the work refusals cannot: the last contract round took seven recording calls with zero refusals because the descriptions taught before the agent wrote. THE ABSENCE BUDGET. Absence stays NAMED; that rule is right and is not weakened. But it collapses to one line with its detail behind a disclosure, and may no longer occupy more space than the facts it is absent from. Two absences are exempt: a missing goal and a missing next step, because a record with no stated purpose is one a reviewer should distrust. Also: the same revision-contradiction sentence was computed per check and printed verbatim on every row that shared a revision; it is emitted once at the block level now. Check summaries clipped at a character count, cutting exactly the clause carrying the observed-vs-expected value; they clip at a sentence boundary instead. Result/exit/type/source were emitted as separate words that surfaces joined with full stops, so "Passed. Exit 0. test." read as a stutter; they are one line with a stated separator. 4,829 Python tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
… keys
The page's sections were named after the receipt's `dimensions` keys, so a
reader got Actions, Cost, Evidence, Gaps, Recording and Sources -- the
recorder's vocabulary for its own bookkeeping. It is now four sections, each
answering a question a reviewer actually arrives with:
1. What was this for -- the goal, under the title. Net-new; it takes the
slot of the dead `Recording -> TASK` row, which printed the title a third
time on every record.
2. Did it work -- one state word, its consequence, the agent's summary
and ONE exhibit, in the hero. The separate attention/evidence callout band
(302-326px) moves inside it as that exhibit slot.
3. Can I trust it -- the tier stated ONCE, coverage, and the check rows
grouped by revision.
4. What do I do now -- the recorded next step, in full, unclamped, in its
single remaining print.
Deleted: the five-tile strip (the SESSIONS tile read `1` on four of five
records); the `Usage` and `Recording` headings; the WEEKLY PLAN row; the SOURCES
chip row; the AGENTS row (its string moves into the hero meta line rather than
being dropped); the TASK ID row (into the evidence disclosure, with its copy
button); the `+N more objective(s)` suffix.
Said once instead of N times: the evidence tier went from three registers -- a
prose sentence, a bare word and a pip -- to one. `HEAD when recorded:` was
printed four times for two distinct values; the rows are grouped by revision and
each group states it once. The shared contradiction went from three
byte-identical prints to one banner. The callout's verbatim copy of a check row
is gone.
Measured on task_5f7dbea9: named absence 11.7% -> 1.9% of page height, page
7,270 -> 6,254px. Within the evidence section absence is 4.6% of the section,
inverting the 4.4:1 absence-to-fact ratio the rebuild started from.
Not fixed here, and named in the PR rather than hidden: there is no commit, repo
or PR URL anywhere on the payload, so "what do I do now" is the next step alone;
and task identity is the client session, so one task can mix unrelated work
under a single verdict. The absence budget is also still 53% on a usage-only
task -- a task with no recorded work is honestly mostly absence, and that may be
correct rather than broken.
716 Swift tests, 6 skipped (the visual baselines, stale against this page).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
# Conflicts: # apps/agentacct/Sources/agentacct/SnapshotRunner.swift # apps/agentacct/Sources/agentacct/WorkTimelineView.swift # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/PLATFORM.json # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-minimum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-minimum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-trust-unavailable-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-trust-unavailable-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-weekly-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/dashboard-weekly-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/menu-connected-dense-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/menu-connected-dense-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/menu-connected-sparse-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/menu-connected-sparse-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/setup-failure-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/setup-failure-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-disconnected-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-disconnected-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-minimum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-minimum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-weekly-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/usage-weekly-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-dynamic-type-stress-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-dynamic-type-stress-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-exact-compact-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-exact-compact-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-exact-regular-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-exact-regular-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-layout-stress-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-layout-stress-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-semantic-edge-cases-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-semantic-edge-cases-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-semantic-gallery-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-actions-semantic-gallery-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-attention-receipt-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-attention-receipt-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-accessibility-rtl-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-accessibility-rtl-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-all-passed-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-all-passed-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-compact-accessibility-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-compact-accessibility-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-compact-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-compact-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-expanded-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-expanded-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-overview-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-checks-overview-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-empty-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-empty-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-list-error-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-list-error-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-list-loading-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-list-loading-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-accessibility-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-accessibility-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-accessibility-maximum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-accessibility-maximum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-error-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-error-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-loading-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-loading-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-minimum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-minimum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-stale-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-receipt-stale-reference-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-compact-accessibility-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-compact-accessibility-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-compact-checks-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-compact-checks-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-dense-checks-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-dense-checks-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-expanded-current-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-expanded-current-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-expanded-history-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-expanded-history-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-hierarchy-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-hierarchy-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-load-failure-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-load-failure-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-retrying-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-retrying-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-rtl-accessibility-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-rtl-accessibility-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-rtl-stress-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-session-steps-rtl-stress-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-accessibility-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-accessibility-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-accessibility-maximum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-accessibility-maximum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-minimum-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-minimum-light.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-reference-dark.png # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/work-table-reference-light.png # src/agentacct/tui.py
…eproducible
test_worked_example_docs_are_in_sync regenerates the two worked examples and
diffs them against what is committed. The receipt engine renders wall-clock
times and nothing pinned a zone, so the committed files carried whichever zone
their author happened to be in -- and CI's pytest job runs TZ=UTC.
That made the test a timezone bomb: green on a machine in the right zone, red in
CI, and the diff that explains it ("13:38" -> "20:38") looks like a data change
rather than a clock. Proved both directions on this machine: the previously
committed docs FAIL under TZ=UTC and pass in local time; the regenerated ones do
the opposite.
The generator now pins TZ=UTC before it renders. Verified by generating from
three zones -- ambient, America/New_York and Asia/Tokyo -- and comparing
checksums: identical bytes. The test passes in all three.
This is the same class of defect as the visual-snapshot confound fixed in mikehasa#267,
where a rendered clock made reference images depend on the recording machine's
zone. A generated artifact can only be diffed against its source of truth if
generating it twice gives the same bytes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
MANIFEST.txt was swept into the record-page commit by a broad `git add`. It came from a re-record round that was interrupted before it finished, so it describes images that were regenerated and then discarded -- the PNGs in this directory are the untouched originals. A manifest that does not match its own directory is worse than none. It also broke the promotion tool, whose reference-destination inventory admits PNGs and PLATFORM.json and nothing else: two rollback tests failed with "reference destination inventory mismatch". That check is right, and the file was wrong to be there. A per-image checksum record is a good idea -- it would let the next re-record diff against a number instead of a memory -- but it should be produced BY a real re-record and admitted to the validator deliberately, not arrive as a side effect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
# Conflicts: # apps/agentacct/Sources/agentacct/ReceiptsPane.swift # apps/agentacct/Sources/agentacct/Theme.swift # apps/agentacct/Sources/agentacct/UsageCapacity.swift # apps/agentacct/Sources/agentacct/WorkTimeCanvas.swift # docs/examples/compare-claude-code-and-codex.md # docs/examples/when-an-agent-says-done.md # src/agentacct/cli.py # src/agentacct/receipt.py # src/agentacct/receipt_markdown.py
…lly uses The goal still did not reach the page. There are FOUR places a section's metadata becomes a work event, and the previous commit fixed three: WorkEvent's field, both of its serializers, and the item fold. The one that actually feeds the ledger -- `work_ledger._work_event`, which reads metadata directly and never constructs a WorkEvent -- was untouched, so every work item still had `task_goal: None` and the receipt printed "No goal was recorded for this task". The earlier end-to-end check missed it because it was not end to end. It hand-built a work item and fed that to `_task_dimension`, proving the reader works on an input the writer never produces. Verified now against the real builder on the real store: 2 of the stored events carry a goal, and both reach `dimensions.task.goal` through `build_work_ledger`. The regression test asserts through `build_work_ledger` for the same reason. 4,854 Python tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
CI failed a header assertion that passes on every developer terminal. The
cause was not the test: at 80 columns the `now` windows table wrapped
"fresh tokens" and "cache-read tokens" onto two lines each, and because
rich bottom-aligns headers the last header line read
window | tokens | tokens | cost | basis | sessions
— two adjacent columns with identical names, the qualifier stranded on the
line above. That is the ambiguity this branch exists to remove, so the fix
belongs in the renderer.
Both token columns are now no_wrap. This is not cosmetic: wrapping is the
one degradation that keeps the shared noun and drops the distinguishing
qualifier. Truncation degrades the other way ("fresh toke…" vs
"cache-read…"), so forbidding the wrap makes the narrow case lose the
redundant half instead of the informative one. In practice rich protects
the pinned columns and squeezes the others, so both headers survive intact
down to 60 columns.
The money column is no_wrap for a different reason. Pinning only the token
columns starved it and cost began rendering as "≈$1,554…", which is worse
than an ambiguous header: it reads as a real figure but could be $1,554.67
or $1,554,000. Six columns do not fit in 80, so this fixes the sacrifice
order — what gives way is `sessions` and `cost basis`, whose truncation is
visible and whose meaning survives it. Same treatment in the by-client and
top-models tables.
Also fixes why this reached CI at all. The test tried to pin its width with
monkeypatch.setenv("COLUMNS", "200"), which cannot work: rich reads COLUMNS
once inside Console.__init__ and freezes it in _width, and cli.console is
built at import, long before any test body runs. The pin was a silent no-op
and the test really rendered at whatever width launched pytest.
That was a whole class of latent failure, not one test. A conftest fixture
now pins the render width to 80 — the standard narrow terminal and the
width CI uses, deliberately not a roomy one, since pinning wide would have
hidden this defect. Typer's help and usage-error output builds its own
console from rich_utils.MAX_WIDTH, pinned alongside.
Before the fixture the suite failed 15 tests at 60 columns and 19 at 40.
Now 40, 80 and 200 all return 4854 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
The six visual suites have been failing since mikehasa#267 made them able to run again. They were not wrong: the references date from ea75e2d and this branch has since rewritten the record page, added the verdict and gap lines, fixed focus visibility and the counting copy, and — the reason the churn is pane-wide rather than confined to Work — introduced Metrics.pageMaxWidth, a content cap applied to every pane. Dashboard and Usage moved 22-27% of pixels without either pane being edited directly. Checked first that the churn was not a regression: no Space/radius/Metrics constant changed value, and the deliberate 0.5pt falsification mutation from an earlier session is confirmed absent from the tree. The two size changes are both explainable — menu-connected-sparse 880->990 is the recent-sessions block plus a wrapped cost-basis legend, setup-failure 1028->1044 one added line. Reviewed representative references from every changed suite rather than trusting the green verify, which the record script correctly calls tautological. Menu, Work receipt, Dashboard and Usage all hold the design laws: the decision and evidence axes stay two colours, pip shape carries the evidence tier, cobalt appears only on controls, green only on the externally-verified trust rail, and absence is named everywhere it occurs ("not gradeable", "no usage recorded", "Reset time not reported", "revision not captured") rather than shown as a zero. The Work receipt reference also confirms the task_goal fix end to end: the page renders "Agent-reported · Review the generated PNG matrix" where it previously said no goal was recorded. That field had to be carried through four separate serializers; this is the first proof it survives all the way into a rendered pixel. PLATFORM.json is rewritten by the recorder from what it observed, so it now carries a real host, commit and timestamp instead of the hand-written "unknown" left behind when the directory was renamed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
Two conflicts, both resolved to keep BOTH sides' intent rather than picking a winner. apps/agentacct/Sources/agentacct/MainWindow.swift — main (mikehasa#292) re-added RecordingHealthNoticeStack as a top-trailing overlay and gave it a new `restart:` control; this branch had deliberately moved that same stack out of the overlay and into the page flow, because as an overlay it covered the Signal rail's own title with a card sharing the fill and hairline of the card beneath it, and the design language has no elevation to separate the two layers (K09). Taking main's side would have rendered the stack twice and reinstated the occlusion; taking ours would have dropped the restart control silently, since `restart` defaults to nil and omitting it is not a build error. Kept the in-flow placement and carried `restart: recorderRestart` onto it. tests/test_work_ledger.py — a purely additive collision: both sides append new tests at the same point. Kept all seven (our four, main's two plus its helper). Verified on the merged tree rather than assumed: pytest 4896 passed · swift test -c release 725 passed, 6 skipped · visual references still match, so mikehasa#292's UI change does not re-stale them · task_goal confirmed intact through all four serializers, which auto-merge could have broken silently since main rewrote 150 lines of work_ledger.py. orjson is a new runtime dependency from main (declared in pyproject); a stale venv fails collection on 103 modules until it is installed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
Removes every rule that inferred the meaning or quality of agent-written
text from word lists, regexes, fuzzy comparison or character counts.
semantic_rules.py goes from 1,306 lines to 920; the only regex left in it
strips control characters.
Why. Measured against this branch before removal: on 40 hand-labelled
summaries from a real ledger the classifiers agreed with a human 25-50% of
the time, against 88% for always answering "useful". They would have
attached an advisory to 94% of completed sections, which is an advisory
that carries no information. The refusals ran backwards: "Bumped requests
to 2.32.3; CI green." was refused at 36 characters while "I did some work
on this and then I finished doing it." passed, and a failure summary of
"got 3, want 4" -- the exact observed-vs-expected the rule asked for -- was
refused for being short. Honest review outcomes ("Reviewed the auth flow:
the token is validated before the redirect, so the reported bypass is not
reachable") were told they described process. And nothing anywhere showed
an advisory changing what an agent wrote on a later call.
A 15-agent experiment (three contract designs, blind judges, one partly
fixed bug) found content completeness at ceiling under every design,
including bare one-line descriptions. What the long field descriptions
bought was density -- 1,784 characters and 32s to read against 3,192 and
69s -- so the descriptions stay and the grading goes.
Removed: the outcome/process/status and consequence word lists with their
advisories; the 40/20/20-character floors (presence is still required --
a terminal section owes a readable summary, a blocked one a blocker, a
failed check a description); the generic-check-name list that refused
"e2e"; the fuzzy name/result echo detector with its refusal, advisory and
display drop; the goal-echoes-title and thin-goal advisories; and the
contrast-marker list that decided which sentence of a check summary "is
the finding". Previews still clip at whole sentences.
Two things the review found that a grep would not:
receipt_markdown re-parsed the reducer's own attention label by word
matching, and ran the same matcher over step titles agents wrote, so
"Triage the release blocker in CI" lost the word "Blocker". The payload
already carries `kind`; only a failed check's reducer-built label is split
now, and an agent's title prints as written.
The echo detector was also hiding something that was never agent prose:
released servers filled an omitted summary with the literal
"<name>: <result>". That is a machine format and is inverted exactly by
check_recorded_summary, with a test pinning that anything merely
resembling it ("pytest passed", "pytest: passed.") is shown untouched.
Kept: every structural rule (presence, enums, path shape, the placeholder
refusal, the file anchor), hygiene, the field descriptions, and the
structured fields and their serializers.
verify-fixes.py now reads 29 of 30, up from 28 on the previous commit: its
blocked fixture never named files, so the kept file-anchor rule refused it.
The remaining miss replays the recording machine's own live ledger against
a <1% bar that predates its history; it fails identically without this
change, and REVIEW_GUIDE.md now says so instead of the transcript claiming
"30 of 30 -- all claims verified".
pytest 4870 passed at 40 and 200 columns.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
Every other test of the recording contract checks what the server does with a record. None could say whether a real agent, given only what agentacct ships, leaves behind something a reviewer can use -- and the audit of the word-list layer found the repo had no after-measurement for any contract change. This is that measurement. benchmarks/agent_recording drops a real headless agent into a small sandboxed repository whose task never mentions recording, serves it ONE checkout's MCP server and instruction block (--src picks whose, so a change can be measured before and after), and grades the result twice: objectively, by comparing what the record claims with the repository's diff and the harness's own run of the verification command; and with a blind judge on the four questions a reviewer opens a record with. Neither grader inspects wording with word lists. Five scenarios, each a distinct recording situation: a clean fix, a partial fix where half the ask is out of bounds, a reported bug that is not a bug, a blocked task, and two asks of which only one has a test. Adapters for Claude Code and opencode are verified; Codex and Gemini are written but unverified here (out of quota; CLI refused the account). Three guards exist because live runs did the thing they guard against: - agent_env sets PWD. subprocess(cwd=) does not, and opencode trusted PWD: its first run worked in the REAL repository and read this eval's ground truth. It changed nothing, and declined to edit the fixture. - verification ignores conftest.py, and off-limits paths are restored before reality is judged. On partial_fix agents met "do not edit vendor/" by monkeypatching the vendored function -- from a conftest, and from vendor/__init__.py -- and then recorded CI as green. What 38 runs found (README has the detail). Removing the word lists did not change what agents record: four of five scenarios score identically before and after, and on the fifth the structured record is identical in all 12 runs. Replayed over what the agent wrote under the old contract, the word lists would have told it to rewrite 4 of 8 summaries, including both correct "not a bug" conclusions the judge scored 6 and 7 of 8. The findings that matter are about the contract. A partial result is recorded as a success 12 times out of 12 -- completed, no failed check, no next_step -- which the word lists never touched. Checks are recorded when something is fixed, not when something is found. The title is frozen at `started`, before the outcome is known. And `files` invites a false claim on work that changed nothing. CI runs the deterministic half (29 tests: scenarios are what they claim, the objective grader tells an honest record from a misleading one, the guards hold). The live half is opt-in via AGENTACCT_LIVE_AGENT_EVAL. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4
# Conflicts: # apps/agentacct/Tests/agentacctTests/ReferenceImages/macos-26-xcode-26.6-arm64-2x/PLATFORM.json
Merging main brought the `ingestion_healthy_sources` and `ingestion_degraded` fixture lanes (the Diagnostics review matrix from mikehasa#294). They were recorded before this branch moved the source, watcher and overall state wording into the reducer (`state_title` / `state_detail`), so on this branch every row rendered "Source state not reported". Attach the reducer's copy to those two lanes with `with_ingestion_state_copy` — the same function the daemon uses — and re-record the four Sources references on the canonical renderer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Collaborator
Author
|
Merged
🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #211, which is contained in this branch. Two halves that only work together: rules that make a record complete (#211) were never enough, because a complete record can still be meaningless — so this also makes the contract ask for meaning, and rebuilds the page that displays it.
Why
A reviewer using the app said: "noone will understand the page", "all of these are useless info". Measured on a real render, they were right. On
task_5f7dbea9the Usage+Recording tail was a near-constant ~1,250px regardless of how much work was done, and it broke down as:Twelve statements, of which seven said "we don't know" and two repeated the title. The cause was structural: the page's sections were named after the receipt's own
dimensionskeys — Actions, Cost, Evidence, Gaps, Recording — so it faithfully reported what the recorder knew about its own recording.The write path
A goal, at last. There was no goal field anywhere, so
completedwas unjudgeable:objectivesis the list of section titles, which are steps, and on most recordsobjectives[0]is the task title again.task_goalis recorded once on a task's first section. It is first-write-wins, unlikesummarywhich is deliberately last-write-wins — a later section restating it must not redefine what the task was opened for. A task with none says so and never borrows a step title.That chain broke in the middle once and nothing noticed: the MCP accepted it and the receipt read it off the work item, but
WorkEventdropped it in between. It is pinned end to end now, because only the whole chain is the behaviour.summaryasks for consequence. Its description said "lead with the result, not the process", which produced changelog entries. This one was fully compliant and the reviewer called it meaningless:All mechanism, no purpose, no cost. The description now asks for the consequence first, with a worked weak-vs-strong contrast in the description itself. The description is the mechanism: across 38 live agent runs (below) no recording call was refused by a rule, and the records came back shaped by what the descriptions ask for. What an agent produces under the new description, reading only the tool descriptions:
Pre-rules history is accepted, not backfilled. Rules bind new writes; inventing summaries for work nobody remembers is fabrication. Those records are named as predating the rules rather than implied to be sloppy.
Prose is asked for and shown, never graded
An earlier revision of this branch also graded what agents wrote: 13 detectors over 119 hard-coded phrases decided whether a summary "stated an outcome" or "named a consequence", refused summaries under 40 characters, refused a check named
e2eas "too generic", and picked which sentence of a check summary "is the finding". That layer is gone (7ad4c1a, −889 lines;semantic_rules.py1,306 → 920, its only remaining regex strips control characters).It was measured before it was removed. On 40 hand-labelled summaries from a real ledger the classifiers agreed with a human 25–50% of the time, against 88% for always answering "useful"; they would have attached an advisory to 94% of completed sections; they refused "Bumped requests to 2.32.3; CI green." while accepting "I did some work on this and then I finished doing it."; and they told honest review outcomes that they "described process". Nothing showed an advisory ever changing a later write.
Every structural rule stays — required fields, enums, path shape, the placeholder refusal, the file anchor — as do hygiene, the descriptions, and the structured fields. Two things the review found that a grep would not: the markdown renderer re-parsed the reducer's own attention label by word matching and ran the same matcher over titles agents wrote (it now splits on the payload's
kind); and the old echo detector had been hiding a server artifact — released servers filled an omitted summary with the literal"<name>: <result>"— which is now inverted exactly, as the machine format it is.Measured with real agents
benchmarks/agent_recordingruns real headless agents (Claude Code and opencode verified; Codex and Gemini adapters written, unverified on this machine) in sandboxed repositories whose task never mentions recording, serves them one checkout's MCP server and instruction block, and grades the result twice: objectively, against the repository's diff and the harness's own run of the verification command; and with a blind judge on the four reader questions.--srcpoints it at a different checkout, which is how the removal above was tested before and after.The removal changed nothing agents record. Four of five scenarios score identically (read time 20.9s vs 21.0s); on the fifth the structured record is identical in all 12 runs, and the judge gap has no mechanism behind it and is within noise (3/6 vs 0/6, p≈0.18). Replayed over what the agent wrote under the old contract, the word lists would have told it to rewrite 4 of 8 summaries — including both correct "this is not a bug" conclusions, which the judge scored 6 and 7 of 8.
What it found instead is the real to-do list, and every item is about shaping the ask:
vendor/, the agent monkeypatched the vendored function, then recordedcompleted, no failed check, nonext_step. The record reads green over a red repository. The word lists never touched this.started, before the outcome is known — "Fix is_business_day wrong for 2026-07-03" still heads a record whose conclusion is that nothing was wrong.filesinvites a false claim on work that changed nothing.Three harness guards exist because live runs did what they guard against:
PWDis set (opencode trusted the inherited one and first ran in the real repository, where it read the eval's ground truth — it changed nothing); verification ignoresconftest.py; and off-limits paths are restored before reality is judged.The page
Four sections, each answering a question a reviewer arrives with — what was this for / did it work / can I trust it / what do I do now.
HEAD when recorded:Passed. Exit 0. test. Agent-reported— four fragments punctuated as sentences — is one separated line. Summaries clipped at a character count, cutting exactly the clause carrying observed-vs-expected; they clip at a sentence boundary. Deleted: the five-tile strip (its SESSIONS tile read1on four of five records), theUsageandRecordingheadings, the WEEKLY PLAN and SOURCES rows, and theRecording ▸ TASKrow that printed the title a third time.Three latent CI failures this found, unrelated to the change
TZ=UTC. Green on a machine in the right zone, red in CI, with a diff (13:38→20:38) that reads like a data change. The generator now pins UTC — verified by generating from three zones and comparing checksums.nowwrappedfresh tokensandcache-read tokensonto two lines each, and since rich bottom-aligns headers the last header line readtokens │ tokens— two adjacent columns named identically. The test guarding exactly this tried to pin its width withmonkeypatch.setenv("COLUMNS", ...), which cannot work: rich readsCOLUMNSonce insideConsole.__init__andcli.consoleis built at import, so the pin was a silent no-op and the test really rendered at whatever width launched pytest. Green on every developer terminal, red in CI. Fixed in the renderer — both token columns and the money column areno_wrap, so the narrow case drops the redundant half (fresh toke…) instead of the informative one (tokens), and a starved money column can never render≈$1,554…— and in the harness, where a conftest fixture pins the render width to 80. Before it, the suite failed 15 tests at 60 columns and 19 at 40; now 40, 80 and 200 return the same verdict.MANIFEST.txtin the reference directory from an interrupted re-record, describing images that were discarded. It broke the promotion tool's inventory check; that check was right.Verified
Every CI step run locally under CI's own environment (
TZ=UTC, release config), because the failures on #267 all lived in shell and Python scriptsswift testnever executes.pytest4,899 passed, 1 skipped (the opt-in live eval) ·swift test -c release725 passed, 6 skipped · all four CI scripts pass ·visual-snapshots verifymatches · tree byte-clean afterwards, which is the property that actually failed before.The 96 visual references, re-recorded
#267 made the six suites able to run, and running them showed the references were stale against every pane, not only this page:
Metrics.pageMaxWidthcaps pane content width, so Dashboard and Usage each moved 22–27% of pixels without being edited directly.Re-recorded on a host matching the pinned axes, after first ruling out a regression — no
Space/radius/Metricsconstant changed value, and both size changes are explainable (menu 880→990 is the recent-sessions block plus a wrapped cost-basis legend; setup-failure 1028→1044 is one added line). Reviewed per suite rather than trusting the green verify, which the record script itself calls tautological.The Work receipt reference is also the first end-to-end proof of the goal fix: the page renders
Agent-reported · Review the generated PNG matrixwhere it used to say no goal was recorded — a field that had to survive four separate serializers to get there.Known gaps, stated rather than hidden
verify-fixes.pyreads 29 of 30. The miss replays the recording machine's own live ledger against a <1% bar that predates its history (6.6% here, by the file-anchor rule and a few missing summaries); it fails identically without this branch's last changes, andREVIEW_GUIDE.mdsays so rather than the transcript claiming 30 of 30.🤖 Generated with Claude Code
https://claude.ai/code/session_01LJgWrWqv5QB2JbzLdx9JG4