Memory rows naming gotoi object IDs go stale silently #91
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Labels: memory, priority/low
remember stores one-line facts per host. Nothing invalidates them when the referenced object is deleted.
Live example, on adam-slave-0:
code
[id f88f134f-d212-4015-864c-f76b35c6081b]
[note] Remediation plan 0c52ac87-8ab6-42cb-a072-b02bc116e6f0: fix /home rsnapshot
self-nesting on slave-0 + prune container storage on master /.
That plan was deleted at the operator's hand during this session. The row now points at nothing and will keep steering future sessions toward a plan that does not exist. Same shape risk for rule IDs, execution IDs, and incident IDs.
Fix: a ref memory kind carrying {kind, id}. On delete of that object, mark the row stale rather than leaving it authoritative. recall surfaces stale rows distinctly.
Acceptance criteria
Issue #91 plan — memory rows pointing at deleted gotoi objects go stale
Branch:
issue/91-memory-row-invalidation(baseagent-develop@3122e89). Worktree:/home/eduardez/workspace/gotoi-wt/issue-91.Restated goal
rememberstores one-line facts per host. A row that names a plan / rule / execution / incidentkeeps steering future control sessions at that object even after the object is deleted. Deliver:
ref?: { kind: 'plan'|'rule'|'execution'|'incident'; id }.contentstays free-form; untyped rows are unchanged.stale: true— kept (audit retention), notdeleted, and no longer authoritative.
recall(and the control-chat memory prompt block) surface stale rows distinctly:- [stale: plan 0c52ac87 deleted] <content> (id …), sorted after live rows.Verified base facts (this worktree,
3122e89)AgentMemoryhas no ref/stalemiddleware/src/types.ts:588-598agent_memorieshas 9 columns, no ref/stalemiddleware/src/db/schema.ts:485-495,middleware/drizzle/0025_control_plan_mode_memories.sqladdMemory/memoriesFor/updateMemory/removeMemoryshapemiddleware/src/agent-memory.ts:21-132- [kind] content (id …)middleware/src/agent-memory.ts:44-56recallrenders the same line inlinemiddleware/src/control-agent.ts:1309-1315middleware/src/routes/plans.ts:654,middleware/src/control-agent.ts:1145(plan_delete)middleware/src/routes/rules.ts:98,middleware/src/control-agent.ts:1265(rule_delete)pruneExecutions(middleware/src/ops.ts:117), a pure helper with no caller except its testIncidentStore(middleware/src/incidents.ts:15-123) has no remove/delete method0034_execution_trigger; next free number = 0035UC-MW-178(max existing = 177)Project fact that narrows the task file
Task step 3 says "plans/rules/executions/incidents
on_deletecalls". Executions and incidents haveno delete path at runtime — nothing tombstones them (
ops.ts:117is an unused pure helper; theincident store has no removal method). So the invalidation hook exists for all four kinds, but only
plan and rule deletes can call it today. Recorded, not silently dropped: a typed ref of kind
execution/incidentis storable and will go stale the moment such a delete path appears.Scope
In:
middleware/src/types.ts—MemoryRefKind,MemoryRef,AgentMemory.ref?,AgentMemory.stale?.middleware/src/db/schema.ts—ref jsonb,stale boolean not null default false.middleware/drizzle/0035_memory_ref_stale.sql+.down.sql.middleware/src/db/persist.ts— writeref/staleback on flush.middleware/src/agent-memory.ts—addMemoryacceptsref;markMemoriesStale(stores, kind, id);stale-last sort in
memoriesFor/memoriesForAgent; stale marker inmemoryPromptBlock.middleware/src/control-agent.ts—remembergainsref_kind/ref_id;recallstale marker;markMemoriesStaleafterplan_deleteandrule_delete.middleware/src/routes/plans.ts,middleware/src/routes/rules.ts—markMemoriesStaleafter delete.middleware/src/routes/agents.ts— optionalrefonPOST /agents/:id/memories.dashboard/src/lib/types.ts— mirrorref+stale(type-mirror gate).middleware/tests/uc-mw-178-memory-ref-staleness.test.ts— NEW (red first).docs/use-cases/middleware/UC-MW-178-memory-ref-staleness.md— NEW;UC-MW-153related link.Out:
agent/**;plans/(product content); executions/incidents store classes (no delete to hook);any dashboard page work (types mirror only — the FE page already lists rows and will show the flag
whenever it renders it; no new UI surface is added).
UC decision
UC-MW-178-memory-ref-staleness.md— "memory rows referencing a deleted object go stale".related: [UC-MW-153](bidirectional: addUC-MW-178to UC-MW-153's YAML + Related heading).Implementation cites
middleware/src/agent-memory.ts,middleware/src/routes/plans.ts,middleware/src/routes/rules.ts,middleware/src/control-agent.ts.Tests cite
middleware/tests/uc-mw-178-memory-ref-staleness.test.ts.Ordered tasks
cd middleware && bun test tests/uc-mw-178-memory-ref-staleness.test.ts→ redtest: repro memory-ref staleness (red) Refs #91git log -1bun test tests/uc-mw-db.test.tsagent-memory.ts: ref input,markMemoriesStale, stale-last sort, prompt markerbun test tests/uc-mw-153*.test.ts tests/uc-mw-control-hardening.test.tsplan_delete/rule_deleterememberparams +recallmarker +POST /agents/:id/memoriesrefpython3 scripts/check-type-mirror.pypython3 docs/use-cases/_check.pyfeat(memory): … Refs #91), STOPOpen questions and the assumption taken
staleneed its own column or ride inside the ref jsonb? Two columns (ref,stale): theflag is queryable and a DB default covers old rows. Assumed.
updateMemorydoes notclear the flag. Assumed.
forgetstill delete a stale row? Yes (unchanged). Assumed.Risk and boring fallback
Risk: a store delete path added later (e.g. execution pruning) forgets the hook. Boring fallback if
event-driven marking proves unreliable: derive staleness at read time from
stores.<kind>.has(id)inmemoriesFor. Not implemented now — the task froze the delete-time hook and the persisted flag.RUNNER-TIMEOUT — wall cap reached (2026-10-09 14:57, 3 h). State for the next runner:
c916504test: repro red;914bcfffeat(memory): typed refs go stale when the referenced object is deleted), tree clean. Base3122e89(current agent-develop). Its own gate claims are NOT verified.Refs #91
REVIEW: CLEAN — findings: none; all 3 ACs pass (typed ref in remember + POST /agents/:id/memories, markMemoriesStale on plan/rule delete sites, recall + prompt render [stale: kind id8 deleted] with stale rows last); plan Scope 14/14 files, no out-of-scope edits; checkers exit 0 (_check.py, check-ai-headers.py, check-type-mirror.py, no 'Showing lines' artifacts); targeted tests 5+10+19 pass (uc-mw-178, uc-mw-db clean-state, uc-mw-169+control-hardening); zero test lines deleted/skipped. Note: executions/incidents have no runtime delete path (hook exists for all 4 kinds, documented in plan). UNFIXED: none. Refs #91
DONE — branch issue/91-memory-row-invalidation, PR #104 (#104) targeting agent-develop. 2 commits (
c916504red repro,914bcfffeat): MemoryRef side-band {kind,id} on remember, migration 0035_memory_ref_stale, markMemoriesStale wired to plan/rule delete sites (routes + control-agent), recall + prompt block render stale rows last. Executions/incidents have no runtime delete path (plan documents this). Review: CLEAN (independent reviewer, no findings). Gates: UC catalog ok, ai-headers 582 ok, type-mirror ok, Showing-lines empty, dashboard test+check GREEN, middleware full suite 538 pass / 1 fail — UC-MW-154 secrets GET-org, verified pre-existing (fails identically on clean origin/agent-develop baseline; not touched by this diff). Refs #91