feat(bin): record per-card worker time in a work ledger and report only drift edges - #8
Merged
Merged
Conversation
added 2 commits
September 18, 2026 19:07
…ly drift edges Append one row per turn boundary, dispatch, PR-ready registration, and merge to state/work-ledger/<id>.events from the scripts that already run at those moments, so a card's cost is on record without anyone remembering to write it. Capture never blocks or fails a turn, and a harness that reports no turn boundaries is recorded as unmeasured rather than as zero. bin/fm-work-ledger.sh copies local homes' ledgers into the primary home's store and is a registered check that prints only edges: a card crossing 3x or 6x its rating's budget, capture going dead for a home, and a five-card lane digest. A run in which nothing changed prints nothing. Retiring a second mate copies its ledger first and refuses when the copy fails.
There was a problem hiding this comment.
🔵 Needs a closer look
Critical ledger-preservation issues and moderate correctness gaps remain unresolved.
Pull request overview
Adds an edge-triggered per-card work ledger for tracking worker time, lifecycle events, and drift reporting.
Changes:
- Records turn, spawn, PR-ready, merge, and retirement events.
- Adds ledger copying, validation, cursor-based reporting, and primary-only arming.
- Adds lifecycle tests, documentation, and agent guidance.
File summaries
| File | Description |
|---|---|
tests/fm-work-ledger.test.sh |
Tests ledger behavior and reporting. |
tests/fm-teardown.test.sh |
Tests teardown preservation. |
tests/fm-secondmate-lifecycle-e2e.test.sh |
Tests secondmate retirement handling. |
tests/fm-busy-adapter-wiring.test.sh |
Tests spawn and capture wiring. |
docs/documentation-audiences.json |
Registers documentation audiences. |
docs/configuration.md |
Documents ledger configuration. |
bin/fm-work-ledger.sh |
Copies ledgers and evaluates drift edges. |
bin/fm-work-ledger-lib.sh |
Defines ledger rows and append behavior. |
bin/fm-test-run.sh |
Registers the ledger tests. |
bin/fm-teardown.sh |
Preserves ledgers during teardown. |
bin/fm-spawn.sh |
Records spawn metadata and ratings. |
bin/fm-pr-check.sh |
Records PR-ready events. |
bin/fm-merge-outcome-lib.sh |
Records merge outcomes. |
bin/fm-busy-event.sh |
Records worker lifecycle events. |
AGENTS.md |
Adds ledger workflow guidance. |
.agents/skills/work-ledger/SKILL.md |
Defines ledger wake handling. |
Review details
- Files reviewed: 8/16 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Intent
We need to be able to see whether agent-run work is drifting - whether a card is taking far longer than its difficulty should warrant - while it is still happening, rather than reconstructing it afterwards.
The measurement has to be a hook rather than a habit, and it must not require anyone, human or agent, to remember to record anything.
What is measured is difficulty per active hour together with tasks completed - not lines, and not tasks per hour.
One constraint on the approved design, verbatim: "Let's just make sure that does not ping you too often."
What changed
bin/fm-busy-event.sh- inside its existing lock, after the record write (or the retirement) succeeded,arm,applyandretireappend one row to<state>/work-ledger/<id>.events. A failed append never changes the exit code or output; it is counted inwork-ledger/.errors.bin/fm-spawn.sh- onespawnrow per launch (relaunches included) with harness, model, kind, parent,capture=supported|unsupported, and the card's frozen rating read from(rating: ...)/(parent: ...)fields in the backlog title. No rating recordsrating=none; the card is unrated, never dropped.bin/fm-pr-check.sh,bin/fm-merge-outcome-lib.sh-pr-readyandmergedrows, stamped by firstmate's clock. The merged row shares the merge outcome's existing deduplication.bin/fm-work-ledger-lib.sh(new) - single owner of the row format and the never-fails-a-turn append.bin/fm-work-ledger.sh(new) -copy,check,arm,disarm. Copies local homes' ledgers intodata/work-ledger/<lane>/, keeps a cursor, and prints only edges.armwrites the staticstate/work-ledger.check.shshim, registers it throughbin/fm-check-register.sh, and refuses in a second mate's home.bin/fm-teardown.sh- retiring a second mate's home copies its ledger first and refuses the removal when the copy fails.work-ledgerskill (what to do on each wake, the title fields), one trigger line and three layout lines inAGENTS.md, and a section indocs/configuration.md.How often it pings
Edge-triggered only. There is no timer and no periodic summary: elapsed time alone never produces output.
Every line is recorded in the cursor before it is printed, and a line is printed only if the cursor landed, so a store that cannot be written goes quiet rather than repeating every sweep.
Nothing is reported per merge, per harness, per model, or per agent.
Two easy-to-get-wrong points
capture=unsupportedand the card is unmeasured, never zero. Codex children stay hooks-disabled; nothing here tries to enable them. "Supported" is read from whether spawn armed the busy-state contract, so it cannot drift from spawn's own decision.Decisions worth a second look
row=kind (arm|turn|retire|spawn|pr-ready|merged), which the report's example row did not have; the reader needs it.(kind: ...). I checked tasks-axi 0.2.5: there the fields survive reads and state transitions, whereas after(kind: ...)they break kind parsing. Spawn reads the title withshow --fullbecause plainshowtruncates long titles.stop,stop-failureandafter-agent. An idle event arriving while already idle is otherwise normal - a live Claude turn endsstopthensession-end- so a broader rule would have marked almost every card incomplete.bin/fm-work-ledger.sh armis a one-time step in the primary home.Verification
tests/fm-work-ledger.test.sh(new, 17 cases): rows follow the record's seq; a stale incarnation is not appended; a relaunch keeps both incarnations in one file; a failed append is silent, counted, and exits 0; 12 concurrent appends neither interleave nor skip a seq; the check is silent on repeated runs when nothing changed; copy is idempotent and ignores a partial trailing line; over-budget fires once per level; sub-cards fold into the parent by union, not sum; unrated and unmeasured cards never alarm; capture dead needs two sweeps and fires once per episode; a seq gap and a live record running ahead are detected; no per-merge output and no digest before five cards; a second mate's lane is copied without writing to it; arm is primary-only; the retirement copy fails closed; a merge is stamped once.tests/fm-teardown.test.sh: teardown leaveswork-ledger/intact, including the PR-ready row from the realfm-pr-check.sh.tests/fm-secondmate-lifecycle-e2e.test.sh: retirement refuses while the ledger cannot be copied, then keeps it after the home is removed.tests/fm-busy-adapter-wiring.test.sh: a realfm-spawnrecords the frozen rating and parent, an unrated card, and Codex ascapture=unsupportedwith no turn rows.claude -pturn with the same hook shape spawn installs wrotearm,user-prompt-submit,stop,session-endrows in order.FM_LINT_JOBS=1 bin/fm-lint.sh,bin/fm-doc-audience-check.sh, andbin/fm-test-run.sh --check-coveragepass.Not done here: the live re-check on Pi 0.85.1 and the five-card calibration overlap against the transcript method (report section 6, item 6). Both need real cards to run.
Follow-up work, deliberately not in this change
Any idle alarm; capture for Codex, Cursor, muse, Grok, Rovo, Kimi or agy; the orchestrator's own time; remote second mates; tokens, cost, lines, test or step counts; any per-harness, per-model or per-agent aggregate; trend alarms; any automatic action on a card; the plan-drift consumer.
Also noticed: a
local-onlylanding throughbin/fm-merge-local.shwrites nomergedrow, so those cards never complete in the ledger; and a second mate retiring its own child home copies into its own store, which the primary does not read.