Skip to content

feat: resolve journal blockers by entry id and stop listing stale ones - #424

Draft
levifig wants to merge 4 commits into
mainfrom
claude/gracious-cori-08ce27
Draft

levifig wants to merge 4 commits into
mainfrom
claude/gracious-cori-08ce27

Conversation

@levifig

@levifig levifig commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

The continuity digest's unresolved-blockers list only grew. A blocker cleared only when someone logged an unblock with exactly the same scope, and scopes are often generic (ship, review), so that rarely happened. On 2026-10-22 this repository had 24 open blocks; 13 of them cite work that has since merged or closed, and blocks sharing a scope hid behind the newest one.

  • An agent can now resolve one block by its entry id: loaf journal log "unblock(<block id>): <what cleared it>". Only that block is resolved; other blocks with the same scope stay open. The block itself is never edited. unblock(<scope>) still resolves every open block with that scope.
  • Each open block is listed on its own line with the id that resolves it, plus a one-line hint on how to resolve one.
  • An unblock that names an unknown entry, an entry that isn't a block, or a block that was already resolved is refused with a clear message and writes nothing. Under --from-hook it warns instead, as unmatched unblocks already did.
  • The default digest lists blockers from the last 30 days and counts older ones next to the command that lists them all. This is fix: keep the continuity digest within harness injection limits #422's compact digest, so it applies to the plain CLI, every SessionStart hook and post-compaction resumption. --layer unresolved-blockers still lists every blocker in full. --json keeps its fields and adds stale_count and a per-item stale flag.
  • Ship now resolves, by id, the journal blockers a landing cleared. Implement resolves those about resumed work that no longer hold.

Native work

Implements #420.

Verification

Ran on c5276d8, which is #420 rebased onto main after #422 landed, with the 30-day collapse moved into #422's renderer and the re-review nits fixed. The first round on 89da3ba is superseded.

  • Fixture tests for unblock by id. An id unblock clears exactly that block and leaves another block with the same scope open. An exact-scope unblock still works, also after id unblocks. Refusals cover:

    • unknown ids (minted and legacy formats);
    • non-block ids;
    • already-resolved blocks;
    • imported blocks with no scope.

    Refusals write nothing, and a block whose scope merely looks like an id is resolved by that scope. Tests: TestJournalUnblockByEntryIDResolvesExactlyThatBlock, TestJournalUnblockByIDThenScopeLeavesNoStrayDiagnostics, TestJournalUnblockEdgeShapesAgreeWithTheDigest, TestJournalLogUnblockByEntryIDAndBlockerLayerIDs. The existing exact-scope tests pass unchanged.

  • Fixture tests for the age limit. With a fixed clock, blocks more than 30 UTC calendar days old are flagged and counted, including past the shown page. The day boundary is exact: 30 days is still listed, 31 is not. Tests: TestJournalContextFlagsBlockersOlderThanTheAgeLimit and TestJournalContextAgeDaysCountsUTCCalendarDays.

    • TestJournalContextDefaultPathsAgeRealBlockersByTheReadClock logs real blocks and moves the read clock forward. Through the plain CLI and the Claude Code SessionStart hook, the digest then shows showing 0 of 2 (more: … --limit 2; 2 older than 30 days not listed) with no block ids, while --layer unresolved-blockers lists both.
    • TestJournalContextDefaultDigestCountsStaleBlockersInsteadOfListingThem covers the mixed and all-stale renders.
  • Resolving this repository's stale blockers: not run live. The check ran on a byte copy of the live journal, which was kept read-only. On the copy, 13 blocks were resolved by id: 11 cite merged or closed work (Verify native work before entering implementation #267/chore: retire legacy Amp modes and forced delegation #314, Record harness provenance and local gateway outcomes (#286) #361, build: emit JavaScript adapters and require Node 24+ #308 ×3, Retire the inert Amp loaf-modes plugin #309, fix: label implementation threads as implementer #280, fix: isolate CI tests and prioritize local verification #253, feat: integrate the vNext Loaf baseline #225, docs: codify shippable Git history #212) and 2 record their own resolution. The digest then reads unresolved-blockers: showing 0 of 11 (more: … --limit 11; 11 older than 30 days not listed), with no diagnostics, at 4,229 bytes. The live run waits for a decision; see the Ship comment.

  • Skill changes pass the authoring checklist. Ship and Implement edits are body-only, use harness-neutral prose and the skill's own journal command form, and change no frontmatter, sidecars or references; the journal template's links are unchanged. The dist/ and plugins/ copies are regenerated and committed.

  • env -u LOAF_DB make verify-local passes on c5276d8.

Also ran:

  • Before the fix: the installed binary refused unblock(<id>) ("no matching block exists"), and its layer showed only the newest ship block, without an id.
  • Dogfood on throwaway state: with an isolated LOAF_DB, unblock by id resolved one of two ship blocks. The three bad cases were refused with their messages and exit 1.
  • Live digest, read-only: with 7d9ea21, which has the same renderer output as c5276d8 whenever a blocker is listed, it is 5,944 bytes. It lists 7 recent blockers with the resolve hint and counts 17 older ones.
  • Changelog coverage: loaf check issue-changelog passes for Let agents resolve journal blockers by entry and stop listing stale ones #420 against origin/main.

Ship

Pending Ship review.

Migration / risks

  • No schema change. An id unblock is an ordinary unblock entry whose scope is the block's id.
  • Older binaries: an id unblock shows up as an unmatched-unblock diagnostic, and the block stays listed, until the reading binary has this change. Upgrade the installed loaf before resolving the live journal's blockers by id.
  • Count change: the blockers layer now counts each open block, so it can be larger than the old per-scope count (24 instead of 19 here). previous_unblock_id now names the unblock that last resolved a block with the same scope before this one opened.
  • Age limit: a real blocker older than 30 days drops out of the default digest. The count line and its command keep it reachable.

Out of scope

Fixes #420


Agent notes

levifig added a commit that referenced this pull request Oct 22, 2026
…ssages

Review of #424: Implement now lists blockers with --layer unresolved-blockers because the default digest only counts those older than 30 days; Ship resolves blockers independently of local reconciliation; the not-a-block and scope-less block messages read correctly; the stale count line says "list them". Adds tests for an id-shaped scope, a scope-less imported block, a historical duplicate id unblock and a truncated collapsed page.
@levifig

levifig commented Oct 22, 2026

Copy link
Copy Markdown
Owner Author

Ship: request changes on 89da3ba. An independent reviewer found no code defects. The write check and the digest agree in every case it traced. One Verification box on #420 isn't met as written, and the PR still has to move onto #422's new digest renderer.


Agent notes
  • Reviewed: 89da3ba0 by a fresh read-only reviewer against Let agents resolve journal blockers by entry and stop listing stale ones #420; criteria DoD 1–6 and V1, V2, V4 met, V5 claimed (verify-local on 89da3ba), V3 not met as written.
  • Fixed in cbdbbce1:
    • Implement names loaf journal context --layer unresolved-blockers, because the default digest only counts blockers older than 30 days.
    • Ship step 4 runs independently of local reconciliation.
    • The not-a-block message reads "its type is X, not block", and a scope-less imported block gets its own message.
    • The count line says "list them".
    • New tests: id-shaped scope, scope-less block, historical duplicate id unblock (read-time diagnostic), and a truncated collapsed page.
  • Accepted as is: the --limit cap at 100 (paging continues past it); RFC3339Nano string ordering (pre-existing, same assumption as the old check); mixed old and new binaries (covered by the upgrade-first note).
  • Still owed before merge: box 3 decision; rebase onto fix: keep the continuity digest within harness injection limits #422 and port; re-run env -u LOAF_DB make verify-local and re-review the delta.

An unblock can now name one block by its entry id, which resolves only that block; unblock by scope still resolves every open block with that scope. The unresolved-blockers layer lists each open block separately, led by its id. An unblock naming an unknown entry, a non-block entry or an already resolved block is refused with a clear message and writes nothing. The default digest lists blockers from the last 30 days and counts older ones with the command that lists every blocker. Ship and Implement now resolve, by id, the blockers that landed or resumed work cleared.

Fixes #420
…ssages

Review of #424: Implement now lists blockers with --layer unresolved-blockers because the default digest only counts those older than 30 days; Ship resolves blockers independently of local reconciliation; the not-a-block and scope-less block messages read correctly; the stale count line says "list them". Adds tests for an id-shaped scope, a scope-less imported block, a historical duplicate id unblock and a truncated collapsed page.
After #422 the default digest, every SessionStart hook and post-compaction resumption render through renderJournalContextDigest, so the 30-day blocker collapse moves there: aged-out blockers are counted next to the layer's list command instead of listed, and listed blockers carry the resolve-by-id hint. The --layer read lists every blocker in #422's [date] id order. A Runner clock lets a test age real blockers and drive the CLI and Claude Code hook paths end to end.
@levifig
levifig force-pushed the claude/gracious-cori-08ce27 branch from cbdbbce to 7d9ea21 Compare October 22, 2026 00:52
Review nits on #424: the digest's resolve-by-id hint now appears only when packing lists at least one blocker; Ship and Implement read blockers with --limit 100 so older ones are not hidden behind the first page; the #345 changelog clause no longer contradicts #420's JSON additions; renderer tests cover a limit-truncated page and a budget cut with stale blockers present.
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.

Let agents resolve journal blockers by entry and stop listing stale ones

1 participant