This commit adds the read path (Cortex) to complement the write path (Spine): ## Crates - stemedb-api: HTTP API with axum + utoipa OpenAPI - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit - Metered endpoints with quota enforcement - Ed25519 signature verification - stemedb-lens: Truth resolution lenses - RecencyLens, ConsensusLens, ConfidenceLens - VoteAwareConsensusLens (Ballot Box pattern) - TrustAwareAuthorityLens (The Hive pattern) - SkepticLens (conflict analysis) - EpochAwareLens (paradigm-safe queries) - stemedb-query: Query engine with materialized views ## Storage Extensions - VoteStore: Vote aggregation with cached counts - TrustRankStore: Agent reputation with decay - AuditStore: Query audit trail - IndexStore: SP/P/S index structures - SupersessionStore: Epoch supersession chains ## SDKs - sdk/go/steme: Go HTTP client with Ed25519 signing - sdk/go/adk: ADK-Go tools for AI agents ## Documentation - Updated CLAUDE.md, architecture.md, roadmap.md - New ai-lookup entries for all services - Use case docs for consumer health intelligence - Arena roadmap for simulation advancement Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
40 KiB
Episteme (StemeDB) Roadmap
Goal: Build the "Git for Truth" substrate for autonomous AI research. Current Phase: Phase 2.5 (Hardening) Target Vertical: BioTech/Pharma ("The Living Review")
High-Level Timeline
| Phase | Codename | Focus | Key Deliverable |
|---|---|---|---|
| 1 | The Spine | Storage & Safety | Append-only WAL + KV Store |
| 2 | The Lattice | Indexing & Async | Materialized Views + Ballot Box |
| 2.5 | Hardening | Camp 2 Fixes | MV staleness, epoch behavior, lens cleanup |
| 3 | The Pilot | Vertical Integration | Pharma Ingestion + Living Review Agent |
| 4 | The Hive | Trust & Learning | The Simulator + Super Curator |
Detailed Milestones
Phase 1: The Spine (Foundation)
Goal: Securely ingest assertions and persist them without data loss.
- Project Scaffold: Initialize Rust workspace, set up linting/CI (clippy, fmt).
- Assertion Schema: Define the
Assertionstruct withrkyvserialization.- Add dependencies:
rkyv,blake3,ed25519-dalek,image_hasher. - Define
Assertionstruct (Subject, Predicate, Object, Confidence, SourceHash). - Multi-Sig Expansion: Implement
SignatureEntrystruct andsignatures: Vec<SignatureEntry>field. - Visual Expansion: Add
visual_hash: Option<pHash>field for image provenance. - Test serialization round-trips.
- Add dependencies:
- Ballot Schema: Define the
Votestruct for multi-agent consensus.- Add
Votestruct:assertion_hash,agent_id,weight,signature. - Test serialization round-trips.
- Add
- Paradigm Schema (Epochs): Define the
EpochandSupersessionTypestructs.- Add
epoch: Option<EpochId>toAssertion. - Implement
Epochstruct withsupersedesandSupersessionType. - Test serialization round-trips.
- Add
- WAL Integration: Implement the Quarantine Pattern for write-ahead logging.
- Create
stemedb-walcrate. - Port
FsyncGuardandRecordlogic from established durability patterns. - Implement Record format with BLAKE3 checksums and Headers.
- Verify
fsyncbehavior with tests.
- Create
- Storage Engine: Implement the
Storetrait usingsled(embedded KV).- Add
sleddependency. - Define
KVStoretrait (put, get, delete, scan_prefix, flush). - Implement
SledStorewrapper.
- Add
- Basic Ingestor: Background worker that tails WAL and writes to KV.
- Implement async loop reading from WAL.
- Write deserialized assertions, votes, and epochs to
sled. - Ed25519 signature verification during ingestion.
- Maintains S: and SP: indexes on ingest.
- Persistent cursor/checkpoint (resumes from
__CURSOR__:ingestin KV store).
- Verification: Crash recovery tests (write -> crash -> restart -> read).
- Single and multi-record crash recovery.
- Multiple crash cycles tested.
Phase 2: The Lattice (Connectivity)
Goal: Query data with sub-millisecond latency using Materialized Views.
- Lifecycle Schema: Add
LifecycleStageto Assertion.- Define enum:
Proposed,UnderReview,Approved,Deprecated,Rejected. - Update
Assertionstruct and serialization tests.
- Define enum:
- The Ballot Box: Implement high-velocity vote ingestion.
VoteStoretrait and implementation.VoteAwareConsensusLensfor real vote-based resolution.
- Index Infrastructure: Compound indexes for O(1) queries.
IndexStoretrait with S: and SP: indexes.QueryEnginesmart routing (SP -> S -> scan).
- Materializer: Background worker for O(1) Read Performance.
MaterializedViewtype instemedb-core.Materializerworker instemedb-querywithstep()andrun().- Aggregates Votes via
VoteAwareConsensusLens(or anyAsyncLens). - Updates
MV:{Subject}:{Predicate}with the winning Assertion + metadata. - Event-driven mode via
run_notified()withtokio::sync::Notify. - Fast-path MV lookup in
QueryEngine::try_fast_path().
- The Meter: Implement Economic Throttling (TAN).
QuotaStoretrait andGenericQuotaStoreimplementation.- Token Bucket algorithm with per-agent per-hour quotas.
MeterLayertower middleware for request cost tracking.- Cost model: Assert=10, Vote=1, Query=5+lens, +1/KB payload.
GET /v1/meter/quotaendpoint to check remaining quota.POST /v1/meter/quota/limitadmin endpoint to set custom limits.
- API Surface:
axumHTTP server with OpenAPI (utoipa).POST /v1/assert-> Accepts JSON, writes to WAL.POST /v1/vote-> High-throughput vote endpoint.POST /v1/epoch-> Create epoch with optional supersession.GET /v1/query-> Subject/Predicate/Lens/Lifecycle/Epoch filtering.GET /v1/health-> Health check with assertion count.GET /swagger-ui-> Interactive API docs.- 5 lens types available: Recency, Consensus, Authority, VoteAwareConsensus, TrustAwareAuthority.
- Query Audit: Log every read with provenance.
- Define
QueryAuditstruct: query_id, agent_id, timestamp, params, result_hash, contributing_assertions. - Storage at
AUD:{query_id}with agent index atAUDA:{agent_id}:{timestamp}:{query_id}. GET /v1/audit/queries-> Returns history of agent decisions.GET /v1/audit/query/{id}-> Full reasoning trace for a single query.- Auto-logging on every query via
X-Agent-Idheader.
- Define
Phase 2.5: Hardening (Camp 2 Fixes)
Goal: Close the gaps between "built" and "works right." Every item here addresses a feature that exists but doesn't fully deliver on its promise.
-
2.1 MV Staleness Detection: Make the fast-path aware of stale materialized views.
- Status: ✅ COMPLETE
- Implementation:
- Added
max_stale: Option<u64>toQuerystruct incrates/stemedb-query/src/query.rs. - Added
.max_stale(secs)builder method toQueryBuilder. - In
try_fast_path(): ifquery.max_staleis set and MV age exceeds threshold, falls through to slow path withdebug!log. - Added
max_staleto APIQueryParamsDTO incrates/stemedb-api/src/dto.rs. - Wired through query handler in
crates/stemedb-api/src/handlers/query.rs.
- Added
- Tests:
test_fast_path_stale_view_falls_back: MV 1000 seconds old,max_stale = 60→ slow path used.test_fast_path_fresh_view_used: Fresh MV,max_stale = 300→ fast path used.test_fast_path_no_max_stale_always_uses_mv: Nomax_stale→ any MV age accepted (backward compatible).test_fast_path_max_stale_zero_rejects_old_mv:max_stale = 0, MV 1 second old → slow path.test_fast_path_max_stale_zero_accepts_brand_new_mv:max_stale = 0, brand new MV → fast path.
-
2.2 AuthorityLens -> ConfidenceLens Rename: Eliminate the misleading name.
- Problem:
AuthorityLensselects byconfidencefield, not by agent reputation.TrustAwareAuthorityLensis the real authority lens. The name creates confusion about what "Authority" means. - Solution implemented:
- Renamed
authority.rs→confidence.rs,AuthorityLens→ConfidenceLens - Added
LensDto::Confidencefor the confidence-field selector - Changed
LensDto::Authorityto route toTrustAwareAuthorityLens(the real authority lens) - Updated query handler routing
- Updated ai-lookup/services/lens.md and skill documentation
- Renamed
- Problem:
-
2.3 EpochAwareLens: Give epoch supersession runtime behavior.
- Status: ✅ COMPLETE
- Implementation:
EpochAwareLensincrates/stemedb-lens/src/epoch_aware.rs- Decorator pattern wrapping any inner lens (default: RecencyLens)
- Walks supersession chain from
E:{epoch_id}keys - Cycle detection + max depth guard (100)
- Fail-open on missing epochs
LensDto::EpochAwareadded to API- 11 tests: excludes_superseded, chain_supersession, no_epochs_passes_all, missing_epoch_includes, cycle_detection, consensus_lens_inner, mixed_epochs, etc.
- Documentation updated in
ai-lookup/services/lens.md
- Known Limitation: Filtering only occurs when assertions from the superseding epoch are present in candidates. If all candidates are from old epoch (no new epoch assertions), they pass through (fail-open behavior).
-
2.4 Visual Hash Query Support: Make the stored
visual_hashqueryable.- Problem:
visual_hash: Option<PHash>exists onAssertionand is stored/returned by the API, but there is no way to query by visual similarity. The field is write-only from a query perspective. - Current state:
PHashis[u8; 8](perceptual hash). Stored on assertions. API accepts/returns it. No query parameter. No similarity computation. - Add
visual_near: Option<String>(hex-encoded pHash) andvisual_threshold: Option<u32>(max hamming distance, default: 8) toQuerystruct. - Add
.visual_near(hash, threshold)toQueryBuilder. - In
Query::matches(): ifvisual_nearis set and assertion hasvisual_hash, compute hamming distance (XOR + popcount on the 8 bytes). If distance <= threshold, match. If assertion has novisual_hash, don't match. - Add
visual_nearandvisual_thresholdto APIQueryParamsDTO. - Implement
hamming_distance(a: &[u8; 8], b: &[u8; 8]) -> u32utility function. - Tests:
test_visual_near_exact_match: Same hash, threshold 0. Matches.test_visual_near_within_threshold: Hashes differ by 3 bits, threshold 5. Matches.test_visual_near_exceeds_threshold: Hashes differ by 10 bits, threshold 5. No match.test_visual_near_skips_assertions_without_hash: Assertion has no visual_hash. Not matched.
- Note: This is a brute-force scan approach (O(N) with hamming distance check). A proper VP-tree or BK-tree index is Phase 3. This gives immediate queryability.
- Problem:
-
2.5 Vector Field: No changes needed. Already roadmapped for Phase 3.
- Current state:
vector: Option<Vec<f32>>onAssertion. Stored and returned by API. No index, no search. - Phase 3 plan: Integrate
hnsw-rsorlancefor k-NN search. - No Phase 2.5 work required. The field scaffolding is correct.
- Current state:
-
2.6 E2E Integration Test (Write -> Materialize -> Read): Prove the full pipeline works end-to-end.
- Problem: The IngestWorker, Materializer, and QueryEngine have been tested in isolation. The Notify integration between IngestWorker and Materializer is tested with a single notification. No test wires all three components together to verify the full write-materialize-read loop.
- Current state: IngestWorker has
with_notify(Arc<Notify>)(worker.rs). Materializer hasrun_notified(Arc<Notify>, Duration)(materializer.rs). QueryEngine hastry_fast_path()(engine.rs). Never tested together. - Create integration test in
crates/stemedb-query/tests/e2e_pipeline.rs:- Setup: Create temp WAL + SledStore + VoteStore + TrustRankStore.
- Wire: IngestWorker with
with_notify(notify), Materializer withrun_notified(notify), QueryEngine with same store. - Test steps:
- Write a signed assertion to WAL.
- Run IngestWorker.step() -> verifies assertion ingested to KV.
- Verify IngestWorker triggered Notify.
- Run Materializer.step() -> verifies MV written.
- Execute QueryEngine.execute() with subject+predicate -> verifies fast-path returns MV winner.
- Variant: Write 2 competing assertions, add votes favoring one, run pipeline, verify correct winner in MV.
- Variant: Write assertion, materialize, write new assertion with higher timestamp, re-materialize, verify MV updated, verify query returns new winner.
- Add
stemedb-walandstemedb-ingestas dev-dependencies instemedb-query/Cargo.toml.
Phase 3: The Pilot (BioTech/Pharma)
Goal: Prove value in the "High-Liability" beachhead. Close every Camp 4 gap that blocks a credible demo.
3A. Schema Expansion (Prerequisite for everything below)
-
3A.1 Source-Class Field: Add
source_class: SourceClassto Assertion.- Status: ✅ COMPLETE
- Implementation:
SourceClassenum incrates/stemedb-core/src/types.rs(lines 68-88).- 6-tier system:
Regulatory(0),Clinical(1),Observational(2),Expert(3),Community(4),Anecdotal(5). tier()method returns tier number for ordering.default_decay_days()method for tier-specific confidence decay.authority_weight()method for conflict resolution weighting.- Field on
Assertionstruct at line 152. - Full serialization and indexing support.
-
3A.2 Conflict Score on Resolution: Add
conflict_score: f32to Resolution.- Problem: All 4 use cases reference
conflict_scorein query responses.Resolutionhasresolution_confidencebut no conflict metric. Without a conflict score, the "disagreement is the information" thesis has no numeric representation. - Current state:
Resolutionattraits.rs:10-20haswinner,candidates_count,resolution_confidence. No variance computation. - Add field to
Resolutionincrates/stemedb-lens/src/traits.rs:10:/// Degree of disagreement among candidates (0.0 = full agreement, 1.0 = max conflict). /// Computed as normalized variance of candidate confidence values. pub conflict_score: f32, - Update
Resolution::empty()attraits.rs:24: setconflict_score: 0.0. - Update
Resolution::with_winner()attraits.rs:29: acceptconflict_scoreparameter. - Add utility function
compute_conflict_score(candidates: &[Assertion]) -> f32:- Compute mean confidence:
mean = sum(c.confidence) / N. - Compute variance:
var = sum((c.confidence - mean)^2) / N. - Normalize to [0.0, 1.0]:
conflict = (4.0 * var).min(1.0)(variance of [0,1] range has max 0.25, so 4x normalizes). - Edge cases: 0 or 1 candidates -> 0.0.
- Compute mean confidence:
- Update every
Lens::resolve()andAsyncLens::resolve_async()implementation to callcompute_conflict_score()and pass it toResolution::with_winner():crates/stemedb-lens/src/recency.rscrates/stemedb-lens/src/consensus.rscrates/stemedb-lens/src/authority.rs(orconfidence.rsafter rename)crates/stemedb-lens/src/vote_aware_consensus.rscrates/stemedb-lens/src/trust_aware_authority.rs
- Add
conflict_score: f32toMaterializedViewincrates/stemedb-core/src/types.rs:143. - Update
Materializer::materialize_pair()atmaterializer.rs:164to writeconflict_scorefrom resolution. - Add
conflict_scoretoQueryResponseDTO (or a newResolutionMetasub-object) incrates/stemedb-api/src/dto.rs. - Wire
conflict_scorethrough in the query handler'sapply_lens()athandlers/query.rs:111. - Tests:
test_conflict_score_zero_for_agreement: 3 candidates all confidence 0.9. Score near 0.0.test_conflict_score_high_for_disagreement: Candidates at 0.1, 0.5, 0.9. Score > 0.5.test_conflict_score_zero_for_single: 1 candidate. Score = 0.0.test_conflict_score_on_materialized_view: MV stores conflict_score after materialization.
- Problem: All 4 use cases reference
-
3A.3 Rich Source Metadata: Add structured provenance beyond
source_hash.- Problem:
Assertionhassource_hash: Hash(a 32-byte hash) but no structured metadata. Consumer Health use case shows POST bodies withjournal,DOI,sample_size,subreddit,upvotes. Without this, assertions are opaque about their provenance. - Current state:
source_hash: Hashattypes.rs:64. No metadata field. API accepts/returns only the hash. - Add field to
Assertionincrates/stemedb-core/src/types.rs:65(aftersource_hash):/// Structured source metadata as a JSON-encoded byte string. /// Schema is domain-specific (journal info, social metrics, etc.). pub source_metadata: Option<Vec<u8>>, - Use
Vec<u8>(notString) for rkyv zero-copy compatibility. Callers encode/decode JSON on their side. - Add
source_metadata: Option<Vec<u8>>toAssertionBuilder. Add.source_metadata_json(json: &str)builder method that storesjson.as_bytes().to_vec(). - Add
source_metadata: Option<String>toCreateAssertionRequestDTO (JSON string in API, converted to bytes internally). - Add
source_metadata: Option<String>toAssertionResponseDTO (bytes converted to JSON string). - Wire through create handler and query handler.
- Tests:
- Serialization roundtrip with metadata present and absent.
- API roundtrip: POST with metadata JSON, GET returns same JSON.
- Note: Metadata is stored but NOT indexed in Phase 3. Indexing individual metadata fields is Phase 4+.
- Problem:
3B. Time & Decay (Core Query Features)
-
3B.1 Time-Travel Engine:
as_ofparameter for historical queries.- Problem: All 4 use cases reference
as_offor historical state. "What was the consensus when I made my decision?" The append-only model stores all history, but there's no way to query a past state. - Current state:
Queryatquery.rs:14-29has noas_offield.try_fast_path()returns current MV regardless. - Add
as_of: Option<u64>toQuerystruct incrates/stemedb-query/src/query.rs:14. - Add
.as_of(timestamp: u64)toQueryBuilder. - In
Query::matches()atquery.rs:43: ifas_ofisSome(ts), checkassertion.timestamp <= ts. Assertions created afteras_ofare excluded. - In
QueryEngine::execute()atengine.rs:47: ifquery.as_ofis set, skip the fast path entirely (MVs reflect current state, not historical). Add early check before thetry_fast_pathcall:if query.as_of.is_none() { if let (Some(subject), Some(predicate)) = (&query.subject, &query.predicate) { if let Some(result) = self.try_fast_path(subject, predicate, query).await? { return Ok(result); } } } - Add
as_of: Option<u64>toQueryParamsDTO incrates/stemedb-api/src/dto.rs:102. - Wire in query handler.
- Tests:
test_as_of_excludes_future_assertions: 3 assertions at t=1000, t=2000, t=3000. Queryas_of=2500. Returns only first 2.test_as_of_bypasses_fast_path: MV exists, butas_ofis set. Slow path used, MV ignored.test_as_of_none_uses_fast_path: Normal query still uses fast path (backwards-compatible).test_as_of_with_lens: Time-travel + lens = resolve only among pre-as_of candidates.
- Problem: All 4 use cases reference
-
3B.2 Semantic Decay: Confidence Half-Life at query time.
- Problem: Medical knowledge decays at different rates. A Reddit post from 2022 shouldn't compete equally with a 2024 RCT. No assertion-level decay exists. TrustRank has agent-level decay but not assertion-level.
- Current state:
TrustRank.apply_decay()attrust_rank_store.rsdoes agent decay. No assertion decay. Nodecayquery parameter. - Add
decay_halflife: Option<u64>toQuerystruct (seconds). Represents how quickly old assertions lose effective confidence. - Add
.decay_halflife(seconds: u64)toQueryBuilder. - Add
decay_halflifetoQueryParamsDTO. - Implementation strategy: Apply decay in QueryEngine before passing to lens. In
execute(), after fetching and filtering candidates, ifdecay_halflifeis set:- Get current time (or
as_ofif set). - For each candidate, compute
age = now - assertion.timestamp. - Compute
effective_confidence = confidence * 2_f32.powf(-(age as f32) / (halflife as f32)). - Clone the assertion with the decayed confidence.
- Pass decayed candidates to the lens.
- Get current time (or
- Add helper
apply_decay(assertions: &[Assertion], halflife: u64, now: u64) -> Vec<Assertion>in a newcrates/stemedb-query/src/decay.rsmodule. - Source-class-aware decay (Phase 3 stretch): If assertion has
source_class, use per-tier half-lives fromSourceClass::default_decay_days(). This is already implemented on the enum. - Tests:
test_decay_reduces_old_assertion_confidence: Assertion 1yr old, halflife 1yr. Effective confidence ~= original * 0.5.test_decay_preserves_fresh_assertions: Assertion 1hr old, halflife 1yr. Effective confidence ~= original.test_decay_interacts_with_lens: Two assertions, older has higher base confidence but after decay, newer wins via RecencyLens.test_source_aware_decay: Tier 0 doesn't decay. Tier 5 decays rapidly.
3C. New Lenses
-
3C.1 Skeptic Lens: Surface disagreement, not winners. ✅ COMPLETED
- Status: ✅ COMPLETE
- Implementation:
crates/stemedb-lens/src/skeptic.rs- Full implementation.AnalysisLenstrait for lenses that analyze conflict instead of resolving it.SkepticLensuses normalized Shannon entropy for conflict scoring.- Returns
ConflictAnalysiswith:conflict_score: f32(0.0 = unanimous, 1.0 = chaos)status: ResolutionStatus(Unanimous, Agreed, Contested)claims: Vec<ClaimSummary>- all claims ranked by weight
SkepticResolver+SkepticViewinstemedb-query/src/skeptic.rs.GET /v1/skeptic?subject=X&predicate=YAPI endpoint.- Core types in
stemedb-core/src/types.rs:ResolutionStatusenumConflictAnalysisstructClaimSummary,SourceSummary,AgentSummary
- Comprehensive test coverage (21 test cases).
-
3C.2 Layered Consensus Lens: Per-source-class consensus.
- Problem: Current lenses return a single winner. The Consumer Health use case needs per-tier consensus: "What does Tier 0 say? What does Tier 1 say? What does Tier 5 say?" This is the core differentiator.
- Depends on: Source-Class Field (3A.1) ✅ COMPLETE.
- New file:
crates/stemedb-lens/src/layered_consensus.rs. - New type in
crates/stemedb-lens/src/traits.rs:/// Per-tier resolution result. pub struct TierResolution { pub tier: u8, pub winner: Option<Assertion>, pub candidates_count: usize, pub conflict_score: f32, } /// Multi-tier resolution result. pub struct LayeredResolution { /// Per-tier consensus results, ordered by tier (0 = highest authority). pub tiers: Vec<TierResolution>, /// Overall winner (highest-tier with a winner). pub overall_winner: Option<Assertion>, /// Overall conflict score (cross-tier disagreement). pub overall_conflict_score: f32, } LayeredConsensusLensimplements a newLayeredLenstrait:pub trait LayeredLens: Send + Sync { fn resolve_layered(&self, candidates: &[Assertion]) -> LayeredResolution; fn name(&self) -> &'static str; }resolve_layered()logic:- Group candidates by
source_class(useSourceClass::tier()method). - For each tier group, run
ConsensusLens::resolve()to get within-tier winner. - Compute per-tier
conflict_score. - Overall winner = winner from the highest-authority tier that has candidates (lowest tier number).
- Overall conflict_score = cross-tier disagreement (do tier winners agree on the same object value?).
- Group candidates by
- Also implement standard
Lenstrait onLayeredConsensusLensto maintain compatibility:resolve()returnsoverall_winneras a regularResolution. The richerLayeredResolutionis accessible viaresolve_layered(). - Add
LayeredConsensustoLensDtoenum. - New API response type:
LayeredQueryResponsewith per-tier results. Wire as a variant in the query handler whenlens=LayeredConsensus. - Export from
crates/stemedb-lens/src/lib.rs. - Tests:
test_layered_single_tier: All candidates same source_class. Returns one tier result.test_layered_multi_tier_agreement: Tier 0 and Tier 5 agree on same object. Low cross-tier conflict.test_layered_multi_tier_disagreement: Tier 1 says "safe", Tier 5 says "dangerous". High cross-tier conflict. Overall winner from Tier 1.test_layered_overall_winner_from_highest_authority: Tier 0 present -> its winner is overall winner even if Tier 5 has 1000x more assertions.
-
3C.3 Constraints Lens: Pre-flight check for must_use/forbidden.
- Problem: Agile Agent Team use case needs
lens=constraintsreturning{ must_use: "axios", forbidden: "requests" }. Central to "persistent learning" — agents query constraints before acting. - New file:
crates/stemedb-lens/src/constraints.rs. - Design: Not a traditional lens (doesn't pick one winner from candidates). Instead, it categorizes candidates by predicate pattern:
- Assertions with predicate matching
must_use:*-> must_use list. - Assertions with predicate matching
forbidden:*-> forbidden list. - Assertions with predicate matching
prefer:*-> prefer list.
- Assertions with predicate matching
- Implements
Lenstrait for compatibility:resolve()returns the highest-confidencemust_useassertion as the "winner" (orforbiddenif no must_use exists). The richer result is accessible via a dedicated method. - Add dedicated
resolve_constraints()method:pub struct ConstraintSet { pub must_use: Vec<Assertion>, pub forbidden: Vec<Assertion>, pub prefer: Vec<Assertion>, } impl ConstraintsLens { pub fn resolve_constraints(&self, candidates: &[Assertion]) -> ConstraintSet { ... } } - Add
ConstraintstoLensDtoenum. - New API response type:
ConstraintResponsewith categorized assertions. - Export from
crates/stemedb-lens/src/lib.rs. - Tests:
test_constraints_categorizes_by_predicate: Mixed predicates sorted into must_use/forbidden/prefer.test_constraints_empty_categories: No must_use predicates -> empty must_use list.test_constraints_lens_trait_picks_must_use_winner: Standardresolve()returns highest-confidence must_use.test_constraints_non_constraint_predicates_ignored: Regular predicates not categorized.
- Problem: Agile Agent Team use case needs
3D. Epoch Enhancement
- 3D.1 Epoch Cascade Logic (enhancement of Phase 2.5 EpochAwareLens):
- Problem: Phase 2.5 EpochAwareLens walks the
supersedeschain at query time by readingE:keys. This is O(chain_length) per query. For long supersession chains, this is expensive. - Depends on: Phase 2.5 EpochAwareLens (basic version).
- In
IngestWorker::process_epoch()atcrates/stemedb-ingest/src/worker.rs:- When an epoch with
supersedes = Some(old_epoch_id)is ingested, write a marker keySUPERSEDED:{old_epoch_id}with the new epoch ID as value. - Walk the chain: if old_epoch itself superseded another epoch, write
SUPERSEDED:{grandparent_id}too (transitive closure).
- When an epoch with
- Update
EpochAwareLensto checkSUPERSEDED:{epoch_id}keys (O(1) lookup per candidate) instead of walking the chain at query time. - Tests:
test_cascade_writes_superseded_marker: Ingest epoch B superseding A. VerifySUPERSEDED:Akey exists.test_cascade_transitive: A supersedes B, B supersedes C. Verify bothSUPERSEDED:BandSUPERSEDED:Cexist.test_epoch_aware_uses_marker: Query with EpochAwareLens uses marker key, not chain walk.
- Problem: Phase 2.5 EpochAwareLens walks the
3E. Similarity Search
-
3E.1 Vector Search: Semantic k-NN queries via embeddings.
- Current state:
vector: Option<Vec<f32>>on Assertion. Stored, returned by API. No index, no search. - Add
hnsw-rs(orlance) as a dependency instemedb-storage/Cargo.toml. - New module:
crates/stemedb-storage/src/vector_index.rs. VectorIndextrait:insert(hash: Hash, vector: &[f32]),search(query: &[f32], k: usize) -> Vec<(Hash, f32)>.- Implementation backed by HNSW graph stored alongside KV data.
- IngestWorker: if assertion has
vector, insert into vector index after KV write. - Add
vector_near: Option<Vec<f32>>andk: Option<usize>toQuerystruct and APIQueryParams. - QueryEngine: if
vector_nearis set, use vector index for candidate retrieval instead of SP/S index. - Tests: insert 100 vectors, query nearest 5, verify correct neighbors.
- Current state:
-
3E.2 Visual Hash Index: VP-tree or BK-tree for O(log N) visual similarity.
- Current state: Phase 2.5 adds brute-force hamming scan. This replaces it with an indexed approach.
- New module:
crates/stemedb-storage/src/visual_index.rs. - BK-tree implementation over hamming distance on
PHash([u8; 8]). - IngestWorker: if assertion has
visual_hash, insert into BK-tree. - QueryEngine: if
visual_nearis set, use BK-tree for O(log N) candidate retrieval instead of brute-force scan. - Tests: insert 1000 assertions with visual hashes, query similar, verify correct matches within threshold.
3F. Provenance
- 3F.1 Citation Recall Benchmarking: Verify 100% provenance tracking.
- Benchmark: for every assertion in the store, verify
source_hashresolves to a retrievable source document (or at minimum, the hash is non-zero and unique). - Add
GET /v1/provenance/{hash}endpoint that looks up source by hash. - Add
POST /v1/sourceendpoint to store source documents by content hash. - Source storage at
SRC:{blake3_hash}keys.
- Benchmark: for every assertion in the store, verify
3G. API Cleanup
- 3G.1 Document epoch supersession via existing endpoint: No new
/epoch/supersedeendpoint needed.- Current state:
POST /v1/epochalready acceptssupersedesfield. Use cases showPOST /epoch/supersedeas if it's a separate endpoint. - Update use case docs (consumer-health-intelligence.md, glp1-living-review.md) to use
POST /v1/epochwithsupersedesfield. - Add OpenAPI examples showing the supersession flow.
- No code change. Documentation fix only.
- Current state:
Phase 4: The Hive (Trust & Scale)
Goal: Change tracking, metadata indexing, and the database primitives for training pipelines.
-
TrustRank Engine: Foundation for trust-based resolution.
TrustRankStorefor per-agent reputation storage.TrustAwareAuthorityLensfor reputation-weighted resolution.- Confidence Half-Life: Implement decay calculation engine.
- Learning loop:
record_outcome()for accuracy tracking.
-
4.1 "Since" Parameter: Change tracking for returning consumers.
- Problem: Consumer Health shows
GET /query?since=2023-10-01returningchanges_since_querywith dated change entries. The "returning consumer" story: "What changed since I last looked?" - Depends on: Time-Travel (3B.1) and Materializer.
- Add
since: Option<u64>toQuerystruct andQueryBuilderincrates/stemedb-query/src/query.rs. - Add
sincetoQueryParamsDTO. - MV Changelog: Track when materialized views change.
- New key pattern:
MVC:{subject}:{predicate}:{timestamp}storing the previous winner hash and new winner hash. - In
Materializer::materialize_pair()atmaterializer.rs:164: before overwriting MV, read the existing MV. If the winner changed (different assertion hash), write a changelog entry.
- New key pattern:
- In QueryEngine: if
sinceis set, scanMVC:{subject}:{predicate}:*for entries with timestamp > since. Return these as achanges_sincelist alongside the normal query result. - New response field on
QueryResponse:
Addpub struct ChangeEntry { pub timestamp: u64, pub previous_winner_hash: Option<String>, pub new_winner_hash: String, }changes_since: Option<Vec<ChangeEntry>>toQueryResponseDTO. - Tests:
test_since_returns_changes: Materialize, change winner, re-materialize. Query withsincereturns the change.test_since_no_changes: No MV changes since timestamp. Empty changes list.test_since_multiple_changes: 3 winner changes over time. All returned in order.
- Problem: Consumer Health shows
-
4.2 Source Metadata Indexing (extension of 3A.3): Index key metadata fields.
- Problem: Phase 3 stores
source_metadataas an opaque blob. Phase 4 makes key fields queryable. - Depends on: Rich Source Metadata (3A.3).
- Define a set of indexed metadata keys:
journal,doi,platform,study_design. - New key pattern:
SM:{field}:{value}:{assertion_hash}for metadata field indexes. - IngestWorker: on ingestion, if
source_metadatais present, parse JSON, extract indexed fields, write index entries. - Add metadata field filters to
QueryParams(e.g.,?source_journal=NEJM). - Tests: store assertions with metadata, query by journal name, verify correct filtering.
- Problem: Phase 3 stores
-
4.3 Batch TrustRank Decay API: Expose scheduled decay for external orchestration.
- Current state:
decay_all_trust_ranks()exists on TrustRankStore. No API endpoint. - Add
POST /v1/admin/decay-trust-ranksendpoint. - Accepts
now: u64parameter (or uses current time). - Returns count of decayed agents and summary stats.
- Note: The Gardener (Camp 5.2, app layer) calls this endpoint on a schedule. The database just exposes the primitive.
- Current state:
Note: The following items were reclassified as Application Layer responsibilities (see
tmp/ambition-vs-reality.md, Camp 5). They are not Episteme database features. They consume the Episteme API and are built by integrators or vertical-specific teams.
- The Simulator (Training Data Pipeline) -> Camp 5.3
- The Super Curator (Reviewer Agent swarm) -> Camp 5.4
- Background Gardener (Cluster detection, signal processing) -> Camp 5.2
- Agent Wallet (Key management sidecar) -> Camp 5.1
Tracking
Active Tasks
- Phase 2.5 Hardening: Camp 2 fixes (
staleness, epoch behavior, lens rename, visual query, E2E test).
Next Up (Phase 3 sequencing)
- 3A.2 Conflict Score: Add to Resolution. Propagate through all lenses.
- 3A.3 Rich Source Metadata: Structured provenance field.
- 3B.1 Time-Travel:
as_ofparameter. Unblocks all 4 use cases. - 3C.2 Layered Consensus Lens: Per-tier resolution (now unblocked by 3A.1).
Recently Completed
- SkepticLens + SkepticView (3C.1): "Trust but Verify" conflict analysis that surfaces all claims with conflict scores.
AnalysisLenstrait for lenses that map conflict instead of resolving it.SkepticLensusing normalized Shannon entropy for conflict scoring.SkepticResolver+SkepticViewin stemedb-query.GET /v1/skeptic?subject=X&predicate=YAPI endpoint.- Types:
ResolutionStatus,ConflictAnalysis,ClaimSummary,SourceSummary,AgentSummary.
- Source-Class Field (3A.1): 6-tier
SourceClassenum with authority weighting and decay rates.SourceClassenum: Regulatory, Clinical, Observational, Expert, Community, Anecdotal.tier(),default_decay_days(),authority_weight()methods.- Field on Assertion struct with full serialization support.
- The Meter: Token bucket quota system with MeterLayer middleware (10K tokens/agent/hour).
- Query Audit Trail: Every query logged with provenance at
AUD:{query_id}.X-Agent-Idheader for attribution. - Event-Driven Materialization:
run_notified()+ IngestWorker Notify integration. - Fast-Path MV Lookup:
QueryEngine::try_fast_path()for O(1) reads. - Materializer: Background worker for O(1) MV reads via
AsyncLens. - VoteAwareConsensusLens: Real vote-based consensus resolution.
- Compound SP Index: O(1) subject+predicate lookups.
- TrustRank System: Agent reputation with decay and learning loop.
- API Surface: axum HTTP server with 7 endpoints + OpenAPI docs.
Blockers
- None.
Dependency Graph
Phase 2.5 (Hardening) Phase 3 (The Pilot) Phase 4 (The Hive)
======================== ======================== ==================
[2.1 MV Staleness] ---------> [3B.1 Time-Travel] -----+
| |
[2.2 Confidence Rename] -----> (API clarity for all) +----------> [4.1 "Since" Param]
|
[2.3 EpochAwareLens] --------> [3D.1 Epoch Cascade] ---|----------> Invalidation Cascades pillar
|
[2.4 Visual Hash Query] -----> [3E.2 Visual Hash Index] |
|
[2.6 E2E Integration] -------> (pipeline confidence) |
|
[3A.1 Source-Class] ✅ --+----------> [3C.2 Layered Consensus]
| [3B.2 Source-Aware Decay]
+-----------------------------[4.2 Metadata Indexing]
|
[3A.2 Conflict Score] ---> (enhance Resolution)
|
[3A.3 Source Metadata] ---> [4.2 Metadata Indexing]
|
[3C.1 Skeptic Lens] ✅ (standalone, COMPLETE)
[3C.3 Constraints Lens] (standalone)
[3E.1 Vector Search] (standalone)
[3F.1 Citation Recall] (standalone)
Critical Path for Consumer Health Demo
[3A.1 Source-Class] ✅ --> [3A.2 Conflict Score] --> [3C.2 Layered Consensus]
|
+----> CONSUMER HEALTH MVP
|
[3B.1 Time-Travel] ------------------------------------------+
|
[3A.3 Source Metadata] --------------------------------------+
|
[3C.1 Skeptic Lens] ✅ --------------------------------------+
Critical Path for Financial DD Demo
[3A.2 Conflict Score] --> [3C.1 Skeptic Lens] ✅ -------+
|
[3B.1 Time-Travel] -------------------------------------+----> FINANCIAL DD MVP
|
[2.3 EpochAwareLens] --> [3D.1 Epoch Cascade] ----------+
|
[3B.2 Semantic Decay] ----------------------------------+
Critical Path for Agile Agent Team Demo
[3C.3 Constraints Lens] (standalone) ------+
|
[3B.1 Time-Travel] -----------------------+----> AGENT TEAM MVP
|
[2.3 EpochAwareLens] ---------------------+
|
[Query Audit (Phase 2)] ✅ ----------------+