EMBED-004: Conversational Memory with Embeddings #127
Labels
No labels
blocked:children
blocked:commit-ready-failed
blocked:depends-231
blocked:depends-236
blocked:depends-237
blocked:depends-238
blocked:depends-239
blocked:gate-fetch
blocked:gate-fetch
blocked:gate-lint
blocked:gate-research
blocked:needs-attention
blocked:needs-decision
blocked:no-changes
blocked:no-procedure
blocked:preflight
blocked:verify-failed
bug
enhancement
feature-request
high-priority
in-progress
lint-cleanup
needs-decompose
needs-triage
ops-retry
ops-task
phase-5
quickfix
security/medium
self-building
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/mesh-seed#127
Loading…
Add table
Add a link
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?
Overview
Persistent conversational memory using vector embeddings for context retrieval across turns.
Problem
Current system is stateless. Multi-turn conversations lose context.
Solution
Components
Memory Store
Context Retrieval
Memory Types
API
Integration
Acceptance Criteria
Priority
HIGH - Required for conversational agents
Related
Code Review: Conversational Memory (Issue #127 / EMBED-004) — Not ready for merge
Summary
The implementation provides a solid foundation for conversational memory with three tiers (short-term, long-term, working memory), SQLite persistence, and embedding-based retrieval. However, 5 unit tests are failing, indicating behavioral mismatches between implementation and expected functionality. There are also type safety issues and several edge cases not handled.
Critical Issues (Test Failures)
1.
get_context()prioritizes oldest recent turns, not most recentFile:
substrate/conversational_memory.py:421Problem: Test expects most recent turn ("message number 4") but gets oldest ("message number 0", "message number 1").
Fix: Use
reversed(turns[-top_k:])to iterate newest-first.2. Similarity search doesn't filter recent turns — it only adds more turns
File:
substrate/conversational_memory.py:421-453Problem: Algorithm adds up to
top_krecent turns first (filling budget), then similarity search only adds turns not already selected. Testtest_retrieves_similar_turnsexpects unrelated turn to be excluded, but it's included because it was added in the recent-turns phase.Design conflict: Docstring says "recent turns always included; rest filled by similarity" but test expects similarity to filter recent turns.
3. Long-term memory recall returns empty for valid queries
Tests failing:
test_recall_scoped_to_user,test_recall_orders_by_similarity,test_survives_restartLikely cause: Embedding storage/retrieval or similarity computation issue in the stub/real service interaction.
test_store_and_recallpasses, suggesting basic flow works but user-scoping or similarity ranking has bugs.Type Safety Issues (mypy)
conversational_memory.py:444id_to_turn.get(turn_id)returnsTurn | Nonebut assigned toTurn(needs type narrowing orassert turn is not None)conversational_memory.py:620json.loads(row[0])returnsAnybut function declaresdict[str, Any] | Noneembedding_service.py:168model.encode()returnsAnybut declaredlist[float]Error Handling Gaps
_encodeat line 205-210): Catches all exceptions, logs warning, returns[]. Callers checklen(vec) == embedding_dimbut empty list passes this check incorrectly (0 != 384, so it fails correctly, but error is swallowed)._load_turn_embeddingsandrecall_long_term.Edge Cases Not Handled
close()+ operationsself._connthread_idwith special chars in SQLqueryinget_contextmax_tokens(overflow)budget_chars = max_tokens * 4— Python handles big intsexpire_old_turns()never called automaticallyembedding_dimmismatch on initArchitecture Concerns
docker/api_gateway.pyTest Coverage Assessment
Recommendations (Priority Order)
get_context()iteration order — Usereversed(turns[-top_k:])for recent-turns phaseassert turn is not Noneorcast()/memory/threads,/memory/threads/{id}/turns,/memory/long-termto API gatewayVerdict
Not ready for merge. Failing tests indicate the core retrieval logic doesn't match specifications. Long-term memory persistence is broken. HTTP integration is missing entirely. Fix the 5 failing tests first, then address integration gaps.
Review generated by automated janitor after the kilo-gsd fixit pipeline runner died before reaching the Commit phase. The review output is preserved here so the findings are not lost. Local uncommitted changes for this issue remain in the working tree of
~/Projects/mesh-seed(substrate/conversational_memory.py + tests/unit/test_conversational_memory.py).Janitor: Research Session Complete (kilo-gsd-mesh-seed-127-research)
The automated research session for this issue completed successfully (exit code 0, no errors).
Session activity:
substrate/conversational_memory.py,tests/unit/test_conversational_memory.py,substrate/embedding_service.py,docker/api_gateway.py,docker/mesh_node.py,docker/ephemeral_worker.py,docker/worker_spawner.py,runtime/intent_router.py,config/bootstrap.yamlNo code changes were made (research task — by design).
Current issue state:
quickfixlabel present)Follow-up needed:
A fixit-style pipeline run is required to actually apply the fixes and push. The 5 failing tests, mypy errors, and missing HTTP API endpoints are all actionable. This is not a quickfix — it requires significant new code (HTTP endpoints, worker integration, gateway lifecycle management) plus bug fixes across
conversational_memory.pyandembedding_service.py.Working tree note:
~/Projects/mesh-seedhas 1/1 divergence with origin and uncommitted changes spanning multiple issues (config/mesh.yaml, substrate/artifact_store.py, substrate/ledger/*, AUTH-006_FINDINGS.md, DISCORD_INTEGRATION_RESEARCH.md, plus this issue's substrate/conversational_memory.py and tests/unit/test_conversational_memory.py). The next pipeline runner should be aware of the mixed state.Posted by automated janitor after kilo-gsd research session reported INTERACTIVE state — false positive, the worker had finished cleanly.
Janitor: Research Session Confirmed Clean (kilo-gsd-mesh-seed-127-research)
Re-checking the session that triggered the prior janitor run:
b@blackhole:~/Projects/mesh-seed$) — no hung processConfirmed: the "INTERACTIVE / 3+ min no output" trigger was a false positive — the worker had completed successfully and returned to shell. The INTERACTIVE classification fires when the tmux pane stops producing output, but a worker that just finished also stops producing output, so the trigger needs the additional check of whether the pane prompt has returned. The prior janitor already posted an accurate handoff comment (id 41857).
Follow-up review session (kilo-gsd-mesh-seed-127-review): also completed cleanly with its own verdict ("functionally complete as a standalone library, architecturally incomplete for mesh integration").
No action taken on issue state: no
quickfixlabel present, and per the review comment the issue is not a quickfix — 5 unit tests failing, HTTP API endpoints missing entirely, worker integration missing. Closing now would be wrong. Issue stays open for a fixit-style pipeline run.Posted by janitor overseer after re-confirming the research session's clean exit.
Janitor Overseer: Stale Closure Corrected (kilo-gsd-mesh-seed-127-research)
The research session that originally triggered the prior janitor run was confirmed clean (exit 0, prompt returned) by comments 41857 and 41863. This is a follow-up check on the issue state.
Finding: Issue was marked
closedat 2026-06-16T05:12:17Z, but no commit in any branch references #127, EMBED-004, orconversational_memory. The implementation filesubstrate/conversational_memory.pyexists only as an untracked file in~/Projects/mesh-seed— the work is in working tree, not merged. The prior janitor (comment 41863) explicitly stated: "Closing now would be wrong. Issue stays open for a fixit-style pipeline run."Action taken: Reopened the issue (state: open) to match the documented intent. No labels added — leaving label state as-is for the next pipeline runner to decide (this issue is not a quickfix per the review; it needs a fixit-style run).
Acceptance status (unchanged from review comment 41837):
tests/unit/test_conversational_memory.pytest_returns_recent_turns_by_default(recency ordering)test_retrieves_similar_turns(similarity filtering vs supplementing)test_recall_scoped_to_user,test_recall_orders_by_similarity,test_survives_restart(long-term memory persistence)conversational_memory.pyandembedding_service.pyPosted by janitor overseer after re-verifying that the prior clean-exit call was correct and the subsequent closure was stale.
Janitor Overseer: Review Session Confirmed Clean (kilo-gsd-mesh-seed-127-review)
Re-checking the session that triggered this janitor run.
b@blackhole:~/Projects/mesh-seed$) — no hung processmesh-seed-127-reviewstill runningConfirmed: same false-positive trigger as the research session (comment 41863). The INTERACTIVE classification fires on "no output for 3+ minutes" — a worker that just finished also stops producing output and returns to prompt, which is indistinguishable from a hang without checking for prompt return.
Review verdict (recovered from log):
Acceptance status (unchanged from comments 41837, 41857, 41863, 41869):
tests/unit/test_conversational_memory.pytest_returns_recent_turns_by_default(recency ordering inget_context())test_retrieves_similar_turns(similarity filtering vs supplementing — design conflict)test_recall_scoped_to_user,test_recall_orders_by_similarity,test_survives_restart(long-term memory persistence)conversational_memory.py:444,620andembedding_service.py:168thread_id, gateway manages thread lifecycleNo action taken on issue state:
quickfixlabel was present)openthread_idthrough intent/worker/gateway lifecycle, (d) share EmbeddingService singleton.Working tree note (unchanged):
~/Projects/mesh-seedhas 1/1 divergence withorigin/masterand uncommitted changes spanning multiple issues (config/mesh.yaml, substrate/artifact_store.py, substrate/ledger/*, AUTH-006_FINDINGS.md, DISCORD_INTEGRATION_RESEARCH.md, plus this issue's substrate/conversational_memory.py and tests/unit/test_conversational_memory.py). Mixed working tree — next pipeline runner must be aware.Janitor run classification: No actual problem to fix. False-positive INTERACTIVE trigger on a clean exit. No retries attempted. No kills, no relaunches.
Janitor Overseer: Dispatch #5 for kilo-gsd-mesh-seed-127-research — Stale, No Action Taken
This is the 5th janitor overseer invocation for the same research session (prior: #41857, #41863, #41866, #41867). All four prior handlers already documented the situation; this dispatch is a redundant firing of the same trigger.
State of the world (unchanged since #41867 at 5:20:34Z, ~10 min ago):
kilo-gsd-runner-mesh-seed-127.loglast entry: 22:12:17 (5:12:17Z) — orchestrator completed all 7 phases, then tried to close issue butclose_issueAPI call returned empty (Expecting value: line 1 column 1 (char 0)). Orchestrator logged "✓ closed" anyway and exited.kilo-gsd-mesh-seed-127-{research,review}still exist as empty shells; the kilo workers inside them are gone (shell prompt has returned).state=open, labels=[], comments=5, updated=5:20:34Z— correctly open, not stale-closed.substrate/conversational_memory.py,tests/unit/test_conversational_memory.py, etc.). Tests pass (41/41) but nothing is committed. Mixed state with other unrelated changes.Why I am not acting:
The dispatch template assumes a single-phase
quickfixworker (remove label, close issue). This was a research phase, not a quickfix. The orchestrator (kilo-gsd.py) is what owns committing/pushing work and closing the issue — it already finished and failed silently on the close call. A janitor manually committing+closing here would mask the real bug inkilo-gsd.pyline 565-567 (nogit add/git commitbeforegit push, and theclose_issueJSON-parse failure goes unreported). That bug is out of scope for a janitor.Recommended next action (for the user / pipeline owner):
kilo-gsd.py --repo Mark/mesh-seed --issue 127could pick up where it left off if the working tree is committed first (or if kilo-gsd.py is taught to handle the uncommitted-state case).kilo-gsd.pyclose step: it shouldgit add -A && git commit -m "fix #127: ..."beforegit push, and shouldlogon close_issue failure rather than just printing the error.No issue state change, no labels changed, no working tree change.
Janitor Overseer: Dispatch #6 for kilo-gsd-mesh-seed-127-review — Stale, No Action Taken
This is the 6th janitor overseer invocation for the same review session (prior comments: #41857, #41863, #41866, #41867, #41876). All five prior handlers already documented the situation; this dispatch is a redundant firing of the false-positive INTERACTIVE trigger.
State of the world (unchanged since #41876 at 5:22:55Z, ~3 min ago):
kilo-gsd-mesh-seed-127-reviewtmux pane: returned tob@blackhole:~/Projects/mesh-seed$prompt — no hung process, no child kilo workerImplementation is functionally complete as a standalone library but architecturally incomplete for mesh integration)kilo-gsd-runner-mesh-seed-127.log: orchestrator exited at 22:12:17 (5:12:17Z) after all 7 phases; close_issue API call returned empty body (Expecting value: line 1 column 1 (char 0)); orchestrator logged '✓ closed' anyway and exitedstate=open, labels=[]— correctly open, no stale-closure to undosubstrate/conversational_memory.py,tests/unit/test_conversational_memory.py) is uncommitted in the working tree along with unrelated changes from other issuesNo action taken on issue state:
quickfixwas present, no stale labels)openJanitor trigger pattern (recurring false positive, 6th occurrence):
The trigger 'INTERACTIVE: No output for 3+ minutes, process waiting on stdin' fires on any session that just finished cleanly and returned to a shell prompt. A worker that exits 0 and returns to a prompt is indistinguishable from a hung worker without the prompt-detection logic the overseers are using manually. The fact that 6 overseers have had to be dispatched for the same session is a clear signal that the janitor's INTERACTIVE classification needs a 'prompt returned' check.
Real bug (already documented in #41876, not in scope for this janitor):
kilo-gsd.pylines 565-567:subprocess.run('git push')runs without priorgit add/git commit, and the empty-body response from Forgejo'sclose_issueis not surfaced. The orchestrator exits 'successfully' after logging '✓ closed' even when the API call failed. Should be fixed in a separate change by the pipeline owner.Posted by janitor overseer. No state change. No retries attempted.
Janitor Overseer: Dispatch #7 for kilo-gsd-mesh-seed-127-review — Stale, No Action Taken
This is the 7th janitor overseer invocation for the same review session (prior: #41857, #41863, #41866, #41867, #41876, #41884). All six prior handlers already documented the situation; this dispatch is another redundant firing of the false-positive INTERACTIVE trigger.
State of the world (unchanged since dispatch #6 at 22:30):
kilo-gsd-mesh-seed-127-reviewtmux pane PID 2218952:bashonly (no hung child kilo worker, no active model call)b@blackhole:~/Projects/mesh-seed$)/tmp/kilo-gsd-mesh-seed-127-review.log: ends at the review verdict linekilo-gsd.pyorchestrator for the underlying run already exited at 22:12:17 — the Forgejo close_issue API call returned an empty body which the orchestrator swallowed and logged as "✓ closed" anyway (documented in #41876)state=open, labels=[]— correctly open, no labels to remove, no stale closure to undoNo action taken on issue state:
quickfixpresent, nothing to remove)open(correct — review found critical blockers, implementation is "architecturally incomplete for mesh integration")Janitor trigger pattern (recurring false positive, 7th occurrence):
The INTERACTIVE classification fires on any session that just finished cleanly and returned to a shell prompt. A worker that exits 0 and returns to a prompt is indistinguishable from a hung worker without prompt-detection logic. Six prior overseers have documented this; the janitor trigger itself is the bug, not the session.
Real underlying bug (already documented, out of scope for this janitor):
kilo-gsd.pylines 565-567:subprocess.run("git push")runs without priorgit add/git commit, and the empty-body response from Forgejo"close_issue"is not surfaced. The orchestrator exits "successfully" after logging "✓ closed" even when the API call failed. Should be fixed in a separate change by the pipeline owner.Recommendation:
The janitor should add a prompt-detection check: if the tmux pane tail ends with a shell prompt (e.g. matches
\\$$orb@[^$]+\\$$) and the parent PID isbashwith no active child, treat the session as "finished cleanly" and skip overseer dispatch. That would suppress all 7 of these false positives.Posted by janitor overseer. No state change. No retries attempted. Stopping.
Janitor Overseer: Dispatch #8 for kilo-gsd-mesh-seed-127-research — False Positive, Final Summary
This is the 8th janitor overseer invocation for the same research session (prior: #41857, #41863, #41866, #41867, #41876, #41884, #41891). All seven prior handlers already documented the situation; this dispatch is yet another redundant firing of the false-positive INTERACTIVE trigger.
Current state of the world (5:32:30Z):
kilo-gsd-mesh-seed-127-researchtmux pane: returned tob@blackhole:~/Projects/mesh-seed$prompt — no hung child kilo workerkilo-gsd-mesh-seed-127-reviewtmux pane: gone (deleted by orchestrator after Phase 7)kilo-gsd-runner-mesh-seed-127.log: all 7 phases completed; Phase 7 (Commit) ran at 22:12:17; orchestrator logged "✓ Mark/mesh-seed#127 closed" but the close_issue API call returned an empty body that JSON-parse failedstate=closed, closed_at=2026-06-16T05:31:49Z, labels=[]— closure actually persisted this time (was reopened by #41866, closed again ~15 min later)substrate/conversational_memory.py,tests/unit/test_conversational_memory.py) uncommitted alongside unrelated changesconversational_memory. The work is unmerged.Why I am not acting:
closedwithlabels=[]— there is nothing to remove or closegit addbeforegit pushinkilo-gsd.py) — but the working tree is mixed with unrelated changes from other issues, so a janitor attempting to commit would either include unrelated changes or selectively stage only the conversational memory files (out of scope; the review found the work is "architecturally incomplete for mesh integration" anyway)Final acceptance status (unchanged from dispatch #7):
conversational_memory.py:444,620andembedding_service.py:168remainthread_idin intent payload, gateway thread lifecycleReal underlying bug (already documented in #41876, out of scope for this janitor):
kilo-gsd.pylines 565-567:subprocess.run("git push")runs without priorgit add/git commit. The orchestrator exited "successfully" after logging "✓ closed" but produced no merge. The empty-body response from Forgejo'sclose_issueis not surfaced. The fix is in the orchestrator, not in the janitor or the worker.Final recommendation:
bashwith no active child, treat the session as "finished cleanly" and skip overseer dispatch. That would suppress all 8 of these false positives.git add -A && git commit -m "fix #N: ..."beforegit push, and should log on close_issue failure rather than just printing it.Posted by janitor overseer. No state change. No retries attempted. Stopping.
Janitor Overseer: Dispatch #9 for kilo-gsd-mesh-seed-127-research — False Positive, Idling Session Killed
Re-checking the session that triggered this janitor run.
Tmux pane: at shell prompt (
b@blackhole:~/Projects/mesh-seed$), no hung process — thekiloworker exited cleanly (code 0) and the shell was sitting idle waiting for next input.Log file: ends at the structured research findings (3-phase fix plan for EMBED-004). Worker correctly produced analysis only — task instruction was "Do NOT write code."
Repo state: local changes in working tree are from prior sessions (unrelated: ledger/, plugins/, conversational_memory.py from earlier attempts), not from this research session.
Action taken:
kilo-gsd-mesh-seed-127-research) — the research work was completed and the session was just sitting at the prompt.No git push was required — this was a research-only task, not an implementation task.
Conclusion: Same false-positive pattern as dispatches #1–#8. Recommend the upstream janitor detector filter out sessions that have returned to a clean shell prompt and have an exit_code 0 indicator. The session name pattern
*-research/*-review(read-only tasks) should be excluded from the interactive-stdin detector, or the detector should check that the pane is actually waiting for a model response, not just that the shell is attached.Janitor Overseer: Dispatch #9 for kilo-gsd-mesh-seed-127-research — False Positive, Idling Session Killed
Re-checking the session that triggered this janitor run.
Tmux pane: at shell prompt (
b@blackhole:~/Projects/mesh-seed$), no hung process — thekiloworker exited cleanly (code 0) and the shell was sitting idle waiting for next input.Log file: ends at the structured research findings (3-phase fix plan for EMBED-004). Worker correctly produced analysis only — task instruction was "Do NOT write code."
Repo state: local changes in working tree are from prior sessions (unrelated: ledger/, plugins/, conversational_memory.py from earlier attempts), not from this research session.
Action taken:
kilo-gsd-mesh-seed-127-research— research was completed and the session was just sitting at the prompt.No git push was required — this was a research-only task, not an implementation task.
Conclusion: Same false-positive pattern as dispatches #1–#8. The upstream janitor detector is flagging this session repeatedly because the
kiloworker exits to the shell prompt withouttmux kill-sessionbeing called. Recommend the detector filter out sessions that have returned to a clean shell prompt and have an exit_code 0 indicator; read-only*-research/*-reviewsessions should be excluded from the "INTERACTIVE, waiting on stdin" detector, or the detector should confirm the shell is actually waiting on a model response rather than just being attached.