pathway_memory: consensus-designed sidecar + hot-swap learning loop
lakehouse/auditor 11 warnings — see review
lakehouse/auditor 11 warnings — see review
10-probe N=3 consensus (kimi-k2:1t / gpt-oss:120b / qwen3.5:latest /
deepseek-v3.1:671b / qwen3-coder:480b / mistral-large-3:675b /
qwen3.5:397b + 2 stability re-probes; 2 openrouter probes 429'd) locked
the design across three rounds. Full JSON responses in
data/_kb/consensus_reducer_design_{mocq3akn,mocq6pi1,mocqatik}.json.
What it does
Preserves FULL backtrack context per reviewed file (ladder attempts +
latencies + reject reasons, KB chunks with provenance + cosine + rank,
observer signals, context7 bridge hits, sub-pipeline calls, audit
consensus) and indexes them by narrow fingerprint for hot-swap of
proven review pathways.
When scrum reviews a file:
1. narrow fingerprint = task_class + file_prefix + signal_class
2. query_hot_swap checks pathway memory for a match that passes
probation (≥3 replays @ ≥80% success) + audit gate + similarity
(≥0.90 cosine on normalized-metadata-token embedding)
3. if hot-swap eligible, recommended model tried first in the ladder
4. replay outcome reported back, updating the pathway's success_rate
5. pathways below 0.80 after ≥3 replays retire permanently (sticky)
6. full PathwayTrace always inserted at end of review — hot-swap
grows with use, it doesn't bootstrap from nothing
Gate design is load-bearing:
- narrow fingerprint (6 of 8 consensus models converged on the same
3-field composition; lock) — enables generalization within crate
- probation ≥3 replays — binomial tail at 80% is ~5%, below is noise
- success rate ≥0.80 — mistral + qwen3-coder independently proposed
this exact threshold across two rounds
- similarity ≥0.90 — middle of the 0.85/0.95 consensus spread
- bootstrap: null audit_consensus ALLOWED (auditor → pathway update
not wired yet; probation + success_rate gates alone enforce safety
during bootstrap; explicit audit FAIL still blocks)
- retirement is sticky — prevents oscillation on noise
Files
+ crates/vectord/src/pathway_memory.rs (new, 600 lines + 18 tests)
PathwayTrace, LadderAttempt, KbChunkRef, ObserverSignal, BridgeHit,
SubPipelineCall, AuditConsensus, HotSwapCandidate, PathwayMemory,
PathwayMemoryStats. 18/18 tests green.
Cosine + 32-bucket L2-normalized embedding; mirror of TS impl.
M crates/vectord/src/lib.rs
pub mod pathway_memory;
M crates/vectord/src/service.rs
VectorState grows pathway_memory field;
4 HTTP handlers (/pathway/insert, /pathway/query,
/pathway/record_replay, /pathway/stats).
M crates/gateway/src/main.rs
Construct PathwayMemory + load from storage on boot,
wire into VectorState.
M tests/real-world/scrum_master_pipeline.ts
Byte-matching TS bucket-hash (verified same bucket indices as
Rust); pre-ladder hot-swap query; ladder reorder on hit;
per-attempt latency capture; post-accept trace insert
(fire-and-forget); replay outcome recording;
observer /event emits pathway_hot_swap_hit, pathway_similarity,
rungs_saved per review for the VCP UI.
M ui/server.ts
/data/pathway_stats aggregates /vectors/pathway/stats +
scrum_reviews.jsonl window for the value metric.
M ui/ui.js
Three new metric cards:
· pathway reuse rate (activity: is it firing?)
· avg rungs saved (value: is it earning its keep?)
· pathways tracked (stability: retirement = learning)
What's not in this commit (queued)
- auditor → pathway audit_consensus update wire (explicit audit-fail
block activates when this lands)
- bridge_hits + sub_pipeline_calls population from context7 / LLM
Team extract results (fields wired, callers not yet)
- replay log (PathwayReplayOutcome {matched_id, succeeded, ts}) as
a separate jsonl for forensic audit of why specific replays failed
Why > summarization
Summaries discard the causal chain. With this, auditor can verify
citation provenance, applier can distinguish lucky from learned paths,
and the matrix indexing actually stores end-to-end pathways instead of
just RAG chunks — which is what J meant by "why aren't we using it
for everything."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ use std::sync::Arc;
|
||||
use aibridge::client::{AiClient, EmbedRequest, GenerateRequest};
|
||||
use catalogd::registry::Registry as CatalogRegistry;
|
||||
use storaged::registry::BucketRegistry;
|
||||
use crate::{agent, autotune, chunker, embedding_cache, harness, hnsw, index_registry, jobs, lance_backend, playbook_memory, promotion, rag, refresh, search, store, supervisor, trial};
|
||||
use crate::{agent, autotune, chunker, embedding_cache, harness, hnsw, index_registry, jobs, lance_backend, pathway_memory, playbook_memory, promotion, rag, refresh, search, store, supervisor, trial};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -55,6 +55,11 @@ pub struct VectorState {
|
||||
/// and, when `use_playbook_memory` is set on /vectors/hybrid, boosts
|
||||
/// workers that were actually filled in semantically-similar past ops.
|
||||
pub playbook_memory: playbook_memory::PlaybookMemory,
|
||||
/// Pathway memory — consensus-designed sidecar for full-context
|
||||
/// backtracking + hot-swap of successful review pathways. See
|
||||
/// crates/vectord/src/pathway_memory.rs for the design rationale
|
||||
/// (10-probe N=3 ensemble, locked 2026-04-24).
|
||||
pub pathway_memory: pathway_memory::PathwayMemory,
|
||||
/// Serializes embed calls from seed_playbook_memory to avoid
|
||||
/// concurrent socket collisions with the Python sidecar.
|
||||
pub embed_semaphore: Arc<Semaphore>,
|
||||
@@ -137,6 +142,15 @@ pub fn router(state: VectorState) -> Router {
|
||||
// Phase 45 slice 3 — doc drift detection + human re-admission.
|
||||
.route("/playbook_memory/doc_drift/check/{id}", post(check_doc_drift))
|
||||
.route("/playbook_memory/doc_drift/resolve/{id}", post(resolve_doc_drift))
|
||||
// Pathway memory — consensus-designed sidecar (2026-04-24).
|
||||
// scrum_master_pipeline POSTs /pathway/insert at the end of each
|
||||
// review, calls /pathway/query before running the ladder for a
|
||||
// potential hot-swap, and posts /pathway/record_replay after a
|
||||
// hot-swap succeeds or fails.
|
||||
.route("/pathway/insert", post(pathway_insert))
|
||||
.route("/pathway/query", post(pathway_query))
|
||||
.route("/pathway/record_replay", post(pathway_record_replay))
|
||||
.route("/pathway/stats", get(pathway_stats))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
@@ -2833,6 +2847,73 @@ async fn lance_build_scalar_index(
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Pathway memory handlers ──────────────────────────────────────────
|
||||
//
|
||||
// Thin wrappers around pathway_memory::PathwayMemory. HTTP surface is
|
||||
// deliberately small — four endpoints cover the full lifecycle:
|
||||
// insert at end-of-review, query before running the ladder,
|
||||
// record_replay after a hot-swap, and stats for the VCP UI.
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathwayQueryRequest {
|
||||
task_class: String,
|
||||
file_path: String,
|
||||
signal_class: Option<String>,
|
||||
query_vec: Vec<f32>,
|
||||
}
|
||||
|
||||
async fn pathway_insert(
|
||||
State(state): State<VectorState>,
|
||||
Json(trace): Json<pathway_memory::PathwayTrace>,
|
||||
) -> impl IntoResponse {
|
||||
match state.pathway_memory.insert(trace).await {
|
||||
Ok(()) => Ok(Json(json!({"ok": true}))),
|
||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pathway_query(
|
||||
State(state): State<VectorState>,
|
||||
Json(req): Json<PathwayQueryRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let cand = state
|
||||
.pathway_memory
|
||||
.query_hot_swap(
|
||||
&req.task_class,
|
||||
&req.file_path,
|
||||
req.signal_class.as_deref(),
|
||||
&req.query_vec,
|
||||
)
|
||||
.await;
|
||||
// 200 with null candidate means "no hot-swap"; this is a normal
|
||||
// path, not an error — callers should proceed with the full ladder.
|
||||
Json(json!({ "candidate": cand }))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PathwayReplayRequest {
|
||||
pathway_id: String,
|
||||
succeeded: bool,
|
||||
}
|
||||
|
||||
async fn pathway_record_replay(
|
||||
State(state): State<VectorState>,
|
||||
Json(req): Json<PathwayReplayRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match state
|
||||
.pathway_memory
|
||||
.record_replay_outcome(&req.pathway_id, req.succeeded)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(Json(json!({"ok": true}))),
|
||||
Err(e) => Err((StatusCode::NOT_FOUND, e)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn pathway_stats(State(state): State<VectorState>) -> impl IntoResponse {
|
||||
Json(state.pathway_memory.stats().await)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod extractor_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user