feat(stella-core): parked waits — waiting on external state without model steps (#1471) - #1860
Merged
Conversation
added 7 commits
August 6, 2026 03:15
…e without model calls The pure decision half (crate::waiting), the ToolExecutor drain port, and the driver's park loop (driver/waiting.rs, the settlement.rs split pattern). Refs #1471
…el re-invocation Refs #1471
…every tool-stack decorator TaskTap moves to command_deck/task_tap.rs (the settlement.rs split pattern) so the god file shrinks instead of growing. Pinned end-to-end by the_production_tool_stack_forwards_wait_requests, the wait twin of the spend-forwarding witness. Refs #1471
…-phase list The step loop's phase sequence gains its tail phase (maybe_park) and the layout table names the two new waiting modules. Closes #1471
Contributor
There was a problem hiding this comment.
Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
Reviewer's GuideIntroduces parked waits as a core runtime primitive so tools can request the engine to wait on external state between model steps, rewiring ci_status to use this mechanism and ensuring all ToolExecutor decorators forward wait requests, while adding pure waiting logic, driver integration, and tests. Sequence diagram for parked wait lifecycle with ci_statussequenceDiagram
actor User
participant Model
participant Engine
participant ToolExecutor
participant CiStatus
participant Sleeper
participant ExternalCI
User->>Model: Request CI status (wait=true)
Model->>Engine: Tool call ci_status {wait: true}
Engine->>ToolExecutor: execute("ci_status", input)
ToolExecutor->>CiStatus: execute(name, input)
CiStatus->>ExternalCI: run_github(primary_command, root, timeout_secs)
ExternalCI-->>CiStatus: (status list)
CiStatus->>ExternalCI: run_github(settled_command, root, timeout_secs)
ExternalCI-->>CiStatus: ("pending")
CiStatus->>CiStatus: park_request(input, pr_head)
CiStatus->>CiStatus: pending_wait.lock().insert(WaitRequest)
CiStatus-->>ToolExecutor: ToolOutput::Ok (current status + "Parking the turn…")
ToolExecutor-->>Engine: ToolOutput::Ok
Engine->>ToolExecutor: drain_wait_request()
ToolExecutor-->>Engine: WaitRequest
Engine->>Sleeper: sleep(request.interval_secs())
Sleeper-->>Engine: woke
loop Probe until changed or deadline
Engine->>ToolExecutor: execute_with_repair(WaitCall { probe })
ToolExecutor->>CiStatus: execute(name, {probe: true, target})
CiStatus->>ExternalCI: run_github(settled_command, root, timeout_secs)
ExternalCI-->>CiStatus: ("settled" or "pending")
CiStatus-->>ToolExecutor: ToolOutput::Ok {content}
ToolExecutor-->>Engine: ToolOutput
Engine->>Engine: probe_fingerprint / decide
alt [state changed]
Engine->>Engine: break
else [deadline not reached]
Engine->>Sleeper: sleep(request.interval_secs())
Sleeper-->>Engine: woke
end
end
alt WakeReason::Changed and on_wake present
Engine->>ToolExecutor: execute_with_repair(WaitCall { on_wake })
ToolExecutor->>CiStatus: execute(name, wake_input)
CiStatus->>ExternalCI: run_github(primary_command, root, timeout_secs)
ExternalCI-->>CiStatus: (fresh status)
CiStatus-->>ToolExecutor: ToolOutput::Ok {content}
ToolExecutor-->>Engine: ToolOutput
end
Engine->>Engine: wake_message(request, reason, polls_used, detail)
Engine->>Model: Inject CompletionMessage::user(wake_message)
Model->>Model: Next step using unchanged prompt prefix
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
macanderson
added a commit
that referenced
this pull request
Aug 6, 2026
…ter the #1875/#1898 growth (#1906) ## What & why Unbreaks main's `file-size` gate. `crates/stella-mcp/src/toolset.rs` reached **1756 lines** after three stacked merges — #1875 (sort the MCP schema segment for byte-stability, #1848), #1898 (bound one server's contribution to the advertised toolset, #1856), and the parked-waits work (#1860) — crossing the hard 1500-line limit for files not in `scripts/file-size-baseline.txt`. **What moved where:** the `#[cfg(test)] mod tests` block (~945 lines, more than half the file) moved out to a sibling submodule, `crates/stella-mcp/src/toolset/tests.rs`, declared as `#[cfg(test)] mod tests;`. This is the pattern AGENTS.md names (`crates/stella-core/src/driver/settlement.rs`, split out of `driver.rs`) and the exact shape `stella-model` already uses for `src/zai/tests.rs` and `src/anthropic/tests.rs`. Purely mechanical: the block is de-indented one level (plus the rustfmt repack that de-indent allows) — **no visibility changes** (a child module reaches the parent's private items via `use super::*`), no renames, no behavior change. The new file carries a `//!` module doc per crate idiom, and the README Layout row for `toolset.rs` now names the tests file (matching `stella-model`'s README style). - `src/toolset.rs`: 1756 → **812** lines - `src/toolset/tests.rs` (new): **948** lines **Why no baseline entry:** the guard's own policy — `scripts/file-size-baseline.txt` accepts no new entries; a file over the limit gets split, not grandfathered (AGENTS.md § God files). **Two more main unbreaks riding along:** - main's `cargo doc -D warnings` fails with *public documentation for `MAX_SERVER_SCHEMA_BYTES` links to private item `crate::client::ingest`*. The intra-doc link in that const's doc block (same file, `toolset.rs`) is now plain code font — same fix shape as #1823/#1830. - main's `Cargo.lock` is stale: `stella-diff`'s manifest says 0.6.127 but the lock records 0.6.126, so any build rewrites the lock and CI's `Cargo.lock` sync check fails. One-line resync, generated by cargo itself. **Heads-up for reviewers:** this PR alone does not green the gate — the `stella-pipeline` compile break and the Makefile duplicate target are being fixed in a separate PR by the coordinator. ## The witness - [x] No witness needed (pure refactor / docs / CI) — because: this is a mechanical module split plus a doc-link defuse and a lockfile resync; behavior is proven unchanged by the existing suite, not by a new test. All 122 `stella-mcp` unit tests (the relocated `toolset::tests` among them) and every integration suite pass unchanged. ## The gate GitHub Actions is in a major outage, so CI will not run on this PR — local verification is the evidence, run in this branch's worktree: - [x] `./scripts/check-file-size.sh` — OK, none over 1500 except the 32 grandfathered (none grew) - [x] `./scripts/check-god-files.sh` — OK; `stella-mcp`'s "no god files" README claim stays true - [x] `cargo fmt -p stella-mcp -- --check` — clean - [x] `cargo check -p stella-mcp` — clean - [x] `cargo clippy -p stella-mcp --all-targets -- -D warnings` — clean - [x] `cargo test -p stella-mcp` — 122 unit + 6/3/3/15/1 integration (wiremock + fixture-server suites included), all pass - [x] `RUSTDOCFLAGS="-D warnings" cargo doc -p stella-mcp --no-deps` — clean (verifies the private-link fix) - [x] Docs updated where behavior changed (README Layout row) - [x] CLA signed ## Nothing left behind - [x] There is nothing: everything I noticed is fixed in this PR (the remaining main breaks — `stella-pipeline` compile, Makefile duplicate target — are already owned by the coordinator's separate PR) ## Anything reviewers should know? Do not merge on green checks alone — Actions is down; the checklist above is the verification record.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Waiting on external state (CI, a deploy) was a model behavior: every poll a full model round-trip on a growing transcript, every long in-tool sleep a prompt-cache expiry. The measured damage (#1471): two-thirds of a monitoring session's spend went to waiting, a 600s
gh run watchvoided the cache into a 54.8k-token cold step ($0.166 for one status check), and accumulated poll output forced a mid-task compaction.This PR makes waiting a runtime primitive:
stella-core::waiting— pure decision logic (invariant 2):WaitRequest/WaitCall(serde round-tripped, invariant 4), interval/deadline clamps, poll-count deadline arithmetic (no clock: the injectedSleeperowns real time), fingerprint compare, wake-message rendering.ToolExecutor::drain_wait_request— a tool deposits a request duringexecute; the engine drains it at the step boundary. The exactdrain_sub_agent_spend_usdseam, with the same "decorators MUST forward" contract, forwarded through every wrapper in the workspace and pinned end-to-end bythe_production_tool_stack_forwards_wait_requests.driver/waiting.rs(thesettlement.rssplit pattern —driver.rsnet shrinks) —maybe_parkruns after dispatch, between model calls (invariant 6): sleep → replay a read-only probe through the standard dispatch path (hooks, timeout, repair) → compare → wake on change or deadline. Probe outputs never touch the transcript; the wake rides ONE volatile tail message (invariant 7 — the stable prefix is byte-identical across the park). Cancel is answered mid-park; a latched soft stop ends the wait early. Non-read-only probes are refused outright.ci_statusis the first client:wait=truenow returns current status immediately and deposits a park request with asettled/pendingprobe (stable vocabulary — no elapsed-time noise can fake a change; ci_status wait=true watches only the newest-created run — returns instantly with unchanged output while CI is still running #1466's whole-incomplete-set semantics kept, ci_status: a pr-target call with wait=true resolves the PR head up to three times — share one resolution per execute #1526's resolve-once discipline kept). The old composed bash poll loop — the exact thing that aged the cache inside one tool call and died at the 600s exec cap — is gone; waits now default to 30 minutes and clamp at 2 hours, bounds the in-tool wait could never offer.An arbitrarily long wait now costs O(1) model steps, forces no compaction, and leaves the prompt-cache prefix untouched.
Closes #1471
Refs #1473 — the loop-detector steer for polling loops is the model-behavior half; the primitive it should prescribe now exists.
The witness
main, passes here)crates/stella-core/src/driver/tests/parked_wait.rs, through the realrun_turn:a_change_on_the_nth_probe_re_invokes_the_model_exactly_once— the DoD from Monitoring burns model steps, prompt cache, and context on polling — make waiting a runtime primitive with change-notification #1471 verbatim: a condition that changes on the 3rd engine-side probe costs exactly 2 provider calls (request + wake), zero poll debris in the transcript, wake marked on the volatile tail.an_unchanged_condition_wakes_once_at_the_deadline— deadline/interval affords exactly N probes, then one wake markedtimed out.a_non_read_only_probe_is_refused_not_replayed— the structural mutation guard.Plus: pure-logic tests in
stella-core::waiting(clamps, error-keeps-waiting, serde round-trip),ci.rscomposition tests (probe stability, park deposit shape, resolved-head discipline), and the production-stack forwarding witness instella-cli.The gate
cargo fmt --checkstella-core(993),stella-tools(718),stella-clisubagent suite all pass locally; full workspace gate runs in CIci_statusschema description states the new contract;stella-coreREADME module map + step-phase listCloses #1471appears above and as a commit trailerNothing left behind
TurnParked/TurnWokenwire events + TUI heartbeat — slice 1 uses syntheticTextdeltas becauseevent.rsis a god file and every variant restalesdocs/wire), stella-serve: RemoteToolExecutor cannot forward parked-wait requests across the wire #1858 (stella-serve'sRemoteToolExecutorhas no wire channel for the drain — served sessions degrade to pre-Monitoring burns model steps, prompt cache, and context on polling — make waiting a runtime primitive with change-notification #1471 polling)Ground-rule check
stella-core— the park loop drives the existingToolExecutor/Sleeperports; no new deps anywhereAnything reviewers should know?
drain_sub_agent_spend_usd(ports.rs) — the workspace's established "written by one object, drained by another" idiom — rather than inventing a control-effect channel inToolOutput. An in-band output sentinel was considered and rejected: tool output is partly attacker-influenced text (PR titles, file contents), and a forgeable park request is a prompt-injection amplifier; the drain is structurally unforgeable.driver.rs2580→2572,registry.rs2184→2180,command_deck.rs4740→4699 (TaskTap moved tocommand_deck/task_tap.rs),driver/tests.rsstays at 3680 (two single-item imports merged pay for the newmod). No ceiling raised;check-file-sizeandcheck-god-filespass.WaitRequest::max_polls).Summary by Sourcery
Introduce parked waits as a core runtime primitive so tools can offload long external waits to the engine without consuming model steps, and adopt this for ci_status, ensuring forwarding through all tool executor decorators.
New Features:
Enhancements:
Documentation:
Tests: