ME-6 (Story-2100) built the read: one importable, fail-open,
O(1)-query memory_pack.v1 and the additive
agent_invoke.v1 memory_pack field. ME-7 wires it into the
live rail and adds the two counsel-facing seams (RFD
docs/notes/rfd-memory-engine.md §3 R-6, §6.4, §8, §12.2):
"remember: …" path — deterministic
string detection, NO model call, that writes an accepted memory item + bumps
the version + acks (§6.4).| Surface | Change |
|---|---|
src/shared/memory/writes.py (§6.4) |
New shared write module. parse_remember_directive() — pure,
case-insensitive grammar, NO model. add_strategy_item() /
add_preference() — insert a born-accepted item and
end by bumping memory_version (the ONE bump code path). Backs
BOTH the broker remember path and the panel add routes, so a memory write is
one implementation, not two. |
src/clerk_worker/runner.py — _stamp_prefetch_packs() |
The one assembly point (R-6 / CE-14 seam). An async
pre-fetch (mirror of _inject_granted_tool_catalog) that assembles
the pack and stamps command["memory_pack"]; _invoke_brain
rides it on agent_invoke.v1. CE-14 adds
command["context_bundle"] here beside it — both additive optional
fields, neither reworks the other. Fail-open: any assembly error degrades to
no pack, never a broken turn. |
src/clerk_worker/runner.py — _handle_remember_directive() |
Runs pre-invoke on the raw counsel message. A remember: match is
a memory DIRECTIVE, not a chat turn: write the item directly
(source='explicit', born accepted), bump the version,
audit in-txn, then land a broker-authored ack turn ("Remembered — …") as the
run's terminal truth and push it. The brain is never invoked.
Routing is scope-deterministic: matter session → Strategy (kind from a
goal:/focus:/constraint: prefix, else
note); pre-matter → Preference. |
src/app_api/router_memory.py + memory_service.py + memory_dtos.py (§8) |
Four routes: GET/POST /app/matters/{matter_id}/memory/strategy,
GET/POST /app/me/memory/preferences. Full contract hygiene
(router tags, response_model=, ErrorResponse
responses, *Request/*View separation). Reads are ONE
bounded query (DR-1); writes reuse the shared writer + audit in-txn.
Caller-scoped: a preference is per-counsel; strategy is firm-RLS + matter. |
openapi/app-api.json |
Regenerated: the four new routes + their DTOs. Admin snapshot unchanged. |
The Memory Pack and the Context Pack are disjoint features that share exactly one joint: the broker pre-fetch that stamps the invoke envelope. ME-7 builds that point so CE-14 lands as an addition, not a rework:
process_command
└─ (remember? → short-circuit, no brain)
└─ _inject_granted_tool_catalog(command, deps) # dock_chat tool catalog
└─ _stamp_prefetch_packs(command, deps) # ← THE seam
├─ command["memory_pack"] = assemble_memory_pack(...) # ME-7 (now)
└─ command["context_bundle"] = assemble_context_bundle(...) # CE-14 (later)
└─ _invoke_brain → envelope_dict["memory_pack"] = command["memory_pack"]
# (+ context_bundle when present) → agent_invoke.v1
Both are additive optional fields on the frozen envelope; a missing pack is a valid pack (the brain behaves as today). The pack is scoped to exactly one of matter|form — a launch with neither carries no durable scope, so nothing is stamped.
The grammar is a single regex on the raw message:
^\s*remember[:,]?\s+(that\s+)?(body)$ (case-insensitive). A match
routes the body into a memory proposal that is already accepted — counsel
said it verbatim, so the confirm happened in the utterance. The turn still settles
with a Mira ack, and the item is undoable like any other. An interceptor
model call never enters this path, which is what makes it cheap, honest,
and testable: the acceptance test drives it with an empty AgentCore stream
list, so a stray model call would IndexError — the "no model call"
gate is structural as well as asserted.
"remember: focus: get the annexures"
→ strategy item {kind: focus, text: "get the annexures", status: accepted, source: explicit}
→ memory_version += 1 (matter-scoped bump — every counsel's fold for the matter)
→ turn.completed {next_message_md: "Remembered — get the annexures"} → run.done
A seeded fixture hides the case that breaks first. The route tests
(tests/integration/test_app_memory_panel.py) drive the full ASGI
pipeline against a real DB: a brand-new matter's strategy list is []
and a fresh counsel's preference list is [] before any add; the add
then returns a born-accepted item that shows up in the next list; an
add against an unseen matter 404s; an exact-dup preference add is idempotent (no
second row). The remember round-trip
(tests/integration/test_clerk_remember_directive.py) proves the
system-boundary gate end-to-end: accepted strategy item + bumped version + ack +
agentcore.invocations == [].
Accept / reject / undo of interceptor proposals (§8's
/app/memory/items/{id}/…) are ME-11's, because there are no
proposed rows to act on until the interceptor (ME-10) exists. ME-7's
write routes are the explicit ADD path only. Brain-side rendering of the
<<MEMORY_PACK_v{n}>> block is ME-8 (agents lane).
uv run ruff check
uv run pytest tests/unit/test_memory_remember_grammar.py \
tests/integration/test_app_memory_panel.py \
tests/integration/test_clerk_remember_directive.py
make openapi # app-api.json gains the four routes; admin unchanged
make test-db && make test # the RLS route + remember round-trip proofs
Recall: ME-7 lands the Memory Pack on the live invoke through
one shared pre-fetch seam CE-14 extends, turns "remember: …" into a
deterministic, model-free accepted write with an ack, and gives the memory panel
its Strategy/Preferences read+add routes.