Skip to content
Merged
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
7 changes: 7 additions & 0 deletions crates/stella-observatory/src/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,13 @@ <h2>${fmtInt(runs)} undated ${runs === 1 ? "run" : "runs"}
if (e.type === "stage") return `<div class="jrnl-stage">stage · ${esc(e.label ?? "")}</div>`;
if (e.type === "speculation_discarded")
return `<div class="jrnl"><div class="kick">◌ speculative ${esc(e.name ?? "")} discarded · ${esc(e.reason ?? "")}</div></div>`;
/* A parked span explains a wall-clock gap that contains no other events
(#1857) — without its own arm it would fall through to "answer" and draw
a blank row exactly where the transcript most needs to say something. */
if (e.type === "turn_parked")
return `<div class="jrnl"><div class="kick">⏳ parked · ${esc(e.description ?? "")} · every ${esc(String(e.poll_interval_secs ?? "?"))}s, up to ${esc(String(e.deadline_secs ?? "?"))}s</div></div>`;
if (e.type === "turn_woken")
return `<div class="jrnl"><div class="kick">▶ woke · ${e.reason === "changed" ? "the watched state changed" : e.reason === "deadline_expired" ? "the deadline expired with no change" : esc(e.reason ?? "")} · ${esc(String(e.polls_used ?? 0))} probe(s)</div></div>`;
const head =
e.type === "tool_start" ? `▸ ${esc(e.name ?? "")}` :
e.type === "tool_result" ? `${e.ok ? "✓" : "✕"} result${e.duration_ms != null ? ` · ${fmtMs(e.duration_ms)}` : ""}${e.speculated ? " · speculated" : ""}` :
Expand Down
16 changes: 15 additions & 1 deletion crates/stella-observatory/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,8 @@ impl Observatory {
WHERE execution_id = ?1
AND seq > ?2
AND event_type IN ('stage', 'text', 'reasoning', 'tool_start',
'tool_result', 'speculation_discarded')
'tool_result', 'speculation_discarded',
'turn_parked', 'turn_woken')
ORDER BY seq ASC";
let mut stmt = match conn.prepare(sql) {
Ok(stmt) => stmt,
Expand Down Expand Up @@ -1228,6 +1229,19 @@ fn journal_entry(row: Value, full: bool) -> Value {
out["name"] = payload["name"].clone();
out["reason"] = payload["reason"].clone();
}
// A parked span is the one thing that explains a wall-clock gap with
// no events in it (#1857). Without its payload the row would say a
// park happened but not what was waited on or for how long — which
// is the entire question an operator opens this transcript to ask.
"turn_parked" => {
out["description"] = payload["description"].clone();
out["poll_interval_secs"] = payload["poll_interval_secs"].clone();
out["deadline_secs"] = payload["deadline_secs"].clone();
}
"turn_woken" => {
out["reason"] = payload["reason"].clone();
out["polls_used"] = payload["polls_used"].clone();
}
_ => {}
}
out
Expand Down
3 changes: 1 addition & 2 deletions crates/stella-pipeline/src/management_prompt/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ fn management_system_block(role: ModelCallRole) -> Option<String> {
| ModelCallRole::SkillAuthor
| ModelCallRole::DomainInference
| ModelCallRole::Reflection
| ModelCallRole::Summarization
| ModelCallRole::Research => None,
| ModelCallRole::Summarization => None,
}
}

Expand Down
1 change: 0 additions & 1 deletion crates/stella-pipeline/src/pipeline/scope_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ impl Pipeline<'_> {
let repo_structure = self.repo.structure_summary().await;
let mut revision: Option<String> = None;
let mut spent_revisions = 0usize;
let mut spend = Spend { budget, total };

loop {
let plan = match self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,14 @@
use super::*;
use crate::LineMutation;

/// The shell tool `flip_halt_arming` scripts its revision against.
/// Both arming witnesses for the mid-turn flip halt (#1793), and the shell
/// doubles they share.
///
/// A distinct name from [`WRITING_TOOL`] because the two fakes answer
/// differently and a flip must be attributable: only this one emits the exit
/// marker [`crate::flip_halt::exit_status`] reads.
const SHELL_TOOL: &str = "bash";

/// One model turn that runs `command` through the shell tool.
///
/// The `command` key is what [`crate::flip_halt::command_of`] looks for, so a
/// call built any other way would be invisible to the halt and the test would
/// pass for the wrong reason.
fn shell_call_result(command: &str) -> CompletionResult {
CompletionResult {
tool_calls: vec![ToolCall {
call_id: "call-shell".into(),
name: SHELL_TOOL.into(),
input: serde_json::json!({ "command": command }),
}],
..text_result("")
}
}

/// A shell whose every command succeeds, reported the way the real bash tool
/// reports it — the trailing `[exit code: 0]` marker.
///
/// That marker is the whole point: [`crate::flip_halt::FlipHalt::observe`]
/// latches only on a tracked command that exited zero, and output without a
/// marker can never stop a turn. A fake returning bare prose would leave the
/// halt unlatched and the arming test green for no reason.
struct PassingShell;
#[async_trait]
impl ToolExecutor for PassingShell {
fn schemas(&self) -> Vec<ToolSchema> {
vec![ToolSchema {
name: SHELL_TOOL.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: "ok\n[exit code: 0]".into(),
}
}
}

/// The authored-witness arming of the mid-turn flip halt (#1793) — a child
/// rather than a sibling module so it reaches the shared fakes through this
/// file's own `use super::*`, and so the already-oversized `tests.rs` does
/// not grow another module declaration.
/// A child rather than a sibling module so the already-oversized `tests.rs`
/// does not grow another module declaration. The doubles deliberately live
/// *inside* it rather than here: see that file's own module doc for why
/// colocating them with the two witnesses is what turns a wholesale rewrite
/// of this parent into a merge conflict instead of a silent deletion.
mod flip_halt_arming;

/// #860 acceptance: a baseline that TIMES OUT observed no failing assertion,
Expand Down
3 changes: 2 additions & 1 deletion crates/stella-protocol/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,8 @@ pub enum AgentEvent {
/// The replacement bytes each in-place rewrite left behind, one entry
/// per digest — what lets reconstruction resolve a compacted block to
/// the bytes the model received rather than the pre-compaction output
/// under the same `call_id` (#1667); see [`CompactionRewrite`].
/// under the same `call_id` (#1667); see
/// [`CompactionRewrite`](crate::CompactionRewrite).
/// `serde(default)` — absent on journals written before rewrites were
/// journaled, whose compacted blocks surface as digest mismatches.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
Expand Down
2 changes: 1 addition & 1 deletion crates/stella-protocol/src/event/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,4 +1486,4 @@ fn a_known_event_wire_format_is_unchanged_by_the_fallback() {
assert!(matches!(back, AgentEvent::Text { text } if text == "hello"));
}

mod tag_table;
mod tag_table;
21 changes: 21 additions & 0 deletions crates/stella-tui/src/fleet_dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ enum LastAction {
Message(String),
/// Waiting on a human — approval, a secret, a decision.
Blocked(String),
/// Parked on an engine-side wait (#1857): the worker is probing external
/// state on its own clock and will resume itself.
///
/// Deliberately not [`LastAction::Blocked`], which means a human must act.
/// A park needs nobody — reading it as blocked would send an operator
/// looking for an approval prompt that does not exist.
Parked(String),
/// A non-retryable error headline.
Error(String),
}
Expand Down Expand Up @@ -295,6 +302,7 @@ impl TaskRow {
LastAction::Thinking => "thinking…".to_string(),
LastAction::Message(s) => s.clone(),
LastAction::Blocked(reason) => format!("waiting: {reason}"),
LastAction::Parked(desc) => format!("parked: {desc}"),
LastAction::Error(msg) => msg.clone(),
}
}
Expand Down Expand Up @@ -457,6 +465,19 @@ impl FleetBoard {
row.action = LastAction::Message(line);
}
}
// A park with no arm here would freeze the row on whatever tool
// ran last, so an operator watching a fleet would read a worker
// that is deliberately waiting as one that is stuck mid-tool.
// The status stays whatever it was — a park is the worker running,
// not blocked (nobody has to act) and not terminal.
AgentEvent::TurnParked { description, .. } => {
row.action = LastAction::Parked(first_line(description));
}
// The wake carries no subject of its own; the next tool or message
// repaints the row a beat later, so clearing to `Idle` here would
// only flicker. Holding the park until then reads as "it woke and
// is picking back up", which is what happened.
AgentEvent::TurnWoken { .. } => {}
AgentEvent::AskUser { question, .. } => {
row.action = LastAction::Blocked(first_line(question));
if !row.status.is_terminal() {
Expand Down
3 changes: 2 additions & 1 deletion docs/wire/agentevent.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,8 @@ export type AgentEvent = {
* The replacement bytes each in-place rewrite left behind, one entry
* per digest — what lets reconstruction resolve a compacted block to
* the bytes the model received rather than the pre-compaction output
* under the same `call_id` (#1667); see [`CompactionRewrite`].
* under the same `call_id` (#1667); see
* [`CompactionRewrite`](crate::CompactionRewrite).
* `serde(default)` — absent on journals written before rewrites were
* journaled, whose compacted blocks surface as digest mismatches.
*/
Expand Down
2 changes: 1 addition & 1 deletion docs/wire/agentevent.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2152,7 +2152,7 @@
"type": "array"
},
"rewrites": {
"description": "The replacement bytes each in-place rewrite left behind, one entry\nper digest — what lets reconstruction resolve a compacted block to\nthe bytes the model received rather than the pre-compaction output\nunder the same `call_id` (#1667); see [`CompactionRewrite`].\n`serde(default)` — absent on journals written before rewrites were\njournaled, whose compacted blocks surface as digest mismatches.",
"description": "The replacement bytes each in-place rewrite left behind, one entry\nper digest — what lets reconstruction resolve a compacted block to\nthe bytes the model received rather than the pre-compaction output\nunder the same `call_id` (#1667); see\n[`CompactionRewrite`](crate::CompactionRewrite).\n`serde(default)` — absent on journals written before rewrites were\njournaled, whose compacted blocks surface as digest mismatches.",
"items": {
"$ref": "#/$defs/CompactionRewrite"
},
Expand Down
3 changes: 2 additions & 1 deletion docs/wire/serveframe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export type AgentEvent = {
* The replacement bytes each in-place rewrite left behind, one entry
* per digest — what lets reconstruction resolve a compacted block to
* the bytes the model received rather than the pre-compaction output
* under the same `call_id` (#1667); see [`CompactionRewrite`].
* under the same `call_id` (#1667); see
* [`CompactionRewrite`](crate::CompactionRewrite).
* `serde(default)` — absent on journals written before rewrites were
* journaled, whose compacted blocks surface as digest mismatches.
*/
Expand Down
2 changes: 1 addition & 1 deletion docs/wire/serveframe.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@
"type": "array"
},
"rewrites": {
"description": "The replacement bytes each in-place rewrite left behind, one entry\nper digest — what lets reconstruction resolve a compacted block to\nthe bytes the model received rather than the pre-compaction output\nunder the same `call_id` (#1667); see [`CompactionRewrite`].\n`serde(default)` — absent on journals written before rewrites were\njournaled, whose compacted blocks surface as digest mismatches.",
"description": "The replacement bytes each in-place rewrite left behind, one entry\nper digest — what lets reconstruction resolve a compacted block to\nthe bytes the model received rather than the pre-compaction output\nunder the same `call_id` (#1667); see\n[`CompactionRewrite`](crate::CompactionRewrite).\n`serde(default)` — absent on journals written before rewrites were\njournaled, whose compacted blocks surface as digest mismatches.",
"items": {
"$ref": "#/$defs/CompactionRewrite"
},
Expand Down
8 changes: 4 additions & 4 deletions scripts/file-size-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
1752 crates/stella-cli/src/agent/tests.rs
4621 crates/stella-cli/src/command_deck.rs
1507 crates/stella-cli/src/fleet_cmd.rs
2126 crates/stella-core/src/bus.rs
2571 crates/stella-core/src/driver.rs
1891 crates/stella-core/src/bus.rs
2572 crates/stella-core/src/driver.rs
3681 crates/stella-core/src/driver/tests.rs
1781 crates/stella-model/src/anthropic/tests.rs
2093 crates/stella-model/src/openai.rs
1565 crates/stella-model/src/zai.rs
1895 crates/stella-model/src/zai/tests.rs
3451 crates/stella-pipeline/src/pipeline.rs
2536 crates/stella-pipeline/src/pipeline/tests.rs
3181 crates/stella-pipeline/src/pipeline.rs
2537 crates/stella-pipeline/src/pipeline/tests.rs
1996 crates/stella-store/src/lib.rs
2266 crates/stella-store/src/tests.rs
1916 crates/stella-store/src/usage.rs
Expand Down