Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions crates/stella-pipeline/src/management_prompt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ const SHARED_MANAGEMENT_PREAMBLE: &str = "";
/// family. Completeness is not compiler-checked here — that job belongs to
/// the exhaustive match in [`management_system_block`], which forces a new
/// variant to declare its prefix posture before this array matters.
const ALL_ROLES: [ModelCallRole; 14] = [
const ALL_ROLES: [ModelCallRole; 15] = [
ModelCallRole::Unknown,
ModelCallRole::Triage,
ModelCallRole::Research,
ModelCallRole::Plan,
ModelCallRole::PlanRepair,
ModelCallRole::WitnessAuthor,
Expand Down Expand Up @@ -82,8 +83,12 @@ fn management_system_block(role: ModelCallRole) -> Option<String> {
// adopt the split these arms move to `Some(...)` and the roles join
// the parity witness automatically.
ModelCallRole::Plan | ModelCallRole::PlanRepair => None,
// Never dispatched through the management chokepoint.
// Never dispatched through the management chokepoint. `Research`
// (#1778) runs as an engine sub-agent turn, so its system prompt
// rides its `SubAgentSpec` rather than this family — the same
// grouping, for the same reason, that `raw_usage.rs` gives it.
ModelCallRole::Unknown
| ModelCallRole::Research
| ModelCallRole::WitnessAuthor
| ModelCallRole::WitnessRepair
| ModelCallRole::AgentAuthor
Expand Down
11 changes: 5 additions & 6 deletions crates/stella-pipeline/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1462,8 +1462,7 @@ impl<'a> Pipeline<'a> {
research: &[ResearchFinding],
repo_structure: &str,
revision: Option<&str>,
budget: &mut BudgetGuard,
total: &mut f64,
spend: &mut Spend<'_>,
) -> Result<Vec<PlanStep>, PipelineBudgetAbort> {
self.emit(AgentEvent::Stage {
name: StageKind::Plan,
Expand Down Expand Up @@ -1491,8 +1490,8 @@ impl<'a> Pipeline<'a> {
overrides: &worker_overrides,
timeout: self.config.engine.model_timeout,
},
budget,
total,
spend.budget,
spend.total,
)
.await
{
Expand All @@ -1516,8 +1515,8 @@ impl<'a> Pipeline<'a> {
overrides: &worker_overrides,
timeout: self.config.engine.model_timeout,
},
budget,
total,
spend.budget,
spend.total,
)
.await
{
Expand Down
8 changes: 6 additions & 2 deletions crates/stella-pipeline/src/pipeline/scope_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ impl Pipeline<'_> {
let repo_structure = self.repo.structure_summary().await;
let mut revision: Option<String> = None;
let mut spent_revisions = 0usize;
// Built once and reborrowed per re-plan: the two halves travel
// together everywhere else in the crate, and bundling them is what
// keeps `plan_stage` structurally inside the argument limit rather
// than behind an `#[allow]`.
let mut spend = Spend { budget, total };

loop {
let plan = match self
Expand All @@ -40,8 +45,7 @@ impl Pipeline<'_> {
research,
&repo_structure,
revision.as_deref(),
budget,
total,
&mut spend,
)
.await
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,10 @@ async fn a_late_plan_is_abandoned_and_falls_back_to_the_single_step_plan() {
&[],
"",
None,
&mut budget,
&mut total,
&mut Spend {
budget: &mut budget,
total: &mut total,
},
)
.await
.expect("a wedged planner is never a run-ending failure");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,144 @@
//! FlipHalt arming on the authored-witness path (#1793).
//! FlipHalt arming in revise turns, on both paths (#1793).
//!
//! The mid-turn early stop used to be armed only from a configured
//! `--test-command` baseline, so on the authored-witness path — the default
//! for every run without a configured command — a revision kept running to
//! its step and loop caps after the witness had already flipped. This pins
//! the repair: `witness_on_demand` arms the latch the moment the witness's
//! failing baseline is credited into the oracle, and the revision receives
//! it (unfired) through the same `run_engine_turn` seam the execute turn
//! uses.
//! `--test-command` baseline, and `revise_turn` passed no latch at all. So on
//! the authored-witness path — the default for every run without a configured
//! command — a revision kept running to its step and loop caps after the
//! witness had already flipped, and even a configured-command run's revisions
//! did the same. This pins both repairs: `witness_on_demand` arms the latch
//! the moment the witness's failing baseline is credited into the oracle, and
//! a revision receives it (unfired) through the same `run_engine_turn` seam
//! the execute turn uses.
//!
//! Both witnesses and the two doubles they need live here rather than in the
//! parent: `verification_hardening.rs` is close enough to the 1500-line
//! ceiling that it cannot hold them, and these are the only users.

use super::*;

/// A shell double whose every command "passes": the output carries the
/// trailing exit-0 marker [`crate::flip_halt::exit_status`] parses. What
/// [`EmptyTools`] can never express — a worker *observing* the tracked test
/// succeed through a tool result.
struct PassingShell;
#[async_trait]
impl ToolExecutor for PassingShell {
fn schemas(&self) -> Vec<ToolSchema> {
vec![ToolSchema {
name: "bash".into(),
description: "run a shell command".into(),
input_schema: serde_json::json!({ "type": "object" }),
read_only: false,
speculation_safe: false,
}]
}
async fn execute(&self, _name: &str, _input: &Value) -> ToolOutput {
ToolOutput::Ok {
content: "1 passed\n[exit code: 0]".into(),
}
}
}

/// A completion that runs `command` through the shell — the observation the
/// flip halt correlates by `call_id` and scores against the tracked test.
fn shell_call_result(command: &str) -> CompletionResult {
CompletionResult {
tool_calls: vec![ToolCall {
call_id: format!("call-shell-{command}"),
name: "bash".into(),
input: serde_json::json!({ "command": command }),
}],
..text_result("")
}
}

/// #1793 witness (configured-command side): a revision that observes the
/// tracked test go fail→pass halts at that step boundary instead of running
/// on. The provider is scripted with steps BEYOND the flip; consuming them
/// is exactly the waste `flip_halt` exists to stop, so the call count is the
/// assertion.
#[tokio::test]
async fn a_revision_halts_at_the_step_where_the_tracked_test_flips() {
let provider = ScriptedProvider::new(vec![
text_result("single"),
// Execute turn: acts, but the suite still fails afterwards.
text_result("done"),
// Revision, step 1: re-run the tracked test — it now passes.
shell_call_result("cargo test -p x"),
// Steps the revision would burn WITHOUT the halt. They must never be
// consumed: the goal was met at the step above.
shell_call_result("cargo test -p x"),
text_result("revision done"),
]);
let resolver = OneProvider(&provider);
// Baseline fails (arms the halt), post-execute fails (forces the
// revision), post-revise passes (the flip), confirmation passes (#859).
let runner = ScriptedRunner::scripted(
vec![
TestScript::Fail,
TestScript::Fail,
TestScript::Pass,
TestScript::Pass,
],
"@@ -1 +1 @@\n-old\n+new",
);
let tools = PassingShell;
let recall = NoContextRecall;
let repo = NoRepoStructure;
let repo_status = NoRepoStatus;
let approvals = AutoApproveGate;
let sleeper = NoopSleeper;
let router = router();
let (tx, _rx) = mpsc::unbounded_channel();

let config = PipelineConfig {
test_command: Some("cargo test -p x".into()),
diff_diagnostic: Some(DiagnosticInvocation::GitDiff),
..PipelineConfig::default()
};
let pipeline = Pipeline::new(
PipelinePorts {
router: &router,
providers: &resolver,
tools: &tools,
recall: &recall,
repo: &repo,
repo_status: &repo_status,
touches: &NoFileTouches,
diagnostics: &runner,
tests: &runner,
lint: None,
mutation: None,
coverage: None,
approvals: &approvals,
sleeper: &sleeper,
hooks: None,
candidate_workspaces: None,
mcp_prefetch: None,
steering: None,
},
tx,
config,
);

let mut messages = vec![CompletionMessage::system("sys")];
let mut budget = BudgetGuard::new(BudgetMode::Off, None, None);
let outcome = pipeline
.run("Fix the failing test", &mut messages, &mut budget)
.await
.expect("run succeeds");

let verdict = outcome.verdict.expect("a verdict was produced");
assert!(verdict.passed, "the flip was confirmed: {verdict:?}");
assert_eq!(
provider.prompts().len(),
3,
"triage, execute, one revision step — the revision must halt at the \
boundary where the tracked test flipped, not spend the scripted \
steps beyond it"
);
}

/// #1793 witness (authored side): after `witness_on_demand` seeds a failing
/// witness, a revision that observes the witness command pass halts at that
/// step boundary. As in the configured-command twin, the provider is
Expand Down
Loading