From e51b74c54cd623dd12c64fa2be6f80153f06f084 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 25 Apr 2026 03:31:52 +0100 Subject: [PATCH] Harden Epiphany graph-semantic proposals --- epiphany-core/src/proposal.rs | 407 ++++++++++++++++++++- notes/epiphany-current-algorithmic-map.md | 45 +-- notes/epiphany-fork-implementation-plan.md | 9 +- notes/fresh-workspace-handoff.md | 10 +- state/evidence.jsonl | 2 + state/map.yaml | 7 +- state/scratch.md | 6 +- 7 files changed, 439 insertions(+), 47 deletions(-) diff --git a/epiphany-core/src/proposal.rs b/epiphany-core/src/proposal.rs index c0f41063..69c1c658 100644 --- a/epiphany-core/src/proposal.rs +++ b/epiphany-core/src/proposal.rs @@ -16,12 +16,31 @@ use std::path::Path; use std::path::PathBuf; const SUMMARY_LIMIT: usize = 220; +const SEMANTIC_REUSE_MIN_SCORE: usize = 4; #[derive(Debug, Clone, PartialEq, Eq)] struct SelectionQuality { observation_count: usize, evidence_count: usize, source_kinds: Vec, + priority_label: String, + primary_observation_id: String, + primary_observation_summary: String, + semantic_terms: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ArchitectureMatchKind { + ExactCodeRef, + SamePath, + DeterministicId, + Semantic, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ArchitectureMatch { + index: usize, + kind: ArchitectureMatchKind, } #[derive(Debug, Clone)] @@ -50,17 +69,25 @@ pub fn propose_map_update(input: EpiphanyMapProposalInput) -> Result Result Result Result Result= 8 { + strong_signal_count += 1; + } + if primary_observation_id.is_empty() || observation_score > primary_score { + primary_observation_id = observation.id.clone(); + primary_observation_summary = observation.summary.clone(); + primary_score = observation_score; + } } Ok(SelectionQuality { observation_count: observations.len(), evidence_count: evidence_ids.len(), source_kinds, + priority_label: selection_priority_label(strong_signal_count, observations.len()), + primary_observation_id, + primary_observation_summary, + semantic_terms, }) } +fn observation_priority_score(observation: &EpiphanyObservation) -> usize { + signal_kind_weight(&observation.source_kind) + + observation.code_refs.len().min(3) + + usize::from(!observation.summary.trim().is_empty()) +} + +fn evidence_priority_score(evidence: &EpiphanyEvidenceRecord) -> usize { + signal_kind_weight(&evidence.kind) + + evidence.code_refs.len().min(2) + + usize::from(!evidence.summary.trim().is_empty()) +} + +fn signal_kind_weight(value: &str) -> usize { + let lower = value.trim().to_ascii_lowercase(); + if lower.contains("verification") || lower.contains("verifier") { + 4 + } else if lower.contains("test") || lower.contains("smoke") { + 3 + } else if lower.contains("tool") || lower.contains("review") { + 2 + } else if lower.contains("observation") { + 1 + } else { + 0 + } +} + +fn selection_priority_label(strong_signal_count: usize, observation_count: usize) -> String { + if strong_signal_count == observation_count { + "high-priority verified selection".to_string() + } else if strong_signal_count > 0 { + "mixed-priority verified selection".to_string() + } else { + "low-priority accepted selection".to_string() + } +} + fn collect_code_refs(observations: &[&EpiphanyObservation]) -> Result> { let mut code_refs = Vec::new(); let mut seen = HashSet::new(); @@ -306,18 +421,27 @@ fn find_architecture_node_for_path( path_code_refs: &[EpiphanyCodeRef], path: &Path, candidate_node_id: &str, -) -> Option { + semantic_terms: &[String], +) -> Option { graphs .architecture .nodes .iter() .position(|node| has_exact_code_ref_overlap(&node.code_refs, path_code_refs)) + .map(|index| ArchitectureMatch { + index, + kind: ArchitectureMatchKind::ExactCodeRef, + }) .or_else(|| { graphs .architecture .nodes .iter() .position(|node| node.code_refs.iter().any(|code_ref| code_ref.path == path)) + .map(|index| ArchitectureMatch { + index, + kind: ArchitectureMatchKind::SamePath, + }) }) .or_else(|| { graphs @@ -325,6 +449,16 @@ fn find_architecture_node_for_path( .nodes .iter() .position(|node| node.id == candidate_node_id) + .map(|index| ArchitectureMatch { + index, + kind: ArchitectureMatchKind::DeterministicId, + }) + }) + .or_else(|| { + find_semantic_architecture_node(graphs, semantic_terms).map(|index| ArchitectureMatch { + index, + kind: ArchitectureMatchKind::Semantic, + }) }) } @@ -342,6 +476,67 @@ fn has_exact_code_ref_overlap( .any(|key| existing_keys.contains(&key)) } +fn find_semantic_architecture_node( + graphs: &EpiphanyGraphs, + semantic_terms: &[String], +) -> Option { + if semantic_terms.is_empty() { + return None; + } + + let mut best_index = None; + let mut best_score = 0usize; + let mut tied = false; + for (index, node) in graphs.architecture.nodes.iter().enumerate() { + let score = semantic_node_score(node, semantic_terms); + if !semantic_score_can_reuse(node, score) { + continue; + } + if score > best_score { + best_index = Some(index); + best_score = score; + tied = false; + } else if score == best_score { + tied = true; + } + } + + if tied { None } else { best_index } +} + +fn semantic_score_can_reuse(node: &EpiphanyGraphNode, score: usize) -> bool { + if score < SEMANTIC_REUSE_MIN_SCORE { + return false; + } + // Once a graph node is anchored to code, keep reuse on concrete ref/path/id checks. + // Semantic matching is only a rescue path for useful map prose that has no refs yet. + node.code_refs.is_empty() +} + +fn semantic_node_score(node: &EpiphanyGraphNode, semantic_terms: &[String]) -> usize { + let mut node_terms = Vec::new(); + let mut seen = HashSet::new(); + push_semantic_terms(&mut node_terms, &mut seen, &node.id); + push_semantic_terms(&mut node_terms, &mut seen, &node.title); + push_semantic_terms(&mut node_terms, &mut seen, &node.purpose); + if let Some(mechanism) = node.mechanism.as_deref() { + push_semantic_terms(&mut node_terms, &mut seen, mechanism); + } + if let Some(metaphor) = node.metaphor.as_deref() { + push_semantic_terms(&mut node_terms, &mut seen, metaphor); + } + if let Some(status) = node.status.as_deref() { + push_semantic_terms(&mut node_terms, &mut seen, status); + } + push_code_ref_semantic_terms(&mut node_terms, &mut seen, &node.code_refs); + + let node_terms = node_terms.into_iter().collect::>(); + semantic_terms + .iter() + .filter(|term| node_terms.contains(term.as_str())) + .count() +} + fn merge_code_refs(target: &mut Vec, additions: Vec) { let mut seen = target.iter().map(code_ref_key).collect::>(); for addition in additions { @@ -414,6 +609,17 @@ where } } +fn unique_strings(values: Vec) -> Vec { + let mut unique = Vec::new(); + let mut seen = HashSet::new(); + for value in values { + if seen.insert(value.clone()) { + unique.push(value); + } + } + unique +} + fn graph_node_id(path: &Path) -> String { let mut hasher = Sha1::new(); hasher.update(path.to_string_lossy().as_bytes()); @@ -463,6 +669,72 @@ fn code_ref_key(code_ref: &EpiphanyCodeRef) -> String { ) } +fn push_code_ref_semantic_terms( + target: &mut Vec, + seen: &mut HashSet, + code_refs: &[EpiphanyCodeRef], +) { + for code_ref in code_refs { + push_path_semantic_terms(target, seen, &code_ref.path); + if let Some(symbol) = code_ref.symbol.as_deref() { + push_semantic_terms(target, seen, symbol); + } + if let Some(note) = code_ref.note.as_deref() { + push_semantic_terms(target, seen, note); + } + } +} + +fn push_path_semantic_terms(target: &mut Vec, seen: &mut HashSet, path: &Path) { + if let Some(file_stem) = path.file_stem().and_then(|value| value.to_str()) { + push_semantic_terms(target, seen, file_stem); + } + if let Some(parent) = path.parent().and_then(|value| value.to_str()) { + push_semantic_terms(target, seen, parent); + } +} + +fn push_semantic_terms(target: &mut Vec, seen: &mut HashSet, value: &str) { + for term in semantic_terms(value) { + if seen.insert(term.clone()) { + target.push(term); + } + } +} + +fn semantic_terms(value: &str) -> Vec { + value + .split(|character: char| !character.is_ascii_alphanumeric()) + .map(str::trim) + .filter(|term| term.len() >= 3) + .map(str::to_ascii_lowercase) + .filter(|term| !is_semantic_stopword(term)) + .collect() +} + +fn is_semantic_stopword(value: &str) -> bool { + matches!( + value, + "and" + | "are" + | "but" + | "can" + | "for" + | "from" + | "has" + | "into" + | "not" + | "now" + | "one" + | "out" + | "the" + | "this" + | "that" + | "through" + | "with" + ) +} + fn truncate_chars(value: &str, limit: usize) -> String { if value.chars().count() <= limit { return value.to_string(); @@ -550,10 +822,12 @@ fn selection_quality_summary(selection_quality: &SelectionQuality, path_count: u selection_quality.source_kinds.join("/") }; format!( - "{} evidence-backed observation(s), {} accepting evidence record(s), {} code path(s), source kind(s): {}", + "{} evidence-backed observation(s), {} accepting evidence record(s), {} code path(s), {}, primary observation: {}, source kind(s): {}", selection_quality.observation_count, selection_quality.evidence_count, path_count, + selection_quality.priority_label, + selection_quality.primary_observation_id, source_kinds ) } @@ -702,6 +976,115 @@ mod tests { assert_eq!(proposal.churn.understanding_status, "proposal_refines_map"); } + #[test] + fn propose_map_update_reuses_strong_semantic_graph_match_without_refs() { + let mut state = state_with_observation("verified"); + state.graphs.architecture.nodes.push(EpiphanyGraphNode { + id: "prompt-renderer".to_string(), + title: "Prompt renderer".to_string(), + purpose: "Inject Epiphany state into developer context".to_string(), + mechanism: Some("Renders typed state as a bounded prompt fragment".to_string()), + ..Default::default() + }); + + let proposal = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-verified".to_string()], + }) + .expect("proposal"); + + assert_eq!(proposal.graphs.architecture.nodes.len(), 1); + assert_eq!(proposal.graphs.architecture.nodes[0].id, "prompt-renderer"); + assert_eq!(proposal.graphs.architecture.nodes[0].code_refs.len(), 1); + assert_eq!( + proposal.graph_frontier.active_node_ids, + vec!["prompt-renderer".to_string()] + ); + assert!( + proposal + .churn + .warning + .as_deref() + .unwrap_or_default() + .contains("1 by semantic graph signal") + ); + } + + #[test] + fn propose_map_update_refuses_ambiguous_semantic_graph_match() { + let mut state = state_with_observation("verified"); + for node_id in ["prompt-renderer-a", "prompt-renderer-b"] { + state.graphs.architecture.nodes.push(EpiphanyGraphNode { + id: node_id.to_string(), + title: "Prompt renderer".to_string(), + purpose: "Inject Epiphany state into developer context".to_string(), + mechanism: Some("Renders typed state as a bounded prompt fragment".to_string()), + ..Default::default() + }); + } + + let proposal = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-verified".to_string()], + }) + .expect("proposal"); + + assert_eq!(proposal.graphs.architecture.nodes.len(), 3); + assert!( + proposal + .graph_frontier + .active_node_ids + .iter() + .any(|node_id| node_id.starts_with("arch-path-")) + ); + assert_eq!(proposal.churn.understanding_status, "proposal_expands_map"); + } + + #[test] + fn propose_map_update_prioritizes_stronger_selected_observation() { + let mut state = state_with_observation("ok"); + state.observations[0].id = "obs-weak".to_string(); + state.observations[0].source_kind = "observation".to_string(); + state.observations[0].summary = "Prompt renderer might be involved".to_string(); + state.observations[0].evidence_ids = vec!["ev-weak".to_string()]; + state.recent_evidence[0].id = "ev-weak".to_string(); + state.recent_evidence[0].kind = "observation".to_string(); + state.recent_evidence[0].summary = "Manual note mentioned prompt renderer".to_string(); + state.observations.push(EpiphanyObservation { + id: "obs-strong".to_string(), + summary: "Live smoke verified prompt renderer Epiphany state injection".to_string(), + source_kind: "smoke".to_string(), + status: "ok".to_string(), + code_refs: vec![code_ref("epiphany-core/src/prompt.rs")], + evidence_ids: vec!["ev-strong".to_string()], + }); + state.recent_evidence.push(EpiphanyEvidenceRecord { + id: "ev-strong".to_string(), + kind: "verification".to_string(), + status: "ok".to_string(), + summary: "Verifier accepted prompt renderer state injection".to_string(), + code_refs: vec![code_ref("epiphany-core/src/prompt.rs")], + }); + + let proposal = propose_map_update(EpiphanyMapProposalInput { + state, + observation_ids: vec!["obs-weak".to_string(), "obs-strong".to_string()], + }) + .expect("proposal"); + + assert!( + proposal.graphs.architecture.nodes[0] + .purpose + .contains("Live smoke verified") + ); + assert!( + proposal + .observation + .summary + .contains("primary observation: obs-strong") + ); + } + #[test] fn propose_map_update_focuses_linked_nodes_and_incident_edges() { let mut state = state_with_observation("verified"); diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index f50c7111..81080c71 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 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 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 prioritize the strongest selected observation, focus or extend architecture graph nodes, rescue unanchored graph nodes through strict semantic overlap, 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:61). - 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, evidence-backed observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:42). +- deterministic map/churn proposal from verified, evidence-backed observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:61). - verifier-backed promotion policy lives in [epiphany-core/src/promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33). ## Whole Control Flow @@ -683,7 +683,7 @@ A client sends experimental `thread/epiphany/propose` for a loaded thread with: 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 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. +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, scores the selected observations so the strongest verifier/test/smoke-backed signal becomes the proposal's primary summary, then tries to match observed code refs against existing architecture nodes. Concrete matching still wins: exact code refs, same-path refs, and deterministic path-node ids are tried before anything semantic. Only after those fail can strict semantic overlap reuse an existing architecture node, and only when that graph node has no code refs yet; anchored nodes stay governed by concrete refs because otherwise the map starts doing interpretive dance in a trench coat. Proposal then enriches matching nodes with 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: @@ -704,7 +704,7 @@ Code refs: - [app-server-protocol/common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350) - [app-server-protocol/v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:3963) -- [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:61) ### Mechanism @@ -724,26 +724,29 @@ The app-server handler: 3. rejects observations whose status is not accepting: `ok`, `accepted`, `verified`, `pass`, or `passed`. 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. +6. scores selected observations and evidence by source kind, evidence kind, code refs, and summary presence so proposal wording is anchored to the strongest selected signal instead of raw id order. +7. builds semantic terms from selected observations, accepting evidence, code-ref paths, symbols, and notes. +8. clones the current graphs and searches architecture nodes for exact code-ref overlap, same-path overlap, then the deterministic path-node id. +9. if no concrete match exists, searches unanchored architecture nodes for a unique strong semantic match and refuses tied semantic matches. +10. enriches matching architecture nodes with newly observed refs, preserving their existing title/purpose/status. +11. creates path-derived candidate architecture nodes only when no existing node matches that code path or unanchored graph language. +12. expands focused node ids through graph links so architecture nodes pull their linked dataflow nodes into the frontier, and vice versa. +13. marks named graph edges incident to focused nodes as active. +14. merges focused node ids, active edge ids, and observed paths into the graph frontier. +15. emits a proposal observation/evidence pair tied to the candidate patch, including evidence-backed selection counts, priority label, primary observation id, and reused/created node counts. +16. 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. +17. 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: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) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:61) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:228) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:419) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:479) +- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:770) ### Output @@ -855,7 +858,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, 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. +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, prioritizes stronger selected observations for proposal wording, reuses existing architecture nodes by concrete code-ref/path/id checks before creating new path nodes, can rescue unanchored graph nodes through strict unique semantic overlap, follows graph links and incident edges into the frontier, and reports churn pressure from the actual proposal shape. The missing organ is broader judgment: automatic observation selection beyond caller-supplied ids, watcher/freshness inputs, and eventually role-scoped specialist ownership without silently auto-promoting anything. ```text model/tool observations @@ -866,9 +869,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 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. +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, prioritizes stronger selected observations, rescues unanchored graph nodes by strict semantic overlap, 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 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. +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, favor the strongest selected observation when drafting that candidate, 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 choose the observation set, 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 88922f4b..ca705af1 100644 --- a/notes/epiphany-fork-implementation-plan.md +++ b/notes/epiphany-fork-implementation-plan.md @@ -19,9 +19,10 @@ This note tracks Epiphany as a fork of Codex with an opinionated modeling archit - 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 +- a graph-semantic proposal hardening slice that scores selected observations/evidence, uses the strongest selected signal for proposal wording, and can reuse unanchored architecture graph nodes through strict unique semantic overlap while refusing ambiguous matches and leaving code-anchored nodes under concrete ref/path/id matching - 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, 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. +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, make selected observations evidence-backed with non-haunted churn pressure, or rescue unanchored graph nodes with strict semantic matching. 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 @@ -271,7 +272,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, 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?" +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, map-delta churn pressure, selected-observation prioritization, and strict unanchored-node semantic reuse 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?", "can it reject selected observations that are not backed by accepting recent evidence?", or "can it use graph language when no concrete refs exist yet?" It can. The next gap is "can Epiphany choose good observation sets and richer map deltas from tool/model output 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 @@ -279,8 +280,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, graph-link, accepting-evidence, and map-delta-pressure checks - - better observation selection beyond explicit ids and evidence links + - better proposal heuristics beyond the current exact-code-ref, same-path, deterministic-id, graph-link, accepting-evidence, semantic-unanchored-node, selected-priority, and map-delta-pressure checks + - better observation set selection beyond caller-supplied 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 b107692e..b8caef9f 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, 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 +- 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, strict semantic reuse for unanchored architecture nodes, linked frontier focus through graph links and named incident edges, evidence-backed selected observations via accepting `recent_evidence`, selected-observation prioritization, 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 @@ -356,7 +356,7 @@ If a future session wakes up from compaction and starts bluffing, this is the pa - 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` - 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 + - the next bounded follow-up is richer observation-set selection and map-delta judgment beyond caller-supplied ids/evidence/path/link/semantic-unanchored 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`: @@ -451,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, linked frontier focus, evidence-backed proposal selection, and map-delta churn pressure 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, selected-observation prioritization, strict unanchored-node semantic reuse, 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, 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. +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, prioritizes stronger selected observations for proposal wording, can reuse unanchored architecture nodes through strict semantic overlap, and reports churn pressure from the proposal shape. The next clean move is automatic observation-set selection and richer map-delta judgment, not pretending the already-shipped slices still need ceremony. Current Phase 5 implementation move: @@ -462,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/evidence links, plus richer observation prioritization than explicit id selection + - better observation-set selection beyond caller-supplied ids/evidence links, plus richer map-delta judgment than the current exact refs/same path/deterministic ids/graph links/strict unanchored semantic matching - 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 15700c5b..5afd7ccc 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -109,3 +109,5 @@ {"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."} +{"ts": "2026-04-25T02:31:31+00:00", "type": "implementation", "status": "ok", "note": "Hardened read-only thread/epiphany/propose graph semantics in epiphany-core. propose_map_update now scores selected observations/evidence, uses the strongest selected verifier/test/smoke-backed signal for candidate wording, reuses unanchored architecture nodes through strict unique semantic overlap, refuses ambiguous semantic matches, and keeps code-anchored nodes governed by concrete exact-ref/same-path/deterministic-id matching."} +{"ts": "2026-04-25T02:31:35+00:00", "type": "verification", "status": "ok", "note": "Verified graph-semantic proposal hardening with cargo fmt --manifest-path .\\epiphany-core\\Cargo.toml, cargo test --manifest-path .\\epiphany-core\\Cargo.toml proposal (13 tests), full cargo test --manifest-path .\\epiphany-core\\Cargo.toml (31 tests), and CARGO_TARGET_DIR=C:\\Users\\Meta\\.cargo-target-codex cargo test -p codex-core --lib proposal."} diff --git a/state/map.yaml b/state/map.yaml index fd24ba3f..ea523f7c 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, linked frontier focus, evidence-backed selection, and map-delta churn pressure while keeping verifier-backed acceptance/rejection explicit. + - Harden the next layer above the landed typed distill/propose/promote/update paths: richer observation-set selection and map-delta judgment beyond graph-node reuse, linked frontier focus, evidence-backed selection, selected-observation prioritization, strict unanchored-node semantic reuse, and map-delta churn pressure while keeping verifier-backed acceptance/rejection explicit. accepted_design: - external typed state files @@ -100,6 +100,7 @@ accepted_design: - 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 + - harden proposal graph semantics incrementally: `thread/epiphany/propose` should score selected observations/evidence, use the strongest selected signal for candidate wording, and reuse unanchored architecture graph nodes only through strict unique semantic overlap while refusing ambiguous semantic matches and keeping code-anchored nodes under concrete ref/path/id matching rejected_paths: - name: pinned_map_only @@ -116,5 +117,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, 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. + 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, selected-observation prioritization, strict unanchored-node semantic reuse, 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 observation-set selection and map-delta judgment beyond current exact-ref/same-path/deterministic-id/graph-link/recent-evidence/semantic-unanchored-node matching, selected-observation prioritization, and map-delta churn pressure, 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 0896a4eb..109d29a9 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, linked frontier focus, evidence-backed selection, and map-delta churn pressure, without hidden writes. +- Continue Phase 5 proposal-quality hardening beyond graph-node reuse, linked frontier focus, evidence-backed selection, selected-observation prioritization, strict unanchored-node semantic reuse, and map-delta churn pressure, without hidden writes. ## Working Notes @@ -16,6 +16,8 @@ This file is intentionally disposable. - 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 evidence-backed selection counts plus reused/created node counts, and marks churn as `proposal_refines_map`, `proposal_expands_map`, or `proposal_updates_map` + - proposal now scores selected observations/evidence so candidate wording uses the strongest selected verifier/test/smoke-backed signal instead of raw id order + - proposal can reuse an unanchored architecture graph node through strict unique semantic overlap, but refuses tied semantic matches and leaves code-anchored nodes under concrete exact/path/deterministic-id matching - 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 @@ -160,6 +162,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 evidence-backed observations be prioritized automatically without turning proposal into automatic graph fanfic? +- How should Epiphany choose the observation set 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.