Phase 19 wiring + Path 1/2 work + chain integrity fixes

Backend:
- crates/vectord/src/playbook_memory.rs (new): Phase 19 in-memory boost
  store with seed/rebuild/snapshot, plus temporal decay (e^-age/30 per
  playbook), persist_to_sql endpoint backing successful_playbooks_live,
  and discover_patterns endpoint for meta-index pattern aggregation
  (recurring certs/skills/archetype/reliability across similar past fills).
- DEFAULT_TOP_K_PLAYBOOKS bumped 5 → 25; old default silently missed
  most boosts when memory had > 25 entries.
- service.rs: new routes /vectors/playbook_memory/{seed,rebuild,stats,
  persist_sql,patterns}.

Bun staffing co-pilot (mcp-server/):
- /search, /match, /verify, /proof, /simulation/run, MCP tools all
  forward use_playbook_memory:true and playbook_memory_k:25 to the
  hybrid endpoint. Boost was previously dark across the entire app.
- /log no longer POSTs to /ingest/file — that endpoint REPLACES the
  dataset's object list, so single-row CSV writes were wiping all prior
  rows in successful_playbooks (sp_rows went 33→1 in one /log call).
  /log now seeds playbook_memory with canonical short text and calls
  /persist_sql to keep successful_playbooks_live in sync.
- /simulation/run cumulative end-of-week CSV write removed for the same
  reason. Per-day per-contract /seed (added in this session) is the
  accumulating feedback path now.
- search.html addWorkerInsight renders a green "Endorsed · N playbooks"
  chip with playbook citations when boost > 0.

Internal Dioxus UI (crates/ui/):
- Dashboard phase list rewritten through Phase 19 (was stuck at "Phase
  16: File Watcher" / "Phase 17: DB Connector" — both wrong).
- Removed fabricated "27ms" stat label.
- Ask tab examples + SQL default replaced with real staffing prompts
  against candidates/clients/job_orders (was referencing nonexistent
  employees/products/events).
- New Playbook tab exposes /vectors/playbook_memory/{stats,rebuild} and
  side-by-side hybrid search (boost OFF vs ON) with citations.

Tests (tests/multi-agent/):
- run_e2e_rated.ts: parallel two-agent (mistral + qwen2.5) build phase
  + verifier rating (geo, auth, persist, boost, speed → /10).
- network_proving.ts: continuous build → verify → repeat with
  staffing-recruiter profile hot-swap; geo-discrimination check.
- chain_of_custody.ts: single recruiter operation traced through every
  layer (Bun /search, direct /vectors/hybrid parity, /log, SQL,
  playbook_memory growth, profile activation, post-op boost lift).
This commit is contained in:
root
2026-04-20 06:21:13 -05:00
parent 8e3cac5812
commit 25b7e6c3a7
13 changed files with 4566 additions and 84 deletions
+108 -32
View File
@@ -74,7 +74,10 @@ server.tool(
top_k: z.number().default(5),
},
async ({ question, sql_filter, dataset, id_column, top_k }) => {
const body: any = { question, index_name: "workers_500k_v1", filter_dataset: dataset, id_column, top_k, generate: true };
const body: any = {
question, index_name: "workers_500k_v1", filter_dataset: dataset, id_column, top_k, generate: true,
use_playbook_memory: true,
};
if (sql_filter) body.sql_filter = sql_filter;
const r = await api("POST", "/vectors/hybrid", body);
return { content: [{ type: "text" as const, text: JSON.stringify(r, null, 2) }] };
@@ -109,6 +112,7 @@ server.tool(
index_name: "workers_500k_v1", sql_filter: filter,
filter_dataset: "ethereal_workers", id_column: "worker_id",
top_k: headcount * 2, generate: false,
use_playbook_memory: true,
});
let matches = r.sources || [];
if (required_certs.length > 0) {
@@ -384,6 +388,11 @@ async function main() {
question: b.question, index_name: b.index || "workers_500k_v1",
sql_filter: b.sql_filter, filter_dataset: b.dataset || "ethereal_workers",
id_column: b.id_column || "worker_id", top_k: b.top_k || 5, generate: b.generate !== false,
use_playbook_memory: b.use_playbook_memory !== false,
// Forward explicitly so Bun /search isn't capped by the
// server's default — boost silently misses good matches when
// memory has >25 entries and only top-5 playbooks are scanned.
playbook_memory_k: b.playbook_memory_k ?? 25,
}));
}
@@ -403,6 +412,8 @@ async function main() {
index_name: b.index || "workers_500k_v1", sql_filter: filter,
filter_dataset: b.dataset || "ethereal_workers",
id_column: "worker_id", top_k: (b.headcount || 5) * 2, generate: false,
use_playbook_memory: true,
playbook_memory_k: 25,
}));
}
@@ -418,14 +429,57 @@ async function main() {
return ok(await api("POST", "/vectors/rag", { index_name: b.index || "workers_500k_v1", question: b.question, top_k: b.top_k || 5 }));
}
// Tool: log success
// Tool: log success.
//
// BUG FIX 2026-04-20: previously this also POSTed a 1-row CSV to
// /ingest/file?name=successful_playbooks. That endpoint REPLACES
// the dataset's object list rather than appending — so every /log
// call destroyed all prior rows in the SQL-queryable
// successful_playbooks table. Chain-of-custody trace caught it:
// sp_rows went 33 → 1 in a single /log call.
//
// Until a proper append endpoint exists (Phase 8 delta write
// surface for the SQL table), /log writes ONLY to playbook_memory
// (in-memory append-only store, works correctly for boost). The
// SQL successful_playbooks table is now treated as derived state
// that gets rebuilt explicitly via /vectors/playbook_memory/rebuild
// — never written to by the recruiter path.
if (url.pathname === "/log") {
const b = await json();
const csv = `timestamp,operation,approach,result,context\n"${new Date().toISOString()}","${(b.operation||"").replace(/"/g,'""')}","${(b.approach||"").replace(/"/g,'""')}","${(b.result||"").replace(/"/g,'""')}","${(b.context||"").replace(/"/g,'""')}"`;
const form = new FormData();
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
const r = await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
return ok({ logged: true, response: await r.text() });
// Result format expected: "{filled}/{needed} filled → Name1, Name2, Name3"
const result = String(b.result || "");
const arrowIdx = result.indexOf("→");
const namesPart = arrowIdx >= 0 ? result.slice(arrowIdx + 1) : "";
const endorsed = namesPart.split(",").map(s => s.trim()).filter(Boolean);
let seeded = 0;
let persisted_rows = 0;
if (endorsed.length && /fill:.+ in .+,.+/i.test(String(b.operation || ""))) {
const canonicalApproach = `${(b.approach || "manual log").split(/[\.\n]/)[0]}`.slice(0, 80);
const canonicalContext = `${(b.context || "").split(/[\.\n]/)[0]}`.slice(0, 80);
const seedRes = await api("POST", "/vectors/playbook_memory/seed", {
operation: b.operation,
approach: canonicalApproach,
context: canonicalContext,
endorsed_names: endorsed,
append: true,
}).catch(() => null) as any;
if (seedRes && seedRes.playbook_id) {
seeded = endorsed.length;
// After every successful seed, persist memory → SQL so the
// successful_playbooks_live table reflects current operator
// activity. /persist_sql writes the FULL state, which is safe
// because in-memory playbook_memory IS the source of truth
// (no concurrent writer outside this process modifies it).
const pr = await api("POST", "/vectors/playbook_memory/persist_sql", {}).catch(() => null) as any;
if (pr && typeof pr.rows_persisted === "number") persisted_rows = pr.rows_persisted;
}
}
return ok({
logged: true,
seeded,
persisted_to_sql: persisted_rows,
note: "successful_playbooks_live (NOT successful_playbooks) is the SQL surface for live operator activity. /log is non-destructive.",
});
}
// Tool: get playbooks
@@ -480,6 +534,7 @@ async function main() {
question: "reliable forklift operator", index_name: "workers_500k_v1",
sql_filter: "role = 'Forklift Operator' AND state = 'IL' AND CAST(reliability AS DOUBLE) > 0.8",
filter_dataset: "workers_500k", id_column: "worker_id", top_k: 5, generate: false,
use_playbook_memory: true,
});
tests.push({
name: "Hybrid SQL+Vector Search", ms: Date.now() - ht0,
@@ -987,6 +1042,7 @@ tr:hover{background:#111827}
question: "reliable forklift operator", index_name: "workers_500k_v1",
sql_filter: "role = 'Forklift Operator' AND state = 'IL' AND CAST(reliability AS DOUBLE) > 0.8",
filter_dataset: "workers_500k", id_column: "worker_id", top_k: 5, generate: false,
use_playbook_memory: true,
});
tests.push({
name: "Hybrid SQL+Vector", ms: Date.now() - ht0,
@@ -1435,6 +1491,26 @@ const SCENARIOS = [
function pick<T>(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)]; }
// Seed playbook_memory from a filled contract so the next hybrid query
// ranks against it. Used by both runWeekSimulation (per-day) and the /log
// endpoint (per manual logging). Fail-soft — seeding is best-effort.
async function seedPlaybookFromContract(c: any) {
const names = (c.matches || []).slice(0, 5)
.map((m: any) => m.name || m.doc_id)
.filter((n: string) => n && !n.startsWith("W500-"));
if (!names.length) return;
const op = `fill: ${c.role} x${c.headcount} in ${c.city}, ${c.state}`;
try {
await api("POST", "/vectors/playbook_memory/seed", {
operation: op,
approach: `${c.situation || c.priority || "fill"} → hybrid search`,
context: `client=${c.client || ""} start=${c.start || ""}`,
endorsed_names: names,
append: true,
});
} catch {}
}
async function runWeekSimulation() {
const days = ["Monday","Tuesday","Wednesday","Thursday","Friday"];
const staffers = ["Sarah (Lead)","Mike (Senior)","Kim (Junior)"];
@@ -1468,7 +1544,7 @@ async function runWeekSimulation() {
if (priority === "urgent") emergencies++;
totalNeeded += headcount;
// Run hybrid search
// Run hybrid search — Phase 19: boost on so past playbooks shape ranking
let filled = 0;
let matches: any[] = [];
try {
@@ -1481,12 +1557,15 @@ async function runWeekSimulation() {
id_column: "worker_id",
top_k: headcount + 2,
generate: false,
use_playbook_memory: true,
});
matches = (r.sources || []).slice(0, headcount).map((s: any) => ({
doc_id: s.doc_id,
name: s.chunk_text?.split("—")[0]?.trim() || s.doc_id,
score: s.score,
chunk_text: s.chunk_text || "",
playbook_boost: s.playbook_boost || 0,
playbook_citations: s.playbook_citations || [],
}));
filled = matches.length;
} catch {}
@@ -1501,7 +1580,15 @@ async function runWeekSimulation() {
});
}
// End of day: log playbook + prepare handoff
// End of day: seed playbook_memory with TODAY's filled contracts so
// tomorrow's hybrid search ranks against them. This is the in-week
// feedback loop — without this, day 5 doesn't benefit from day 1.
for (const c of contracts) {
if (c.matches && c.matches.length) {
await seedPlaybookFromContract(c).catch(() => {});
}
}
if (d < 4) {
handoffs++;
try {
@@ -1530,29 +1617,18 @@ async function runWeekSimulation() {
playbook_entries: playbookEntries,
};
// Log every filled contract as a playbook entry — this is the training data
try {
const ts = new Date().toISOString();
const rows: string[] = [];
for (const day of results) {
for (const c of day.contracts) {
if (c.matches && c.matches.length > 0) {
const workerNames = c.matches.slice(0, 3).map((m: any) => m.name || m.doc_id).join(", ");
const op = `fill: ${c.role} x${c.headcount} in ${c.city}, ${c.state}`;
const approach = `${c.situation} (${c.priority}) → hybrid search`;
const result = `${c.filled}/${c.headcount} filled → ${workerNames}`;
const context = `client=${c.client} start=${c.start} scenario=${c.situation}`;
rows.push(`"${ts}","${op.replace(/"/g,'""')}","${approach}","${result.replace(/"/g,'""')}","${context.replace(/"/g,'""')}"`);
}
}
}
if (rows.length) {
const csv = `timestamp,operation,approach,result,context\n${rows.join("\n")}`;
const form = new FormData();
form.append("file", new Blob([csv], { type: "text/csv" }), "playbook.csv");
await fetch(`${BASE}/ingest/file?name=successful_playbooks`, { method: "POST", body: form });
}
} catch {}
// BUG FIX 2026-04-20: previously this POSTed a multi-row CSV to
// /ingest/file?name=successful_playbooks at end of every simulation.
// That endpoint REPLACES the dataset's object list — so each
// /simulation/run wiped the prior simulation's rows. The SQL
// successful_playbooks table was never accumulating; it always reflected
// only the most-recent simulation batch.
//
// Per-day per-contract seeding via /vectors/playbook_memory/seed
// (added Pass 1, runs inside the day loop above) is the path that
// actually accumulates feedback. The SQL successful_playbooks table is
// intentionally not written by /simulation/run anymore until a proper
// append surface exists.
return { days: results, summary };
}