From 698f19c2214d448f489cd8fd7b6acc26e28cd0bd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 25 Apr 2026 03:08:59 +0100 Subject: [PATCH] Harden Epiphany proposal evidence selection --- epiphany-core/src/proposal.rs | 236 +++++++++++++++++++-- notes/epiphany-current-algorithmic-map.md | 48 +++-- notes/epiphany-fork-implementation-plan.md | 9 +- notes/fresh-workspace-handoff.md | 13 +- state/evidence.jsonl | 2 + state/map.yaml | 7 +- state/scratch.md | 8 +- 7 files changed, 266 insertions(+), 57 deletions(-) diff --git a/epiphany-core/src/proposal.rs b/epiphany-core/src/proposal.rs index 8437d005..c0f41063 100644 --- a/epiphany-core/src/proposal.rs +++ b/epiphany-core/src/proposal.rs @@ -17,6 +17,13 @@ use std::path::PathBuf; const SUMMARY_LIMIT: usize = 220; +#[derive(Debug, Clone, PartialEq, Eq)] +struct SelectionQuality { + observation_count: usize, + evidence_count: usize, + source_kinds: Vec, +} + #[derive(Debug, Clone)] pub struct EpiphanyMapProposalInput { pub state: EpiphanyThreadState, @@ -35,18 +42,20 @@ pub struct EpiphanyMapProposal { pub fn propose_map_update(input: EpiphanyMapProposalInput) -> Result { let observation_ids = normalize_observation_ids(input.observation_ids)?; let observations = select_observations(&input.state, &observation_ids)?; + let selection_quality = evaluate_selection_quality(&input.state, &observations)?; let code_refs = collect_code_refs(&observations)?; + let code_ref_paths = unique_code_ref_paths(&code_refs); let fingerprint = fingerprint(&input.state.revision, &observation_ids, &code_refs); let mut graphs = input.state.graphs.clone(); let mut active_node_ids = Vec::new(); let mut reused_nodes = 0usize; let mut created_nodes = 0usize; - for path in unique_code_ref_paths(&code_refs) { + for path in &code_ref_paths { let path_code_refs = code_refs_for_path(&code_refs, &path); let candidate_node_id = graph_node_id(&path); if let Some(node_index) = - find_architecture_node_for_path(&graphs, &path_code_refs, &path, &candidate_node_id) + find_architecture_node_for_path(&graphs, &path_code_refs, path, &candidate_node_id) { let node = &mut graphs.architecture.nodes[node_index]; active_node_ids.push(node.id.clone()); @@ -82,18 +91,17 @@ pub fn propose_map_update(input: EpiphanyMapProposalInput) -> Result Result Result( Ok(selected) } +fn evaluate_selection_quality( + state: &EpiphanyThreadState, + observations: &[&EpiphanyObservation], +) -> Result { + let mut evidence_ids = Vec::new(); + let mut seen_evidence_ids = HashSet::new(); + let mut source_kinds = Vec::new(); + let mut seen_source_kinds = HashSet::new(); + + for observation in observations { + if observation.evidence_ids.is_empty() { + return Err(anyhow!( + "observation id {:?} must cite at least one evidence record", + observation.id + )); + } + + let mut has_accepting_evidence = false; + for evidence_id in &observation.evidence_ids { + let evidence = state + .recent_evidence + .iter() + .find(|evidence| evidence.id == *evidence_id) + .ok_or_else(|| { + anyhow!( + "observation id {:?} cites missing evidence id {:?}", + observation.id, + evidence_id + ) + })?; + if !is_verified_status(&evidence.status) { + return Err(anyhow!( + "observation id {:?} cites non-accepting evidence id {:?} with status {:?}", + observation.id, + evidence_id, + evidence.status + )); + } + has_accepting_evidence = true; + if seen_evidence_ids.insert(evidence.id.clone()) { + evidence_ids.push(evidence.id.clone()); + } + } + + if !has_accepting_evidence { + return Err(anyhow!( + "observation id {:?} must cite at least one accepting evidence record", + observation.id + )); + } + + let source_kind = observation.source_kind.trim(); + if !source_kind.is_empty() && seen_source_kinds.insert(source_kind.to_string()) { + source_kinds.push(source_kind.to_string()); + } + } + + Ok(SelectionQuality { + observation_count: observations.len(), + evidence_count: evidence_ids.len(), + source_kinds, + }) +} + fn collect_code_refs(observations: &[&EpiphanyObservation]) -> Result> { let mut code_refs = Vec::new(); let mut seen = HashSet::new(); @@ -422,6 +495,69 @@ fn proposal_graph_freshness(reused_nodes: usize, created_nodes: usize) -> String .to_string() } +fn proposal_diff_pressure( + reused_nodes: usize, + created_nodes: usize, + path_count: usize, + observation_count: usize, + existing_churn: Option<&EpiphanyChurnState>, +) -> String { + let proposal_pressure = if created_nodes > 1 + || (reused_nodes > 0 && created_nodes > 0) + || path_count > 2 + || observation_count > 3 + || existing_churn + .and_then(|churn| churn.unexplained_writes) + .is_some_and(|unexplained_writes| unexplained_writes > 0) + { + "high" + } else if created_nodes > 0 || path_count > 1 || observation_count > 1 { + "medium" + } else { + "low" + }; + + let existing_pressure = existing_churn + .map(|churn| churn.diff_pressure.trim()) + .filter(|value| !value.is_empty()) + .unwrap_or("low"); + + max_pressure(existing_pressure, proposal_pressure).to_string() +} + +fn max_pressure<'a>(left: &'a str, right: &'a str) -> &'a str { + if pressure_rank(left) >= pressure_rank(right) { + left + } else { + right + } +} + +fn pressure_rank(value: &str) -> u8 { + match value.trim().to_ascii_lowercase().as_str() { + "critical" => 4, + "high" => 3, + "medium" => 2, + "low" => 1, + _ => 0, + } +} + +fn selection_quality_summary(selection_quality: &SelectionQuality, path_count: usize) -> String { + let source_kinds = if selection_quality.source_kinds.is_empty() { + "unknown sources".to_string() + } else { + selection_quality.source_kinds.join("/") + }; + format!( + "{} evidence-backed observation(s), {} accepting evidence record(s), {} code path(s), source kind(s): {}", + selection_quality.observation_count, + selection_quality.evidence_count, + path_count, + source_kinds + ) +} + fn is_verified_status(status: &str) -> bool { matches!( status.trim().to_ascii_lowercase().as_str(), @@ -459,6 +595,13 @@ mod tests { code_refs: vec![code_ref("epiphany-core/src/prompt.rs")], evidence_ids: vec!["ev-verified".to_string()], }], + recent_evidence: vec![EpiphanyEvidenceRecord { + id: "ev-verified".to_string(), + kind: "verification".to_string(), + status: "ok".to_string(), + summary: "Verified prompt renderer behavior".to_string(), + code_refs: vec![code_ref("epiphany-core/src/prompt.rs")], + }], churn: Some(EpiphanyChurnState { understanding_status: "grounded".to_string(), diff_pressure: "medium".to_string(), @@ -692,4 +835,61 @@ mod tests { assert!(err.to_string().contains("at least one code ref")); } + + #[test] + fn propose_map_update_rejects_observation_without_backing_evidence() { + let mut state = state_with_observation("ok"); + state.recent_evidence.clear(); + + let err = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-verified".to_string()], + }) + .expect_err("missing backing evidence should fail"); + + assert!(err.to_string().contains("cites missing evidence id")); + } + + #[test] + fn propose_map_update_rejects_non_accepting_backing_evidence() { + let mut state = state_with_observation("ok"); + state.recent_evidence[0].status = "failed".to_string(); + + let err = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-verified".to_string()], + }) + .expect_err("non-accepting backing evidence should fail"); + + assert!(err.to_string().contains("non-accepting evidence")); + } + + #[test] + fn propose_map_update_marks_mixed_multi_path_changes_as_high_pressure() { + let mut state = state_with_observation("ok"); + state.churn = Some(EpiphanyChurnState { + understanding_status: "grounded".to_string(), + diff_pressure: "low".to_string(), + ..Default::default() + }); + state.graphs.architecture.nodes.push(EpiphanyGraphNode { + id: "prompt-renderer".to_string(), + title: "Prompt renderer".to_string(), + purpose: "Render Epiphany state into developer context".to_string(), + code_refs: vec![code_ref("epiphany-core/src/prompt.rs")], + ..Default::default() + }); + state.observations[0] + .code_refs + .push(code_ref("epiphany-core/src/proposal.rs")); + + let proposal = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-verified".to_string()], + }) + .expect("proposal"); + + assert_eq!(proposal.churn.understanding_status, "proposal_updates_map"); + assert_eq!(proposal.churn.diff_pressure, "high"); + } } diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 5a6b9d79..f50c7111 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -18,7 +18,7 @@ The spine exists in nine live paths: - per-turn persistence: normal turn setup persists one `RolloutItem::EpiphanyState` after the `TurnContext` when state exists in [session/mod.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/session/mod.rs:2677). - client read hydration: app-server thread views attach live or reconstructed `thread.epiphanyState` in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4520). - explicit distillation proposals: app-server routes read-only `thread/epiphany/distill` through a loaded-thread handler into `epiphany-core` so one explicit observation can become a patch candidate without mutating state in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4166) and [distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28). -- explicit map/churn proposals: app-server routes read-only `thread/epiphany/propose` through a loaded-thread handler into `epiphany-core` so verified observations with code refs can focus or extend architecture graph nodes, carry linked dataflow nodes and incident graph edges into the frontier, update churn candidates, and still avoid mutation until promotion in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4244) and [proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:34). +- explicit map/churn proposals: app-server routes read-only `thread/epiphany/propose` through a loaded-thread handler into `epiphany-core` so verified observations with code refs and accepting recent evidence can focus or extend architecture graph nodes, carry linked dataflow nodes and incident graph edges into the frontier, update churn candidates with map-delta pressure, and still avoid mutation until promotion in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4244) and [proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:42). - explicit promotion gates: app-server routes `thread/epiphany/promote` through a loaded-thread handler into `epiphany-core` policy evaluation, rejects failed verifier evidence without mutation, and applies accepted candidates through the same durable update path in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4316) and [promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33). - explicit state updates: app-server routes `thread/epiphany/update` through a loaded `CodexThread` update method that mutates live `SessionState`, bumps the revision, and persists an immediate `RolloutItem::EpiphanyState` snapshot in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4337) and [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:373). - retrieval/indexing: app-server routes `thread/epiphany/retrieve` and `thread/epiphany/index` through loaded `CodexThread` host methods into `epiphany-core` in [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4039), [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:438), and [codex_thread.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/core/src/codex_thread.rs:456). @@ -29,7 +29,7 @@ The thick Epiphany-owned implementation is mostly outside vendored Codex now: - generic rollout replay for stored-thread reads lives in [epiphany-core/src/rollout.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/rollout.rs:38). - retrieval and indexing live in [epiphany-core/src/retrieval.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/retrieval.rs:144) and [retrieval.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/retrieval.rs:160), with summary-only freshness reads in [retrieval.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/retrieval.rs:100). - deterministic observation distillation lives in [epiphany-core/src/distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28). -- deterministic map/churn proposal from verified observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:34). +- deterministic map/churn proposal from verified, evidence-backed observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:42). - verifier-backed promotion policy lives in [epiphany-core/src/promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33). ## Whole Control Flow @@ -113,7 +113,7 @@ This is the same flow in the language Epiphany is supposed to make the model car | Retrieval | A loaded thread can ask a structured question of the repo through one typed retrieval surface. | A librarian brings back marked pages instead of making the agent rummage through every shelf. | | Indexing | Persistent semantic memory is built only through an explicit indexing path. | The librarian updates the card catalog only when asked, not while pretending to answer a question. | | Distillation | One explicit observation can be normalized into a typed observation/evidence patch, but not promoted automatically. | The clerk drafts a ledger entry in pencil before anyone is allowed to ink it. | -| Proposal | Verified observations with code refs can produce a candidate graph frontier/churn patch without writing it, reusing existing map nodes before adding new ones and focusing linked context. | The clerk overlays tracing paper on the old blueprint before drawing a new wall through the plumbing. | +| Proposal | Verified observations with code refs and accepting recent evidence can produce a candidate graph frontier/churn patch without writing it, reusing existing map nodes before adding new ones, focusing linked context, and reporting pressure from the actual map delta. | The clerk overlays tracing paper on the old blueprint before drawing a new wall through the plumbing, then marks how much load the sketch would put on the frame. | | Promotion | A verifier-backed gate can reject a proposal without mutation or send it through the durable update path. | The foreman stamps the pencil draft before the clerk reaches for the red pen. | | State updates | A loaded thread can accept explicit typed patches that append observations/evidence and replace bounded map/scratch/churn fields. | The clerk finally has the red pen, but still writes only on the ledger page the form allows. | @@ -681,9 +681,9 @@ A client sends experimental `thread/epiphany/propose` for a loaded thread with: ### Plain-language role -Proposal is the tracing-paper path. It starts from observations that are already in the thread's typed state, requires those observations to have verified/accepted status, requires source-grounding through code refs, and returns a candidate patch that can replace graph/frontier/churn fields only if a later promotion accepts it. +Proposal is the tracing-paper path. It starts from observations that are already in the thread's typed state, requires those observations to have verified/accepted status, requires each selected observation to cite accepting `recent_evidence`, requires source-grounding through code refs, and returns a candidate patch that can replace graph/frontier/churn fields only if a later promotion accepts it. -This is the first shipped chewing motion. It is deliberately small: no model call, no watcher invalidation, no automatic graph truth. It now first tries to match observed code refs against existing architecture nodes, then falls back to same-path refs and deterministic path-node ids, enriches matching nodes with any newly observed refs, creates path-derived candidate nodes only for genuinely unmapped surfaces, expands frontier focus through graph links, marks named incident edges active, emits a proposal observation/evidence pair, and marks churn according to whether the proposal refined, expanded, or mixed the map. It is a candidate, not an oracle. We do not crown the goblin because it found a wrench. +This is the first shipped chewing motion. It is deliberately small: no model call, no watcher invalidation, no automatic graph truth. It now first checks that selected observations are backed by current accepting evidence, then tries to match observed code refs against existing architecture nodes, falls back to same-path refs and deterministic path-node ids, enriches matching nodes with any newly observed refs, creates path-derived candidate nodes only for genuinely unmapped surfaces, expands frontier focus through graph links, marks named incident edges active, emits a proposal observation/evidence pair, and marks churn according to whether the proposal refined, expanded, or mixed the map. Diff pressure now reflects the proposed map delta, touched paths, observation count, and unresolved write risk instead of blindly inheriting the previous pressure. It is a candidate, not an oracle. We do not crown the goblin because it found a wrench. Protocol shape: @@ -722,30 +722,32 @@ The app-server handler: 1. normalizes and deduplicates requested observation ids. 2. rejects unknown observations. 3. rejects observations whose status is not accepting: `ok`, `accepted`, `verified`, `pass`, or `passed`. -4. rejects proposals without code refs. -5. clones the current graphs and searches architecture nodes for exact code-ref overlap, same-path overlap, then the deterministic path-node id. -6. enriches matching architecture nodes with newly observed refs, preserving their existing title/purpose/status. -7. creates path-derived candidate architecture nodes only when no existing node matches that code path. -8. expands focused node ids through graph links so architecture nodes pull their linked dataflow nodes into the frontier, and vice versa. -9. marks named graph edges incident to focused nodes as active. -10. merges focused node ids, active edge ids, and observed paths into the graph frontier. -11. emits a proposal observation/evidence pair tied to the candidate patch, including reused/created node counts. -12. emits churn with `proposal_refines_map`, `proposal_expands_map`, or `proposal_updates_map` depending on whether the candidate reused existing nodes, created new nodes, or did both. +4. rejects selected observations that do not cite existing accepting `recent_evidence`. +5. rejects proposals without code refs. +6. clones the current graphs and searches architecture nodes for exact code-ref overlap, same-path overlap, then the deterministic path-node id. +7. enriches matching architecture nodes with newly observed refs, preserving their existing title/purpose/status. +8. creates path-derived candidate architecture nodes only when no existing node matches that code path. +9. expands focused node ids through graph links so architecture nodes pull their linked dataflow nodes into the frontier, and vice versa. +10. marks named graph edges incident to focused nodes as active. +11. merges focused node ids, active edge ids, and observed paths into the graph frontier. +12. emits a proposal observation/evidence pair tied to the candidate patch, including evidence-backed selection counts and reused/created node counts. +13. emits churn with `proposal_refines_map`, `proposal_expands_map`, or `proposal_updates_map` depending on whether the candidate reused existing nodes, created new nodes, or did both. +14. emits `diff_pressure` from the proposal shape: low for narrow refinements, medium for new single-surface expansion, and high for mixed/multi-path/multi-observation changes or existing unexplained writes. Code refs: - [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4244) - [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4287) - [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4301) -- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:34) -- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:281) -- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:307) -- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:396) -- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:480) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:42) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:200) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:354) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:380) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:498) ### Output -The response is a promotion-ready map/churn patch. Existing mapped surfaces now keep their node identity and accumulate observed refs instead of being duplicated as new path nodes, and the frontier points at the linked context the prompt renderer already knows how to display. The live smoke proved `thread/epiphany/propose` does not mutate state by itself; `thread/read` stayed at revision `1` with no persisted churn after proposal, then `thread/epiphany/promote` advanced revision to `2` and persisted the graph/churn patch only after verifier evidence accepted it. +The response is a promotion-ready map/churn patch. Existing mapped surfaces now keep their node identity and accumulate observed refs instead of being duplicated as new path nodes, the frontier points at the linked context the prompt renderer already knows how to display, and churn pressure describes how risky the candidate map change is. The live smoke proved `thread/epiphany/propose` does not mutate state by itself; `thread/read` stayed at revision `1` with no persisted churn after proposal, then `thread/epiphany/promote` advanced revision to `2` and persisted the graph/churn patch only after verifier evidence accepted it. ### Invariant @@ -853,7 +855,7 @@ stored Epiphany state -> explicit update patches can revise durable map/evidence/churn state ``` -The current remaining missing organ is not the red pen and not the first chewing motion. Those exist. Proposal now has the first bits of map memory and focus: it reuses existing architecture nodes by code-ref overlap before creating new path nodes, then follows graph links and incident edges into the frontier. The missing organ is broader judgment: richer observation selection, stronger proposal heuristics than path/link overlap, watcher/freshness inputs, and eventually role-scoped specialist ownership without silently auto-promoting anything. +The current remaining missing organ is not the red pen and not the first chewing motion. Those exist. Proposal now has the first bits of map memory, focus, and selection hygiene: it requires accepting recent evidence behind selected observations, reuses existing architecture nodes by code-ref overlap before creating new path nodes, follows graph links and incident edges into the frontier, and reports churn pressure from the actual proposal shape. The missing organ is broader judgment: stronger proposal heuristics than path/link/evidence overlap, watcher/freshness inputs, and eventually role-scoped specialist ownership without silently auto-promoting anything. ```text model/tool observations @@ -864,9 +866,9 @@ model/tool observations -> durable evidence/map/churn mutation ``` -The typed write path exists, the first deterministic observation/evidence distillation path exists, the first deterministic map/churn proposal path exists, proposal-quality hardening now reuses existing graph nodes by observed code refs and focuses linked frontier context, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence or that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, or churn fields. +The typed write path exists, the first deterministic observation/evidence distillation path exists, the first deterministic map/churn proposal path exists, proposal-quality hardening now requires accepting recent evidence, reuses existing graph nodes by observed code refs, focuses linked frontier context, reports map-delta pressure, and the first verifier-backed promotion gate exists. The first map/churn safety layer also exists: promotion can reject state replacement patches that are not tied to explicit observations/evidence or that contain structurally broken subgoal, invariant, graph, frontier, checkpoint, or churn fields. -In natural language: current Epiphany can preserve a map, show a map, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified observations with code refs, reject or accept a verified candidate, apply explicit map/evidence/churn edits, and sanity-check those edits before they hit the ledger. It still does not automatically know which observations matter most, invalidate stale graph regions, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the next slices are about taste, not jawbones. +In natural language: current Epiphany can preserve a map, show a map, retrieve evidence for the map, draft one explicit observation/evidence patch, derive a bounded graph/frontier/churn candidate from verified evidence-backed observations with code refs, reject or accept a verified candidate, apply explicit map/evidence/churn edits, and sanity-check those edits before they hit the ledger. It still does not automatically know which observations matter most beyond explicit ids and evidence links, invalidate stale graph regions, or coordinate specialist ownership. Teeth are installed; chewing is supervised; the next slices are about taste, not jawbones. ## Verification Hooks diff --git a/notes/epiphany-fork-implementation-plan.md b/notes/epiphany-fork-implementation-plan.md index b0e2c117..88922f4b 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -18,9 +18,10 @@ This note tracks Epiphany as a fork of Codex with an opinionated modeling archit - a first typed map/churn proposal slice that adds read-only loaded-thread-only `thread/epiphany/propose`, producing candidate graph frontier/churn patches from verified observations with code refs for explicit promotion through `thread/epiphany/promote` - a first proposal-quality hardening slice that reuses existing architecture graph nodes by exact code-ref, same-path overlap, or deterministic path-node id before creating new candidate nodes, and records whether a proposal refines, expands, or updates the map in churn - a frontier-focus hardening slice that expands proposal frontier focus through existing graph links and marks named incident graph edges active, so prompt rendering gets the relevant architecture/dataflow context instead of one lonely node with a tiny hat +- an evidence-selection/churn hardening slice that requires selected proposal observations to cite accepting `recent_evidence` and derives proposal `diff_pressure` from the candidate map delta, touched paths, selected observation count, and unresolved write risk - current phase: Phase 5 semantic distillation/promotion/proposal machinery -The next job is no longer to prove the retriever exists, sketch the persistent follow-up in prose, invent the first red-pen path, build the first observation proposal surface, make promotion notice broken map/churn replacements, ship the first read-only map/churn proposal surface, teach proposal to avoid duplicating already-mapped code surfaces, or make proposal frontier focus follow existing graph links. Those parts are landed. The next job is to keep the landing zone honest: do not casually turn `thread/epiphany/retrieve`, `thread/epiphany/distill`, or `thread/epiphany/propose` into durable Epiphany-state writers, and do not widen the explicit indexing/update paths into watcher-driven or GUI-shaped machinery before they earn it. +The next job is no longer to prove the retriever exists, sketch the persistent follow-up in prose, invent the first red-pen path, build the first observation proposal surface, make promotion notice broken map/churn replacements, ship the first read-only map/churn proposal surface, teach proposal to avoid duplicating already-mapped code surfaces, make proposal frontier focus follow existing graph links, or make selected observations evidence-backed with non-haunted churn pressure. Those parts are landed. The next job is to keep the landing zone honest: do not casually turn `thread/epiphany/retrieve`, `thread/epiphany/distill`, or `thread/epiphany/propose` into durable Epiphany-state writers, and do not widen the explicit indexing/update paths into watcher-driven or GUI-shaped machinery before they earn it. ## Summary @@ -270,7 +271,7 @@ Add tests for: ## Immediate Next Step -Treat the current retrieval baseline and explicit indexing follow-up as landed Phase 4. Treat the explicit update path, distillation proposal path, read-only map/churn proposal path, verifier-backed promotion gate, structural map/churn promotion validation, graph-node reuse, and linked frontier focus as the active Phase 5 baseline. The next machine gap is no longer "can Epiphany retrieve code?", "can it propose one durable observation patch?", "can it draft one bounded map/churn candidate?", "can it avoid duplicating an existing graph node when an observation points at already-mapped code?", or "can it focus linked graph context?" It can. The next gap is "can Epiphany make better proposal choices from richer evidence without silently promoting them?" +Treat the current retrieval baseline and explicit indexing follow-up as landed Phase 4. Treat the explicit update path, distillation proposal path, read-only map/churn proposal path, verifier-backed promotion gate, structural map/churn promotion validation, graph-node reuse, linked frontier focus, evidence-backed selection, and map-delta churn pressure as the active Phase 5 baseline. The next machine gap is no longer "can Epiphany retrieve code?", "can it propose one durable observation patch?", "can it draft one bounded map/churn candidate?", "can it avoid duplicating an existing graph node when an observation points at already-mapped code?", "can it focus linked graph context?", or "can it reject selected observations that are not backed by accepting recent evidence?" It can. The next gap is "can Epiphany make better proposal choices from richer evidence and stronger graph semantics without silently promoting them?" 1. treat the current verified-and-landed query-time hybrid retriever as the Phase 4 slice 1 baseline 2. treat the verified and live-smoked `thread/epiphany/index` slice as the bounded persistent-semantic follow-up @@ -278,8 +279,8 @@ Treat the current retrieval baseline and explicit indexing follow-up as landed P 4. do not add durable retrieval-summary writes from `thread/epiphany/retrieve` without a clean out-of-band rollout/update semantic 5. continue Phase 5 by hardening the layer above the landed distill/propose/promote/update surfaces: - richer typed observation distillation from tool/model outputs - - better proposal heuristics beyond the current exact-code-ref, same-path, deterministic-id, and graph-link matching - - better observation selection and churn semantics + - better proposal heuristics beyond the current exact-code-ref, same-path, deterministic-id, graph-link, accepting-evidence, and map-delta-pressure checks + - better observation selection beyond explicit ids and evidence links - promotion policy beyond structural validation, but only when it can stay evidence-backed - verifier-backed acceptance/rejection evidence - no hidden retrieval writes diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 857eea50..b107692e 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -87,7 +87,7 @@ Current verified typed state/promotion layer: - experimental loaded-thread-only `thread/epiphany/propose` - typed patch shape for observations, evidence, objective, subgoals, invariants, graphs, graph frontier/checkpoint, scratch, churn, and mode - deterministic observation/evidence proposal generation in `epiphany-core` -- deterministic map/churn proposal generation in `epiphany-core`, now with first-pass graph-node reuse by exact code-ref, same-path overlap, or deterministic path-node id before new candidate nodes are created, plus linked frontier focus through graph links and named incident edges +- deterministic map/churn proposal generation in `epiphany-core`, now with first-pass graph-node reuse by exact code-ref, same-path overlap, or deterministic path-node id before new candidate nodes are created, linked frontier focus through graph links and named incident edges, evidence-backed selected observations via accepting `recent_evidence`, and churn pressure derived from the proposed map delta - verifier-backed promotion policy evaluation in `epiphany-core` - core `CodexThread.epiphany_update_state(...)` applies the patch to live `SessionState`, bumps revision, persists `RolloutItem::EpiphanyState`, and flushes rollout - replay helpers in both `codex-core` and `epiphany-core` accept an out-of-band Epiphany snapshot before the first real user turn so seed/update snapshots survive resume @@ -194,7 +194,7 @@ What is wired right now: - app-server protocol now also declares experimental `thread/epiphany/update` - app-server message handling now routes those methods through the core host seam - `thread/epiphany/distill` is the first read-only proposal surface; it returns an `expectedRevision` plus a patch and requires explicit promotion through `thread/epiphany/update` -- `thread/epiphany/propose` is the first read-only map/churn proposal surface; it selects verified observations with code refs, focuses existing architecture graph nodes when refs overlap, creates new candidate path nodes only for unmapped surfaces, follows graph links into related architecture/dataflow nodes, marks named incident graph edges active, and returns replacement graphs/frontier/churn for explicit promotion +- `thread/epiphany/propose` is the first read-only map/churn proposal surface; it selects verified observations with accepting `recent_evidence` and code refs, focuses existing architecture graph nodes when refs overlap, creates new candidate path nodes only for unmapped surfaces, follows graph links into related architecture/dataflow nodes, marks named incident graph edges active, derives churn pressure from the proposal shape, and returns replacement graphs/frontier/churn for explicit promotion - `thread/epiphany/promote` is the first explicit promotion gate; it rejects failed verifier evidence without mutation and applies accepted candidates through the update path - `thread/epiphany/promote` now has a first richer map/churn safety layer: state replacement patches must carry explicit observations and patch evidence, and subgoal/invariant/graph/frontier/checkpoint/churn structure is validated before the durable update path is called - `thread/epiphany/update` is the first explicit durable Epiphany state write surface; it rejects empty patches, supports optional revision matching, appends observations/evidence, replaces bounded typed fields, increments state revision, and persists immediately @@ -355,7 +355,8 @@ If a future session wakes up from compaction and starts bluffing, this is the pa - Phase 5 semantic distillation/promotion is active: - dedicated experimental `thread/epiphany/update`, `thread/epiphany/distill`, and `thread/epiphany/promote` exist and are live-smoked - structural map/churn promotion validation is landed in `epiphany-core` - - the next bounded follow-up is richer observation-to-map/churn proposal machinery, not proving the retriever exists again + - proposal selection now rejects observations without accepting `recent_evidence`, and churn pressure now reflects map-delta shape + - the next bounded follow-up is richer graph-semantic proposal machinery beyond explicit ids/evidence/path/link matching, not proving the retriever exists again - There is still no live `thread/epiphany/*` notification stream. - The next phase is **not** GUI work. - The current pushed baseline also extracted most Epiphany-owned implementation into `epiphany-core`: @@ -450,9 +451,9 @@ Without that, you get to learn about `symlink_dir failed: ... A required privile ## Recommended Next Implementation -Do not restart verification from superstition. Phase 4 retrieval/indexing/core-extraction is already verified, committed, and pushed. Phase 5 distill/propose/promote/update, the first structural promotion safety layer, graph-node reuse, and linked frontier focus are also landed. +Do not restart verification from superstition. Phase 4 retrieval/indexing/core-extraction is already verified, committed, and pushed. Phase 5 distill/propose/promote/update, the first structural promotion safety layer, graph-node reuse, linked frontier focus, evidence-backed proposal selection, and map-delta churn pressure are also landed. -The durable state seam exists, the turn loop reads it, clients can load typed Epiphany state directly, the first retrieval slice is real, the explicit persistent-semantic indexing path is landed, the distill/propose/promote/update path is live-smoked, promotion now has a first structural map/churn safety layer, and proposal now reuses existing architecture nodes before creating new path nodes and expands frontier focus through linked graph context. The next clean move is improving proposal quality from richer evidence, not pretending the already-shipped slices still need ceremony. +The durable state seam exists, the turn loop reads it, clients can load typed Epiphany state directly, the first retrieval slice is real, the explicit persistent-semantic indexing path is landed, the distill/propose/promote/update path is live-smoked, promotion now has a first structural map/churn safety layer, and proposal now reuses existing architecture nodes before creating new path nodes, expands frontier focus through linked graph context, rejects selected observations without accepting recent evidence, and reports churn pressure from the proposal shape. The next clean move is stronger proposal heuristics from richer evidence/graph semantics, not pretending the already-shipped slices still need ceremony. Current Phase 5 implementation move: @@ -461,7 +462,7 @@ Current Phase 5 implementation move: 3. keep `thread/epiphany/retrieve`, `thread/epiphany/distill`, and `thread/epiphany/propose` read-only 4. continue Phase 5 by hardening the next layer above the distill/propose/promote/update surfaces: - richer map/churn patch generation from verified observations and tool/model outputs - - better existing-graph matching beyond exact refs/same path/deterministic ids/graph links, observation selection, and churn semantics + - better existing-graph matching beyond exact refs/same path/deterministic ids/graph links/evidence links, plus richer observation prioritization than explicit id selection - promotion policy beyond structural validation only when the evidence path stays explicit - verifier-backed acceptance/rejection evidence - no GUI, watcher, or specialist-agent machinery yet diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 77358471..15700c5b 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -107,3 +107,5 @@ {"ts": "2026-04-25T01:31:16+00:00", "type": "verification", "status": "ok", "note": "Reverified final proposal-quality hardening with cargo fmt, cargo test --manifest-path .\\epiphany-core\\Cargo.toml proposal, full cargo test --manifest-path .\\epiphany-core\\Cargo.toml (24 tests), CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-core --lib proposal, and CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-app-server --lib epiphany."} {"ts": "2026-04-25T01:44:47+00:00", "type": "verification", "status": "ok", "note": "Verified linked-frontier proposal hardening with cargo fmt --manifest-path .\\epiphany-core\\Cargo.toml, cargo test --manifest-path .\\epiphany-core\\Cargo.toml proposal (7 tests), full cargo test --manifest-path .\\epiphany-core\\Cargo.toml (25 tests), CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-core --lib proposal, and CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-app-server --lib epiphany."} {"ts": "2026-04-25T01:44:47+00:00", "type": "implementation", "status": "ok", "note": "Hardened read-only thread/epiphany/propose frontier focus in epiphany-core. Proposal now expands focused graph nodes through existing architecture/dataflow links and marks named incident graph edges active, so promoted frontier patches carry connected map context rather than only the directly observed architecture node."} +{"ts": "2026-04-25T02:08:39+00:00", "type": "implementation", "status": "ok", "note": "Hardened read-only thread/epiphany/propose selection and churn semantics in epiphany-core. propose_map_update now requires selected accepting observations to cite accepting recent_evidence, summarizes evidence-backed selection quality in proposal observation/evidence, and derives churn diff_pressure from reused/created nodes, touched paths, observation count, existing pressure, and unresolved write risk."} +{"ts": "2026-04-25T02:08:39+00:00", "type": "verification", "status": "ok", "note": "Verified evidence-backed proposal selection/churn hardening with cargo fmt --manifest-path .\\epiphany-core\\Cargo.toml, cargo test --manifest-path .\\epiphany-core\\Cargo.toml proposal (10 tests), full cargo test --manifest-path .\\epiphany-core\\Cargo.toml (28 tests), CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-core --lib proposal, and CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-app-server --lib epiphany."} diff --git a/state/map.yaml b/state/map.yaml index 5accc92f..fd24ba3f 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -49,7 +49,7 @@ active_subgoals: - Keep the Phase 5 distill/propose/promote/update work aligned with reality in code and repo memory. - Keep vendored Codex as a thin typed host seam while Epiphany-owned prompt and retrieval/indexing logic live in the repo-owned `epiphany-core` crate. - Preserve exact/path lookup and BM25 fallback while the Qdrant path stays explicit and bounded. - - Harden the next layer above the landed typed distill/propose/promote/update paths: richer evidence-to-map proposal quality beyond graph-node reuse and linked frontier focus while keeping verifier-backed acceptance/rejection explicit. + - Harden the next layer above the landed typed distill/propose/promote/update paths: richer evidence-to-map proposal quality beyond graph-node reuse, linked frontier focus, evidence-backed selection, and map-delta churn pressure while keeping verifier-backed acceptance/rejection explicit. accepted_design: - external typed state files @@ -99,6 +99,7 @@ accepted_design: - keep Windows app-server verification explicit about stack pressure: Cargo config sets `RUST_MIN_STACK=67108864` so the test binary does not depend on shell folklore or tiny default test-thread stacks - harden read-only map/churn proposal quality incrementally: `thread/epiphany/propose` should reuse existing architecture graph nodes by exact code-ref, same-path overlap, or deterministic path-node id before creating new candidate nodes, preserve existing node identity/metadata, and describe whether the candidate refines, expands, or updates the map in churn - harden proposal frontier focus incrementally: `thread/epiphany/propose` should follow existing graph links between architecture and dataflow nodes and mark named incident graph edges active so prompt rendering sees relevant connected context + - harden proposal selection and churn incrementally: `thread/epiphany/propose` should require selected verified observations to cite accepting `recent_evidence`, summarize the evidence-backed selection in the candidate observation/evidence, and derive `diff_pressure` from the candidate map delta, touched paths, selected observation count, and unresolved write risk instead of merely inheriting old churn rejected_paths: - name: pinned_map_only @@ -115,5 +116,5 @@ rejected_paths: reason: audited candidates were either terminal-only, stale/aspirational, or incoherent wrappers; the comparison clones were removed from vendor to avoid building on untrusted drift current_status: - summary: Workspace initialized with protocol files, typed state, handoff notes, architecture maps, and a rolling implementation plan against vendored Codex. Phase 1 durable state, Phase 2 prompt integration, the minimal Phase 3 typed read surface, and Phase 4 retrieval/indexing/core-extraction are landed on `main`. Phase 5 semantic distillation/promotion/proposal is now active: the current baseline includes explicit Qdrant indexing with BM25 fallback, typed `thread/epiphany/update`, read-only `thread/epiphany/distill`, read-only `thread/epiphany/propose`, verifier-backed `thread/epiphany/promote`, structural map/churn promotion validation, graph-node reuse and linked frontier focus in `epiphany-core`, and thin vendored Codex host seams. The Windows app-server stack-pressure regression was narrowed to test-thread stack size; repo-local Cargo config sets `RUST_MIN_STACK=67108864`, and boxed-future request-dispatch experiments were removed after config-only verification passed. The repo also has source-available/commercial licensing plus a CLA gate for external contributions. - next_action: Continue Phase 5 by hardening the layer above `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/promote`, and `thread/epiphany/update`: richer evidence-to-map proposal quality beyond current exact-ref/same-path/deterministic-id/graph-link matching, better observation selection and churn semantics, while keeping retrieval/distillation/proposal read-only and avoiding watcher, GUI, or specialist-agent machinery. + summary: Workspace initialized with protocol files, typed state, handoff notes, architecture maps, and a rolling implementation plan against vendored Codex. Phase 1 durable state, Phase 2 prompt integration, the minimal Phase 3 typed read surface, and Phase 4 retrieval/indexing/core-extraction are landed on `main`. Phase 5 semantic distillation/promotion/proposal is now active: the current baseline includes explicit Qdrant indexing with BM25 fallback, typed `thread/epiphany/update`, read-only `thread/epiphany/distill`, read-only `thread/epiphany/propose`, verifier-backed `thread/epiphany/promote`, structural map/churn promotion validation, graph-node reuse, linked frontier focus, evidence-backed proposal selection, map-delta churn pressure in `epiphany-core`, and thin vendored Codex host seams. The Windows app-server stack-pressure regression was narrowed to test-thread stack size; repo-local Cargo config sets `RUST_MIN_STACK=67108864`, and boxed-future request-dispatch experiments were removed after config-only verification passed. The repo also has source-available/commercial licensing plus a CLA gate for external contributions. + next_action: Continue Phase 5 by hardening the layer above `thread/epiphany/distill`, `thread/epiphany/propose`, `thread/epiphany/promote`, and `thread/epiphany/update`: richer evidence-to-map proposal quality beyond current exact-ref/same-path/deterministic-id/graph-link/recent-evidence matching and map-delta churn pressure, especially stronger graph-semantic heuristics and observation prioritization, while keeping retrieval/distillation/proposal read-only and avoiding watcher, GUI, or specialist-agent machinery. diff --git a/state/scratch.md b/state/scratch.md index 36501a58..0896a4eb 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -4,7 +4,7 @@ This file is intentionally disposable. ## Current Subgoal -- Continue Phase 5 proposal-quality hardening beyond graph-node reuse and linked frontier focus, without hidden writes. +- Continue Phase 5 proposal-quality hardening beyond graph-node reuse, linked frontier focus, evidence-backed selection, and map-delta churn pressure, without hidden writes. ## Working Notes @@ -12,9 +12,11 @@ This file is intentionally disposable. - First read-only `thread/epiphany/propose` slice is landed in the working tree: - `epiphany-core/src/proposal.rs` owns deterministic `propose_map_update` - selected observations must already exist in thread state, have accepting status, and carry code refs + - selected observations must cite accepting `recent_evidence`; missing or failed backing evidence now rejects proposal before graph/churn drafting - proposal now tries exact code-ref overlap, same-path overlap, and deterministic path-node id fallback against existing architecture graph nodes before creating new candidate path nodes - matching nodes are focused and enriched with newly observed refs without overwriting their existing title, purpose, or status - - proposal expands frontier focus through existing graph links, marks named incident architecture/dataflow edges active, emits proposal observation/evidence with reused/created node counts, and marks churn as `proposal_refines_map`, `proposal_expands_map`, or `proposal_updates_map` + - proposal expands frontier focus through existing graph links, marks named incident architecture/dataflow edges active, emits proposal observation/evidence with evidence-backed selection counts plus reused/created node counts, and marks churn as `proposal_refines_map`, `proposal_expands_map`, or `proposal_updates_map` + - churn `diff_pressure` now comes from the candidate map delta, touched path count, selected observation count, and existing unexplained write risk, with existing pressure kept as a floor - app-server exposes experimental loaded-thread-only `thread/epiphany/propose` - live stdio smoke proved propose is read-only, and propose -> promote persists graph/churn only after verifier evidence - The Phase 4 retrieval/indexing/core-extraction anchor is `80c29e0` on `main`. The older `360dfea` commit is only the first hybrid retrieval anchor, and later `main` also includes the typed update/distill/promote surfaces, promotion safety layer, and app-server stack-pressure fix. @@ -158,6 +160,6 @@ This file is intentionally disposable. ## Open Questions - How much more can move into `epiphany-core` without sacrificing the typed Codex host seam that makes the integration first-class? -- How should verified observations propose map/churn edits without turning promotion into automatic graph fanfic? +- How should verified evidence-backed observations be prioritized automatically without turning proposal into automatic graph fanfic? Do not promote anything from here into the map unless it survives verification or repeated reuse without contradiction.