Move distill propose route policy into bridge

This commit is contained in:
Meta Vens 2026-06-09 05:50:12 +01:00
commit 0503ca1c5c
6 changed files with 80 additions and 47 deletions

View file

@ -35,8 +35,11 @@ use crate::coordinator_protocol::protocol_crrc_recommendation;
use crate::coordinator_protocol::protocol_role_board_lanes;
use crate::cultnet::EpiphanyFreshnessSurface;
use crate::cultnet::EpiphanySurfaceSource;
use crate::error::EpiphanyBridgeError;
use crate::error::Result as BridgeResult;
use crate::jobs::map_epiphany_jobs;
use crate::pressure::derive_epiphany_pressure;
use crate::protocol_edge::protocol_distill_params_to_core;
use crate::protocol_edge::protocol_job_from_surface;
use crate::protocol_edge::protocol_pressure_from_core;
use crate::protocol_edge::protocol_reorient_decision;
@ -570,6 +573,19 @@ pub fn map_epiphany_distill_response(
})
}
pub fn map_thread_epiphany_distill_response(
params: ThreadEpiphanyDistillParams,
state: Option<&EpiphanyThreadState>,
) -> BridgeResult<ThreadEpiphanyDistillResponse> {
let expected_revision = state.map(|state| state.revision).unwrap_or(0);
let input = protocol_distill_params_to_core(params);
map_epiphany_distill_response(expected_revision, input).map_err(|err| {
EpiphanyBridgeError::InvalidRequest(format!(
"failed to distill Epiphany observation: {err}"
))
})
}
pub fn map_epiphany_propose_response(
state: EpiphanyThreadState,
observation_ids: Vec<String>,
@ -593,3 +609,18 @@ pub fn map_epiphany_propose_response(
},
})
}
pub fn map_thread_epiphany_propose_response(
params: ThreadEpiphanyProposeParams,
state: Option<EpiphanyThreadState>,
) -> BridgeResult<ThreadEpiphanyProposeResponse> {
let thread_id = params.thread_id.clone();
let Some(state) = state else {
return Err(EpiphanyBridgeError::InvalidRequest(format!(
"thread has no Epiphany state: {thread_id}"
)));
};
map_epiphany_propose_response(state, params.observation_ids).map_err(|err| {
EpiphanyBridgeError::InvalidRequest(format!("failed to propose Epiphany map update: {err}"))
})
}

View file

@ -752,9 +752,16 @@ The app-server handler:
1. parses the thread id.
2. requires the thread to be loaded.
3. reads the current Epiphany revision, defaulting to `0` if the thread has not yet created state.
4. calls `distill_observation`.
5. returns a `ThreadEpiphanyUpdatePatch` containing one observation and one evidence record.
3. reads the current Epiphany state as a host fact.
4. calls `epiphany-codex-bridge::view_protocol::map_thread_epiphany_distill_response`.
5. maps bridge invalid-request errors to JSON-RPC errors.
`map_thread_epiphany_distill_response`:
1. chooses `expectedRevision` from the supplied Epiphany state, defaulting to `0` if the thread has not yet created state.
2. converts the Codex protocol params into the core distillation input.
3. calls `distill_observation`.
4. returns a `ThreadEpiphanyUpdatePatch` containing one observation and one evidence record.
`distill_observation`:
@ -768,9 +775,8 @@ The app-server handler:
Code refs:
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4173)
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4202)
- [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4225)
- [epiphany_read_routes.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs:479)
- [view_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/view_protocol.rs:576)
- [epiphany-core/src/distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28)
### Output
@ -794,7 +800,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, chooses a small proposal-ready set when `observationIds` is omitted, requires selected 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. When ids are omitted, proposal now ranks proposal-ready observations by source/evidence strength, code refs, and current map focus, then keeps a bounded coherent path cluster instead of grabbing every shiny pebble in the yard. It then 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, and 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 judges the map delta by match kind: exact-ref reuse is a narrow refinement, same-path reuse is broadening, deterministic-id reuse preserves a known candidate, semantic reuse anchors previously unanchored prose, and new nodes expand the map. Diff pressure now reflects that judgment, 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. When ids are omitted, proposal now ranks proposal-ready observations by source/evidence strength, code refs, and current map focus, then keeps a bounded coherent path cluster instead of grabbing every shiny pebble in the yard. It then 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, and 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 judges the map delta by match kind: exact-ref reuse is a narrow refinement, same-path reuse is broadening, deterministic-id reuse preserves a known candidate, semantic reuse anchors previously unanchored prose, and new nodes expand the map. Diff pressure now reflects that judgment, touched paths, observation count, and unresolved write risk instead of blindly inheriting the previous pressure. It is a candidate, not an oracle.
Protocol shape:
@ -823,10 +829,16 @@ The app-server handler:
1. parses the thread id.
2. requires the thread to be loaded.
3. requires existing Epiphany state.
4. reads the current state revision as `expectedRevision`.
5. calls `propose_map_update`.
6. returns a `ThreadEpiphanyUpdatePatch` containing proposal observation/evidence, replacement graphs/frontier, and churn.
3. reads the current Epiphany state as a host fact.
4. calls `epiphany-codex-bridge::view_protocol::map_thread_epiphany_propose_response`.
5. maps bridge invalid-request errors to JSON-RPC errors.
`map_thread_epiphany_propose_response`:
1. requires existing Epiphany state.
2. reads the current state revision as `expectedRevision`.
3. calls `propose_map_update`.
4. returns a `ThreadEpiphanyUpdatePatch` containing proposal observation/evidence, replacement graphs/frontier, and churn.
`propose_map_update`:
@ -853,9 +865,8 @@ The app-server handler:
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_read_routes.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs:528)
- [view_protocol.rs](E:/Projects/EpiphanyAgent/epiphany-codex-bridge/src/view_protocol.rs:613)
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115)
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:244)
- [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:271)

View file

@ -840,7 +840,7 @@ loading and watcher registration because those are host lifecycle seams.
propose patch-response builders have followed into the same bridge view module;
app-server still checks that the target thread is loaded and maps errors onto
JSON-RPC responses, but it no longer shapes those patches itself.
`epiphany_read_routes.rs` is about 579 lines after this cut.
`epiphany_read_routes.rs` is about 570 lines after this latest cut.
The retrieve read route has now lost its local normalization/projection opinion
too. `epiphany-codex-bridge::retrieve_protocol::retrieve_thread_epiphany_for_paths`
@ -849,6 +849,13 @@ retrieve response projection. App-server keeps thread-id parsing, loaded-thread
lookup, config/Codex-home fact supply, response emission, and bridge-error to
JSON-RPC mapping.
Distill/propose route response policy is bridge-owned too.
`map_thread_epiphany_distill_response` and
`map_thread_epiphany_propose_response` own expected-revision selection,
missing-state rejection, protocol-to-core input conversion, candidate patch
construction, and invalid-request text. App-server supplies the loaded thread
state as a host fact and maps bridge errors to JSON-RPC responses.
Also: MCP itself is allowed to be JSON. The target is not "replace MCP JSON";
the target is an Epiphany-owned boundary that speaks typed Epiphany
intent/result/receipt documents internally and normal MCP JSON-RPC externally.

Binary file not shown.

View file

@ -81,7 +81,7 @@ active_subgoals:
- The GameCult integrated investment dossier at `E:\Projects\gamecult-site\docs\gamecult_integrated_dossier.tex` raises the Mom-ready bar from local demo to diligence-ready loop. Epiphany must be ready to prove Bifrost-first external work: a human/agent work item or topic originates in Bifrost, Epiphany decomposes and executes through bounded roles, maintainers review artifacts, Bifrost records receipts/credit, and operator-safe public evidence can be shown without exporting private worker/operator/agent context. The concrete readiness asks are fresh-repo Epiphany demo, Bifrost work item/topic/agent transport/receipt/ledger demo, compute spend and accepted-artifact metrics, and a security model for secrets, write permissions, and public/private exports. Discord, VoidBot, repo Personas, and ad hoc scripts must not become shadow governance or reward authorities.
- Use `notes/epiphany-investor-readiness-roadmap.md` as this Codex operator's investor-scale todo rail and as the optional Epiphany Imagination sub-agent planning rail. Angel capital is fuel for compute, review capacity, legal diligence, infrastructure, and public proof, not a governance throne. The operator roadmap is now: make the local Mom MVP a repeatable proof bundle; build the Bifrost-first external-work bridge; add model-spend/review-load/accepted-artifact accounting; prove fresh-repo operation without supervisor contamination; and keep public/operator-safe exports sealed against private worker/operator/agent context. Imagination should translate world-scale Colossus pressure into Bifrost-first Objective Drafts with consent, maintainer review, receipts/ledger evidence, compute/output metrics, public/private export safety, and bounded proof artifacts.
- The May 2026 teardown/rebuild migration is archived under `notes/archive/epiphany-ideal-architecture-rebuild-plan.md` unless a concrete regression reopens it. Preserve the new foundation: Epiphany view/coordinator policy lives in `epiphany-core`, runtime-spine owns job/result lifecycle identity through `runtime_links`, duplicate acceptance is receipt-keyed, public findings stay typed/sealed, view expansion requires a core contract, and app-server remains routing/adaptation rather than a second policy throne.
- Treat Phase 5 update/distill/propose/promote/read/notification as complete enough unless a concrete regression appears.
- Treat Phase 5 update/distill/propose/promote/read/notification as complete enough unless a concrete regression appears; distill/propose route-level response construction now lives in the bridge, with app-server supplying loaded-thread state as host fact and mapping bridge errors to JSON-RPC.
- Preserve read-only boundaries for retrieve, distill, propose, freshness, context, graphQuery, roleResult, reorientResult, and the canonical view lenses for scene, jobs, roles, planning, pressure, reorient, crrc, and coordinator.
- Use `notes/epiphany-memory-graph-unified-plan.md` as the source-grounded memory architecture direction. The repo fractal dataflow graph and agent memory are one `EpiphanyMemoryGraph` substrate with repo, role, short-term, incubation, agency, candidate-intervention, and evidence profiles. Qdrant stores rebuildable semantic embeddings for typed graph documents; it is never canonical memory. The first live skeleton now exists in `epiphany-state-model` plus `epiphany-core::memory_graph`: typed graph documents, stable ids, profile/lifecycle validation, freshness propagation, context cuts, a CultCache store seam, and the native `epiphany-memory-graph` status/validate/context/smoke CLI. Do not add repo scanners, sleep runners, Qdrant cache plumbing, CultNet advertisement, or prompt integration before routing them through this shared substrate.
- Keep the landed Phase 6 coordinator, limited safe-boundary CRRC automation, and pre-compaction checkpoint intervention ready for the next bridge-equipped dogfood run.

View file

@ -13,7 +13,6 @@ use epiphany_codex_bridge::protocol_edge::core_epiphany_view_needs_reorientation
use epiphany_codex_bridge::protocol_edge::core_epiphany_view_needs_runtime_store;
use epiphany_codex_bridge::protocol_edge::default_core_epiphany_view_lenses;
use epiphany_codex_bridge::protocol_edge::protocol_context_params_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_distill_params_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_freshness_response_from_surface;
use epiphany_codex_bridge::protocol_edge::protocol_graph_query_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_role_id_to_core;
@ -26,12 +25,12 @@ use epiphany_codex_bridge::view_protocol::EpiphanyRoleResultResponseInput;
use epiphany_codex_bridge::view_protocol::EpiphanyViewResponseInput;
use epiphany_codex_bridge::view_protocol::derive_epiphany_freshness_surface;
use epiphany_codex_bridge::view_protocol::map_epiphany_context_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_distill_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_graph_query_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_propose_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_reorient_result_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_role_result_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_view_response;
use epiphany_codex_bridge::view_protocol::map_thread_epiphany_distill_response;
use epiphany_codex_bridge::view_protocol::map_thread_epiphany_propose_response;
use super::CodexMessageProcessor;
use super::ConnectionRequestId;
@ -502,20 +501,15 @@ impl CodexMessageProcessor {
}
};
let expected_revision = thread
.epiphany_state()
.await
.map(|state| state.revision)
.unwrap_or(0);
let input = protocol_distill_params_to_core(params);
let response = match map_epiphany_distill_response(expected_revision, input) {
let state = thread.epiphany_state().await;
let response = match map_thread_epiphany_distill_response(params, state.as_ref()) {
Ok(response) => response,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_invalid_request_error(
request_id,
format!("failed to distill Epiphany observation: {err}"),
)
.await;
self.send_internal_error(request_id, err.to_string()).await;
return;
}
};
@ -552,25 +546,15 @@ impl CodexMessageProcessor {
}
};
let state = match thread.epiphany_state().await {
Some(state) => state,
None => {
self.send_invalid_request_error(
request_id,
format!("thread has no Epiphany state: {thread_uuid}"),
)
.await;
let state = thread.epiphany_state().await;
let response = match map_thread_epiphany_propose_response(params, state) {
Ok(response) => response,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
};
let response = match map_epiphany_propose_response(state, params.observation_ids) {
Ok(response) => response,
Err(err) => {
self.send_invalid_request_error(
request_id,
format!("failed to propose Epiphany map update: {err}"),
)
.await;
self.send_internal_error(request_id, err.to_string()).await;
return;
}
};