Stop powerless Modeling retries

This commit is contained in:
Meta Vens 2026-08-07 00:33:38 +01:00
commit e6d6e4f699
4 changed files with 189 additions and 6 deletions

View file

@ -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<String, String>,
constraints: Vec<String>,
scope_hints: Vec<String>,
evidence_refs: Vec<String>,
}
impl Args {
fn parse(values: impl Iterator<Item = String>) -> Result<Self> {
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<String> {
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()
);
}
}

View file

@ -507,7 +507,9 @@ fn run_coordinator(args: &Args) -> Result<Value> {
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;

View file

@ -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}"),

View file

@ -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,