Stop powerless Modeling retries
This commit is contained in:
parent
1d21e24a3b
commit
e6d6e4f699
4 changed files with 189 additions and 6 deletions
164
epiphany-core/src/bin/epiphany-frontier-proposal.rs
Normal file
164
epiphany-core/src/bin/epiphany-frontier-proposal.rs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Reference in a new issue