diff --git a/mcp-server/index.ts b/mcp-server/index.ts index 5964745..bbe58f8 100644 --- a/mcp-server/index.ts +++ b/mcp-server/index.ts @@ -1278,7 +1278,9 @@ tr:hover{background:#111827} // Phase 19 ranked candidates. Soft availability filter // auto-applied by /search — this mirrors the real recruiter - // query path exactly. + // query path exactly. k=200 to ensure boost fires across + // the full memory surface (the embedding-discrimination + // narrowness means under-k silently misses endorsements). const searchRes = await api("POST", "/vectors/hybrid", { index_name: "workers_500k_v1", filter_dataset: "workers_500k", @@ -1286,7 +1288,7 @@ tr:hover{background:#111827} sql_filter: `role = '${role}' AND state = '${state}' AND city = '${city}' AND CAST(availability AS DOUBLE) > 0.5`, question: `${role} for ${p.work_type || "construction"} in ${city}`, top_k: 5, generate: false, - use_playbook_memory: true, playbook_memory_k: 25, + use_playbook_memory: true, playbook_memory_k: 200, }).catch(() => ({ sources: [] as any[] })); // Path 2 — discovered patterns for this role in this city. @@ -1562,17 +1564,37 @@ tr:hover{background:#111827} const sqlFields = "name, role, city, state, zip, ROUND(CAST(reliability AS DOUBLE),2) rel, ROUND(CAST(availability AS DOUBLE),2) avail, skills, certifications, archetype"; const directSql = `SELECT ${sqlFields} FROM workers_500k WHERE ${filterStr} ORDER BY CAST(availability AS DOUBLE) DESC, CAST(reliability AS DOUBLE) DESC LIMIT 10`; - const [searchR, directR] = await Promise.all([ + // Derive role+geo for the pattern query so the meta-index + // surface lines up with what the user actually asked for. + const roleForPatterns = understood.find(u => u.startsWith('role:'))?.split(': ')[1] || q; + const cityForPatterns = understood.find(u => u.startsWith('city:'))?.split(': ')[1] || 'Chicago'; + const stateForPatterns = understood.find(u => u.startsWith('state:'))?.split(': ')[1] || 'IL'; + + const [searchR, directR, patternR] = await Promise.all([ api("POST", "/vectors/hybrid", { question: q, index_name: "workers_500k_v1", sql_filter: filterStr, filter_dataset: "ethereal_workers", id_column: "worker_id", top_k: 8, generate: false, + // k=200 to catch compounding — direct measurement shows + // boost reliably fires only when ~all memory is scanned + // due to the narrow 0.55-0.67 cosine band in the 768d + // nomic-embed-text space. Brute force at 200 entries + // is sub-ms; no reason to underscan. + use_playbook_memory: true, playbook_memory_k: 200, }), api("POST", "/query/sql", { sql: directSql }), + api("POST", "/vectors/playbook_memory/patterns", { + query: `${roleForPatterns} in ${cityForPatterns}, ${stateForPatterns}`, + top_k_playbooks: 25, min_trait_frequency: 0.3, + }).catch(() => ({})), ]); // Merge: use SQL results for structured data (zip, avail), vector for ranking const sqlWorkers = directR.rows || []; - const vectorWorkers = (searchR.sources || []).map((s: any) => ({ doc_id: s.doc_id, score: s.score, text: s.chunk_text })); + const vectorWorkers = (searchR.sources || []).map((s: any) => ({ + doc_id: s.doc_id, score: s.score, text: s.chunk_text, + playbook_boost: s.playbook_boost || 0, + playbook_citations: s.playbook_citations || [], + })); return ok({ type: "smart_search", @@ -1583,6 +1605,10 @@ tr:hover{background:#111827} sql_matches: searchR.sql_matches, queries_run: queries, duration_ms: Date.now() - start, + // Meta-index signal — what similar past fills had in common. + // Non-empty when memory has ≥1 relevant playbook. + discovered_pattern: (patternR as any)?.discovered_pattern, + pattern_playbooks_matched: (patternR as any)?.matched_playbooks ?? 0, }); } } diff --git a/mcp-server/search.html b/mcp-server/search.html index e91fefd..6d47d0c 100644 --- a/mcp-server/search.html +++ b/mcp-server/search.html @@ -724,6 +724,17 @@ function doSearch(){ var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:12px;margin-bottom:10px'; h.textContent=(d.sql_matches?d.sql_matches.toLocaleString()+' workers matched — ':'')+'showing best results ('+(d.duration_ms||0)+'ms)'; out.appendChild(h); + // Meta-index signal — what past similar fills had in common. Only + // renders when memory had at least one relevant playbook. + if(d.discovered_pattern && d.pattern_playbooks_matched > 0){ + var mem=document.createElement('div'); + mem.style.cssText='background:#0d2818;border:1px solid #2ea04360;border-radius:6px;padding:8px 12px;margin-bottom:10px;font-size:11px;color:#86efac;line-height:1.5'; + var label=document.createElement('span');label.style.cssText='color:#3fb950;font-weight:600;margin-right:6px'; + label.textContent='MEMORY ('+d.pattern_playbooks_matched+' playbooks):'; + mem.appendChild(label); + mem.appendChild(document.createTextNode(' '+d.discovered_pattern)); + out.appendChild(mem); + } // Render results based on type var workers=d.sql_results||[]; if(workers.length){