Skip to content

Commit c17e244

Browse files
author
codejunkie99
committed
fix(.agent): address post-merge codex review round
Written by Minimax-M2.7 in Claude Code Harness. - [P1] write_candidates now does a claim-level terminal check against accepted lessons before the slug-based lifecycle lookup. Cluster-membership shifts can change `conditions` (intersection of shared tokens) and therefore the pattern id, so accepted patterns could re-stage under a new slug and bypass the "status=accepted = terminal" guard. Checking by claim text is stable regardless of id drift. Provisional and legacy lessons remain absent from the terminal set, so they still allow re-review. - [P2] graduate.py now detects a partial-completion retry and completes the candidate move without re-appending to lessons.jsonl. Previously, if semantic writes succeeded but mark_graduated crashed, the next retry saw its own prior lesson in LESSONS.md and heuristic_check rejected it as a self-duplicate, leaving the candidate stuck. The retry path now recognizes the lesson_id already exists and skips duplicate-check + append, just finishing the move.
1 parent a369df9 commit c17e244

2 files changed

Lines changed: 39 additions & 7 deletions

File tree

.agent/memory/promote.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import os, json, datetime, hashlib
1212
from cluster import content_cluster, extract_pattern
1313
from review_state import _lessons_sha
14-
from validate import extract_lesson_lines
14+
from validate import extract_lesson_lines, check_exact_duplicate
1515

1616

1717
def cluster_and_extract(entries, threshold=0.3):
@@ -87,6 +87,18 @@ def write_candidates(patterns, candidates_dir):
8787
claim = (p.get("claim") or "").strip()
8888
if not claim:
8989
continue
90+
91+
# Claim-level terminal check: if this exact claim is already an
92+
# accepted lesson, skip regardless of slug. Cluster membership
93+
# changes can shift the id (conditions = intersection, shrinks
94+
# when outlier members join), so the slug-based graduated check
95+
# below would miss an accepted pattern under a new id. This
96+
# catches it by claim text, which IS stable. Provisional and
97+
# legacy lessons don't appear in extract_lesson_lines, so they
98+
# correctly do NOT block re-review.
99+
if lessons_text and check_exact_duplicate(claim, lessons_text):
100+
continue
101+
90102
# Prefer the claim+conditions id from extract_pattern — stable slug
91103
# means lifecycle state carries across cluster membership changes.
92104
slug = _slug(p)

.agent/tools/graduate.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from review_state import mark_graduated
1515
from validate import heuristic_check
16-
from render_lessons import append_lesson, render_lessons
16+
from render_lessons import append_lesson, render_lessons, load_lessons
1717

1818
CANDIDATES = os.path.join(BASE, "memory/candidates")
1919
SEMANTIC = os.path.join(BASE, "memory/semantic")
@@ -55,6 +55,28 @@ def main():
5555
with open(cand_path) as f:
5656
cand = json.load(f)
5757

58+
lesson_id = _lesson_id(cand)
59+
60+
# Retry-safety: if a prior graduation run got as far as appending to
61+
# lessons.jsonl but crashed before the candidate move, the staged file
62+
# still exists and the lesson is already recorded. Heuristic check
63+
# would otherwise reject the retry as an exact duplicate of its own
64+
# prior output, leaving the candidate stuck. Detect and complete the
65+
# move without re-appending.
66+
prior_lesson = next(
67+
(l for l in load_lessons(SEMANTIC) if l.get("id") == lesson_id),
68+
None,
69+
)
70+
if prior_lesson:
71+
print(f"retry detected: lesson {lesson_id} already in lessons.jsonl; "
72+
f"completing candidate move")
73+
mark_graduated(
74+
args.candidate_id, args.reviewer, args.rationale, CANDIDATES,
75+
provisional=args.provisional,
76+
)
77+
print(f"graduated {args.candidate_id} → lesson {lesson_id} (retry)")
78+
return
79+
5880
lessons_md = os.path.join(SEMANTIC, "LESSONS.md")
5981
existing = open(lessons_md).read() if os.path.exists(lessons_md) else ""
6082
# When superseding, exclude the target lesson from the duplicate check —
@@ -73,13 +95,11 @@ def main():
7395

7496
# Atomicity: write to semantic memory BEFORE moving the candidate.
7597
# If we crash mid-graduation, the staged candidate remains and the
76-
# reviewer can retry. Append-only lessons.jsonl is deduped by id at
77-
# render time, so a retry that re-appends produces one lesson bullet,
78-
# not two. Crashing AFTER the move would leave the candidate lost and
79-
# the lesson unlogged — the failure mode this reorder prevents.
98+
# reviewer can retry. The retry-safety block above catches the
99+
# specific "lesson appended but candidate not moved" scenario.
80100
accepted_at = datetime.datetime.now().isoformat()
81101
lesson = {
82-
"id": _lesson_id(cand),
102+
"id": lesson_id,
83103
"claim": cand.get("claim"),
84104
"conditions": cand.get("conditions", []),
85105
"evidence_ids": cand.get("evidence_ids", []),

0 commit comments

Comments
 (0)