Memory/sqlite vec retrieval - #1
Merged
Merged
Conversation
saucam
added a commit
that referenced
this pull request
Jun 14, 2026
…scaling) (#12) Adversarially-verified high-severity findings from a full-codebase audit. (The scarier "stuck-status" race claims were refuted on verification and are not touched here.) #1 Dropped message after interrupt-then-fast-send (session.ts). The consumer `finally` nulled #inputQueue/#consumerTask without the identity guard it already used for #query/#abortController, so an un-awaited interrupt() + a fast send() let the stale loop clobber the new loop's queue/task and the next push was silently dropped. Capture a loop-local queue/task snapshot and guard the nulls by identity. #2 Token never re-verified after handshake (auth.ts/server.ts/types.ts). An open socket honored an expired/revoked token forever. Carry `exp` into AuthContext, reject missing/expired exp in verifyToken (60s skew), and close 4003 on a per-message expiry check. (Instant revocation of a still -valid token still needs a periodic re-verify — tracked separately.) #3 Web reconnect replayed the dead JWT forever (ws.ts/connection.ts). Add a getToken() supplier called on every (re)connect open that re-exchanges the stored zid_sk_ key for a fresh JWT; fall back to the last token if none. +2 tests. #4 Vector recall had no cache despite the comment (memory/store.ts). Every recall re-read + re-decoded all embeddings and brute-forced cosine. Memoize the decoded matrix per workspace; invalidate on insert-with -embedding / setEmbedding. #5 Memory init was all-or-nothing (engine.ts). An embedder download hiccup nulled the whole engine, also killing FTS recall + usage persistence. Wrap embedder.init() in try/catch and run FTS-only (vector signal off) on failure; recall and the embed pump guard on the ready flag. #6 Unbounded session resume blocked startup (session-manager.ts, issue #6). Sort newest-first, cap to RESUME_MAX_SESSIONS, time-box to RESUME_DEADLINE_MS, and log what was left on disk. Typecheck clean; 554 daemon tests + 95 web tests pass; build OK; live smoke (auth+exp accepted, session.list) verified against the dev daemon. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 2, 2026
… via upsert (#74) * fix: streamed messages pushed into scrollback twice — commit finalize via upsert, not a second push The #50 fix covered only #artificiallyStreamText. The main streaming path still called #persistAndBuffer at BOTH stream start (text_delta creates the message) and finalize (text_done), and the same double-push lived in #flushActiveAssistant (interrupt/turn-boundary flush) and #finalizeActiveThinking (every thinking block). Consequences: - scrollback.replay carried two entries per streamed messageId; clients rendered the message twice and the web virtualizer's messageId-keyed caches collided (the residual cause of the 'intermittent message overlap' that #73 partially fixed) - the memory chunker received the stream-start push with empty content, emitting a prompt-only user_turn episode and then a promptless assistant_turn — every plain turn fragmented into two half-episodes - byte accounting drifted negative (push #1 accounted the empty size, eviction subtracted the grown size twice), permanently disabling the 20MB scrollback cap Fixes: - ScrollbackBuffer now records the accounted size per entry and upserts by messageId: re-pushing a buffered id re-accounts the existing entry in place (keeping its replay position) instead of appending a duplicate. Eviction subtracts exactly what was added — negative drift is structurally impossible. updateMessage is O(1) via the id index (was a front-to-back scan). - Session stream-start sites push to scrollback only; the new #commitStreamed emits the durable transcript row and the chunker event exactly once, at finalize, with final content. #artificiallyStreamText drops its bespoke reset-and-updateMessage dance for the same helper. - #seq now seeds past the persisted transcript tail on resume instead of restarting at 0, making seq usable as a monotonic replay cursor. Tests: session-stream-commit.test.ts pins one-scrollback-entry-per- messageId across all four finalize paths (text_done, thinking_done, batch-reply artificial streaming, turn-boundary flush), buffer upsert + byte-cap accounting under by-reference growth, chunker episode pairing (including a test documenting the pre-fix fragmentation), and seq continuation after resume. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: account scrollback bytes as UTF-8, add live-session chunker regression test CodeRabbit review follow-ups on #74: - String.length counts UTF-16 code units; use Buffer.byteLength so the 20MB cap holds for non-ASCII payloads - end-to-end test that a real Session + MemoryEngine ingests exactly one combined user+assistant episode per streamed turn (a stray stream-start chunker feed would fail it) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: StubEmbedder missing close() from the Embedder interface Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 5, 2026
…ed number Wires a baseline resolver over the current workspace-scoped searchSessions and measures it against the real Hetzner corpus (16 sessions / 11 workspaces / 11,938 episodes) with 37 hand-labeled fuzzy references. Result: within-workspace P@1 = 89.2% (primitives sound), cross-workspace P@1 = 21.6% with R@5 = 81% — a ranking/fusion problem, not recall: searchSessions normalizes BM25 batch-relative PER workspace, so a small workspace's inflated scores dominate the naive cross-workspace merge (~22 misses return the same wrong small-workspace session at #1). 21.6% is the number P1 must beat; global normalized fusion + cross-encoder rerank should convert the 81% R@5 into P@1. See src/daemon/eval/BASELINE.md. memory.db is NOT committed (real session content); only the derived fuzzy-reference fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 5, 2026
…ed number Wires a baseline resolver over the current workspace-scoped searchSessions and measures it against the real Hetzner corpus (16 sessions / 11 workspaces / 11,938 episodes) with 37 hand-labeled fuzzy references. Result: within-workspace P@1 = 89.2% (primitives sound), cross-workspace P@1 = 21.6% with R@5 = 81% — a ranking/fusion problem, not recall: searchSessions normalizes BM25 batch-relative PER workspace, so a small workspace's inflated scores dominate the naive cross-workspace merge (~22 misses return the same wrong small-workspace session at #1). 21.6% is the number P1 must beat; global normalized fusion + cross-encoder rerank should convert the 81% R@5 into P@1. See src/daemon/eval/BASELINE.md. memory.db is NOT committed (real session content); only the derived fuzzy-reference fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 5, 2026
The conductor resolves a fuzzy reference across ALL workspaces, which the current workspace-scoped searchSessions can't do. Adds engine.recallGlobal(): unions FTS + vector candidates across every workspace and ranks in ONE batch, so the ranker's BM25 min-max normalization is GLOBAL (fixes the small-workspace-domination failure a naive per-workspace merge has). searchSessions() goes global when no workspaceId is passed. New store primitives: listWorkspaceIds, ftsSearchGlobal, episodesByIds. Measured on the real Hetzner corpus (37 labeled refs), vs the naive-merge baseline: - latency 4224 -> 24ms p95 (~200x: one search vs 11 per-workspace) - R@5 81% -> 92%, R@3 76% -> 81%, MRR 0.54 -> 0.61 - P@1 35.1% -> 37.8% (modest; remaining misses are rank 2-3 as big verbose sessions fill #1 — slice 2 cross-encoder rerank targets exactly this) 4 new deterministic cross-workspace tests (17 total green); tsc + biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 6, 2026
…ed number Wires a baseline resolver over the current workspace-scoped searchSessions and measures it against the real Hetzner corpus (16 sessions / 11 workspaces / 11,938 episodes) with 37 hand-labeled fuzzy references. Result: within-workspace P@1 = 89.2% (primitives sound), cross-workspace P@1 = 21.6% with R@5 = 81% — a ranking/fusion problem, not recall: searchSessions normalizes BM25 batch-relative PER workspace, so a small workspace's inflated scores dominate the naive cross-workspace merge (~22 misses return the same wrong small-workspace session at #1). 21.6% is the number P1 must beat; global normalized fusion + cross-encoder rerank should convert the 81% R@5 into P@1. See src/daemon/eval/BASELINE.md. memory.db is NOT committed (real session content); only the derived fuzzy-reference fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 6, 2026
The conductor resolves a fuzzy reference across ALL workspaces, which the current workspace-scoped searchSessions can't do. Adds engine.recallGlobal(): unions FTS + vector candidates across every workspace and ranks in ONE batch, so the ranker's BM25 min-max normalization is GLOBAL (fixes the small-workspace-domination failure a naive per-workspace merge has). searchSessions() goes global when no workspaceId is passed. New store primitives: listWorkspaceIds, ftsSearchGlobal, episodesByIds. Measured on the real Hetzner corpus (37 labeled refs), vs the naive-merge baseline: - latency 4224 -> 24ms p95 (~200x: one search vs 11 per-workspace) - R@5 81% -> 92%, R@3 76% -> 81%, MRR 0.54 -> 0.61 - P@1 35.1% -> 37.8% (modest; remaining misses are rank 2-3 as big verbose sessions fill #1 — slice 2 cross-encoder rerank targets exactly this) 4 new deterministic cross-workspace tests (17 total green); tsc + biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
added a commit
that referenced
this pull request
Jul 6, 2026
…ner-delegated identity (P2) (#51) * docs: add conductor design, session-resolution architecture, and build plan Spec set for the codeoid conductor: a single global, identity-native supervisor session that resolves fuzzy natural-language references to the right coding session across all workspaces, coordinates the existing session fleet, and never goes out of context. - conductor-design.md: architecture + locked decisions (owner-delegated privileged identity, durable conductor / disposable children, confirm before send-class acts, approval-gated egress, metrics-only cost guard) - conductor-session-resolution.md: SOTA retrieval architecture (BGE-M3 hybrid + bge-reranker-v2-m3 + LFM2-350M-Extract cards + bi-temporal state), backed by a 9-agent research fan-out - conductor-build-plan.md: phased plan P0-P8 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: P0 conductor foundations — session-card store + eval metrics First P0 slice of the conductor (see docs/conductor-build-plan.md). - SessionCardStore (src/daemon/memory/cards.ts): per-session digest cards with a standalone FTS5 mirror for keyword/identifier recall, plus a bi-temporal fact log (Zep/Graphiti pattern) — state changes are invalidated-not-deleted (valid_at/invalid_at event time + created_at/expired_at system time), giving time-travel + a lossless audit trail. - Eval harness metrics (src/daemon/eval/metrics.ts): precision@1 / MRR / recall@k + latency percentiles for known-item session resolution — the go/no-go gate for the whole feature. 13/13 unit tests pass; tsc + biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: firstmate prior-art analysis + reconcile conductor plan with mobile app - conductor-prior-art-firstmate.md: analysis of firstmate (a shipped bash+prompt conductor, codeoid's architectural inverse). Borrows ranked: read-only-by-construction, zero-token event-driven supervision, ship/scout task shapes, per-project autonomy modes, /afk + /stow, harness dispatch profiles, secondmates (nested-conductor scaling). Where codeoid is already better: cryptographic identity, semantic session resolution, determinism (code vs 122KB prompt), daemon-native events. - build-plan: added 'Informed by firstmate' refinements and 'Reconciliation with the mobile app plan'. Key reconciliation: the conductor needs ZERO new client wire types (mobile doc §8), so the P3 protocol-level fleet.find is downgraded to optional; shared @codeoid/core extraction up front; sequencing = conductor backend (P1 session resolution) first as the risk-retiring gate, mobile app in parallel on today's protocol, converging at mobile-P5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: hermes prior-art + upgrade P4 (durable queue/roles/hardening) + add P4.5 routines - conductor-prior-art-hermes.md: analysis of NousResearch/hermes-agent, the most complete personal-assistant prior art (multi-platform gateway, cron routines, autonomous skills, delegate + Kanban). Borrows: routines (cron+webhooks+script-injection [SILENT]), durable Kanban work-queue, leaf/orchestrator delegate role model, session-lifecycle hardening, multi-platform gateway shape, zero-context-cost tool-RPC scripts, Curator safe-autonomy invariants, ACP + serverless-persistence notes. Where codeoid stays ahead: cryptographic identity, rerank+bi-temporal retrieval, typed modular daemon (hermes is a Python monolith). - build-plan: upgraded P4 dispatch (durable Kanban-style queue + leaf/orchestrator roles enforced via ZeroID scopes + session-lifecycle hardening: resume_pending/stuck-loop/clean-shutdown/burst-collapse queue) and added P4.5 Routines (scheduled + webhook-triggered autonomy, [SILENT] monitors, cron hardening). Updated phase table, dependency graph, and added the 'Informed by hermes' section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: P0 baseline — session-resolution eval runner + fixture + measured number Wires a baseline resolver over the current workspace-scoped searchSessions and measures it against the real Hetzner corpus (16 sessions / 11 workspaces / 11,938 episodes) with 37 hand-labeled fuzzy references. Result: within-workspace P@1 = 89.2% (primitives sound), cross-workspace P@1 = 21.6% with R@5 = 81% — a ranking/fusion problem, not recall: searchSessions normalizes BM25 batch-relative PER workspace, so a small workspace's inflated scores dominate the naive cross-workspace merge (~22 misses return the same wrong small-workspace session at #1). 21.6% is the number P1 must beat; global normalized fusion + cross-encoder rerank should convert the 81% R@5 into P@1. See src/daemon/eval/BASELINE.md. memory.db is NOT committed (real session content); only the derived fuzzy-reference fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: refresh P0 baseline after rebase onto main (cross-workspace P@1 21.6% -> 35.1%) feat/conductor was 22 commits behind main; rebased onto origin/main (conflict-free — P0 adds only new files). Re-ran the baseline on the current base: cross-workspace P@1 rose 21.6% -> 35.1% (MRR 0.45 -> 0.54) because main's #94 (append-to-vector-cache) improves vector coverage. Within-workspace unchanged at 89.2%; R@5 still 81%; same small-workspace-domination failure mode. 35.1% is the refreshed P1 target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: P1 slice 1 — cross-workspace global fusion for session resolution The conductor resolves a fuzzy reference across ALL workspaces, which the current workspace-scoped searchSessions can't do. Adds engine.recallGlobal(): unions FTS + vector candidates across every workspace and ranks in ONE batch, so the ranker's BM25 min-max normalization is GLOBAL (fixes the small-workspace-domination failure a naive per-workspace merge has). searchSessions() goes global when no workspaceId is passed. New store primitives: listWorkspaceIds, ftsSearchGlobal, episodesByIds. Measured on the real Hetzner corpus (37 labeled refs), vs the naive-merge baseline: - latency 4224 -> 24ms p95 (~200x: one search vs 11 per-workspace) - R@5 81% -> 92%, R@3 76% -> 81%, MRR 0.54 -> 0.61 - P@1 35.1% -> 37.8% (modest; remaining misses are rank 2-3 as big verbose sessions fill #1 — slice 2 cross-encoder rerank targets exactly this) 4 new deterministic cross-workspace tests (17 total green); tsc + biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: P1 slice 2 — cross-encoder rerank clears the resolution gate Adds a Reranker interface + transformers.js cross-encoder impl (Xenova/ms-marco-MiniLM-L-6-v2, swappable for bge-reranker-v2-m3). MemoryEngine reranks the top-8 candidate sessions by (query, evidence) when a reranker is present; searchSessions({rerank}) gates it (defaults on when ready) and degrades to fusion-only if the model fails to load. Measured on the real Hetzner corpus (37 refs): cross-workspace P@1 37.8% -> 86.5% (MRR 0.61 -> 0.91, R@3 81% -> 95%), latency +~30ms (88ms p95). Converts slice 1's 92% R@5 into precision@1 — essentially the within-workspace ceiling (97.3% with rerank on). P1 go/no-go gate CLEARED: cross-workspace P@1 35.1% (baseline) -> 86.5%, p95 < 100ms vs the 2s budget. 18 tests green (added a deterministic rerank test); tsc + biome clean. Remaining P1 slices (BGE-M3, identifier-aware lexical, session cards) are now optional polish. See src/daemon/eval/BASELINE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: add session-resolution how-it-works explainer First-class explainer of the shipped cross-workspace session-resolution capability (two-stage: global fusion -> cross-encoder rerank). Distinct from the design/plan doc (conductor-session-resolution.md) and the eval writeup (BASELINE.md): covers the problem, the pipeline, why two stages, measured results (P@1 35.1% -> 86.5%), the local models, a code map, repro, and limitations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: genericize eval fixture references + refresh numbers codeoid is public; the eval fixture named internal project specifics. Genericized all 37 reference strings to neutral software-work descriptions (gold labels are opaque UUIDs — unchanged) and scrubbed an internal name from BASELINE.md. Re-ran: pure-conceptual references (no exact identifiers) are a harder, conservative eval — cross-workspace P@1 21.6% (naive) -> 35.1% (fusion) -> 73.0% (rerank), R@5 92%, <100ms. Two-stage story unchanged; identifier-bearing references resolve higher still. Refreshed numbers in BASELINE.md, docs/session-resolution.md, and the build plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: P2 conductor identity foundation — durable owner-delegated identity Conductor scope profile (R1): session:read / session:dispatch join the protocol scopes; CONDUCTOR_SCOPES deliberately omits tools:write and tools:execute, so ZeroID's per-hop scope intersection makes the conductor's whole delegation subtree read-only-by-construction on targets. Durable identity (R2): registerConductor(ownerSub) registers a ZeroID orchestrator identity and persists {identityId, wimseUri, apiKey} to the Store; resumeSessions reloads it on daemon restart — one stable WIMSE URI across process lifetimes, with the actor keypair regenerated (never at rest) and re-registered per boot. mintConductorToken exchanges the owner's subject token for the conductor's working token (RFC 8693), and deactivateConductor cascade-revokes the subtree via ZeroID's parent_jti walk. Integration test (bun run test:integration, live ZeroID required): mints the owner → conductor → child → sub-agent chain at delegation_depth 3 with a verified act chain per hop, proves tools:write can't be minted below the conductor, and asserts deactivation kills conductor/child/sub-agent tokens while the owner's token stays active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: unit-test the conductor lifecycle and reranker backend Fetch-stubbed ZeroID covers register/resume/mint/deactivate — including the stale-row drop, key rotation on resume, and the actor-assertion wire contract — plus Store persistence per tenant. The cross-encoder reranker is covered with @xenova/transformers mocked (no model download): batching shape, single-logit vs two-class score extraction, and re-init after close. The live-ZeroID integration test remains the depth-3 / cascade-revocation proof; these keep the CI patch-coverage gate honest without a server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: address CodeRabbit review — write ordering, transactions, bounded init - registerConductor persists the identity BEFORE exposing it in memory, so a Store failure reads as registration failure, not a phantom durable identity. - deactivateConductor keeps the persisted row when the remote deactivation fails — it's the only durable record of a still-live identity, and the next call retries against it (test added). - Card upsert + FTS mirror refresh and assertFact's supersede + insert each commit in one transaction, so a crash can't leave the FTS drifted or a (subject, predicate) with no open fact. - assertFact rejects out-of-order validAt instead of closing the open fact with invalid_at < valid_at, which made the row unsatisfiable for every factsAsOf() read (test added). - reranker.init() is bounded by a 120s timeout — a stalled model download degrades to fusion-only instead of wedging daemon startup; close() disposes the ONNX model to actually free WASM memory. - conductor-design.md: fleet MCP allowlist gotcha noted for P3, §4 updated to the CONDUCTOR_SCOPES profile as implemented, §9 Shield marked later-phase (v1 = owner approval only, per R4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.