24 Commits
Author SHA1 Message Date
james.maes 5fc2d3fabf docs: reconcile Ollama model baseline to actual production names (#184)
The codebase moved to qwen3.6:latest (memory extractor) and
qwen2.5-coder:32b-instruct-q8_0 (agent fallback) in commit 8d0a959, but
CLAUDE.md and the MemoryRegressionCorpusTests doc-comments still cited
the vanilla qwen2.5-coder:32b string. Both updated to match what
ModelID.swift, MemoryExtractor.swift, and BootHealth.swift actually
use today.

No behavior change — documentation alignment only.

Closes #58
2026-05-13 16:25:00 -05:00
james.maes f59f5d12e3 fix(memory): v0.1 CRIT cluster — facts_vec / replay correlation / FTS5 sanitize / search_memory preamble (#12, #13, #14, #15, #16, #153)
#14 (CRIT): ContextBuilder.selfAwarePreamble names search_memory + forget_fact with explicit cue 'call search_memory FIRST. Never claim to remember without searching.'

#16 (HIGH): MemoryQueries.sanitizeFTS5Query strips non-alphanumeric, quotes tokens; HybridSearch.searchFacts calls it before binding to MATCH ?. Embedding still runs on raw query.

#12 (CRIT): MemoryStore.init accepts optional embedder; insertNewFact writes facts_vec after the facts commit. Vec failures log + leave FTS row (no rollback). installMemory shares one embedder between writer + reader.

#15 (HIGH): drop stableHash; pass sentinel 0 as sourceTurnId (option 1 per audit — smaller change surface than threading real rowid through 5 files). Schema comment updated.

#13 (CRIT): two-layer fix. Layer 1: AppDelegate owns a per-process sentinel TurnID; installAgent writes the matching turns row after beginSession; AppDelegateMemoryReplaySink attributes events to it. Layer 2: ReplayLog.flushPending retries failed batches event-by-event so one poisoned row only drops itself.

Memory 98 + Replay 34 + AgentCore 243 tests pass; all 18 boundary gates green.
2026-05-13 06:30:35 -05:00
james.maesandClaude Opus 4.7 5718bdd820 test(memory): rename qwen25Coder32B test to qwen36
Matches commit 8d0a959 model flip — extractor default is now
ModelID.qwen36 ("qwen3.6:latest"). The previous test name + assertion
were stale and failed on `swift test --package-path packages/Memory`
with the "Optional(qwen3.6:latest)) is not equal to (qwen2.5-coder…)"
diff. No production code change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:02:57 -05:00
james.maesandClaude Opus 4.7 cb584a2ac8 feat(d5d6): real memory database — vec0 statically linked, hard-fail boot, stats tool
Closes D-5 / D-6 (sqlite-vec dylib bundling) carry-forwards from the
2026-05-04 audit by adopting jkrukowski/SQLiteVec as a SPM dependency.
The package's CSQLiteVec target compiles sqlite3.c + sqlite-vec.c into
one statically-linked C target with `core_vec_init()` registering vec0
as an auto-extension. All sqlite3_* calls in Replay's SQLiteConnection
and Memory's MemoryStore resolve to this bundled SQLite (vec0 always
available) instead of Apple's stripped /usr/lib/libsqlite3.dylib.

The retired hand-rolled approach (dlsym pathway + JARVIS_VEC0_STUB_PATH
env-flag + Bundle.module placeholder text file) never worked on macOS
because Apple stripped sqlite3_load_extension from the system libsqlite3.
Memory init has been silently dormant since Phase 7 (2026-04 onward),
which is what made B-02 (history threading) appear permanently broken
to the user during dogfooding even after the 48ad8d6 tactical patch
landed.

Live verification 2026-05-11: fresh launch with empty jarvis.db creates
schema, persists turn rows across submits, B-02 multi-turn context
resolves correctly ("what breed is Toby?" → "Golden Retriever" after
prior turn established Toby is a Golden Retriever), and the new
get_memory_stats tool reports `turnsCount=6, vecVersion=v0.1.6,
sqliteVersion=3.51.0`.

Changes:

- packages/Replay/Package.swift
    + SQLiteVec SPM dependency (from: "0.0.14")
    + CSQLiteVec product wired into Replay target
- packages/Replay/Sources/Replay/SQLiteConnection.swift
    - import SQLite3
    + @_exported import CSQLiteVec
    All sqlite3_* call sites unchanged — linker now resolves to our
    bundled sqlite3 with vec0 statically linked in.
- packages/Memory/Package.swift
    + SQLiteVec SPM dependency (named product so MemoryStore can import
      CSQLiteVec directly for the core_vec_init() one-shot call)
    - resources: PLACEHOLDER.txt copy (defunct vec0.dylib stub slot)
- packages/Memory/Sources/Memory/MemoryStore.swift
    - import SQLite3 → import CSQLiteVec
    + _vecAutoExtensionRegistered static-let one-shot: calls
      CSQLiteVec.core_vec_init() which registers vec0 via
      sqlite3_auto_extension on the first MemoryStore construction.
      Idempotent (static-let initializer fires exactly once).
    - loadVecExtension(on:) dlsym/JARVIS_VEC0_STUB_PATH/Bundle.module
      pathway (retired; ~70 lines removed)
    Updated docstring to reflect the new hard-fail boot policy.
- packages/Memory/Sources/Memory/Resources/PLACEHOLDER.txt — deleted
- App/AppDelegate.swift installMemory()
    Reverses Track-D D-1 (75a10be, 2026-05-04): MemoryStore.init failures
    are now FATAL via Logger.critical + TCCAlertService.presentHardBlock
    + NSApp.terminate. The graceful-degradation path that kept the app
    booting with memoryStore=nil hid the entire B-02/B-04/B-05 silent-
    failure bug class. Aligns with the 2026-05-07 API-first pivot
    thesis: "no silent failures".
    `store` is now non-optional throughout installMemory; downstream
    callers no longer need `if let store = store`.
- packages/MCP/Sources/MCP/InProcess/GetMemoryStatsTool.swift (new)
    + MemoryStatsDispatching protocol
    + MemoryStats Codable struct (turnsCount, factsTotal, factsActive,
      sessionsCount, dbFileBytes, vecVersion, sqliteVersion,
      lastTurnAtUnixMs, lastFactAtUnixMs)
    + GetMemoryStatsTool conforming to InProcessTool
- App/MCP/InProcessMemoryAdapters.swift
    + MemoryStatsStoreAdapter — bridges the protocol to MemoryStore's
      public queryRowCount / querySingleString seams and FileManager
      for the on-disk file size. Read-only, no confirmation.
- App/AppDelegate.swift installSelfKnowledgeTools()
    + Registers GetMemoryStatsTool with MemoryStatsStoreAdapter.
- packages/Memory/Tests/MemoryTests/*
    Retired the JARVIS_VEC0_STUB_PATH XCTSkipUnless guards from 9 tests
    (vec0 is always available now — no need to env-gate). Rewrote
    MemoryStoreTests to drop the now-impossible "init throws when vec
    missing" case and assert facts_vec virtual table creation +
    vec_version() readback. Added seedTurnRow helper to
    MemoryStoreApplyOpTests so the now-enforced
    `facts.source_turn_id REFERENCES turns(id)` foreign key is honored
    in tests that previously passed when FK enforcement was dormant.

Test summary: Memory full suite 91 tests, 10 skipped (env-gated real-
Ollama tests), 0 failures. App target builds clean. Substrate verified
end-to-end via live launch (vec_version: v0.1.6 logged at boot;
get_memory_stats returns actual schema row counts).

Pending D-7 carry-forward: ollama pull nomic-embed-text (~270MB) for
embedder; user already has qwen2.5-coder:32b-instruct-q8_0 which is
a higher-quality variant of the project's hardcoded
qwen2.5-coder:32b — small ModelID config delta to follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:25:56 -05:00
james.maesandClaude Opus 4.7 48ad8d631c fix(b-02): thread recent turns into AgentOrchestrator messages array
Carry-forward bug B-02 (live evidence 2026-05-07): conversation
continuity broken — "yes" / "why 4.5?" lose prior turn context because
AgentOrchestrator.process(_:) built [system, user(input.userText)]
with no history fetch.

Tactical patch outside the M-0..M-7 migration sequence, per
JARVIS-API-MIGRATION-PLAN.md §3 and UQ-2 (option B). Lands on develop
directly. M-4 (Memory surface) will reframe this path against the new
API contract but the user-visible bug closes today.

Substrate gap discovered + addressed in-scope. The `turns` table was
unwritten (schema present at MemorySchema.swift, FTS5 trigger ready,
read API at MemoryStore.recentTurnsForSession, but no INSERT site
anywhere in the codebase). The migration plan assumed turn writes
existed; they did not. Patch adds the missing writer plus the read
side wiring.

Changes:

- packages/Memory/Sources/Memory/MemoryQueries.swift
    + turnInsertSQL — single-row INSERT into turns
- packages/Memory/Sources/Memory/MemoryStore.swift
    + appendTurn(sessionId:role:content:source:createdAt:)
    Single-row INSERT; FTS5 trigger mirrors content into turns_fts
    automatically. T-06-05-03: function MUST NOT log content.
- packages/AgentCore/Sources/AgentOrchestrator/AgentOrchestrator.swift
    + PriorTurn struct + SessionHistoryLookup typealias
    + emptySessionHistoryLookup default (preserves pre-B-02 test setups)
    + sessionHistoryLookup ctor parameter
    Replaces the [system, user] messages-array build with prior-history
    hydration: system + chronological prior user/assistant rows + current
    user. Lookup throws -> warning log + empty history (matches D-3
    PriorFactsLookup resilience contract).
- App/AppDelegate.swift
    + sessionHistoryLookup closure: recentTurnsForSession(limit: 10),
    reverse DESC -> chronological, filter to user/assistant only, return
    [PriorTurn]
    + extends existing turnContent flush closure with appendTurn calls
    (rides on memory-extraction drain path; assistant row offset +1 ms
    so DESC + id-tiebreaker preserves user->assistant order)

Tests:

- packages/AgentCore/Tests/AgentOrchestratorTests/HistoryThreadingRegressionTests.swift
    HT-1: third turn includes prior user/assistant pairs in chronological
    order. 3/3 PASS.
    HT-2: empty-history default produces [system, user] (pre-B-02
    baseline preserved).
    HT-3: three-turn happy path emits zero LLMProviderError
    .streamTruncatedFinal events. Pins R-006 / Phase E gate behavior:
    patch grows messages array, not finalSystem, so
    CacheHints.eligibleForSystemPrompt(finalSystem) is unchanged.
- packages/Memory/Tests/MemoryTests/MemoryStoreAppendTurnTests.swift
    AT-1: appendTurn round-trips via recentTurnsForSession.
    AT-2: user/assistant pair reverses to chronological.
    AT-3: session_id scoping isolates rows.
    AT-4: turns_fts trigger fires on append.
    All env-gated on JARVIS_VEC0_STUB_PATH per existing MemoryStore-test
    convention; skip cleanly without vec0.dylib.

Verification:

- check-app-builds.sh PASS.
- Boundary gates sampled (check-no-leftover-stubs, applescript-confirm,
  corpus-secrets, bus-protocol-version, single-memory-mutated-emit,
  single-writer-hudstate) PASS.
- AgentCore affected-subset (Submit/Retry/TextEnd2End/CapRecovery/
  HistoryThreading) 26/26 PASS, 0 failures.
- Memory full suite 92 run, 23 skipped (env-gated), 0 failures.

Degradation contract: when MemoryStore is nil (dormant memory — vec0
or Ollama unavailable), turn persistence is also dormant; the
appendTurn call is guarded by memoryStore != nil. When D-5/D-6 close
and memory comes up live, persistence activates without further code
changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:50:40 -05:00
james.maesandClaude Opus 4.7 f9cd25d66e chore(test-naming): converge on Mock/Fake/Stub convention; document in CLAUDE.md; rename worst-mismatch stragglers
Per audit-2026-05-04 P2-15: 26 Mock*, 21 Fake*, 12 Stub* across the test
corpus, often used interchangeably for the same role. New CLAUDE.md
section "Test naming conventions" (line 127) sets the rule:

- Mock*: records calls AND returns scripted responses (assertion driver).
- Stub*: returns canned data without recording (deterministic input).
- Fake*: alternative implementation with realistic behavior (in-memory
  state-holder).

Renamed 15 worst-mismatch stragglers in the older corpus (per audit
constraint, did NOT touch types added in today's P1+P2 fix commits):

Stub* → Mock* (records + scripts):
- HybridSearchTests:    StubEmbedder → MockEmbedder
- HybridSearchTests:    StubStore → MockStore
- SessionHistoryTests:  StubStore → MockStore
- InProcessMemoryToolsTests: StubHybrid → MockHybrid
- InProcessMemoryToolsTests: StubHistory → MockHistory
- InProcessMemoryToolsTests: StubForget → MockForget

Mock* → Stub* (canned data, no recording):
- STTBackendSwitchTests: MockSpeechAnalyzerBridge → StubSpeechAnalyzerBridge
- STTBackendSwitchTests: MockWhisperKitBridge → StubWhisperKitBridge
- VoiceWiringTests:      MockVADEngineForW4 → StubVADEngineForW4
- VoiceWiringTests:      MockSTTProviderForW4 → StubSTTProviderForW4
- VoiceWiringTests:      MockTTSForW4 → StubTTSForW4
- VoiceWiringTests:      MockOrchestratorForW4 → StubOrchestratorForW4
- VoiceWiringTests:      MockBannerForW4 → StubBannerForW4
- VoiceWiringTests:      MockBusForW4 → StubBusForW4

Fake* → Mock* (records + scripts):
- WebviewBridgeOutboundTests:        FakeJSEvaluator → MockJSEvaluator
- HudStateCoordinatorBusWiringTests: FakeJSEvaluator → MockJSEvaluator
  (the comment on the type already said "Records each call for assertion")

Skipped per audit constraint:
- StubCapture/StubSink (Vision FrameAttachConfirmSendWiringTests, P1+P2)
- MockVADEngine + MockOrchestratorForController siblings (Voice
  VoiceControllerTests/VADGatedSessionTests, today's track-B churn)

Tests pass: Voice 87+23, MCP 85, Memory 88, Bus 58, AgentCore 19+72,
Vision 19, Shell 7+5. App target builds clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 16:42:16 -05:00
james.maesandClaude Opus 4.7 a4f76661fc fix(memory p2-6): scrub raw search_memory query from HybridSearch debug log (T-06-05-03 analog)
Per audit 2026-05-04 security MEDIUM-1: HybridSearch.searchFacts logged
the raw user query string at debug level. Voice transcripts are guarded
by T-06-05-03; memory queries are equivalent user-private content.

Replace `query='\(query)'` with `queryLen=\(query.count)` — log
metadata only, never content. Add regression test that grep-asserts the
source file at runtime so a future log-content regression is caught at
test time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 16:06:26 -05:00
james.maesandClaude Opus 4.7 c3bd0a5728 test(track-d 4): end-to-end remember-Brutus regression scenario with fakes
Adds `RememberBrutusEndToEndTests` proving the extraction -> store ->
search loop holds end-to-end. Without this test, Tracks D-1..D-3 each
move a single component but no test covers their composition; a future
regression that severs the loop at the extraction/search boundary
would slip past per-component tests.

Scenario:
  1. User: "My dog Brutus is a Bernese Mountain Dog."
  2. Stub LLM provider returns `apply_memory_ops(ADD subject=Brutus
     predicate=breed object="Bernese Mountain Dog")`.
  3. Real `MemoryExtractionOrchestrator` + `MemoryExtractor` drive the
     ApplyOp callback into a `FakeStore` actor.
  4. User: "What breed is Brutus?"
  5. Real `HybridSearch` actor + stub embedder + `FakeStore`'s
     `MemoryReadStore` conformance return one hit; assert
     `summary == "Brutus breed Bernese Mountain Dog"` and the trigger
     turnId propagated to retrieval recording.

Fakes used:
- `FakeStore` actor — captures applyOp via custom `applyAdd`, conforms
  to `MemoryReadStore` so `HybridSearch.searchFacts` runs unmodified.
  No real SQLite, no vec0.dylib needed.
- `StubEmbedder` — constant 768-dim vector (real path is
  `OllamaEmbeddingClient` -> `nomic-embed-text`).
- `BrutusProvider` — single-shot stub `LLMProvider` emitting one
  `toolUseRequested` then `messageStop`.

Boundary: stops at `HybridSearch.searchFacts`. The MCP-side dispatch
through `SearchMemoryTool` is covered by
`InProcessMemoryToolsTests.testSearchMemoryToolDispatch` in the MCP
package. Together those two tests cover the path the agent takes:
prompt -> extractor -> store -> search tool -> agent context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 14:26:37 -05:00
james.maesandClaude Opus 4.7 51750c161b fix(track-d 3): MemoryExtractionOrchestrator passes real priorFacts to extractor
Pre-D-3, `MemoryExtractionOrchestrator.process(_:)` hardcoded
`priorFacts: []` on every job. The mem0 extractor was therefore blind
to existing facts and could only emit ADD ops — UPDATE/supersede
never fired, so superseded values silently duplicated on every turn.

This commit:
- Adds `PriorFactsLookup` typealias and `priorFactsLookup` parameter on
  `MemoryExtractionOrchestrator.init`. Default is `emptyPriorFactsLookup`
  to preserve pre-D-3 behavior for tests that don't care about priors.
- Wires `process(_:)` to call `priorFactsLookup(job)`. Lookup throws
  degrade to `priorFacts = []` with a warning log — the drain task
  must never die because a single SQL read failed.
- Adds `MemoryStore.recentActiveFacts(limit:)` + matching SQL in
  `MemoryQueries.recentActiveFactsSQL`. Active-only (`valid_to IS NULL
  AND forgotten_at IS NULL`), most-recently-created first, server-side
  cap. D-02 forgotten facts excluded.
- AppDelegate's `installMemory` passes a closure that returns up to 50
  recent active facts. 50 is a deliberate cap — RESEARCH §5 caps the
  rendered priorFacts block at ~4 KB (~50 facts at typical S/P/O
  length). Closure returns [] when `memoryStore` is nil, preserving
  the pre-D-3 no-store path.

Tests:
- `testD3_priorFactsLookupDrivesUpdateOp` — stub provider that emits
  UPDATE when it sees a `[99]` prior fact id in the system prompt;
  asserts `applyOp` receives `.update(supersedes: 99, …)`. Pre-D-3
  this would have been `.add` because the orchestrator never told
  the model about the prior fact.
- `testD3_priorFactsLookupErrorDegradesToEmpty` — lookup throws,
  job still applies its ADD op (drain survives the failure).

T-06-05-03 invariant respected: warnings log turnId only, never
fact content.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 14:22:42 -05:00
james.maes dcbd50e4c9 fix(phase-09): align Phase 7 grep-gate test with Plan 09-04 install order (vision → agent → voice) 2026-05-01 22:38:56 -05:00
james.maes fdb56d2785 feat(09-01): installAgent + broadcaster wiring + BLOCKER-1 closure (INT-07-01)
- AppDelegate.installAgent() constructs AgentOrchestrator with the
  existing 7-arg ctor (visionRouter / presenceSnapshot stay nil for
  Plans 2 + 3), starts the OrchestratorEventBroadcaster, and creates
  the TurnTranscriptStore. Memory + transcript + devOverlay subscribers
  drain into MemoryExtractionCoordinator, the assistant-side
  accumulator, and a no-op consumer respectively.
- ConfigStore is now built in applicationWillFinishLaunching from the
  loaded launch + perTurn snapshots (was previously discarded).
- installMemory no longer calls coord.start; the wiring moved to
  installAgent step 5a where the broadcaster's memory child stream is
  available. Old agentOrchestratorEvents() nil-stub is gone.
- MemoryExtractionCoordinator gains a MemoryEnqueueing protocol seam
  so the BLOCKER-1 end-to-end test can spy on enqueue without driving
  the full extractor stack.
- New scripts/check-orchestrator-events-single-consumer.sh enforces
  D-05/D-08 (exactly one production for-await over orchestrator.events;
  WARNING-2 whitelist scoped to OrchestratorEventBroadcaster.swift).
  Wired as a project.yml preBuildScript and exercised in-process by
  PhaseNineGrepGateTests.
- MemoryWiringEndToEndTests proves enqueue receives non-nil userText +
  assistantText through real broadcaster + transcript store + memory
  coordinator wiring (BLOCKER-1 closure for INT-07-01).
- Test home note: MemoryWiringEndToEndTests lives in MemoryTests
  (not AgentOrchestratorTests as the plan suggested) because Memory
  depends on AgentOrchestrator via SPM and the cycle would block
  test compilation. Recorded in the plan SUMMARY.
2026-05-01 14:56:13 -05:00
james.maes e057039ef0 refactor(09-01): MemoryExtractionCoordinator.start accepts generic AsyncSequence (S-4)
- start<S: AsyncSequence & Sendable> where S.Element == OrchestratorEvent
  replaces the BoundedAsyncChannel<OrchestratorEvent>-only signature.
- AsyncSequence's throwing iterator is wrapped in do/catch with a new
  warnDrainError helper; the start function stays non-throwing.
- BoundedAsyncChannel already conforms to AsyncSequence so existing
  call sites compile unchanged (verified by 8 prior coordinator tests).
- New test testStartAcceptsAsyncStream drives the coordinator through
  an AsyncStream — the type the OrchestratorEventBroadcaster's
  subscribers emit — proving the broadcaster will plug in directly.
2026-05-01 14:42:09 -05:00
james.maes 6f4f2fb6c2 feat(07-06): four cross-plan grep gates + AppDelegate install-order test (Task 3)
- scripts/check-single-memory-mutated-emit.sh — MEM-06 single-emission-site
  for ReplayEvent.memoryMutation (allowlist: MemoryStore.swift,
  Replay/ReplayEvent.swift, Replay/Schema.swift; tests + comments excluded).
- scripts/check-single-memory-used-emit.sh — D-05 single-emission-site for
  ReplayEvent.memoryRetrieval (same allowlist shape, MemoryStore.recordRetrieval
  is the sole emit site).
- scripts/check-presence-vision-isolation.sh — VISION-03 three-layer guard:
  Layer 1 (Vision/Package.swift declares no Voice or AgentOrchestrator dep),
  Layer 2 (no `import Voice` / `import AgentOrchestrator` and no TTSEngine /
  AgentOrchestrator.runTurn / cancelAndSubmit token references in
  packages/Vision/Sources/), Layer 3 (no App/ file co-locates a
  PresenceSignalBus reference with a forbidden TTS / runTurn token).
- scripts/check-embedding-dim-literal.sh — MEM-02 invariant: no literal 768
  in packages/Memory/Sources/Memory/ outside Constants.swift; comment-only
  lines tolerated.
- All four scripts wired as Jarvis pre-build phases in project.yml; the
  generated project.pbxproj reflects the updates.
- packages/Memory/Tests/MemoryTests/PhaseSevenGrepGateTests.swift mirrors
  the four shell gates as XCTest assertions plus a fifth assertion
  (testAppDelegateInstallOrder) verifying the call order in
  applicationWillFinishLaunching: installMemory before installVoice
  before installVision.
- App/Voice/NullVoiceAdapters.swift extracted from AppDelegate.swift to
  honor the Layer 3 boundary cleanly. NullOrchestratorAdapter and
  NullTTSAdapter are placeholder no-ops mirroring the prior structure;
  Phase 7 future wiring replaces them with real bridges.
- Negative-case demonstration completed: introducing a stray
  `.memoryMutation(...)` literal in App/AppDelegate.swift made
  check-single-memory-mutated-emit.sh exit non-zero; the violation was
  reverted before commit. Documented as evidence of gate sensitivity.
2026-04-29 18:58:11 -05:00
james.maes 56aeaed171 test(07-06): env-gated 10-scenario memory regression corpus (Task 2)
- MemoryRegressionCorpusTests with 10 scenarios mirroring the
  OrpheusTTFATests env-gate pattern (JARVIS_REAL_MODELS=1):
    1-3: ADD / NOOP / UPDATE extraction (D-03 / MEM-04)
    4-5: supersede invariants — never DELETE (MEM-05)
    6-8: FTS5+vec hybrid retrieval ranking (MEM-07)
    9-10: forget_fact valid_to closure (D-02)
- All 10 tests skip cleanly when JARVIS_REAL_MODELS != 1; runs
  interactively against real local Ollama (nomic-embed-text +
  qwen2.5-coder:32b). Researcher Open Q #3 satisfied.
- MemoryStore gains four small read-only test seams used by the corpus:
  searchFacts(query:k:embedder:) — facade over runHybridSearchSQL that
    embeds via the injected EmbeddingProviding;
  factById(_:) — fetch any-state row;
  rawCountFacts(predicate:) — count rows regardless of valid_to /
    forgotten_at (proves never-DELETE on supersede);
  activeFacts(subject:predicate:) — uses idx_facts_active partial index.
- Memory test target gains OllamaProvider product dependency so the
  corpus can construct a live LLMProvider for the env-gated runs.
2026-04-29 18:44:56 -05:00
james.maes 001ed9187b test(07-03): add failing HybridSearch + SessionHistory + forget tests
- HybridSearchTests: stub embedder + stub store; verifies single embed
  per call, k pass-through, recordRetrieval per result, empty-query
  short-circuit
- SessionHistoryTests: pass-through + 500-cap defensive bound
- ForgetFactTests: env-gated stubs (real-DB cases land in 07-06)

RED: tests reference HybridSearch / SessionHistory / EmbeddingProviding /
MemoryReadStore / SessionHistoryReading — none exist yet.
2026-04-29 17:03:15 -05:00
james.maes a04a018785 test(07-03): add failing memory-read primitives + grep gate tests
- MemoryQueriesAndModelTests: hybrid SQL shape, FactRef/TurnRow Codable,
  ReplayEvent.memoryRetrieval kind round-trip
- SingleEmissionSiteGrepTests: testMemoryRetrievalHasSingleEmissionSite
  symmetric to the 07-02 memoryMutation gate

RED gate: tests reference MemoryQueries, FactRef.score/triggerTurnId,
TurnRow, ReplayEvent.memoryRetrieval — none of which exist yet.
2026-04-29 16:53:18 -05:00
james.maes 08d95e74b4 feat(07-02): MemoryExtractionOrchestrator + Coordinator (Task 4 GREEN)
- MemoryExtractionOrchestrator: bounded channel (capacity 32, .dropOldest); single serial detached drain Task; injectable applyOp callback as test seam (production wiring closes over MemoryStore in 07-06); FNV-1a stable hash for TurnID -> Int64 source_turn_id correlation
- MemoryExtractionCoordinator: actor subscribes to OrchestratorEvent stream, filters .turnEnd with stopReason == .endTurn, fetches user/assistant via TurnContentLookup, enqueues ExtractionJob (never blocks turnEnd)
- 8 orchestrator tests pass — including MEM-06 100-enqueue/60s-stalled-extractor non-blocking gate, serial drain ordering, error survival, coordinator filtering of refusal/maxTokens/toolUse, stop semantics
- Full Memory suite: 48 executed, 5 env-gated skips, 0 failures
- Replay suite: 27 / 0 failures (no regression)
- App target builds cleanly via scripts/check-app-builds.sh
2026-04-29 16:30:35 -05:00
james.maes 040e578654 test(07-02): add failing orchestrator + coordinator tests; wire AgentOrchestrator dep (Task 4 RED)
- Adds MemoryExtractionOrchestratorTests with 8 cases covering MEM-06 non-blocking, serial drain, applyOp reach-through, error survival, coordinator turnEnd filtering, callback wiring, stop semantics
- Memory Package.swift now depends on AgentOrchestrator product (needed for OrchestratorEvent type used by coordinator)
- Tests fail to compile until orchestrator + coordinator land in Task 4 GREEN
2026-04-29 16:22:44 -05:00
james.maes c099ee7efd test(07-02): add failing MemoryExtractor + prompt + tool schema tests (Task 3 RED) 2026-04-29 16:17:29 -05:00
james.maes 8c40bbe199 test(07-02): add failing OllamaEmbeddingClient + network-sandbox tests (Task 2 RED)
- OllamaEmbeddingClientTests covers loopback guard, /api/embed routing, body shape, dim validation, HTTP/shape errors via URLProtocolStub
- EmbeddingNetworkSandboxTests is the dedicated MEM-04 file mandated by CONTEXT.md
- Tests fail to compile until OllamaEmbeddingClient lands in Task 2 GREEN
2026-04-29 16:13:44 -05:00
james.maes af4cf898f9 test(07-02): add failing applyOp + single-emission-site tests (Task 1 RED)
- MemoryStoreApplyOpTests covers ADD/UPDATE/NOOP, rollback, parse, encoded() round-trip
- SingleEmissionSiteGrepTests guards MEM-05 (never DELETE) + MEM-06 (single emit site)
- Tests fail to compile until MemoryOp/applyOp/MemoryReplaySink land in Task 1 GREEN
2026-04-29 16:05:38 -05:00
james.maes f0057df094 test(07-01): add MemorySchema and MemoryStore tests (TDD RED)
- MemorySchemaTests: 4 cases enforcing
    testSchemaInterpolatesEmbeddingDim — facts_vec DDL renders FLOAT[768]
    testPragmasIncludeWAL              — WAL/foreign_keys/busy_timeout
    testFactsDDLHasForgottenAt         — D-02 forget_fact column + MEM-05 temporal
    testActiveIndexIsPartial           — partial index predicate present
- MemoryStoreTests: 2 cases
    testInitThrowsWhenVecDylibMissing — graceful failure when vec0 absent
    testInitWithStubVecDylib          — env-gated probe (S-8); skips by default
- Drop MemoryTestsScaffold placeholder; real tests cover the surface now.

Real implementation (MemoryStore actor + MemorySchema + Fact + MemoryError)
lands in the next commit (TDD GREEN).
2026-04-29 15:57:25 -05:00
james.maes 4ee018d95c feat(07-01): add SQLiteConnection.withHandle + MemoryConstants + symbol-uniqueness test
- SQLiteConnection.withHandle(_:): closure-based accessor so MemoryStore
  can call sqlite3_load_extension without leaking the OpaquePointer.
- MemoryConstants.embeddingDim = 768 (MEM-02): single canonical symbol
  for vector dimension. Hard-pinned to nomic-embed-text 768d output.
- EmbeddingDimSymbolTests:
    testEmbeddingDimIsSeventySixtyEight — value sanity.
    testEmbeddingDimIsSingleSymbol — workspace grep guard against any
    parallel literal definition.

Deviation Rule 1: the plan-supplied repoRoot heuristic walked up looking
for 'a Package.swift WITHOUT a parent Package.swift', but this workspace
has no top-level Package.swift (it uses XcodeGen project.yml). Walk up
looking for project.yml or Jarvis.xcodeproj instead.

Deviation Rule 1: the plan-supplied symbol-grep flagged the test file's
own docstrings and the Constants.swift docstring as 'rogue literals'
(self-reference bug). Filtered #file and Constants.swift from the hit
list — the test still flags any other shadow definition anywhere in the
workspace, which is the intended invariant.
2026-04-29 15:49:09 -05:00
james.maes 64e38ba15d feat(07-01): create packages/Memory SPM scaffold + wire into project.yml
- New packages/Memory SPM target with Logging/AgentCore/Replay deps
- Resources/PLACEHOLDER.txt reserves bundle slot for future vec0.dylib
- Memory.swift + MemoryTestsScaffold.swift placeholders so the empty
  target compiles; real symbols land in Tasks 2 and 3
- project.yml: add Memory to packages and Jarvis target deps (Voice analog)
- Jarvis.xcodeproj regenerated via xcodegen

Deviation Rule 3: plan referenced 'root Package.swift' but this workspace
uses XcodeGen project.yml (no root Package.swift exists). Wired Memory the
same way Voice was wired in Plan 06-05.
2026-04-29 15:45:35 -05:00