feat(memory): missions remember their verdicts, per repository

Until now missions wrote no memory. The chat path records every turn into
the claw's .brain, but a mission's crew is minted per mission, so a brain
keyed by agent would be written once and never read. What persists across
missions is the repository: mission_memory keeps one .brain per repo_id,
writes each judge verdict into it (reason when met, sanitized guidance when
not — the operator reason may quote the acceptance text), and recalls
against the next phase's task text into the brief, under a heading all
three executors carry because it rides on the task.

Recall is BM25 over the keyword index, no embedder; the harness asserts the
brief carries the section once the repo has one judged mission behind it,
and says 'first mission' rather than failing before that. OpenClaw's
flush-before-compaction was the other half of this item and is moot here:
the chat loop has no compaction and already remembers both halves of
every turn.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
This commit is contained in:
Omar Sobh
2026-09-20 22:11:55 -05:00
co-authored by Claude Opus 5
parent 76ac3714f1
commit 13f7fb3aff
6 changed files with 311 additions and 4 deletions
+31
View File
@@ -355,6 +355,9 @@ assert_microvm() { # <token> <mission> <report>
# calls at all, and none of them may have been a write.
assert_verifier_read_only "$mission" microvm
# Project memory: the brief carries earlier verdicts on this repository.
assert_project_memory "$mission" microvm
# And the verdict: judged, and by whom. `independent` is only true when the
# judge came from a different provider family than the agent.
indep=$(ssh "$HOST" "docker exec clawmates_postgres_1 psql -U postgres -d clawmates -tAc \
@@ -900,6 +903,34 @@ assert_verifier_read_only() { # <mission> <label>
esac
}
# Did this mission's brief carry what earlier missions on the same repository
# learned? `mission_memory` writes every judge verdict into a per-repo brain
# and recalls against the next phase's task, so once the repo has ONE judged
# mission behind it, the section must be in the prompt. Two branches, because
# the first mission on a repo has nothing to recall and must not fail for it:
# the earlier-verdict count is what decides which branch is the truth.
assert_project_memory() { # <mission> <label>
local counts earlier carried
counts=$(ssh "$HOST" "docker exec clawmates_postgres_1 psql -U postgres -d clawmates -tAc \
\"select (select count(*) from mission_phase_evaluations e
join missions m on m.id = e.mission_id
where m.repo_id = (select repo_id from missions where id='$1')
and e.mission_id <> '$1' and e.error is null
and e.created_at < (select created_at from missions where id='$1'))::text
|| ' ' ||
(select count(*) from mission_events
where mission_id='$1' and kind='prompt.composed'
and detail->>'text' like '%What past missions on this repository learned%')::text;\"" \
| head -1 | tr -d '\r')
earlier=${counts%% *}; carried=${counts##* }
case "$earlier:$carried" in
0:0) pass "$2-memory: first judged mission on this repo — nothing to recall, nothing carried" ;;
0:*) fail "$2-memory: the brief carried project memory but no earlier verdict exists for this repo" ;;
*:0) fail "$2-memory: $earlier earlier verdict(s) on this repo and the brief carried NONE — recall is not reaching the prompt" ;;
*) pass "$2-memory: $earlier earlier verdict(s) on this repo; the brief carried the recalled section" ;;
esac
}
# ── Scenario: a model sizes the team ─────────────────────────────
#
# Slice 5. The planner proposes a roster for THIS mission, a human approves it,