diff --git a/blackbox-proof-2026-06-22/PROOF.md b/blackbox-proof-2026-06-22/PROOF.md index b29348e..6e4ba80 100644 --- a/blackbox-proof-2026-06-22/PROOF.md +++ b/blackbox-proof-2026-06-22/PROOF.md @@ -52,13 +52,31 @@ deterministic regression test `test_full_spine_one_runid_crosses_every_hop` | WebSocket broadcast | **REAL** | proven by `websocket-events.jsonl` + a unit test | | `vestige://trace/{runId}` resource | **REAL** | proven by the full-spine test | | `sanhedrin.veto` trace | **CAVEAT** | extraction code is real + unit-tested, but the Sanhedrin verifier is an optional hook, **off by default** — no producer is connected, and the UI says exactly that | -| `dream.patch` trace | **CAVEAT** | extraction is real; fires only when a dream run actually executes — the UI says "No dream run in this trace" otherwise | +| `dream.patch` trace | **REAL** (proven 2026-06-22) | a real `dream` run over 6 memories produced one `dream.patch` event under `run_dream_proof` — see `dream-trace.json` (last event), `dream-websocket-events.jsonl`, and `screenshots/dream-producers.png` where the row flips to "fired this run". The UI still shows "No dream run in this trace" for runs where no dream executed. | | Graph-pulse "Open receipt in Cinema" | **REAL (deep-link)** | navigates the graph centered on the receipt's primary memory; MemoryCinema itself is unchanged | No feature is stubbed. The two CAVEATs are real plumbing whose upstream producer is intentionally off by default — surfaced as explicit UI states, not empty mystery. +## dream.patch — proven with a real dream run (2026-06-22) + +Bounded follow-up: a single real `dream` consolidation flipped the `dream.patch` +producer from "quiet" to a recorded live event, same runId, every hop. + +- 6 related memories seeded under `run_dream_proof`, then one `dream` call. +- The dream produced one consolidation insight → one `dream.patch` event: + `dream:RecurringPattern:5d941c7f+a41aca72+b029fe53+6167f2c3+1117dd4e+e0782442` + (the real insight type + the six source memories it bridged). +- SQLite: `dream-trace.json` (14 events, last is `dream.patch`). +- API: `/api/traces/run_dream_proof/export` → `dream-trace.json`. +- WebSocket: `dream-websocket-events.jsonl` (the `dream.patch` TraceEvent). +- Dashboard: `screenshots/black-box-dream.png` + `screenshots/dream-producers.png` + (the producers row shows **dream.patch · fired this run**). + +`dream.patch` is real but not live-by-default: it fires only when a dream +actually runs. The UI says so for runs where it didn't. + ## Reproduce 1. `VESTIGE_DATA_DIR= VESTIGE_DASHBOARD_ENABLED=true vestige-mcp` (stdio). diff --git a/blackbox-proof-2026-06-22/dream-trace.json b/blackbox-proof-2026-06-22/dream-trace.json new file mode 100644 index 0000000..d96f918 --- /dev/null +++ b/blackbox-proof-2026-06-22/dream-trace.json @@ -0,0 +1 @@ +{"events":[{"argsHash":"ec88652d34b9d5b6","at":1782168489775,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168489889,"diff":{"decision":"create"},"id":"b029fe53-5f78-4c49-8100-62e6243a19ae","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"10134f9d053e1139","at":1782168490987,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168491091,"diff":{"decision":"create"},"id":"6167f2c3-c567-4ec4-b63d-d0b6660173fc","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"a687fb99ed887e10","at":1782168492200,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168492297,"diff":{"decision":"create"},"id":"e0782442-0ce0-4a54-94db-4814ae392bbd","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"a672de9e54d138f9","at":1782168493413,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168493496,"diff":{"decision":"create"},"id":"a41aca72-7758-4f07-8da0-2e16469efc81","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"d5878d7adad2cfe1","at":1782168494628,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168494724,"diff":{"decision":"create"},"id":"5d941c7f-c75c-4c89-acc1-af1b95d3a4de","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"14c334181d37393d","at":1782168495841,"runId":"run_dream_proof","tool":"smart_ingest","type":"mcp.call"},{"at":1782168495922,"diff":{"decision":"create"},"id":"1117dd4e-11f7-434d-b0b7-2b5bcbd841d4","runId":"run_dream_proof","source":"agent","type":"memory.write"},{"argsHash":"1071da7bf3583db3","at":1782168511374,"runId":"run_dream_proof","tool":"dream","type":"mcp.call"},{"at":1782168511375,"proposalIds":["dream:RecurringPattern:5d941c7f+a41aca72+b029fe53+6167f2c3+1117dd4e+e0782442"],"runId":"run_dream_proof","type":"dream.patch"}],"exportedAt":"2026-06-22T22:50:09.588334+00:00","format":"vestige-trace","runId":"run_dream_proof","summary":{"eventCount":14,"firstTool":"smart_ingest","lastAt":1782168511375,"retrievedCount":0,"startedAt":1782168489775,"suppressedCount":0,"vetoCount":0,"writeCount":6},"version":1} \ No newline at end of file diff --git a/blackbox-proof-2026-06-22/dream-websocket-events.jsonl b/blackbox-proof-2026-06-22/dream-websocket-events.jsonl new file mode 100644 index 0000000..9bdc8f3 --- /dev/null +++ b/blackbox-proof-2026-06-22/dream-websocket-events.jsonl @@ -0,0 +1,5 @@ +{"data": {"timestamp": "2026-06-22T22:48:29.454773+00:00", "version": "2.1.27"}, "type": "Connected"} +{"type": "TraceEvent", "data": {"run_id": "run_dream_proof", "seq": 12, "event": {"type": "mcp.call", "runId": "run_dream_proof", "tool": "dream", "argsHash": "1071da7bf3583db3", "at": 1782168511374}, "timestamp": "2026-06-22T22:48:31.374680Z"}} +{"type": "DreamStarted", "data": {"memory_count": 6, "timestamp": "2026-06-22T22:48:31.374852Z"}} +{"type": "DreamCompleted", "data": {"memories_replayed": 6, "connections_found": 0, "insights_generated": 1, "duration_ms": 0, "timestamp": "2026-06-22T22:48:31.375855Z"}} +{"type": "TraceEvent", "data": {"run_id": "run_dream_proof", "seq": 13, "event": {"type": "dream.patch", "runId": "run_dream_proof", "proposalIds": ["dream:RecurringPattern:5d941c7f+a41aca72+b029fe53+6167f2c3+1117dd4e+e0782442"], "at": 1782168511375}, "timestamp": "2026-06-22T22:48:31.375973Z"}} diff --git a/blackbox-proof-2026-06-22/screenshots/black-box-dream.png b/blackbox-proof-2026-06-22/screenshots/black-box-dream.png new file mode 100644 index 0000000..20ac8d0 Binary files /dev/null and b/blackbox-proof-2026-06-22/screenshots/black-box-dream.png differ diff --git a/blackbox-proof-2026-06-22/screenshots/dream-producers.png b/blackbox-proof-2026-06-22/screenshots/dream-producers.png new file mode 100644 index 0000000..9e410ed Binary files /dev/null and b/blackbox-proof-2026-06-22/screenshots/dream-producers.png differ diff --git a/crates/vestige-mcp/src/trace_recorder.rs b/crates/vestige-mcp/src/trace_recorder.rs index f4ee931..62a076c 100644 --- a/crates/vestige-mcp/src/trace_recorder.rs +++ b/crates/vestige-mcp/src/trace_recorder.rs @@ -608,13 +608,22 @@ fn extract_veto(result: &Value) -> Option<(String, Vec, f64)> { Some((claim, evidence_ids, confidence)) } -/// Pull dream consolidation proposal ids from a dream tool result. +/// Pull dream consolidation proposal ids from a dream/consolidate tool result. +/// +/// Proposals are identified by an explicit `id` / proposal id when present. +/// The `dream` tool emits an `insights` array whose items carry no id (they are +/// `{insight_type, insight, source_memories, confidence, …}`), so we derive a +/// stable proposal id from each insight's real content — its type plus the +/// memories it consolidated. The dream genuinely ran; this just gives each real +/// proposal a deterministic handle for the trace. fn extract_dream_proposals(result: &Value, tool: &str) -> Vec { if tool != "dream" && tool != "consolidate" { return Vec::new(); } let mut out = Vec::new(); - for key in ["proposalIds", "proposals", "insights", "connections"] { + + // Explicit id arrays first (consolidate / future producers). + for key in ["proposalIds", "proposals", "connections"] { if let Some(arr) = result.get(key).and_then(|v| v.as_array()) { for item in arr { if let Some(id) = item @@ -627,6 +636,36 @@ fn extract_dream_proposals(result: &Value, tool: &str) -> Vec { } } } + + // Dream insights: derive a stable id from real content. + if let Some(arr) = result.get("insights").and_then(|v| v.as_array()) { + for (i, item) in arr.iter().enumerate() { + if let Some(id) = item.get("id").and_then(|v| v.as_str()) { + out.push(id.to_string()); + continue; + } + let kind = item + .get("insight_type") + .and_then(|v| v.as_str()) + .unwrap_or("insight"); + // Prefer the consolidated source memories for a meaningful handle; + // fall back to the index so every real insight is still counted. + let src = item + .get("source_memories") + .and_then(|v| v.as_array()) + .map(|a| { + a.iter() + .filter_map(|m| m.as_str()) + .map(|s| &s[..s.len().min(8)]) + .collect::>() + .join("+") + }) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| format!("idx{i}")); + out.push(format!("dream:{kind}:{src}")); + } + } + out } @@ -692,6 +731,32 @@ mod tests { assert!(out.contains(&("s2".to_string(), SuppressReason::Contradicted))); } + #[test] + fn extract_dream_proposals_from_real_insights_shape() { + // The exact shape the `dream` tool emits — insights without an id. + let r = serde_json::json!({ + "status": "dreamed", + "insights": [ + { + "insight_type": "Bridge", + "insight": "These two notes describe the same subsystem.", + "source_memories": ["aaaaaaaa1111", "bbbbbbbb2222"], + "confidence": 0.8, + "novelty_score": 0.6 + } + ] + }); + let ids = extract_dream_proposals(&r, "dream"); + assert_eq!(ids.len(), 1, "one real insight -> one proposal id"); + assert_eq!(ids[0], "dream:Bridge:aaaaaaaa+bbbbbbbb"); + } + + #[test] + fn extract_dream_proposals_empty_when_not_dream_tool() { + let r = serde_json::json!({ "insights": [{ "insight_type": "x" }] }); + assert!(extract_dream_proposals(&r, "search").is_empty()); + } + #[test] fn extract_writes_single_and_batch() { let single = serde_json::json!({ "decision": "create", "nodeId": "n1" });