Repository navigation
feat(consolidate): propose curated candidates from capture Records (#368) - #376
Merged
Merged
Conversation
`amem consolidate` read only curated notes, so a decision or fix an agent recorded in its final answer stayed in capture until someone retyped it. It now also proposes candidates from the newest capture `Record` rows of the project. - The source window is `readBriefRecords()` at its 32-row ceiling, so a proposal costs no more than the SessionStart read already pays. Its `partial` flag is reported as `records.truncated`, with a `ponytail:` note naming a dedicated bounded Record scan as the upgrade path. - A row is eligible only when its stored line re-parses unchanged as one canonical `amem:` line. Anchors come from that line, not from the row's 400-byte suffix-truncated `paths` copy. - Grouping uses `sha256(kind, anchors)`, or `sha256(kind, text)` when the Record carried `-` anchors, so two different empty-anchor Records stay two candidates and one turn's two Records are two distinct sources. - Kind mapping adds no curated kind: decision/contract/finding become a `memory` candidate with that note kind, fix a `finding` titled `fix:`, and fail a `fail` candidate whose symptom is the recorded left group. - `CandidateInput` gains optional `about` and `record` fields. `canonical()` drops undefined, so every existing candidate id, hash and payload is unchanged and no index or format version moves. - A group key already pending or accepting is skipped as `alreadyQueued` and an already decided one as `alreadyReviewed`, so a repeat run writes nothing. Each source row and each built input take the existing guarded policy screen and `check()` fence. - Acceptance proves the quoted rows through `CandidateAcceptance.recordSource`, before `beginAcceptance` and again inside the append section, and refuses with a named next step when a row changed or left the window. No capture write, no format floor and no SessionStart change.
Names the #368 window, grouping key, kind mapping, counters and acceptance recheck in `docs/contracts.md`, and adds the README sentence a person reads before running `amem consolidate`.
Codex Review Summary: ❌ FailedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…retry condition Review round 1 (claude, task 224): two behaviour findings and a missing CHANGELOG entry. - I1, acceptance could deadlock. The recheck read the same 32-row brief window `amem consolidate` reads, so once 32 newer Records existed every cited row answered "missing" — and the next step the refusal named (rerun consolidate) reads that window too, so nothing could clear it. `CaptureStore.readRecordTurns` now point-reads the cited turns themselves: 1..8 turns, a 9-row cap each, point lookups on the `at` index, no scan and no brief window. Only a row pruned or forgotten from capture answers missing, and the refusal no longer claims the window as the cause. - I2, the fail mapping dropped the recorded retry condition. The grammar is `<symptom/cause> -> <fix/retry-when>`, so the right group is exactly `retryWhen`; it is no longer discarded, and only `approach` and `cause` stay empty for the reviewer, which keeps the empty-field relaxation to the two fields a Record cannot source. - I3, add the Unreleased CHANGELOG entry: Refs #368, naming the `records` report fields and the additive `about`/`record` candidate inputs, and stating that existing candidate ids are unchanged. A volume fixture (41 Records over five sessions, then 33 newer ones) pins I1 end to end: with the window-based recheck restored the test fails at the missing refusal, and with the retry condition dropped the mapping test fails on retryWhen.
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.
Context
amem consolidateread only curated notes. A decision or fix an agent recorded in its final answer stayed in capture until a person retyped it, which is the last gap left by #313 item 7 and #369 (the Record rows now exist; nothing proposed from them).Closes #368. Refs #313, agent-hub#375.
Change
amem consolidatealso proposes candidates from the newest captureRecordrows of the project. The window is the brief one —readBriefRecords()at its 32-row ceiling, main-session, project-bound, newest first and index-backed — so a proposal costs no more than the SessionStart read already pays. Itspartialflag is reported asrecords.truncated, with aponytail:note naming a dedicated bounded Record scan as the upgrade path.excerptre-parses unchanged as exactly one canonicalamem:line. Anchors come from that line, not from the row'spathscopy, which is suffix-truncated at 400 bytes. Subagent rows, rewritten lines, malformed lines and screened history are counted asrecords.excluded.sha256(kind, anchors); adecision/contractwith-anchors has no anchor set and groups bysha256(kind, text)instead, so two different empty-anchor Records stay two candidates.decision/contract/findingbecome amemorycandidate with that note kind,fixamemorycandidate with note kindfindingtitledfix: ..., andfailafailcandidate whosesymptomis the recorded left group and whoseretryWhenis the recorded right group.CandidateInputgains optionalaboutandrecordfields, andCandidateAcceptancean optionalrecordSource.canonical()drops undefined, so every existing candidate id, hash and payload is unchanged, and no index or format version moves.records.alreadyQueued, and an already accepted or rejected one asrecords.alreadyReviewed, so a repeat run over the same window writes nothing. Each source row and each built input take the existing guarded policy screen andcheck()fence, refusals counted asrecords.refused.beginAcceptanceand again inside the append section, point-reading the cited turns themselves rather than the brief window, so a candidate can outlive 32 newer Records and still be accepted. A row that changed or was pruned refuses with a named next step.recordWindow,recordCaptureLookup) and passes nothing when the home has no capture shard, so a home without capture consolidates exactly as before.Acceptance criteria
test/record-candidates.test.ts"one candidate per eligible Record; subagent, malformed and rewritten rows yield none" (8 rows, 2 eligible,excluded: 6), plusrecords.refusedfor a row the proposal-time screen rejects.grouped: 1and bothatvalues inrecord.sources; the consolidate test asserts it end to end.kind: findingplus thefix:title for the fix, and all five labelled lines withSymptom:,Evidence:andRetry when:filled for the fail.changed) and deletes another (missing), asserting both messages name the next step.src/hooks.ts,src/hook-*.ts,src/session-brief.ts,src/brief-capture.tsare all unchanged in this branch, andamem consolidateis a CLI-only command that no hook calls.Review round 1 (2026-11-11)
All three findings are fixed at
f2b0d42, each with a red proof.amem consolidatereads, so once 32 newer Records existed every cited row answeredmissing— and the next step the refusal named (rerunamem consolidate) reads that window too, so nothing could clear it.CaptureStore.readRecordTurnsnow point-reads the cited turns themselves: 1..8 turns, a 9-row cap each, lookups on theatindex, no scan, no brief window. Only a row pruned or forgotten from capture answersmissing, and the refusal no longer blames the window. The unit test pins that the check asks for the cited turns and never for a window; the volume test pins the end-to-end path.<symptom/cause> -> <fix/retry-when>, so the right group is exactlyretryWhenand is no longer discarded; onlyapproachandcausestay empty for the reviewer, which keeps the empty-field relaxation to the two fields a Record cannot source.Refs #368, naming therecordsreport fields and the additiveabout/recordinputs, and stating that existing candidate ids are unchanged.decisionand afailwith a recorded retry condition — then confirms the queue, the fail mapping (symptom/retryWhenfilled,approach/causeempty) and the empty-anchor candidate, pushes 33 newer Records so the cited row leaves the 32-row brief window, and accepts it:alreadyAccepted: false, the note body carries the quoted lesson.Red proofs, run locally at this head:
recordCaptureLookuprestored to the brief-window read, "a realistic volume …" fails atrecord source row is missing (1 cited, …)and every other test stays green.retryWhenrestored to"", "a fail Record fills the symptom and the retry condition it recorded" fails onretryWhen: ""vs"retry after the queue drains"(plus the three tests that assert the accepted fail body), and the rest stay green.Decisions to review
Two points go past the agreed sketch; each is stated here rather than buried.
abouton the candidate input. The sketch gave a Record's anchors to a fail note throughevidenceRefsonly, which leaves an acceptedmemorycandidate with nopath:edges at all, so path recall, briefs andsharedPathsclustering could never find it. The optionalaboutfield carries the anchors aspath:plussymbol:<name>@<path>(the conventionextractNodesalready uses) and acceptance merges it into the note. Optional, so no existing id changes.session + atalone would have rejected a legitimate two-row group. The acceptance recheck matches the triple, and a row at the cited turn whose line no longer parses reads aschanged.Rulings honored
-anchors key by kind and text.readBriefRecords(32)stays the proposal window and its ceiling is reported astruncated.note-add'sfailFieldsstays strict; only a candidate carrying therecordbinding may leave a fail field empty.Verification
Hidden remote runner (dgx02), focused scope only, no heavy run on the Mac.
f2b0d42(tree4ec72a2, review round 1): install,tsc --noEmitclean, boundary174 source files, 13 files 164 pass / 0 fail / 2007 assertions (record-candidates,consolidate,consolidate-cli,candidates,consolidation-acceptance,candidates-cli,mcp-candidate-review,review-pending,candidate-tools,capture,capture-record-index,capture-brief-readonly,capture-brief-index),check-docsOK with the 6 stale documents,npm pack --dry-run184 files, exit 0.a5534c6(first head, treea71384a): the same pipeline over 11 files, 141 pass / 0 fail / 1793 assertions, exit 0.scripts/check-docs.tsreportsOK (6 stale documents). Four were stale before this branch;README.mdanddocs/contracts.mdare the two edited here, and theirverifiedAgainststamps are left for the tag step to recheck againstorigin/mainas the release rule requires.