DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/personal-recall/loomworks-personal-recall-implementation-notes-v1_0.html

Loomworks — first-class personal-recall — implementation notes — v1.0

Version 1.0
Date 2026-06-11
Status Implementation notes — the record-resident write-up of the first-class personal-recall capability (CR-2026-100), built and shipped to engine main ec0b383, tagged personal-recall-v0_1. Operator Layer unchanged (engine-only fix). Released (v1.0) — the capability is complete, reliable, and confirmed by Operator browser review in the previously-broken engagement. Records the bug, the diagnosis, the build, and the methodology lesson the structural guard earned. Operator-facing summary at top; technical detail below. Markdown primary, HTML companion.
Author posture Descriptive of built behaviour, grounded in the build history. The classifier miss at Checkpoint B and its structural fix are recorded plainly — they are the load-bearing lesson.
Relates to The personal-recall scoping note v0.1 (scoping-notes/, record edfb74a); CR-2026-100 (docs/phase-crs/, engine); CR-2026-099 (orientation — its responder-bypass was the bug's mechanism, and its lessons governed this build); the memory=recall seed-level reframe (this work advances it but does not perform it).

Plain-language summary

The Companion is supposed to recall the Operator's personal memories — birthdays, preferences, who's who — from any engagement, because personal memory is committed as cross-engagement. It was failing: asked "birthdays" in one engagement it returned the list; asked the same word in another it returned a capability menu and no birthdays. Same word, same person, same store — different result.

The root cause: personal-memory storage was correct and cross-engagement, but personal recall had no first-class capability of its own. Recall was emergent — it worked only when a turn happened to reach the responder, which read a personal-memory block from its prompt. When the same query misrouted into the responder-bypassing orientation intent, recall silently failed. And even when it worked, the block loaded only for engagement-scoped turns and was budget-clipped, so old facts could drop silently.

The fix made personal recall first-class and reliable: a recall_personal intent that mirrors the existing engagement-recall intent (ask_about_past_input), pointed at the personal store. It reads the personal engagement directly (works from any engagement), retrieves the Operator's personal memories wholesale (the complete set — no silent clip), and the Companion composes the answer. Plus classifier guidance that makes the intent fire reliably, and a structural guard that makes the failure mode — a recall query landing on the orientation bypass — impossible rather than merely discouraged.

The capability was built with the same gate discipline as the orientation work: a live-classifier hard gate (run with a real key, not just prompt-content tests) and an Operator browser review before a separate push. The live gate caught a real miss — an ambiguous "what can you remember for me" still classified as orientation — and the fix was the load-bearing lesson: soft classifier guidance loses to a strong model prior; a guarantee is enforced in code, not asked for in a prompt.

1. The bug

Personal memory is committed as cross-engagement and person-scoped (functional spec §4.1; classifier remember_about_me — "person-scoped, travels across all the Operator's projects"). Storage honoured this — personal facts are committed assertions on the person's invisible personal engagement (Phase 41), loaded identically from every engagement. But recall diverged: "birthdays" surfaced personal facts in engagement E0005 and not in E0060.

The locus was classification/routing, not storage. There was no personal-recall intent; recall was emergent via the responder reading a personal-memory prompt block. Orientation (CR-2026-099's lever-3 server-compose) bypasses the responder. So "birthdays" classified into a responder-bound intent in E0005 (surfaced the facts) but into orient in E0060 (bypassed the responder, returned the menu). Two further fragilities made even the responder path unreliable: the block loaded only when project_id is not None, and it was budget-clipped newest-first (~500 tokens), dropping old facts silently.

2. The design — mirror ask_about_past_input at the personal store

ask_about_past_input is an existing retrieval-backed, responder-composed recall intent for engagement memory. recall_personal is the same shape with three swaps: engagement_idperson.personal_engagement_id; the no-project guard → a no-personal-engagement guard; the engagement-recall template → a personal-recall template. Personal-recall and engagement-recall became siblings — architecturally consistent, not bespoke. The handler reads the personal engagement directly (removing the project_id gate) and puts recalled facts authoritatively into operation_data (removing the budget-clip fragility).

Wholesale. Recall retrieves the complete set of committed personal assertions; the responder composes from the complete set. Any topic-filtering is a subset of the complete set — a deferred optimization, never a capability the wholesale path lacks. The retrieval limit was set to 500 (not ask_about_past_input's 50) — deliberately, because reusing 50 would have reintroduced the silent clip this CR exists to remove. 500 is an effectively-wholesale backstop bounding only a pathological query; total_count is surfaced so any overflow is visible, not silent. This limit difference is load-bearing — it is the difference between "complete" and "clipped," which is the bug's whole nature.

3. The build

Eight commits on branch personal-recall, atop the CR archive. - Piece 1recall_personal in the IntentLabel Literal; count-lock tests bumped 21→22. - Pieces 2+4_route_recall_personal handler (mirrors _route_ask_about_past_input, reads person.personal_engagement_id, wholesale, no-personal-engagement guard) + router dispatch. - Piece 3 — the personal-recall responder template (plain-terms, lead-expert voice; honours the engagement/general invisibility commitment — no scope/engine narration; offers to remember on a miss). - Piece 5 (load-bearing) — the classifier guidance: a recall_personal section and two disambiguations (vs ask_about_past_input: personal life/world vs this project's content; vs orient: recalling a memory is not a capability query). - Piece 6 — orient-tightening: a bare topic noun is never orient; orient is for explicit capability queries only. - Checkpoint A safe-default — by Operator ruling, ambiguous recall-vs-capability cases default away from orient (orient reserved for unambiguous capability queries). - Checkpoint B structural guard — see §4.

4. The load-bearing lesson — the structural guard

The Checkpoint A guidance was correct and reviewed. The Checkpoint B live-classifier run (real API key, the silent-failure gate) confirmed 11 of 12 cases — including the bug case ("birthdays" → recall_personal, 0.85). One missed: "What can you remember for me?" classified as orient (0.72). The classifier reads "what can you ___" as a capability query, and the soft prompt guidance ("ambiguous → not orient") lost to that strong prior. The query landed on the bypass — the exact failure the safe-default rule forbids. The hard gate halted.

This is the orientation lesson, one level over: soft prompt guidance loses to a model prior under pressure; a guarantee is enforced in code, not asked for in a prompt. (The orient fabrication taught the same — a structural-contract reply is composed server-side, not generated; here, "recall queries never hit the bypass" is enforced by a deterministic guard, not by classifier persuasion.)

The fix was a structural guard, not an additional exemplar. An exemplar ("what can you remember for me" → recall_personal) would fix the tested phrase but not the class — the next untested recall-flavoured "what can you ___" phrasing would leak again, silently. The guard encodes the definition: orientation is a capability-query intent; a query whose content is a recall is definitionally not a capability query. So: if the classifier returns orient and the utterance contains a recall verb ("remember"/"recall") and no clear capability signal, redirect away from orient (to recall_personal if personal, else general_conversation — never the bypass). The "no capability signal" clause is load-bearing — it lets genuine capability-about-memory queries through ("how do I get you to remember things?" stays orient).

The re-run confirmed both sides clean: the ambiguous recall-flavoured query redirects off the bypass; the genuine capability query stays orient. The failure mode is now structurally impossible for the whole class, not enumerated phrase by phrase.

5. Why this was worth doing properly (Fix B over Fix A)

The immediate bug could have been patched by tightening orient's classification so bare nouns stop misrouting (Fix A). That would have left recall emergent — working when classification reaches the responder, failing silently when it doesn't, with old facts still dropping off the clipped block. For a memory product, "recall that usually works" is the worst failure mode — it reads as forgetting. Fix B made recall first-class (a real retrieval-backed intent), reliable (structurally guarded against the bypass), and complete (wholesale, no silent clip). The structural guard is the same standard applied to classification: not "usually routes right," but "cannot route a recall onto the bypass." Reliable by construction, not by luck.

6. Status

Shipped and reliable (v1.0). Engine main ec0b383, tagged personal-recall-v0_1; OL unchanged. The Companion recalls personal memories from any engagement — first-class, wholesale, structurally guarded. Engine suite 2881 passed / 0 failed; gated live-classifier 13/13; Operator browser review passed in the previously-broken Fenwick engagement (E0060) — "birthdays" recalls, "what can you remember for me" redirects to recall not the menu, engagement recall stays separate.

Deferred (logged for queued-directions): - "Ask-first on genuinely ambiguous recall" — the clarification affordance (the Companion asks "Personal, or this project?" and routes on the answer) — scoped as a fast-follow, not built here; the structural guard handles the safe-default now. - Topic-filtered / relevance-ranked personal recall — the subset optimization where the memory=recall "find the relevant thing" vision lives. - The *_about_me write-family name observation — the family is named "about me" but holds personal memories broadly (subject can be anyone); recall_personal diverges deliberately; a family rename is possible future work. - The Companion-Markdown rendering gap (literal ** in Companion replies) — unrelated to this CR, observed during its browser review; the rich-Companion-response thread.

This work advances the memory=recall seed-level reframe (recall becomes first-class and reliable) without performing it (no seed change; the relevance vision is deferred). Decoupled by Operator direction.