From e6d6e4f6999f7f41bdb65beeba3b4718f6817bae Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 7 Aug 2026 00:33:38 +0100 Subject: [PATCH] Stop powerless Modeling retries --- .../src/bin/epiphany-frontier-proposal.rs | 164 ++++++++++++++++++ .../src/bin/epiphany-mvp-coordinator.rs | 4 +- epiphany-core/src/packaged_release.rs | 2 + epiphany-core/src/surfaces/coordinator.rs | 25 ++- 4 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 epiphany-core/src/bin/epiphany-frontier-proposal.rs diff --git a/epiphany-core/src/bin/epiphany-frontier-proposal.rs b/epiphany-core/src/bin/epiphany-frontier-proposal.rs new file mode 100644 index 00000000..069308dc --- /dev/null +++ b/epiphany-core/src/bin/epiphany-frontier-proposal.rs @@ -0,0 +1,164 @@ +use anyhow::{Context, Result, anyhow, bail}; +use epiphany_core::{EpiphanyCoordinatorService, RepoFrontierUserProposalInput}; +use serde_json::json; +use std::{collections::BTreeMap, path::PathBuf}; + +fn main() -> Result<()> { + let args = Args::parse(std::env::args().skip(1))?; + let service = EpiphanyCoordinatorService::new(&args.store); + let proposal = service.intake_user_repo_frontier_proposal(RepoFrontierUserProposalInput { + proposal_id: args.required("--proposal-id")?, + source_actor: args.required("--source-actor")?, + source_ref: args.required("--source-ref")?, + repository: args.required("--repository")?, + workspace: args.required("--workspace")?, + thread_id: args.required("--thread-id")?, + runtime_id: args.required("--runtime-id")?, + title: args.required("--title")?, + body: args.required("--body")?, + desired_outcome: args.required("--desired-outcome")?, + constraints: args.constraints.clone(), + scope_hints: args.scope_hints.clone(), + evidence_refs: args.evidence_refs.clone(), + proposed_at: args.required("--proposed-at")?, + private_state_included: false, + })?; + let selection = service.select_repo_frontier_proposal_for_modeling( + &proposal.proposal_id, + &args.required("--selected-at")?, + )?; + println!( + "{}", + serde_json::to_string(&json!({ + "schemaVersion": selection.schema_version, + "status": "selected-for-modeling", + "proposalId": proposal.proposal_id, + "proposalPayloadSha256": proposal.payload_sha256, + "proposalModelingRequestId": selection.request_id, + "runtimeId": selection.runtime_id, + "threadId": selection.thread_id, + "repository": selection.repository, + "workspace": selection.workspace, + "privateStateExposed": false, + }))? + ); + Ok(()) +} + +struct Args { + store: PathBuf, + fields: BTreeMap, + constraints: Vec, + scope_hints: Vec, + evidence_refs: Vec, +} + +impl Args { + fn parse(values: impl Iterator) -> Result { + let mut values = values; + let command = values + .next() + .context("missing command: expected intake-select")?; + if command != "intake-select" { + bail!("unknown command {command:?}; expected intake-select"); + } + let mut fields = BTreeMap::new(); + let mut constraints = Vec::new(); + let mut scope_hints = Vec::new(); + let mut evidence_refs = Vec::new(); + while let Some(flag) = values.next() { + let value = values + .next() + .with_context(|| format!("missing value for {flag}"))?; + match flag.as_str() { + "--constraint" => constraints.push(value), + "--scope-hint" => scope_hints.push(value), + "--evidence-ref" => evidence_refs.push(value), + "--store" | "--proposal-id" | "--source-actor" | "--source-ref" + | "--repository" | "--workspace" | "--thread-id" | "--runtime-id" | "--title" + | "--body" | "--desired-outcome" | "--proposed-at" | "--selected-at" => { + if fields.insert(flag.clone(), value).is_some() { + bail!("duplicate proposal argument {flag}"); + } + } + _ => bail!("unknown proposal argument {flag}"), + } + } + let store = PathBuf::from(fields.remove("--store").context("missing --store")?); + if !store.is_absolute() { + return Err(anyhow!("proposal store must be absolute")); + } + Ok(Self { + store, + fields, + constraints, + scope_hints, + evidence_refs, + }) + } + + fn required(&self, name: &str) -> Result { + self.fields + .get(name) + .filter(|value| !value.trim().is_empty()) + .cloned() + .ok_or_else(|| anyhow!("missing {name}")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn proposal_cli_keeps_repeated_typed_lists_and_forbids_private_state() { + let args = Args::parse( + [ + "intake-select", + "--store", + "C:/state/runtime.cc", + "--constraint", + "one", + "--constraint", + "two", + "--scope-hint", + "src/lib.rs", + "--evidence-ref", + "operator:request", + ] + .into_iter() + .map(str::to_string), + ) + .expect("typed proposal args"); + + assert_eq!(args.constraints, ["one", "two"]); + assert_eq!(args.scope_hints, ["src/lib.rs"]); + assert_eq!(args.evidence_refs, ["operator:request"]); + } + + #[test] + fn proposal_cli_rejects_relative_store_and_unknown_authority_flags() { + assert!( + Args::parse( + ["intake-select", "--store", "state/runtime.cc"] + .into_iter() + .map(str::to_string) + ) + .is_err() + ); + assert!( + Args::parse( + [ + "intake-select", + "--store", + "C:/state/runtime.cc", + "--grant-hands", + "yes", + ] + .into_iter() + .map(str::to_string) + ) + .is_err() + ); + } +} diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index b2142add..c759b426 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -507,7 +507,9 @@ fn run_coordinator(args: &Args) -> Result { steps.push(step); break; } - if is_stop_action(&action) && !args.auto_review && !is_result_review_action(&action) { + if action == "awaitFrontierProposal" + || (is_stop_action(&action) && !args.auto_review && !is_result_review_action(&action)) + { append_operator_step_jsonl(&steps_path, &step)?; steps.push(step); break; diff --git a/epiphany-core/src/packaged_release.rs b/epiphany-core/src/packaged_release.rs index fc5217c9..71c13ae9 100644 --- a/epiphany-core/src/packaged_release.rs +++ b/epiphany-core/src/packaged_release.rs @@ -117,6 +117,7 @@ pub fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static file_name("epiphany-persona-discord-permit-identity"), ), ("coordinator", file_name("epiphany-mvp-coordinator")), + ("frontier-proposal", file_name("epiphany-frontier-proposal")), ("model-runtime", file_name("epiphany-model-runtime")), ("tool-mcp-runtime", file_name("epiphany-tool-mcp-runtime")), ] @@ -425,6 +426,7 @@ fn required_release_build_target(role: &str) -> Result<(&'static str, &'static s Ok(("epiphany-core", "epiphany-persona-discord-permit-identity")) } "coordinator" => Ok(("epiphany-core", "epiphany-mvp-coordinator")), + "frontier-proposal" => Ok(("epiphany-core", "epiphany-frontier-proposal")), "model-runtime" => Ok(("epiphany-openai-runtime", "epiphany-model-runtime")), "tool-mcp-runtime" => Ok(("epiphany-tool-mcp-runtime", "epiphany-tool-mcp-runtime")), _ => bail!("unknown required release role {role}"), diff --git a/epiphany-core/src/surfaces/coordinator.rs b/epiphany-core/src/surfaces/coordinator.rs index d792dcd1..0d273f7e 100644 --- a/epiphany-core/src/surfaces/coordinator.rs +++ b/epiphany-core/src/surfaces/coordinator.rs @@ -66,6 +66,7 @@ pub enum EpiphanyCoordinatorAction { LaunchVerification, ReviewVerificationResult, ContinueImplementation, + AwaitFrontierProposal, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -1178,6 +1179,17 @@ pub fn recommend_coordinator_action( ); } + if input.modeling_result_accepted && !input.hands_frontier_ready { + return build( + EpiphanyCoordinatorAction::AwaitFrontierProposal, + Some(EpiphanyCoordinatorRoleId::Imagination), + None, + true, + false, + "Mind accepted the current model, but no actionable Hands frontier exists; await a typed user or Imagination proposal selection instead of relaunching Modeling without mutation authority.", + ); + } + build( EpiphanyCoordinatorAction::LaunchModeling, Some(EpiphanyCoordinatorRoleId::Modeling), @@ -1211,7 +1223,8 @@ pub fn coordinator_automation_action( | EpiphanyCoordinatorAction::ReviewModelingResult | EpiphanyCoordinatorAction::LaunchVerification | EpiphanyCoordinatorAction::ReviewVerificationResult - | EpiphanyCoordinatorAction::ContinueImplementation => { + | EpiphanyCoordinatorAction::ContinueImplementation + | EpiphanyCoordinatorAction::AwaitFrontierProposal => { EpiphanyCoordinatorAutomationAction::None } } @@ -1769,7 +1782,7 @@ mod tests { .contains("rejected by admission") ); - let reconcile_model_without_hands_frontier = + let await_proposal_without_hands_frontier = recommend_coordinator_action(EpiphanyCoordinatorInput { signals: EpiphanyCoordinatorSignals { research_result_status: EpiphanyCoordinatorRoleResultStatus::MissingBinding, @@ -1781,8 +1794,8 @@ mod tests { ..input() }); assert_eq!( - reconcile_model_without_hands_frontier.action, - EpiphanyCoordinatorAction::LaunchModeling + await_proposal_without_hands_frontier.action, + EpiphanyCoordinatorAction::AwaitFrontierProposal ); let review_failed_verification = recommend_coordinator_action(EpiphanyCoordinatorInput { @@ -1980,8 +1993,10 @@ mod tests { }); assert_eq!( no_frontier.action, - EpiphanyCoordinatorAction::LaunchModeling + EpiphanyCoordinatorAction::AwaitFrontierProposal ); + assert!(!no_frontier.can_auto_run); + assert!(no_frontier.reason.contains("proposal selection")); let consequences_exist = recommend_coordinator_action(EpiphanyCoordinatorInput { signals: modeling_done,