ADR-048: Keep enrichment synchronous; async is trigger-gated, not now
Date: 2026-07-07 Status: Accepted Author: Rômulo Febasi
Resolves the #160 (SYN-194) placeholder. Confirms BR-13 (insertion-time enrichment). Companion to ADR-008 (backends) and ADR-046 (disposability).
Context
BR-13 runs enrichment (chunk embeddings + mentions:* NER/gazetteer links)
synchronously inside Workspace::tx, before commit, under a latency budget
(median ≤ 500 ms, p95 ≤ 800 ms on 10k entities, Apple M1 Pro). #160 registered a
possible v0.6+ async path: run enrichment after tx.commit() returns, make the
entity searchable on FTS immediately, and reach vector/NER consistency within a
documented window (~2–5 s). It was filed as a placeholder: “do not start work”
until real usage shows the synchronous write penalty is the wrong trade-off.
Decision
Keep enrichment synchronous. The async path stays deferred and is
trigger-gated: build it only when a measured p95 write regression on real usage
crosses the BR-13 budget. No async scheduler, no [enrichment] schedule config
ships now.
Rationale:
- The budget holds. No evidence the synchronous penalty is a problem; the
synapse-benchwrite path (SYN-293/298) stays within budget, and enrichment already runs its embed + NER legs in parallel threads inside the tx. - Async contradicts the enrich-before-commit principle. An eventual-
consistency window means a fact is briefly committed without its semantic
edges/embeddings: searchable by FTS but not by meaning, and momentarily
missing
mentions:*. That weakens the “a write is complete and findable when it lands” guarantee for a latency win nobody has asked for. - Disposability already covers the backfill case. The one real gap async was
partly meant to soften (entities written before models were downloaded) is
handled by
syn reindex --relink(heuristic edges, SYN-271) +syn embed --all(embeddings), both shipped in v1.2 (ADR-046 disposability line). So the pressure for async is lower than when #160 was filed.
Consequences
- Improves: the write contract stays simple and honest, a committed fact is fully enriched and findable, no consistency window to reason about. No new scheduler/retry/queue surface to maintain or verify.
- Worsens / limits: very fast capture flows pay the full enrichment latency per write. If that becomes real (a measured p95 breach, or a user report on a large corpus), the async path is revisited.
- Trigger to revisit: a
persona-bench/write-path p95 above the BR-13 budget on a real corpus, or a concrete user report. At that point: design the opt-in[enrichment] schedule = asyncwith a documented consistency window and a crash-safe queue: its own change, not a default.
Alternatives considered
- Ship async now (opt-in config). Rejected: adds a scheduler + eventual- consistency semantics + crash-safety for a latency problem not observed; against the enrich-before-commit principle.
- Close #160 as won’t-do. Rejected: the trade-off is legitimate at scale; keep it registered as trigger-gated so it is revisited on evidence, not forgotten.