A memory about exercise improving focus belongs to both a
workout-health thread and an adhd thread, and those threads span memories from personal and work scopes alike. Neither scope nor project can express that.
Entity model
Two new tables (23 → 25; the JTBD above is the budget justification — cross-scope, many-to-many thematic grouping is unrepresentable in the v1 layout):
The full tenant-invariant checklist from Data model applies to both: own
user_id, RLS policy, tenant-qualified composite FKs, user_id-leading indexes, isolation tests over their query paths.
Threads are not edges. memory_edges stays memory→memory; membership lives only in the join table. Proposed memberships are rows in memory_threads with status = 'proposed' — no third table, and review_proposals reads them alongside edge proposals.
How memories join a thread
Three paths, all landing inmemory_threads:
- At write time —
rememberaccepts an optionalthreads: string[]. Names must already exist (configure_threadcreates them); unknown names are rejected, not auto-created. Membership lands asconfirmed,added_by: agent. - Curation — pin or unpin any existing memory. Membership metadata is organizational, not memory data: adding or removing a membership row never touches a memory row, so append-and-supersede is not in play here.
- Advisory proposals — the consolidation worker compares memory embeddings against thread embeddings (description vector, blended with the centroid of confirmed members once a thread has enough of them) and writes
proposedrows above a similarity threshold. Two triggers: each new memory is checked against active threads, and creating a thread kicks off a one-time corpus backfill scan so a newadhdthread immediately proposes its historical members.
fact/preference may earn auto-apply above the measured precision bar): every worker-proposed membership requires explicit review, for all memory types including event. Proposing event memberships is safe precisely because a human confirms them; auto-confirm may be revisited later behind the same per-type precision-bar mechanism, but it is out of scope here.
When revise supersedes a memory, the successor inherits the predecessor’s confirmed memberships (added_by: inherited) — a corrected memory should not silently fall out of its threads.
Retrieval
searchgains athreadfilter: results restricted toconfirmedmembers of that thread, combinable with existing filters. Membership filters; it never boosts ranking.briefinggainsthreadas a selector alongside scope, project, andall. A thread briefing deliberately crosses scopes — that is the feature, and the no-firehose rule holds because the selector is explicit.review_proposalslists membership proposals alongside edge proposals (akinddiscriminator:edge|thread_membership); accept confirms, reject recordsrejectedso the worker never re-proposes the same pair.describe_environmentincludes the thread registry.
Tool surface
One new tool —configure_thread — taking the MCP count to 12, which exhausts the hard cap of 12 (AGENTS.md rule 8). That is the real cost of this design: after threads, no further tool can ship without first removing or merging an existing one. It mirrors configure_scope (list / create / rename / set_description / archive / delete) and adds the curation actions (pin / unpin).
Considered and rejected: folding threads into configure_scope. Scopes are exclusive partitions and threads are overlapping collections; overloading one tool with both models confuses the agents reading the tool description, and renaming the shipped tool to something neutral breaks existing clients. The JTBD table gains one row:
Deleting a thread removes the registry entry and its membership rows; archiving hides it from selectors and stops new proposals while keeping memberships. Neither touches a memory row — same registry-only contract as scope deletion.
REST surface
GET/POST /api/v1/threads,PATCH/DELETE /api/v1/threads/:idPUT/DELETE /api/v1/threads/:id/memories/:memoryId(pin/unpin)GET /api/v1/memoriesaccepts?thread=;GET /api/v1/memories/facetsaddsthreads: string[]
packages/core thread service the MCP tools use.
Phasing
- Foundations — schema (+2 tables, migrations, isolation tests),
packages/schemathread types, core thread service,configure_thread,remember.threads,search/briefingfilters, REST routes. - Advisory membership — worker job (new-memory matching + creation-time backfill),
review_proposalsintegration, golden-set additions covering thread retrieval precision. - Dashboard UI — thread browsing/curation (lives in
3ngram-platform).
Open questions
- Membership cap per proposal batch — backfilling a broad thread (“family”) over a large corpus could propose hundreds of rows; the review surface needs pagination and probably a per-thread proposal budget per run.
- Thread-aware
handoff— exporting a thread as a context bundle is attractive but deferred until the base feature proves out. - Centroid weighting — description-vector vs. member-centroid blend ratio is a tuning question for the eval harness, not a design commitment.