All notable changes to sqlite-memory-mcp are recorded here. This file
follows the spirit of Keep a Changelog and the
project uses semantic-ish versioning on the 3.x line.
shared.jsis no longer generated, staged, or published. The file was awindow.__BRIDGE_DATA__ = <shared.json>wrapper — a byte-identical ~24 MB duplicate force-added past.gitignoreon every push. It was derived, never public API, and had no consumer: the Kanbanindex.htmlis fully self-contained (no fetch, no script src, no__BRIDGE_DATA__reference). The writer, thegit add -fstaging special-case, the surface-contract entry, and the managed.gitattributesline are all gone. A stale tracked copy is untracked automatically (git rm --cached --ignore-unmatch) on the next sync; on-disk leftovers stay classified as generated artifacts so cleanup discards them instead of blocking. Transport artifacts (shared.json,index.json,tasks/*.json) are unaffected.
- The legacy shared.json task fallback now fails closed on the transport
path. When
index.jsonis missing or unreadable whileshared.jsonstill carries tasks, the sync previously merged active-only rows — remote tombstones were invisible in that mode, and the push that followed could resurrect deletions made on other peers. The merge is now refused (legacy_fallback_blocked), a full sync aborts before push, and a fresh empty bridge repo (no tasks anywhere) still initializes normally. The same gate covers a manifest that parses but carries no"tasks"list (e.g.{}): every real export writes the key as a list, so its absence marks a broken manifest, not an empty one. Blocked results now carrymessage, so downstream reporters show the actual cause instead of a generic "push was blocked". - A failed shared.js untrack blocks the sync instead of being ignored.
git rm --cached --ignore-unmatchexits non-zero only on a real git failure (index.lock contention, permissions). That result is now propagated from the staging step, so the sync stops before staging/commit/push rather than committing the stale tracked 24 MB copy the prune exists to remove.
- Incremental bridge watermark now matches the exported snapshot. A full
export can stream the large event ledger for long enough that a local write
lands after its read snapshot but before
shared.jsonreceivespushed_at. The next run previously used that later transport timestamp and silently skipped the missed write.last_push_atis now captured before the first export read; transportpushed_atremains the actual payload time and keeps its merge-driver tie-break meaning. - The incremental watermark is rewound behind the export snapshot. A writer
can stamp
updated_ata moment before the export pins its read snapshot and commit a moment after it: that row is invisible to the running export and would also sort below an un-rewound watermark, so no later run would ever export it either.last_push_atis nowexport_snapshot_atminusINCREMENTAL_WATERMARK_MARGIN_SECONDS(10s). Re-examining the window can cost a redundant push; the merge is idempotent last-writer-wins, so that costs bytes, while not re-examining it costs data. memory_audit_stateno longer votes on whether to push. The sync writes that row itself and imports it straight back from its own payload, always stamped inside the margin window — as a trigger it queued a redundant full push, streamed event ledger included, after every real one. It is excluded from the push decision only: the counter stays inbridge_change_summaryfor diagnostics, and the row still travels in the export payload whenever a genuine change causes a push.- Expanded the time-based bridge change summary for all nine previously uncounted export inputs: observations, task field versions, task/entity links, task attachments, collaborators, claim evidence, memory artifacts, memory conflicts, and memory-audit state. These signals prevent incremental skip for timestamped creates and updates that alter generated bridge files.
- Task/entity unlinks are now transportable. A row-presence timestamp
cannot represent a row that is gone, so
changed_task_entity_linkscould never see a removal and a peer never learned the link had been cut. Unlinking now writes atask_entity_link_tombstonesrow — keyed by the exported entity name, which is stable across peers unlikeentity_id, and FK-bound totasksonly so it survives an entity merge or delete. Exactly one canonical record exists per task/entity name; it travels in a new_link_tombstoneswire key rather than as adeleted_atentry inside_links, so a peer on an older build ignores an unknown key instead of importing the deletion as a live link. The DB import and the git merge driver read both keys, treat a missing_link_tombstonesas "no deletions", and resolve records with the same last-writer-wins rule, where an equal timestamp resolves to the deletion so a stale active record can never resurrect a cut link. A re-link must be strictly newer than the tombstone to clear it. - Collaborator removal wakes the incremental gate.
team_manifestis generated from row presence, so a hardDELETEfromcollaboratorsleft the fast path with nothing to compare and the stale manifest stayed published.manage_collaborators(remove)now writes abridge_payload_dirty_atmarker in the same transaction as the delete; the marker is never cleared explicitly and falls out of the comparison once a later push snapshot overtakes it. - Entity merge is visible to the bridge. Merging entities now records a link tombstone for the absorbed source name before the FK cascade erases the live rows, and stamps the re-link to the target with the merge time so the change out-ranks any peer's older record of it.
- Link deletions converge only between peers on v3.13.5 or newer. A peer on
an older build does not read
deleted_at; it imports the tombstone record throughINSERT OR IGNOREand recreates the link locally, which then returns on the next round trip. Upgrade every machine in a bridge before relying on unlink replication. This mirrors the v3.13.4 sequencing note and is a transport-compatibility limit, not a bridge block.
- Bookkeeping events are no longer export authority.
mergeandrepairevents record that a reconciliation happened; they do not author a value. The status-authority resolver nevertheless ranked them alongside real writes, so a bookkeeping event carrying a fresh local clock outranked the write it was describing and the export claimed authorship this machine never had — silently reverting a peer's edit on the next sync. They are now excluded while the event head is chosen (both the local and the remote builder) and again in the resolver, which also covers lookups bysource_event_id. Excluding them only in the resolver would let a bookkeeping event win head selection and take the authoring event ranked below it down with it. - Withdrawn: stamping merge events with absorbed authority (v3.13.3). That
approach wrote a foreign
(machine_id, logical_clock)pair, which collides with the peer's own event under the unique index onmemory_events(machine_id, logical_clock)— reachable in production because a pull imports peer events before merging tasks. Reproduced fornotes,titleandpriority;statuswas immune only because a peer status event in the ledger makes the field a materialization repair, so no merge event is written. Bookkeeping events go back to fresh local clocks, which is now harmless.
- Merge events no longer claim local authorship. Absorbing a peer's field
write recorded an audit event stamped with a fresh local logical clock. That
synthetic event then outranked the field version it came from, so the export
advertised the local machine as the author of a value it never wrote and the
peer's next genuine edit was silently reverted on the next sync. The event is
now stamped with the authority it absorbed, and carries
payload.synthetic_authorityso a peer receiving an event that claims its own authorship can tell it apart from the original. Peers that send no packed clock keep the previous behaviour unchanged. The effect was status-only, because onlystatusis canonicalised on export. - Future field-version clocks are clamped at startup. A future-dated packed
logical clock in
task_field_versions.updated_orderoutranks every later write permanently; the existing clamp applied only in memory for the duration of a single merge, so the row stayed poisoned on disk, warned on every sync, and was re-exported to every peer. Startup now pulls such rows back while preserving their counter, using the same tolerance as the runtime clamp.
- Orphan task field-version cleanup. Startup now removes
task_field_versionsrows whose parent task no longer exists. The cleanup is idempotent and preserves every version row with a live parent; configured production connections continue to enforce the existingON DELETE CASCADEforeign key so new hard deletes cannot recreate the orphan set.
- Legacy debate role/session migration. Databases created before the
one-active-role-per-session invariant can contain multiple active roles for
the same
(topic_id, session_id). Startup now keeps the most recently updated binding, retires older duplicates with an audit reason, and only then creates the unique index. This preventssqlite_bridgeandsqlite_intelstartup paths from failing while preserving the complete binding history. - Foreign-key baseline during debate schema rebuilds. The v1 envelope migration now rejects only foreign-key violations introduced by its own rebuild, instead of aborting on unrelated legacy violations that already existed elsewhere in the database.
- Bridge sync no longer blocks on the
kanban_payload.jsonartifact. v3.12.4 wiredkanban_payload.jsonintosurface_contractand the merge driver but misseddb_utils.is_generated_bridge_path()/ thegenerated_pathsrestore list. Because the export regenerates the file each run and leaves it uncommitted, the pre-sync readiness check (_path_allowed_dirty) treated it as a user-managed edit and failed closed with "commit or stash bridge repo edits before sync: kanban_payload.json" — silently freezing the mirror (and any downstream restore that relies on it). The file is now recognized as a regenerable generated artifact: allowed-dirty through the readiness gate and restorable from DB state alongsideshared.json/index.json. This restores the v3.12.4 "sync stays ON" guarantee. Regression test added (test_kanban_payload_is_recognized_as_generated_bridge_path).
- Render-only
kanban_payload.jsonbridge artifact. The Kanban PWA was loading the fullshared.json(~18 MB, with single notes up to ~540 KB), which hung the browser render. Exports now also emit a separatekanban_payload.jsonthat mirrors the payload but truncates task descriptions: non-active notes (done/archived/someday) collapse broadly, active notes over 20 KB truncate, small active notes pass through full. Each truncated copy carries_mirror_preview/_full_len/_full_hash(sha256). Generated on both export paths (bridge_sync_workerandbridge_server.bridge_push) before git staging.
- Transport is never truncated.
shared.json,index.json, andtasks/*.jsonkeep full bodies, so a fresh pull / restore recovers the complete description. The new artifact ispull=Falsein the surface contract (never an import source) andmerge=union+ self-heal, so a corrupt union-merged copy is rebuilt from the DB on the next export. Write failures are non-fatal to push.
- This closes the export/artifact-generation side only. The Kanban PWA consumer
repoint (reading
kanban_payload.json) is deferred to a separate change becausepwa/app.jsis read-write and a naive preview-read could write truncated bodies back toshared.json.
- Task Tray full-window launch when Dashboard is empty. The large Task
Manager window now starts on Today instead of forcing an empty curated
Dashboard tab, hides Dashboard when no curated rows exist, and restores the
window through an explicit Win32-visible path when another tray instance sends
SHOW. - Task Tray enrich-cache worker pileup. Periodic and manual refresh paths now use a single-flight guard so long-running enrich-cache refreshes cannot spawn overlapping background workers every 60 seconds.
- Bridge sync duplicate-redirect marker precision. Archived duplicate
redirects now require the explicit
ARCHIVED DUPLICATEmarker plusDO NOT USEorSUPERSEDED, preventing ordinary archived notes about deduplication/canonicalization from bypassing bridge shrink guards.
- Bridge sync archived-duplicate redirect preservation. The bridge safety
and export paths now recognize archived duplicate redirect tasks as
intentional canonical pointers, so content-aware shrink guards do not restore
stale full task bodies over a short
DO NOT USEredirect stub.
Release notes for the
v3.11.xline — summarizing the work landed onmainafter thev3.11.19tag.
debate_add_role— flexible debate roster. Roles can now be added to a debate topic afterdebate_init, instead of being fixed at topic creation. This lets a running multi-agent debate grow its participant set (for example, binding a new EXECUTOR or ADVOCATE mid-flight) without recreating the topic. Role addition goes through the same validation and mutation ledger as the rest of the debate lifecycle.
- Push-aware tombstone retention in tray purge sync. When the Task Tray purges a task and that deletion is synced across machines, the tombstone is now retained in a push-aware way so a peer that has not yet seen the deletion cannot resurrect the task on the next pull. This closes a class of "deleted task comes back" regressions in the cross-machine bridge sync path.
These are not new in this delta; they are part of the shipped 3.x foundation
that the above work extends, and are listed for launch-context clarity:
- Debate Protocol v2 — the schema, validators, and lifecycle state machine
behind multi-agent debate (addressed messages, role bindings, watermarks,
topic state transitions). See
docs/DEBATE_PROTOCOL.md. - Hybrid BM25 + semantic search (RRF). FTS5 BM25 ranking fused with
optional sqlite-vec vector results via Reciprocal Rank Fusion
(
vec_search.py::rrf_merge). The vector path is opt-in via thevectorextra; without it, search falls back to pure FTS5 BM25.
- Existing databases migrate forward automatically on first
init_db(); no manual migration step is required for this delta. - No grand tool-count total is asserted here. Tool counts per server are documented in the README/Tool Reference, which is the canonical surface; this changelog tracks behavioral deltas, not a headline number.
For releases up to and including v3.11.x, see the historical GitHub release
descriptions.