Story 2101: Memory Engine ME-7 — broker ingest stamping + explicit remember path

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):

  1. Broker ingest stamping — a single pre-fetch assembly point that stamps the Memory Pack onto every launch, built as the shared seam the Context Engine's CE-14 extends without rework (R-6).
  2. The explicit "remember: …" path — deterministic string detection, NO model call, that writes an accepted memory item + bumps the version + acks (§6.4).
  3. Memory-panel read/add routes for the Strategy + Preferences tabs (§8 route sketch), with OpenAPI snapshots the app consumes.

What changed

SurfaceChange
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 shared seam (R-6 / §12.2) — one assembly point, two packs

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 explicit path is deterministic — no model, ever (§6.4)

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

"Done" = the empty first-run state, through the real router + DB

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 == [].

Scope boundary — what ME-7 does NOT build

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).

Verify

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.