From ef34918a0a7f4555e6df8b8b3e713b72ada1d9b8 Mon Sep 17 00:00:00 2001 From: Mac Anderson Date: Thu, 10 Sep 2026 11:35:17 -0700 Subject: [PATCH] refactor(stella-core): take the two shared sleeper doubles from stella-time About thirty test files across stella-core, stella-engine and their tests/ directories each wrote the same Sleeper double, a no-op one under three names and one on tokio's paused clock, and each copy grew a now() body in #6486. They are deleted. Integration tests and stella-engine take PausedSleeper and NoopSleeper from stella_time::test_util through a dev-dependency; stella-core's own unit tests keep one copy in src/tests.rs, because a lib's unit tests are a second build of the lib and a dev-dependency that links the lib implements the trait for the first. stella-time's one_home witness now scans test files too. Closes #6484 --- AGENTS.md | 2 +- Cargo.lock | 4 +- crates/stella-core/Cargo.toml | 4 + crates/stella-core/README.md | 11 +- crates/stella-core/src/accounted_call.rs | 54 ++------- crates/stella-core/src/driver/capabilities.rs | 4 +- crates/stella-core/src/driver/dispatch.rs | 18 +-- crates/stella-core/src/driver/drive.rs | 18 +-- crates/stella-core/src/driver/restore.rs | 33 ++---- crates/stella-core/src/driver/tests.rs | 95 ++++++---------- .../src/driver/tests/audit_fixes.rs | 28 ++--- .../src/driver/tests/budget_boundaries.rs | 16 +-- .../src/driver/tests/calibration.rs | 10 +- .../src/driver/tests/compute_passes.rs | 10 +- .../src/driver/tests/context_efficiency.rs | 6 +- .../src/driver/tests/context_overflow.rs | 23 +--- .../src/driver/tests/deadline_notice.rs | 19 +--- .../src/driver/tests/lifecycle_bus.rs | 23 +--- .../src/driver/tests/live_services.rs | 2 +- .../src/driver/tests/loop_abort.rs | 18 +-- .../src/driver/tests/model_fallback.rs | 19 +--- .../src/driver/tests/output_budget.rs | 19 +--- .../src/driver/tests/parked_wait.rs | 18 +-- .../src/driver/tests/provider_outcomes.rs | 19 +--- .../stella-core/src/driver/tests/requery.rs | 17 +-- .../src/driver/tests/steer_midturn.rs | 6 +- .../src/driver/tests/streaming_deadline.rs | 4 +- .../src/driver/tests/unbounded_by_default.rs | 2 +- .../src/driver/tests/usage_anchor.rs | 2 +- .../src/driver/tests/usage_completeness.rs | 22 ++-- .../src/driver/tests/user_hooks.rs | 34 +++--- .../src/driver/tests/user_hooks/verdicts.rs | 6 +- .../src/driver/tests/zero_copy_request.rs | 4 +- crates/stella-core/src/goal.rs | 106 +++++++++++++----- crates/stella-core/src/lib.rs | 5 + crates/stella-core/src/subagent/tests.rs | 22 +--- .../src/subagent/tests/failure_and_events.rs | 18 +-- .../src/subagent/tests/fork_scope.rs | 6 +- .../stella-core/src/subagent/tests/seams.rs | 16 +-- .../subagent/tests/spend_and_cancellation.rs | 10 +- .../src/subagent/tests/tools_and_budget.rs | 12 +- .../stella-core/src/subagent/tests/witness.rs | 6 +- crates/stella-core/src/tests.rs | 66 +++++++++++ .../tests/engine_emits_no_stage.rs | 17 +-- .../stella-core/tests/hard_drop_write_back.rs | 26 +---- crates/stella-core/tests/parallel_dispatch.rs | 27 +---- crates/stella-core/tests/spend_gate.rs | 19 +--- crates/stella-core/tests/tool_wall_clock.rs | 17 +-- crates/stella-engine/Cargo.toml | 2 + crates/stella-engine/src/tests.rs | 31 ++--- crates/stella-engine/tests/embedding.rs | 23 +--- crates/stella-time/README.md | 14 +-- crates/stella-time/tests/one_home.rs | 55 +++------ ...ine-reads-time-through-the-sleeper-port.md | 6 +- 54 files changed, 418 insertions(+), 656 deletions(-) create mode 100644 crates/stella-core/src/tests.rs diff --git a/AGENTS.md b/AGENTS.md index 7e7397736d..b519cb066a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1366,7 +1366,7 @@ the files you must plan around (see below). | Change the wrapper socket's **trait** — `TurnWrapper`, `admissible`, `judge`/`again`, the in-process/subprocess transports | [`stella-runtime`](crates/stella-runtime/README.md) | `src/wrapper/` (#3380, landed #3479, `doc:wrapper-socket`). Lives one layer above `stella-core` because `before_turn`/`after_turn` do I/O, which invariant 2 bans in the engine; consumes `stella-plugin`'s wire types rather than redefining them. It is reached four ways now: a `--pipeline` run, a goal round, a fleet attempt, and an auto-resolved plugin — see the crate's own README. | | Change how a plugin is **installed, listed, removed, or trusted** — `.stella/plugins/` and `~/.stella/plugins/` resolution, install consent, the project-tier trust gate | [`stella-cli`](crates/stella-cli/README.md) | `src/plugin_cmd.rs` + `src/plugin_cmd/{roster,process}.rs` — renders `stella_plugin::consent_text` before anything executes, gates a cloned repository's plugins on `project_code_execution_trusted()` (#3509), and is the one place `LoopGrant::permits_hook`/`permits_point` get consulted against an installed manifest. | | Decide whether a human is present to see/answer a mid-run prompt | [`stella-tty`](crates/stella-tty/README.md) | **A leaf with NO dependencies at all** (#3036) — one pure `human_can_answer(interactive_output, stdin_is_terminal, prompt_is_visible)`, which is what lets `stella-cli`'s approval prompts and `stella-model`'s credential prompt share one derivation without `stella-model` depending on `stella-cli` (invariant 1). | -| Read the real clock, wait on the real timer, or stand in for either under test, behind `stella-core`'s `Sleeper` and `Clock` ports | [`stella-time`](crates/stella-time/README.md) | **Near-leaf: `stella-core` is its only workspace dependency.** `TokioSleeper` (the engine's real sleeper and `now`), `WallClock` (Unix epoch, for a stamp another process reads) and `MonotonicClock` (one origin per process, for a span compared as a number), plus `test_util`'s `PausedSleeper` and `NoopSleeper` behind the `test-util` feature. One home, because `stella-serve` may not link `stella-cli` or `stella-runtime` and `stella-core` may not carry a timer (ADR 0042). | +| Read the real clock, wait on the real timer, or stand in for either under test, behind `stella-core`'s `Sleeper` and `Clock` ports | [`stella-time`](crates/stella-time/README.md) | **Near-leaf: `stella-core` is its only workspace dependency.** `TokioSleeper` (the engine's real sleeper and `now`), `WallClock` (Unix epoch, for a stamp another process reads) and `MonotonicClock` (one origin per process, for a span compared as a number), plus `test_util`'s `PausedSleeper` and `NoopSleeper` behind the `test-util` feature. One home, because `stella-serve` may not link `stella-cli` or `stella-runtime` and `stella-core` may not carry a timer (ADR 0042); `stella-core` tests against it through a dev-dependency cycle, the tokio / tokio-test shape. | | Emit a diagnostic — a record explaining *why* the program did something | [`stella-diag`](crates/stella-diag/README.md) | **A leaf: `serde` only, so anything may depend on it.** Field values cannot hold a `String`, a `Path`, or model output — that is a compile error, not a review question. Design: [`docs/spec/diagnostics.md`](docs/spec/diagnostics.md). | | Compute a line-oriented unified diff (`@@` hunks, git's exact shape) | [`stella-diff`](crates/stella-diff/README.md) | **A leaf with NO dependencies at all** (#1511) — pure functions over borrowed strings, which is what lets [`stella-observatory`](crates/stella-observatory/README.md) and [`stella-cli`](crates/stella-cli/README.md) share one differ without costing the observatory its isolation. | | Strip ANSI escape sequences from tool output | [`stella-ansi`](crates/stella-ansi/README.md) | **A leaf with NO dependencies at all** — one pure function over a borrowed `&str`, extracted from `stella-tui` so [`stella-observatory`](crates/stella-observatory/README.md) could strip a colourised tool's output before it reaches the journal route's `
` without linking `ratatui`. `stella-tui`'s `ansi` module re-exports it and keeps only the `ratatui`-shaped emission half. |
diff --git a/Cargo.lock b/Cargo.lock
index 3172d1a34d..92c9fa450c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3284,6 +3284,7 @@ dependencies = [
  "serde_json_canonicalizer",
  "sha2 0.11.0",
  "stella-protocol",
+ "stella-time",
  "thiserror 2.0.20",
  "tokio",
 ]
@@ -3329,6 +3330,7 @@ dependencies = [
  "serde_json",
  "stella-core",
  "stella-protocol",
+ "stella-time",
  "tokio",
 ]
 
@@ -3577,7 +3579,7 @@ dependencies = [
 
 [[package]]
 name = "stella-time"
-version = "0.9.416"
+version = "0.9.417"
 dependencies = [
  "async-trait",
  "rand 0.10.2",
diff --git a/crates/stella-core/Cargo.toml b/crates/stella-core/Cargo.toml
index a11be42137..f47d338ff9 100644
--- a/crates/stella-core/Cargo.toml
+++ b/crates/stella-core/Cargo.toml
@@ -20,6 +20,10 @@ futures-util = { workspace = true }
 sha2 = { workspace = true }
 
 [dev-dependencies]
+# The engine's tests take the shared sleeper doubles from the crate that
+# holds the real sleeper. A dev-dependency cycle is allowed, and this is the
+# tokio / tokio-test shape.
+stella-time = { path = "../stella-time", features = ["test-util"] }
 tokio = { workspace = true, features = ["test-util"] }
 # Seeds the retry tests' jitter double. Shipping code draws nothing: the
 # entropy behind backoff jitter is a port (`retry::Sleeper::jitter`), and
diff --git a/crates/stella-core/README.md b/crates/stella-core/README.md
index 0f3f6bb2a7..778ef310c5 100644
--- a/crates/stella-core/README.md
+++ b/crates/stella-core/README.md
@@ -295,9 +295,10 @@ system message and the latest user message are never touched.
   [`src/subagent.rs`](src/subagent.rs)'s `run_sub_agent`, which constructs the
   child in-crate and carries every seam. The crate exports the `Sleeper` and
   `Clock` ports with no production implementation — `stella-time` holds
-  those, and every host passes them to `Engine::assemble`. Tests wire a
-  no-op or a paused-clock double, so a retry costs no wall clock and a
-  timeout still means what it says.
+  those, and every host passes them to `Engine::assemble`. Tests take
+  `PausedSleeper` and `NoopSleeper` from `stella_time::test_util` — the unit
+  tests from `crate::tests`, the one copy the compiler forces — so a
+  retry costs no wall clock and a timeout still means what it says.
 - **A sub-agent's steering is filtered, not inherited.** `drain_steering` is
   destructive by contract, so a child that inherited the parent's `TurnSteering`
   would swallow a message the user addressed to the parent. `ChildSteering`
@@ -332,8 +333,8 @@ turn-driver audit witnesses; also `budget_boundaries.rs`,
 [`src/loop_detect.rs`](src/loop_detect.rs); a failing case writes its seed to
 `proptest-regressions/`, and that seed is committed. No feature flag, no env var, no
 fixture server and no network — driver tests wire scripted `Provider`s, counting
-`ToolExecutor`s and sleeper doubles on a paused runtime, so the suite runs in
-seconds. Keep it that
+`ToolExecutor`s and `stella-time`'s shared sleeper doubles on a paused runtime,
+so the suite runs in seconds. Keep it that
 way: a test here that needs a file or a socket means the logic under test is in
 the wrong crate.
 
diff --git a/crates/stella-core/src/accounted_call.rs b/crates/stella-core/src/accounted_call.rs
index 0bd3355ead..6f142fb137 100644
--- a/crates/stella-core/src/accounted_call.rs
+++ b/crates/stella-core/src/accounted_call.rs
@@ -144,12 +144,12 @@ pub enum AccountedCallError {
 /// measured since the last streamed fragment, so it re-arms every time the
 /// dispatch is observed to still be producing — the same distinction
 /// `crate::step::bounded_generation` draws for the engine's own step loop.
-/// A flat wall-clock deadline here used to abandon a call the instant total
-/// elapsed time crossed the ceiling even while the provider was actively
-/// answering, which lost OpenRouter's trailing usage/cost frame (it arrives
-/// in a final SSE frame *after* the content, once the gateway has settled
-/// the routed call's price) to a ceiling sized to catch silence, not a
-/// slow-but-live generation (#1467).
+/// A flat wall-clock deadline here abandoned a call the instant total
+/// elapsed time crossed the ceiling, even while the provider was still
+/// answering. That lost OpenRouter's trailing usage/cost frame, which
+/// arrives in a final SSE frame *after* the content, once the gateway has
+/// settled the routed call's price. The ceiling was sized to catch silence,
+/// not a slow-but-live generation (#1467).
 pub async fn run_accounted_call(
     call: AccountedCall<'_>,
     budget: &mut BudgetGuard,
@@ -460,24 +460,9 @@ mod tests {
     use stella_protocol::{BudgetMode, CompletionMessage, CompletionRequestRef, CompletionUsage};
 
     use super::*;
+    use crate::tests::{NoopSleeper, PausedSleeper};
     use std::time::Instant;
 
-    struct NoopSleeper;
-
-    #[async_trait]
-    impl Sleeper for NoopSleeper {
-        async fn sleep(&self, _duration_ms: u64) {}
-
-        // The floor: a test that asserts on retry timing wants no spread in it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
-
     struct RetryThenSuccess {
         attempts: Mutex,
     }
@@ -982,27 +967,6 @@ mod tests {
         );
     }
 
-    /// A [`Sleeper`] backed by real (here, paused-virtual) tokio time so a
-    /// caller-supplied per-call timeout can expire *during* a backoff sleep.
-    struct TokioSleeper;
-
-    #[async_trait]
-    impl Sleeper for TokioSleeper {
-        async fn sleep(&self, duration_ms: u64) {
-            tokio::time::sleep(Duration::from_millis(duration_ms)).await;
-        }
-
-        // The floor: the timeout under test is placed against the exact
-        // backoff, so the draw must not move it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
-
     struct AlwaysRetryable;
 
     #[async_trait]
@@ -1048,7 +1012,7 @@ mod tests {
             },
             &mut budget,
             &EventSender::new(tx),
-            &TokioSleeper,
+            &PausedSleeper,
         )
         .await;
 
@@ -1170,7 +1134,7 @@ mod tests {
             },
             &mut budget,
             &EventSender::new(tx),
-            &TokioSleeper,
+            &PausedSleeper,
         )
         .await
         .expect("the trailing gap must not abandon a call that was actively answering");
diff --git a/crates/stella-core/src/driver/capabilities.rs b/crates/stella-core/src/driver/capabilities.rs
index 3d9b0ecc04..3c88ea2ebd 100644
--- a/crates/stella-core/src/driver/capabilities.rs
+++ b/crates/stella-core/src/driver/capabilities.rs
@@ -431,7 +431,7 @@ mod tests {
 
         let provider = crate::subagent::tests::ScriptedProvider::new(vec![]);
         let tools = crate::subagent::tests::MixedTools::default();
-        let sleeper = crate::subagent::tests::TokioSleeper;
+        let sleeper = crate::tests::PausedSleeper;
 
         let owned = built_elsewhere();
         let seams = owned.as_borrowed();
@@ -473,7 +473,7 @@ mod tests {
     fn assemble_carries_the_bare_capability_set_onto_the_engine() {
         let provider = crate::subagent::tests::ScriptedProvider::new(vec![]);
         let tools = crate::subagent::tests::MixedTools::default();
-        let sleeper = crate::subagent::tests::TokioSleeper;
+        let sleeper = crate::tests::PausedSleeper;
 
         let seams = TurnCapabilities::none();
         let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
diff --git a/crates/stella-core/src/driver/dispatch.rs b/crates/stella-core/src/driver/dispatch.rs
index 919298ac64..736a095bf0 100644
--- a/crates/stella-core/src/driver/dispatch.rs
+++ b/crates/stella-core/src/driver/dispatch.rs
@@ -348,8 +348,8 @@ mod tests {
 
     use super::super::TurnHalt;
     use crate::event_sender::EventSender;
-    use crate::retry::Sleeper;
     use crate::step::{BudgetSnapshot, CHECKPOINT_VERSION, Checkpoint, TurnState};
+    use crate::tests::NoopSleeper;
     use crate::{Engine, EngineConfig, TurnCapabilities, TurnOutcome};
     use stella_protocol::{
         AgentEvent, BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult,
@@ -449,22 +449,6 @@ mod tests {
         }
     }
 
-    #[derive(Debug)]
-    struct NoopSleeper;
-    #[async_trait]
-    impl Sleeper for NoopSleeper {
-        async fn sleep(&self, _duration_ms: u64) {}
-
-        // The floor: a test that asserts on retry timing wants no spread in it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
-
     /// Fires once the executor's flag is up — the shape of the pipeline's
     /// `FlipHalt`, which latches synchronously inside the `ToolResult` tap.
     #[derive(Debug)]
diff --git a/crates/stella-core/src/driver/drive.rs b/crates/stella-core/src/driver/drive.rs
index e86a1a1759..7cb1ba7c34 100644
--- a/crates/stella-core/src/driver/drive.rs
+++ b/crates/stella-core/src/driver/drive.rs
@@ -207,8 +207,8 @@ mod tests {
 
     use super::super::TurnHalt;
     use crate::event_sender::EventSender;
-    use crate::retry::Sleeper;
     use crate::step::{BudgetSnapshot, CHECKPOINT_VERSION, Checkpoint, TurnState};
+    use crate::tests::NoopSleeper;
     use crate::{Engine, EngineConfig, TurnCapabilities, TurnOutcome};
     use stella_protocol::{
         BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult, CompletionUsage,
@@ -278,22 +278,6 @@ mod tests {
         }
     }
 
-    #[derive(Debug)]
-    struct NoopSleeper;
-    #[async_trait]
-    impl Sleeper for NoopSleeper {
-        async fn sleep(&self, _duration_ms: u64) {}
-
-        // The floor: a test that asserts on retry timing wants no spread in it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
-
     /// Armed from the start, so the first committed boundary ends the turn.
     #[derive(Debug)]
     struct AlwaysHalt;
diff --git a/crates/stella-core/src/driver/restore.rs b/crates/stella-core/src/driver/restore.rs
index e04190db5f..09d24d8ab8 100644
--- a/crates/stella-core/src/driver/restore.rs
+++ b/crates/stella-core/src/driver/restore.rs
@@ -575,25 +575,10 @@ mod tests {
     use crate::driver::{Engine, EngineConfig};
     use crate::event_sender::EventSender;
     use crate::ports::ToolExecutor;
-    use crate::retry::Sleeper;
     use crate::step::SummarizerHealth;
+    use crate::tests::NoopSleeper;
     use stella_protocol::BudgetMode;
 
-    struct NoSleep;
-    #[async_trait]
-    impl Sleeper for NoSleep {
-        async fn sleep(&self, _duration_ms: u64) {}
-
-        // The floor: a test that asserts on retry timing wants no spread in it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
-
     /// Always answers "SUMMARY" — the summarizer path under test is the
     /// restoration that follows the splice, not the summary itself.
     struct SummaryProvider;
@@ -752,7 +737,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -793,7 +778,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -837,7 +822,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -883,7 +868,7 @@ mod tests {
             active: vec!["deploy".into()],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -924,7 +909,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -957,7 +942,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -1083,7 +1068,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
@@ -1138,7 +1123,7 @@ mod tests {
             active: vec![],
         };
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&provider, &tools, config(), &NoSleep, seams);
+        let engine = Engine::assemble(&provider, &tools, config(), &NoopSleeper, seams);
         let mut messages = vec![
             CompletionMessage::system("sys"),
             CompletionMessage::user("the task"),
diff --git a/crates/stella-core/src/driver/tests.rs b/crates/stella-core/src/driver/tests.rs
index 086053f76e..4b96489b04 100644
--- a/crates/stella-core/src/driver/tests.rs
+++ b/crates/stella-core/src/driver/tests.rs
@@ -11,30 +11,7 @@ use tokio::sync::mpsc;
 use super::{CONTINUATION_MARKER_PREFIX as NUDGE, *};
 use crate::TurnCapabilities;
 use crate::hooks::{HookAction, HookExecError, HookExecResult, HookMatcher};
-use crate::retry::Sleeper;
-
-/// A `Sleeper` on tokio's clock, which every test here runs paused: a sleep
-/// costs nothing while the runtime is idle and still lets a pending call
-/// finish first, and `now` reads the same virtual timeline. A sleeper that
-/// returned at once would make every engine timeout fire the moment a
-/// provider future waited on another task, which is not what a timeout is.
-#[derive(Default)]
-struct TokioSleeper;
-#[async_trait]
-impl Sleeper for TokioSleeper {
-    async fn sleep(&self, duration_ms: u64) {
-        tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
-    }
-
-    fn now(&self) -> std::time::Instant {
-        tokio::time::Instant::now().into_std()
-    }
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
+use crate::tests::PausedSleeper;
 
 /// A `ToolExecutor` that always succeeds and counts real invocations — the
 /// counter is what `retry_never_re_executes_a_tool_call` asserts against.
@@ -252,7 +229,7 @@ async fn run_speculation_turn(
     provider: &SpeculatingProvider,
     tools: &dyn ToolExecutor,
 ) -> (TurnOutcome, Vec) {
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(provider, tools, EngineConfig::default(), &sleeper, seams);
     let (tx, mut rx) = mpsc::unbounded_channel();
@@ -444,7 +421,7 @@ async fn budget_abort_after_speculation_discards_the_pool() {
         executed,
     };
 
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, mut rx) = mpsc::unbounded_channel();
@@ -557,7 +534,7 @@ async fn a_failed_attempts_speculative_pool_emits_discarded_events() {
         calls: calls.clone(),
         executed,
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![CompletionMessage::user("read a.rs")];
@@ -617,7 +594,7 @@ async fn text_deltas_precede_the_authoritative_text_and_concatenate_to_it() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, mut rx) = mpsc::unbounded_channel();
@@ -737,7 +714,7 @@ async fn a_wedged_generation_trips_the_deadline_once_instead_of_burning_the_retr
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         model_timeout: Some(Duration::from_millis(50)),
         ..EngineConfig::default()
@@ -778,7 +755,7 @@ async fn simple_turn_with_no_tool_calls_completes() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -848,7 +825,7 @@ async fn a_halt_ends_the_turn_at_the_next_step_boundary_as_completed() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         turn_halt: Some(Arc::new(AlwaysHalt)),
         ..EngineConfig::default()
@@ -896,7 +873,7 @@ async fn a_halt_that_never_fires_leaves_the_turn_exactly_as_it_was() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         turn_halt: Some(Arc::new(NeverHalt)),
         ..EngineConfig::default()
@@ -995,7 +972,7 @@ async fn steered_messages_inject_before_the_next_model_call() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let steering = TestSteering {
         queue: std::sync::Mutex::new(vec!["also check the tests".into()]),
         stop_after_drains: None,
@@ -1053,7 +1030,7 @@ async fn soft_stop_ends_the_turn_keeping_completed_steps() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     // Stop latches after the first boundary: step 0 runs fully (model
     // call + tool), step 1's boundary honors the stop.
     let steering = TestSteering {
@@ -1100,7 +1077,7 @@ async fn overflow_of_protected_content_is_summarized_and_metered() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = vec![
@@ -1165,7 +1142,7 @@ async fn summarization_disabled_leaves_history_untouched() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         summarize_overflow: false,
         ..overflow_config()
@@ -1205,7 +1182,7 @@ async fn summarizer_failure_is_non_fatal_and_leaves_history() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = vec![
@@ -1244,7 +1221,7 @@ async fn summarization_never_orphans_tool_results_at_the_span_edge() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     // The naive span end (len - keep_recent) lands ON the tool-result
@@ -1330,7 +1307,7 @@ async fn empty_completion_aborts_with_a_visible_message_not_a_silent_success() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1381,7 +1358,7 @@ async fn a_step_out_of_time_completes_with_a_truthful_partial_instead_of_abortin
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(
         &provider,
@@ -1475,7 +1452,7 @@ async fn a_length_truncated_tool_less_step_continues_the_turn_instead_of_complet
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1535,7 +1512,7 @@ async fn length_continuations_are_bounded_per_turn() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1592,7 +1569,7 @@ async fn tool_calls_execute_and_feed_back_into_history() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1645,7 +1622,7 @@ async fn retry_never_re_executes_a_tool_call() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1693,7 +1670,7 @@ async fn malformed_tool_call_input_is_repaired_not_executed_blindly() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1737,7 +1714,7 @@ async fn stuck_loop_aborts_the_turn_cleanly_before_the_step_cap() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1776,7 +1753,7 @@ async fn stuck_loop_steers_once_then_aborts_on_re_detection() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1898,7 +1875,7 @@ async fn identical_polls_with_changing_output_complete_without_abort() {
     let tools = PollingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1960,7 +1937,7 @@ async fn period_three_cycle_with_no_progress_steers_then_aborts() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         loop_detection: LoopDetectionConfig {
             exact_repeat_threshold: 3,
@@ -2014,7 +1991,7 @@ async fn enforced_budget_aborts_the_turn_cleanly_between_steps() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -2141,10 +2118,10 @@ async fn run_synthetic_survival_turn(dialect: &str, id_style: fn(u32) -> String)
         }
     }
     let tools = GrowingTools;
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         // Keep the retry backoff floor at 0 so 200 steps with injected
-        // 429s/drops still runs near-instantly under TokioSleeper.
+        // 429s/drops still runs near-instantly under PausedSleeper.
         retry_policy: RetryPolicy::new(3, 0, 0),
         // A tight-ish compaction budget so the growing tool output
         // actually forces multiple compaction passes over 200 steps.
@@ -2263,7 +2240,7 @@ async fn read_only_calls_in_one_step_execute_concurrently() {
     let tools = BarrierTools {
         barrier: tokio::sync::Barrier::new(2),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -2361,7 +2338,7 @@ async fn mutating_calls_are_barriers_and_history_keeps_call_order() {
         read1_started: tokio::sync::Notify::new(),
         read2_done: tokio::sync::Notify::new(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -2464,7 +2441,7 @@ async fn every_committed_step_emits_exactly_one_step_usage_record() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -2555,7 +2532,7 @@ async fn a_wedged_tool_trips_the_dispatch_ceiling_instead_of_hanging() {
         ]),
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         tool_timeout: Some(Duration::from_secs(900)),
         ..EngineConfig::default()
@@ -2611,7 +2588,7 @@ async fn a_none_ceiling_leaves_tool_dispatch_unbounded() {
         ]),
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         tool_timeout: None,
         ..EngineConfig::default()
@@ -2704,7 +2681,7 @@ async fn a_turn_checkpoints_at_every_step_boundary_and_clears_when_it_ends() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let sink = Arc::new(RecordingSink::default());
     let config = EngineConfig {
         checkpoint_sink: Some(sink.clone() as Arc),
@@ -2766,7 +2743,7 @@ async fn a_turn_without_a_sink_is_unchanged() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     assert!(
         EngineConfig::default().checkpoint_sink.is_none(),
         "durability is opt-in: a default engine writes no checkpoints"
diff --git a/crates/stella-core/src/driver/tests/audit_fixes.rs b/crates/stella-core/src/driver/tests/audit_fixes.rs
index 4877b53fcc..88d470e7d5 100644
--- a/crates/stella-core/src/driver/tests/audit_fixes.rs
+++ b/crates/stella-core/src/driver/tests/audit_fixes.rs
@@ -19,7 +19,7 @@ async fn summarize_keep_recent_zero_does_not_panic() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         summarize_keep_recent: 0,
         ..overflow_config()
@@ -61,7 +61,7 @@ async fn observed_budget_breach_emits_a_warning_event() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -116,7 +116,7 @@ async fn budget_abort_synthetic_results_are_visible_in_the_event_stream() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -160,7 +160,7 @@ async fn whitespace_only_completion_aborts_without_a_text_event() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -335,7 +335,7 @@ async fn hard_cancel_mid_stream_emits_a_cancelled_usage_envelope() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![CompletionMessage::user("secret prompt text")];
@@ -497,7 +497,7 @@ async fn overflow_summarizer_retries_a_transient_error() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = overflow_messages();
@@ -551,7 +551,7 @@ async fn budget_aborted_summary_is_applied_not_discarded() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = overflow_messages();
@@ -615,7 +615,7 @@ async fn overflow_summary_names_the_folded_tool_result_blocks() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
 
@@ -716,7 +716,7 @@ async fn repeated_summarizer_failures_emit_events_and_latch() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut budget = BudgetGuard::new(BudgetMode::Off, None, None);
@@ -814,7 +814,7 @@ async fn observed_budget_breach_warns_once_per_axis_per_turn() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -871,7 +871,7 @@ async fn enforced_session_breach_abort_reason_names_the_axis() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -906,7 +906,7 @@ async fn enforced_session_breach_at_step_boundary_names_the_axis() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -1267,7 +1267,7 @@ async fn a_recycled_speculation_call_id_reports_the_execution_it_displaces() {
     let tools = CountingReadTools {
         executions: executions.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, mut rx) = mpsc::unbounded_channel();
@@ -1385,7 +1385,7 @@ async fn the_system_prefix_stays_byte_stable_across_a_compacting_turn() {
         prefixes: std::sync::Mutex::new(Vec::new()),
         step: AtomicU32::new(0),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         // Small enough that every step after the first compacts, and small
         // enough that the pure passes cannot get under it alone — so the
diff --git a/crates/stella-core/src/driver/tests/budget_boundaries.rs b/crates/stella-core/src/driver/tests/budget_boundaries.rs
index d5c8d354da..5b7bfafe62 100644
--- a/crates/stella-core/src/driver/tests/budget_boundaries.rs
+++ b/crates/stella-core/src/driver/tests/budget_boundaries.rs
@@ -71,7 +71,7 @@ async fn an_over_cap_budget_abort_hands_back_a_well_paired_transcript() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = transcript_with_an_unanswered_tool_call();
@@ -107,7 +107,7 @@ async fn a_past_deadline_abort_hands_back_a_well_paired_transcript() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = transcript_with_an_unanswered_tool_call();
@@ -211,7 +211,7 @@ async fn summary_induced_budget_breach_aborts_with_cost_before_next_provider_cal
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = vec![
@@ -263,7 +263,7 @@ async fn an_existing_budget_breach_stops_before_paid_compaction() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = vec![
@@ -310,7 +310,7 @@ async fn a_past_task_deadline_stops_the_turn_before_the_next_call_with_partial_w
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -363,7 +363,7 @@ async fn cancellation_after_billed_completion_before_speculation_finishes_keeps_
         provider_completed: provider_completed.clone(),
     };
     let tools = ForeverRead;
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![CompletionMessage::user("read")];
@@ -406,7 +406,7 @@ async fn a_normal_completion_charges_the_budget_exactly_once() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![CompletionMessage::user("answer")];
@@ -479,7 +479,7 @@ async fn a_slow_tool_stops_the_turn_before_the_deadline() {
     };
     let provider_calls = provider.calls.clone();
     let tools = SlowTool { took: tool_time };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/calibration.rs b/crates/stella-core/src/driver/tests/calibration.rs
index 034d4c71d4..43e3d99074 100644
--- a/crates/stella-core/src/driver/tests/calibration.rs
+++ b/crates/stella-core/src/driver/tests/calibration.rs
@@ -62,7 +62,7 @@ async fn calibrated_estimate_changes_the_compaction_decision() {
         let tools = CountingTools {
             calls: Arc::new(AtomicU32::new(0)),
         };
-        let sleeper = TokioSleeper;
+        let sleeper = PausedSleeper;
         let mut messages = compactable_history();
         // A budget the RAW estimate just fits under: uncalibrated, no
         // compaction can fire.
@@ -141,7 +141,7 @@ async fn each_committed_step_feeds_the_calibration_and_reports_its_estimate() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let calibration = CalibrationMap::new();
     let seams = TurnCapabilities {
         calibration: Some(&calibration),
@@ -228,7 +228,7 @@ async fn cache_write_tokens_count_toward_the_calibration_actual() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let calibration = CalibrationMap::new();
     let seams = TurnCapabilities {
         calibration: Some(&calibration),
@@ -273,7 +273,7 @@ async fn attachment_weight_is_excluded_from_the_drift_sample_estimate() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let mut messages = vec![
         CompletionMessage::system("sys"),
         CompletionMessage::user_with_attachments(
@@ -359,7 +359,7 @@ async fn a_fresh_sessions_calibration_factor_leaves_identity_within_the_turn() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     // Unseeded, exactly like a bench container's first run: warm-up happens
     // (or fails to matter) entirely inside this one turn.
     let calibration = CalibrationMap::new();
diff --git a/crates/stella-core/src/driver/tests/compute_passes.rs b/crates/stella-core/src/driver/tests/compute_passes.rs
index 007400c0b6..26c77d9a63 100644
--- a/crates/stella-core/src/driver/tests/compute_passes.rs
+++ b/crates/stella-core/src/driver/tests/compute_passes.rs
@@ -39,7 +39,7 @@ async fn a_step_walks_the_transcript_to_estimate_it_at_most_twice() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -94,7 +94,7 @@ async fn the_receipt_and_the_usage_record_report_one_estimate_per_step() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -197,7 +197,7 @@ async fn per_step_hashing_grows_with_the_turn_not_with_its_square() {
             }
         }
         let tools = EchoingTools;
-        let sleeper = TokioSleeper;
+        let sleeper = PausedSleeper;
         let seams = TurnCapabilities::none();
         let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
         let mut messages = vec![
@@ -384,7 +384,7 @@ async fn compaction_mid_turn_invalidates_the_receipt_ledgers_digest_memo() {
         calls: Arc::new(AtomicU32::new(0)),
     };
     let tools = BigOutputTools { filler: 4_000 };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         // Small enough that a couple of 4 KB results blow it, and no summarizer so
         // the rewrite comes purely from eviction/aging.
@@ -481,7 +481,7 @@ async fn a_compaction_pass_journals_the_replacement_bytes_it_wrote() {
         calls: Arc::new(AtomicU32::new(0)),
     };
     let tools = BigOutputTools { filler: 4_000 };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         compaction_budget_tokens: 800,
         summarize_overflow: false,
diff --git a/crates/stella-core/src/driver/tests/context_efficiency.rs b/crates/stella-core/src/driver/tests/context_efficiency.rs
index 4b8b2c8983..63417eec4f 100644
--- a/crates/stella-core/src/driver/tests/context_efficiency.rs
+++ b/crates/stella-core/src/driver/tests/context_efficiency.rs
@@ -59,7 +59,7 @@ async fn a_long_turn_ages_old_tool_results_far_below_the_compaction_budget() {
     let tools = BigOutputTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         compaction_budget_tokens: 20_000,
         ..EngineConfig::default()
@@ -147,7 +147,7 @@ async fn a_spent_allowance_retains_an_elided_partial_not_the_scratchpad() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -213,7 +213,7 @@ async fn a_truncated_step_with_tool_calls_retains_elided_narration() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/context_overflow.rs b/crates/stella-core/src/driver/tests/context_overflow.rs
index c7805aaeef..4970592f73 100644
--- a/crates/stella-core/src/driver/tests/context_overflow.rs
+++ b/crates/stella-core/src/driver/tests/context_overflow.rs
@@ -28,6 +28,7 @@ use stella_protocol::{CompletionResult, ToolSchema, UsageIncompleteReason};
 
 use super::super::*;
 use crate::TurnCapabilities;
+use crate::tests::NoopSleeper;
 
 /// Rejects the first `overflows` calls as context overflow, then completes.
 struct OverflowThenComplete {
@@ -101,22 +102,6 @@ impl ToolExecutor for NoTools {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// Completes every call — the wider-window replacement the spent ladder
 /// hands the transcript to.
 struct HealthyFallback {
@@ -186,7 +171,7 @@ async fn run_turn_with_fallback(
     resolver: Option<&dyn crate::ports::FallbackResolver>,
 ) -> (TurnOutcome, Vec) {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities {
         fallback: resolver,
         ..TurnCapabilities::none()
@@ -444,7 +429,7 @@ async fn a_summarizer_request_that_overflows_drops_its_head_and_still_folds_the_
     let tools = super::CountingTools {
         calls: std::sync::Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, super::overflow_config(), &sleeper, seams);
     let mut messages = vec![
@@ -496,7 +481,7 @@ async fn head_dropping_is_bounded_when_no_span_size_is_accepted() {
     let tools = super::CountingTools {
         calls: std::sync::Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, super::overflow_config(), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/deadline_notice.rs b/crates/stella-core/src/driver/tests/deadline_notice.rs
index ef2af88b07..9535f9c7f9 100644
--- a/crates/stella-core/src/driver/tests/deadline_notice.rs
+++ b/crates/stella-core/src/driver/tests/deadline_notice.rs
@@ -17,6 +17,7 @@ use stella_protocol::{CompletionResult, ToolSchema};
 
 use super::super::*;
 use crate::TurnCapabilities;
+use crate::tests::NoopSleeper;
 
 /// Answers immediately, recording the transcript it was handed.
 #[derive(Default)]
@@ -59,22 +60,6 @@ impl Provider for RecordingProvider {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 struct NoTools;
 
 #[async_trait::async_trait]
@@ -96,7 +81,7 @@ async fn run_turn_with_deadline(
     deadline: Option,
 ) {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
diff --git a/crates/stella-core/src/driver/tests/lifecycle_bus.rs b/crates/stella-core/src/driver/tests/lifecycle_bus.rs
index b5c3c5533c..794b614ec6 100644
--- a/crates/stella-core/src/driver/tests/lifecycle_bus.rs
+++ b/crates/stella-core/src/driver/tests/lifecycle_bus.rs
@@ -17,6 +17,7 @@ use std::sync::{Arc, Mutex};
 use super::super::*;
 use crate::TurnCapabilities;
 use crate::bus::{HookBus, HookEvent, names};
+use crate::tests::NoopSleeper;
 use serde_json::Value;
 use stella_protocol::{CompletionResult, ToolSchema};
 
@@ -129,25 +130,9 @@ impl ToolExecutor for NoTools {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 async fn run_one_turn(provider: &dyn Provider, bus: &HookBus) -> TurnOutcome {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities {
         bus: Some(bus),
         ..TurnCapabilities::none()
@@ -166,7 +151,7 @@ async fn run_one_turn_in_lane(
     lane: stella_protocol::TurnLane,
 ) -> TurnOutcome {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = crate::driver::capabilities::TurnCapabilities {
         bus: Some(bus),
         lane: Some(lane),
@@ -337,7 +322,7 @@ async fn a_step_reports_how_it_ended() {
 #[tokio::test]
 async fn an_engine_without_a_bus_runs_identically() {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(
         &OneShotProvider,
diff --git a/crates/stella-core/src/driver/tests/live_services.rs b/crates/stella-core/src/driver/tests/live_services.rs
index 45cb2e98e1..a740719644 100644
--- a/crates/stella-core/src/driver/tests/live_services.rs
+++ b/crates/stella-core/src/driver/tests/live_services.rs
@@ -98,7 +98,7 @@ async fn run(
         script: TokioMutex::new(script.into_iter().map(Ok).collect()),
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/loop_abort.rs b/crates/stella-core/src/driver/tests/loop_abort.rs
index 48a3e39c48..13de49bcaa 100644
--- a/crates/stella-core/src/driver/tests/loop_abort.rs
+++ b/crates/stella-core/src/driver/tests/loop_abort.rs
@@ -124,7 +124,7 @@ async fn a_loop_that_resumes_after_a_successful_course_correction_still_aborts()
     let tools = ConstantTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     // A low cap, so a broken ladder shows up as "ground to the cap" in a
     // second.
     let config = EngineConfig {
@@ -231,7 +231,7 @@ async fn a_second_distinct_loop_earns_its_own_steer_before_the_turn_dies() {
     let tools = ConstantTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, exact_repeat_only(30), &sleeper, seams);
     let mut messages = vec![
@@ -341,7 +341,7 @@ async fn a_third_loop_is_not_steered_and_is_not_blamed_on_the_warned_one() {
     let tools = ConstantTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, exact_repeat_only(30), &sleeper, seams);
     let mut messages = vec![
@@ -442,7 +442,7 @@ async fn a_tool_answering_identically_to_every_new_argument_is_steered_then_kill
     let tools = ConstantTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         max_steps: Some(60),
         ..EngineConfig::default()
@@ -512,7 +512,7 @@ async fn a_confident_zero_never_reports_as_completed() {
     let tools = ConstantTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -565,7 +565,7 @@ async fn a_direct_zero_tool_answer_still_completes() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -602,7 +602,7 @@ async fn a_turn_that_did_mutating_work_still_completes_despite_a_bare_closing_li
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -641,7 +641,7 @@ async fn a_terminated_short_answer_after_investigation_still_completes() {
     let tools = ConstantTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -692,7 +692,7 @@ async fn a_stuck_loop_steer_names_the_loop_rung_as_its_cause() {
     let tools = ConstantTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, exact_repeat_only(30), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/model_fallback.rs b/crates/stella-core/src/driver/tests/model_fallback.rs
index 08be2a88f1..a72f5af5d3 100644
--- a/crates/stella-core/src/driver/tests/model_fallback.rs
+++ b/crates/stella-core/src/driver/tests/model_fallback.rs
@@ -24,6 +24,7 @@ use super::super::*;
 use crate::TurnCapabilities;
 use crate::ports::{FallbackResolver, ResolvedFallback};
 use crate::retry::RetryPolicy;
+use crate::tests::NoopSleeper;
 
 /// Fails every call with the error `build` produces, counting the calls.
 struct AlwaysFailing {
@@ -165,22 +166,6 @@ impl ToolExecutor for NoTools {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// One real turn against `provider` with `resolver` attached (when given),
 /// returning the outcome and every event.
 async fn run_turn_collecting(
@@ -190,7 +175,7 @@ async fn run_turn_collecting(
     config: EngineConfig,
 ) -> (TurnOutcome, Vec) {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities {
         fallback: resolver,
         ..TurnCapabilities::none()
diff --git a/crates/stella-core/src/driver/tests/output_budget.rs b/crates/stella-core/src/driver/tests/output_budget.rs
index bd8da7f5bd..36232569a3 100644
--- a/crates/stella-core/src/driver/tests/output_budget.rs
+++ b/crates/stella-core/src/driver/tests/output_budget.rs
@@ -26,6 +26,7 @@ use stella_protocol::{CompletionResult, ToolSchema};
 use super::super::*;
 use crate::TurnCapabilities;
 use crate::driver::output_budget_recovery::SessionOutputCeilings;
+use crate::tests::NoopSleeper;
 
 /// Rejects the first `refusals` calls as an unaffordable ceiling, naming
 /// `affordable` each time, then completes. Records the ceiling every attempt
@@ -153,22 +154,6 @@ impl ToolExecutor for NoTools {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// One real turn against `provider` with a configured output ceiling, and
 /// optionally the session-scoped carry a host attaches (#3307). Passing the
 /// same handle to two calls is what makes them two turns of ONE session
@@ -179,7 +164,7 @@ async fn run_turn_with_ceiling_and_carry(
     carry: Option<&std::sync::Arc>,
 ) -> TurnOutcome {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let config = EngineConfig {
         max_output_tokens: Some(ceiling),
         session_output_ceilings: carry.cloned(),
diff --git a/crates/stella-core/src/driver/tests/parked_wait.rs b/crates/stella-core/src/driver/tests/parked_wait.rs
index b28e9d2055..fc66bc48fd 100644
--- a/crates/stella-core/src/driver/tests/parked_wait.rs
+++ b/crates/stella-core/src/driver/tests/parked_wait.rs
@@ -124,7 +124,7 @@ async fn a_change_on_the_nth_probe_re_invokes_the_model_exactly_once() {
     let tools = ParkingTools::depositing(ci_wait_request(600), 3);
     let probe_calls = tools.probe_calls.clone();
     let wake_calls = tools.wake_calls.clone();
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -239,7 +239,7 @@ async fn an_unchanged_condition_wakes_once_at_the_deadline() {
     let tools = ParkingTools::depositing(ci_wait_request(20), u32::MAX);
     let probe_calls = tools.probe_calls.clone();
     let wake_calls = tools.wake_calls.clone();
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -297,7 +297,7 @@ async fn a_non_read_only_probe_is_refused_not_replayed() {
     request.probe.name = "bash".into(); // not in the read-only schema set
     let tools = ParkingTools::depositing(request, 1);
     let probe_calls = tools.probe_calls.clone();
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -361,7 +361,7 @@ async fn sustained_rate_limiting_parks_within_budget_and_recovers() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -425,7 +425,7 @@ async fn a_long_retry_after_hint_is_honored_by_parking_when_budget_allows() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -475,7 +475,7 @@ async fn a_hint_past_the_remaining_deadline_still_fails_fast_without_parking() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -534,7 +534,7 @@ async fn a_sustained_529_brownout_parks_within_budget_and_recovers() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -597,7 +597,7 @@ async fn a_sustained_transport_fault_still_exhausts_the_ladder_and_aborts() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -662,7 +662,7 @@ async fn a_soft_stop_during_a_park_ends_the_turn_as_a_deliberate_stop() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     // Ask 1 is the step boundary's, which must answer "no" or the turn ends
     // before a park exists; ask 2 is the park's first per-chunk tick.
     let steering = StopOnAsk {
diff --git a/crates/stella-core/src/driver/tests/provider_outcomes.rs b/crates/stella-core/src/driver/tests/provider_outcomes.rs
index c97570d1a1..0ada4f429b 100644
--- a/crates/stella-core/src/driver/tests/provider_outcomes.rs
+++ b/crates/stella-core/src/driver/tests/provider_outcomes.rs
@@ -15,6 +15,7 @@ use super::super::*;
 use crate::TurnCapabilities;
 use crate::ports::Clock;
 use crate::router::{CircuitBreaker, ProviderProfile, RoleTable, Router};
+use crate::tests::NoopSleeper;
 use serde_json::Value;
 use stella_protocol::{CompletionResult, ModelRef, Role, ToolSchema};
 
@@ -76,22 +77,6 @@ impl ToolExecutor for NoTools {
     }
 }
 
-struct NoSleep;
-
-#[async_trait::async_trait]
-impl crate::retry::Sleeper for NoSleep {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// Cooldown timing is irrelevant here (nothing advances), so a frozen clock
 /// keeps the breaker's open state deterministic for the whole test.
 struct FrozenClock;
@@ -121,7 +106,7 @@ fn router() -> Router {
 /// production wiring shape (`&Router`, shared, at the call site).
 async fn run_turn_feeding(provider: &dyn Provider, router: &Router) -> TurnOutcome {
     let tools = NoTools;
-    let sleeper = NoSleep;
+    let sleeper = NoopSleeper;
     let seams = TurnCapabilities {
         outcomes: Some(router),
         ..TurnCapabilities::none()
diff --git a/crates/stella-core/src/driver/tests/requery.rs b/crates/stella-core/src/driver/tests/requery.rs
index a648d3dc97..3008189d81 100644
--- a/crates/stella-core/src/driver/tests/requery.rs
+++ b/crates/stella-core/src/driver/tests/requery.rs
@@ -22,22 +22,7 @@ use crate::TurnCapabilities;
 use crate::budget::BudgetGuard;
 use crate::driver::*;
 use crate::ports::SteeringRequery;
-use crate::retry::Sleeper;
-
-struct NoopSleeper;
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
+use crate::tests::NoopSleeper;
 
 struct OkTools;
 #[async_trait]
diff --git a/crates/stella-core/src/driver/tests/steer_midturn.rs b/crates/stella-core/src/driver/tests/steer_midturn.rs
index 0f42d8ea51..12453ec487 100644
--- a/crates/stella-core/src/driver/tests/steer_midturn.rs
+++ b/crates/stella-core/src/driver/tests/steer_midturn.rs
@@ -62,7 +62,7 @@ async fn a_soft_stop_closes_caller_supplied_open_tool_calls() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let steering = StopNow;
     let seams = TurnCapabilities {
         steering: Some(&steering),
@@ -126,7 +126,7 @@ async fn a_steer_after_a_tool_round_keeps_every_call_paired_with_its_result() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let steering = SteerAtDrain {
         queue: std::sync::Mutex::new(vec!["also check the tests".into()]),
         fire_on_drain: 2,
@@ -199,7 +199,7 @@ async fn a_mid_tool_round_steer_leaves_a_tool_message_immediately_before_it() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let steering = SteerAtDrain {
         queue: std::sync::Mutex::new(vec!["actually, stop after this".into()]),
         fire_on_drain: 2,
diff --git a/crates/stella-core/src/driver/tests/streaming_deadline.rs b/crates/stella-core/src/driver/tests/streaming_deadline.rs
index 95e21e0518..d8d0adda4e 100644
--- a/crates/stella-core/src/driver/tests/streaming_deadline.rs
+++ b/crates/stella-core/src/driver/tests/streaming_deadline.rs
@@ -61,7 +61,7 @@ async fn a_streaming_generation_outlives_the_deadline_because_it_is_not_stalled(
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         model_timeout: Some(Duration::from_millis(50)),
         ..EngineConfig::default()
@@ -153,7 +153,7 @@ async fn a_call_only_stream_outlives_the_deadline_because_it_is_not_stalled() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         model_timeout: Some(Duration::from_millis(50)),
         ..EngineConfig::default()
diff --git a/crates/stella-core/src/driver/tests/unbounded_by_default.rs b/crates/stella-core/src/driver/tests/unbounded_by_default.rs
index 2fa0b165d8..86a27137ac 100644
--- a/crates/stella-core/src/driver/tests/unbounded_by_default.rs
+++ b/crates/stella-core/src/driver/tests/unbounded_by_default.rs
@@ -65,7 +65,7 @@ async fn run_productive_turn(steps: u32, config: EngineConfig) -> (TurnOutcome,
         script: TokioMutex::new(productive_script(steps)),
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &DistinctTools, config, &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/src/driver/tests/usage_anchor.rs b/crates/stella-core/src/driver/tests/usage_anchor.rs
index 5bed702a63..f6b0313d30 100644
--- a/crates/stella-core/src/driver/tests/usage_anchor.rs
+++ b/crates/stella-core/src/driver/tests/usage_anchor.rs
@@ -61,7 +61,7 @@ async fn compaction_fired_with_report(usage: CompletionUsage) -> bool {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let mut messages = compactable_history();
     let raw = crate::estimator::estimate_conversation_tokens(&messages);
     let config = EngineConfig {
diff --git a/crates/stella-core/src/driver/tests/usage_completeness.rs b/crates/stella-core/src/driver/tests/usage_completeness.rs
index 3bbf8f7d5b..e7152a08e2 100644
--- a/crates/stella-core/src/driver/tests/usage_completeness.rs
+++ b/crates/stella-core/src/driver/tests/usage_completeness.rs
@@ -14,7 +14,7 @@ async fn exhausted_worker_call_emits_one_content_free_incompleteness_event() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -97,7 +97,7 @@ async fn a_failed_call_names_the_model_that_made_it() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -164,7 +164,7 @@ async fn a_failed_attempts_recovered_usage_reaches_the_event_stream() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -221,7 +221,7 @@ async fn exhausted_retries_emit_typed_reasons_before_the_error() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -281,7 +281,7 @@ async fn auth_failure_on_first_attempt_reports_not_retryable() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -338,7 +338,7 @@ async fn successful_retry_keeps_the_failed_attempt_usage_incomplete() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         retry_policy: RetryPolicy::new(1, 0, 0),
         ..EngineConfig::default()
@@ -394,7 +394,7 @@ async fn step_usage_carries_the_requests_effort_and_output_ceiling() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let config = EngineConfig {
         effort: Some(stella_protocol::completion::ReasoningEffort::High),
         max_output_tokens: Some(32_000),
@@ -450,7 +450,7 @@ async fn step_usage_carries_the_requests_generation_params() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let asked = stella_protocol::completion::GenerationParams {
         top_p: Some(0.9),
         seed: Some(4_621),
@@ -520,7 +520,7 @@ async fn overflow_summarizer_emits_its_own_usage_envelope() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = overflow_messages();
@@ -555,7 +555,7 @@ async fn failed_overflow_summarizer_emits_content_free_incompleteness() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, overflow_config(), &sleeper, seams);
     let mut messages = overflow_messages();
@@ -613,7 +613,7 @@ async fn two_calls_at_one_step_are_separable_by_their_usage_rows() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams)
         .with_turn_instance(TURN);
diff --git a/crates/stella-core/src/driver/tests/user_hooks.rs b/crates/stella-core/src/driver/tests/user_hooks.rs
index 8271002bce..ceb30bbab3 100644
--- a/crates/stella-core/src/driver/tests/user_hooks.rs
+++ b/crates/stella-core/src/driver/tests/user_hooks.rs
@@ -54,7 +54,7 @@ async fn pre_tool_use_hook_nonzero_exit_blocks_the_tool_and_model_sees_it() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 1,
@@ -128,7 +128,7 @@ async fn post_tool_use_hook_runs_after_the_tool_and_never_blocks() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     // Exit 3 (non-zero) proves a *failing* PostToolUse hook is still a
     // pure observation — it can neither block nor abort the turn.
@@ -196,7 +196,7 @@ async fn non_blocking_hook_failure_surfaces_as_one_retryable_error_event() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 3,
@@ -270,7 +270,7 @@ async fn no_hooks_configured_leaves_the_turn_path_unchanged() {
     let tools = CountingTools {
         calls: tool_calls.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     // Built WITHOUT `with_hooks` — `hooks` stays `None`.
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
@@ -319,7 +319,7 @@ async fn run_turn_never_fires_session_start_hooks() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 0,
@@ -383,7 +383,7 @@ async fn a_hooked_read_fires_its_hook_once_never_for_a_dropped_speculative_attem
         calls: calls.clone(),
         executed,
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     // exit 0: non-blocking, so the tool runs and the hook is a pure
     // observation — what matters is how many times it is invoked.
@@ -493,7 +493,7 @@ async fn pre_tool_use_modify_decision_rewrites_the_input_the_tool_receives() {
         schemas: vec![bash_schema(false)],
         inputs: inputs.clone(),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 0,
@@ -624,7 +624,7 @@ async fn a_hook_require_approval_parks_on_the_route_and_the_answer_decides() {
         runner,
         hooks,
     } = require_approval_fixture(ask);
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let route = ScriptedRoute {
         resolution: crate::hooks::decision::ApprovalRouteResolution::Approved,
         calls: Arc::new(AtomicU32::new(0)),
@@ -722,7 +722,7 @@ async fn require_approval_without_a_route_refuses_with_the_grant_path() {
         runner,
         hooks,
     } = require_approval_fixture(ask);
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities {
         hooks: Some((&hooks, &runner)),
         ..TurnCapabilities::none()
@@ -773,7 +773,7 @@ async fn pre_tool_use_payload_carries_the_schemas_read_only_bit() {
         schemas: vec![bash_schema(true)],
         inputs: Arc::new(TokioMutex::new(Vec::new())),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 0,
@@ -869,7 +869,7 @@ async fn an_always_denying_stop_hook_is_consulted_to_the_bound_then_the_turn_sta
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 0,
@@ -977,7 +977,7 @@ async fn a_deny_then_allow_is_reconsulted_and_the_allowed_completion_stands() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = ScriptedHookRunner {
         stdouts: TokioMutex::new(vec![
@@ -1051,7 +1051,7 @@ async fn a_failing_stop_hook_never_holds_the_turn_open() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = RecordingHookRunner {
         exit_code: 3,
         stdout: String::new(),
@@ -1125,7 +1125,7 @@ async fn a_stop_hooks_require_approval_resolves_both_ways() {
         let tools = CountingTools {
             calls: Arc::new(AtomicU32::new(0)),
         };
-        let sleeper = TokioSleeper;
+        let sleeper = PausedSleeper;
         let runner = RecordingHookRunner {
             exit_code: 0,
             stdout: r#"{"action":"require_approval","reason":"verification budget exhausted, continue?"}"#
@@ -1220,7 +1220,7 @@ async fn a_stop_hooks_require_approval_with_no_route_lets_the_turn_complete() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = RecordingHookRunner {
         exit_code: 0,
         stdout: r#"{"action":"require_approval","reason":"ask a human"}"#.into(),
@@ -1275,7 +1275,7 @@ async fn pre_compact_hook_veto_skips_the_summarization_round() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let hook_payloads = Arc::new(TokioMutex::new(Vec::new()));
     let runner = RecordingHookRunner {
         exit_code: 0,
@@ -1346,7 +1346,7 @@ async fn pre_compact_modify_instructions_reach_the_summarizer_request() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = RecordingHookRunner {
         exit_code: 0,
         stdout: r#"{"action":"modify","payload":{"instructions":"keep every file path verbatim"}}"#
diff --git a/crates/stella-core/src/driver/tests/user_hooks/verdicts.rs b/crates/stella-core/src/driver/tests/user_hooks/verdicts.rs
index 723c7927d1..11dbfcc46f 100644
--- a/crates/stella-core/src/driver/tests/user_hooks/verdicts.rs
+++ b/crates/stella-core/src/driver/tests/user_hooks/verdicts.rs
@@ -29,7 +29,7 @@ async fn a_structured_stop_denial_reaches_the_model_and_the_journal_intact() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = ScriptedHookRunner {
         stdouts: TokioMutex::new(vec![
             r#"{"action":"deny","reason":"the witness is still red",
@@ -123,7 +123,7 @@ async fn a_prose_only_stop_denial_grows_no_evidence_section() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = ScriptedHookRunner {
         stdouts: TokioMutex::new(vec![
             r#"{"action":"deny","reason":"the checklist is not done"}"#.into(),
@@ -209,7 +209,7 @@ async fn run_always_denying_stop_gate(allowance: Option, answers: usize) ->
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let runner = RecordingHookRunner {
         exit_code: 0,
         stdout: r#"{"action":"deny","reason":"not yet"}"#.into(),
diff --git a/crates/stella-core/src/driver/tests/zero_copy_request.rs b/crates/stella-core/src/driver/tests/zero_copy_request.rs
index 9433d58298..a1917f3352 100644
--- a/crates/stella-core/src/driver/tests/zero_copy_request.rs
+++ b/crates/stella-core/src/driver/tests/zero_copy_request.rs
@@ -65,7 +65,7 @@ async fn a_retried_attempt_re_sends_the_same_slices_it_did_the_first_time() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, _rx) = mpsc::unbounded_channel();
@@ -122,7 +122,7 @@ async fn the_adapter_serializes_off_the_callers_own_transcript() {
     let tools = CountingTools {
         calls: Arc::new(AtomicU32::new(0)),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let (tx, _rx) = mpsc::unbounded_channel();
diff --git a/crates/stella-core/src/goal.rs b/crates/stella-core/src/goal.rs
index 46815c5aa1..2ef31ff755 100644
--- a/crates/stella-core/src/goal.rs
+++ b/crates/stella-core/src/goal.rs
@@ -554,23 +554,7 @@ mod tests {
     use crate::TurnCapabilities;
     use crate::driver::EngineConfig;
     use crate::ports::ToolExecutor;
-    use crate::retry::Sleeper;
-
-    /// Sleeper that never really sleeps — goal tests run instantly.
-    struct NoSleep;
-    #[async_trait]
-    impl Sleeper for NoSleep {
-        async fn sleep(&self, _duration_ms: u64) {}
-
-        // The floor: a test that asserts on retry timing wants no spread in it.
-        fn now(&self) -> std::time::Instant {
-            std::time::Instant::now()
-        }
-
-        fn jitter(&self, _upper: u64) -> u64 {
-            0
-        }
-    }
+    use crate::tests::NoopSleeper;
 
     /// A provider that returns a fixed sequence of results, then errors.
     struct ScriptedProvider {
@@ -670,7 +654,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, rx) = mpsc::unbounded_channel();
@@ -736,7 +726,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, rx) = mpsc::unbounded_channel();
@@ -823,7 +819,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -883,7 +885,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         // Mirrors `build_budget_guard(Some(0.05))`: the cap is on the session
         // axis. (A per-turn axis here would reset each round and let the loop
@@ -963,7 +971,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1014,7 +1028,13 @@ mod tests {
             gate: Some(&gate),
             ..TurnCapabilities::none()
         };
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1065,7 +1085,13 @@ mod tests {
         ]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1105,7 +1131,13 @@ mod tests {
         let verifier = ScriptedProvider::new(vec![Err(ProviderError::Auth("bad key".into()))]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1137,7 +1169,13 @@ mod tests {
         let verifier = ScriptedProvider::new(vec![]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1195,7 +1233,13 @@ mod tests {
         let verifier = ScriptedProvider::new(vec![]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         let (tx, _rx) = mpsc::unbounded_channel();
@@ -1230,7 +1274,13 @@ mod tests {
         ))]);
         let tools = NoTools;
         let seams = TurnCapabilities::none();
-        let engine = Engine::assemble(&worker, &tools, EngineConfig::default(), &NoSleep, seams);
+        let engine = Engine::assemble(
+            &worker,
+            &tools,
+            EngineConfig::default(),
+            &NoopSleeper,
+            seams,
+        );
         let mut messages = vec![CompletionMessage::system("sys")];
         let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
         budget.reseed_session_spend(0.75);
diff --git a/crates/stella-core/src/lib.rs b/crates/stella-core/src/lib.rs
index b61507c014..dcb3421567 100644
--- a/crates/stella-core/src/lib.rs
+++ b/crates/stella-core/src/lib.rs
@@ -27,6 +27,9 @@ pub mod ports;
 pub mod receipts;
 pub mod restore;
 pub mod retry;
+
+// The two sleeper doubles the unit tests share. The module doc says why
+// this copy of `stella-time`'s exists.
 pub mod router;
 pub mod running_task;
 pub mod shell_text;
@@ -37,6 +40,8 @@ pub mod steering;
 pub mod step;
 pub mod subagent;
 mod summarize;
+#[cfg(test)]
+pub(crate) mod tests;
 pub mod waiting;
 
 pub use budget::{BudgetGuard, BudgetOutcome};
diff --git a/crates/stella-core/src/subagent/tests.rs b/crates/stella-core/src/subagent/tests.rs
index c980411cd9..535234a587 100644
--- a/crates/stella-core/src/subagent/tests.rs
+++ b/crates/stella-core/src/subagent/tests.rs
@@ -23,30 +23,10 @@ use super::*;
 use crate::TurnCapabilities;
 use crate::budget::BudgetOutcome;
 use crate::ports::TurnGate;
-use crate::retry::Sleeper;
+pub(crate) use crate::tests::PausedSleeper;
 
 // ---- fakes -----------------------------------------------------------
 
-/// A `Sleeper` on tokio's clock, run paused by every test here: a sleep is
-/// free while the runtime is idle and still lets a pending child finish
-/// first, and `now` reads the same virtual timeline.
-pub(crate) struct TokioSleeper;
-#[async_trait]
-impl Sleeper for TokioSleeper {
-    async fn sleep(&self, duration_ms: u64) {
-        tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
-    }
-
-    fn now(&self) -> std::time::Instant {
-        tokio::time::Instant::now().into_std()
-    }
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// A provider that returns a fixed sequence of results, then errors.
 pub(crate) struct ScriptedProvider {
     script: Mutex>>,
diff --git a/crates/stella-core/src/subagent/tests/failure_and_events.rs b/crates/stella-core/src/subagent/tests/failure_and_events.rs
index 124ed91230..fa60e577bf 100644
--- a/crates/stella-core/src/subagent/tests/failure_and_events.rs
+++ b/crates/stella-core/src/subagent/tests/failure_and_events.rs
@@ -33,7 +33,7 @@ async fn an_aborted_child_salvages_the_last_answer_it_paid_for() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -72,7 +72,7 @@ async fn a_failed_child_never_becomes_an_error_the_parent_has_to_handle() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -106,7 +106,7 @@ async fn nesting_deeper_than_the_cap_is_refused_before_spending() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -149,7 +149,7 @@ async fn the_childs_stage_and_narration_never_reach_the_parents_stream() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -210,7 +210,7 @@ async fn a_childs_metering_records_name_the_child_that_spent_them() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -258,7 +258,7 @@ async fn the_leads_own_calls_name_no_sub_agent() {
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -293,7 +293,7 @@ async fn step_usage_and_tool_activity_reach_the_parent_so_cost_rolls_up() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -358,7 +358,7 @@ async fn a_childs_tool_calls_name_the_child_that_ran_them() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -406,7 +406,7 @@ async fn the_leads_own_tool_calls_name_no_sub_agent() {
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
diff --git a/crates/stella-core/src/subagent/tests/fork_scope.rs b/crates/stella-core/src/subagent/tests/fork_scope.rs
index ed35a2d248..2f12cfdcd5 100644
--- a/crates/stella-core/src/subagent/tests/fork_scope.rs
+++ b/crates/stella-core/src/subagent/tests/fork_scope.rs
@@ -15,7 +15,7 @@ use async_trait::async_trait;
 use stella_protocol::{BudgetMode, CompletionRequestRef, CompletionResult, ProviderError};
 use tokio::sync::mpsc;
 
-use super::{MixedTools, ScriptedProvider, TokioSleeper, text_result, tool_call_result};
+use super::{MixedTools, PausedSleeper, ScriptedProvider, text_result, tool_call_result};
 use crate::subagent::*;
 
 // ---- forked-skill scoping: allowed_tools + effort (#2682) ---------------
@@ -81,7 +81,7 @@ async fn a_grant_scoped_child_cannot_see_or_call_outside_its_grant() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -134,7 +134,7 @@ async fn a_forked_skills_effort_overrides_the_parents_and_absent_inherits() {
         ..EngineConfig::default()
     };
     let seams = TurnCapabilities::none();
-    let parent = Engine::assemble(&parent_provider, &tools, config, &TokioSleeper, seams);
+    let parent = Engine::assemble(&parent_provider, &tools, config, &PausedSleeper, seams);
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
     let (tx, _rx) = mpsc::unbounded_channel();
 
diff --git a/crates/stella-core/src/subagent/tests/seams.rs b/crates/stella-core/src/subagent/tests/seams.rs
index 65f9e6612d..3214a37c95 100644
--- a/crates/stella-core/src/subagent/tests/seams.rs
+++ b/crates/stella-core/src/subagent/tests/seams.rs
@@ -36,7 +36,7 @@ async fn a_child_honors_the_soft_stop_but_never_eats_the_parents_steering() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -111,7 +111,7 @@ async fn a_child_polls_the_parents_pause_gate() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -161,7 +161,7 @@ async fn owned_turn_controls_stop_a_child_without_clobbering_an_attached_gate()
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     )
     .with_turn_controls(&controls);
@@ -220,7 +220,7 @@ fn turn_controls_carrying_both_seams_give_a_child_both() {
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     )
     .with_turn_controls(&both);
@@ -247,7 +247,7 @@ fn empty_turn_controls_leave_an_engine_exactly_as_it_was() {
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     )
     .with_turn_controls(¬hing);
@@ -313,7 +313,7 @@ async fn the_child_claims_its_own_receipt_turn_slot() {
         ..EngineConfig::default()
     };
     let seams = TurnCapabilities::none();
-    let parent = Engine::assemble(&parent_provider, &tools, config, &TokioSleeper, seams);
+    let parent = Engine::assemble(&parent_provider, &tools, config, &PausedSleeper, seams);
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
     let (tx, mut rx) = mpsc::unbounded_channel();
 
@@ -398,7 +398,7 @@ async fn subagent_start_and_stop_hooks_fire_around_a_child_turn() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -484,7 +484,7 @@ async fn a_forked_child_stamps_the_subagent_fork_lane() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
diff --git a/crates/stella-core/src/subagent/tests/spend_and_cancellation.rs b/crates/stella-core/src/subagent/tests/spend_and_cancellation.rs
index 34be303bf3..882ab6756d 100644
--- a/crates/stella-core/src/subagent/tests/spend_and_cancellation.rs
+++ b/crates/stella-core/src/subagent/tests/spend_and_cancellation.rs
@@ -62,7 +62,7 @@ async fn tool_dispatched_child_spend_aborts_the_parent_at_the_next_step_boundary
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut messages = vec![CompletionMessage::user("go")];
@@ -135,7 +135,7 @@ async fn the_drain_is_destructive_so_child_spend_is_never_charged_twice() {
         &provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut messages = vec![CompletionMessage::user("go")];
@@ -244,7 +244,7 @@ async fn a_cancelled_child_closes_its_bracket_with_committed_steps_and_cost() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -379,7 +379,7 @@ async fn a_child_is_bounded_by_the_ceiling_its_whole_run_sits_under() {
             model_timeout: None,
             ..EngineConfig::default()
         },
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -491,7 +491,7 @@ async fn a_ceiling_too_short_refuses_the_whole_spawn_loudly() {
             tool_timeout: Some(Duration::from_secs(30)),
             ..EngineConfig::default()
         },
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
diff --git a/crates/stella-core/src/subagent/tests/tools_and_budget.rs b/crates/stella-core/src/subagent/tests/tools_and_budget.rs
index daae2369a1..39e5f26e5a 100644
--- a/crates/stella-core/src/subagent/tests/tools_and_budget.rs
+++ b/crates/stella-core/src/subagent/tests/tools_and_budget.rs
@@ -15,7 +15,7 @@ async fn a_read_only_child_cannot_execute_a_mutating_tool_even_when_it_tries() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -50,7 +50,7 @@ async fn write_access_is_opt_in_per_spawn() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -82,7 +82,7 @@ async fn child_spend_settles_into_the_parent_exactly_once() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
@@ -124,7 +124,7 @@ async fn an_enforced_carve_stops_the_child_without_touching_the_parents_turn() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Enforced, None, Some(100.0));
@@ -167,7 +167,7 @@ async fn a_child_can_never_be_carved_past_the_parents_remaining_headroom() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Enforced, None, Some(1.0));
@@ -214,7 +214,7 @@ async fn an_enforced_parent_with_no_headroom_refuses_before_spending_anything()
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Enforced, None, Some(1.0));
diff --git a/crates/stella-core/src/subagent/tests/witness.rs b/crates/stella-core/src/subagent/tests/witness.rs
index 7c0703a69f..a36615fdaa 100644
--- a/crates/stella-core/src/subagent/tests/witness.rs
+++ b/crates/stella-core/src/subagent/tests/witness.rs
@@ -30,7 +30,7 @@ async fn the_parent_transcript_does_not_grow_by_the_childs_intermediate_work() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
 
@@ -139,7 +139,7 @@ async fn a_child_turn_never_writes_or_clears_the_parents_resume_point() {
         ..EngineConfig::default()
     };
     let seams = TurnCapabilities::none();
-    let parent = Engine::assemble(&parent_provider, &tools, config, &TokioSleeper, seams);
+    let parent = Engine::assemble(&parent_provider, &tools, config, &PausedSleeper, seams);
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
     let (tx, mut rx) = mpsc::unbounded_channel();
 
@@ -188,7 +188,7 @@ async fn the_report_is_clamped_to_the_spec_cap_and_says_so() {
         &parent_provider,
         &tools,
         EngineConfig::default(),
-        &TokioSleeper,
+        &PausedSleeper,
         seams,
     );
     let mut budget = BudgetGuard::new(BudgetMode::Observed, None, None);
diff --git a/crates/stella-core/src/tests.rs b/crates/stella-core/src/tests.rs
new file mode 100644
index 0000000000..a80e9f9d29
--- /dev/null
+++ b/crates/stella-core/src/tests.rs
@@ -0,0 +1,66 @@
+//! The two [`Sleeper`] doubles this crate's own unit tests share.
+//!
+//! They are the same two `stella-time` ships behind its `test-util`
+//! feature. Every other crate takes them from there. This crate's unit
+//! tests cannot. A lib's unit tests are a second build of the lib. A
+//! dev-dependency that links the lib links the first build, so its
+//! `impl Sleeper` is for a trait these tests do not see. The integration
+//! tests under `tests/` are separate crates and use `stella-time`'s.
+//!
+//! So this is the one copy the compiler forces, and `stella-time`'s
+//! `one_home` test names it as such.
+
+use std::time::Instant;
+
+use async_trait::async_trait;
+
+use crate::retry::Sleeper;
+
+/// A [`Sleeper`] on tokio's clock, for a test that runs its runtime paused.
+///
+/// A sleep costs nothing while the runtime is idle. It still lets a pending
+/// call finish first, and `now` reads the same virtual timeline. That is
+/// what a timeout is. So this is the double for any test that arms a tool
+/// or model timeout. A sleeper that returned at once would fire every
+/// engine timeout as soon as a provider future waited on another task.
+///
+/// Jitter is zero: a test that asserts on retry timing wants no spread.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct PausedSleeper;
+
+#[async_trait]
+impl Sleeper for PausedSleeper {
+    async fn sleep(&self, duration_ms: u64) {
+        tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
+    }
+
+    fn now(&self) -> Instant {
+        tokio::time::Instant::now().into_std()
+    }
+
+    fn jitter(&self, _upper: u64) -> u64 {
+        0
+    }
+}
+
+/// A [`Sleeper`] that never waits.
+///
+/// For a test that arms no timeout and wants a retry ladder to run at once.
+/// `now` reads the real monotonic clock, so a deadline set from it still
+/// means what it says. Do not drive a timed turn with it: see
+/// [`PausedSleeper`] for why.
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct NoopSleeper;
+
+#[async_trait]
+impl Sleeper for NoopSleeper {
+    async fn sleep(&self, _duration_ms: u64) {}
+
+    fn now(&self) -> Instant {
+        Instant::now()
+    }
+
+    fn jitter(&self, _upper: u64) -> u64 {
+        0
+    }
+}
diff --git a/crates/stella-core/tests/engine_emits_no_stage.rs b/crates/stella-core/tests/engine_emits_no_stage.rs
index 519c75f926..35233c148f 100644
--- a/crates/stella-core/tests/engine_emits_no_stage.rs
+++ b/crates/stella-core/tests/engine_emits_no_stage.rs
@@ -23,27 +23,12 @@ use serde_json::Value;
 use stella_core::budget::BudgetGuard;
 use stella_core::event_sender::EventSender;
 use stella_core::ports::ToolExecutor;
-use stella_core::retry::Sleeper;
 use stella_core::{Engine, EngineConfig, TurnCapabilities};
 use stella_protocol::{
     AgentEvent, BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult,
     CompletionUsage, Provider, ProviderError, ToolOutput, ToolSchema,
 };
-
-struct NoopSleeper;
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
+use stella_time::test_util::NoopSleeper;
 
 /// Answers once, with text and no tool calls, so the turn completes in one
 /// step — the shortest turn that still passes through the whole framing.
diff --git a/crates/stella-core/tests/hard_drop_write_back.rs b/crates/stella-core/tests/hard_drop_write_back.rs
index a545d371e3..ddf28db08a 100644
--- a/crates/stella-core/tests/hard_drop_write_back.rs
+++ b/crates/stella-core/tests/hard_drop_write_back.rs
@@ -22,34 +22,12 @@ use serde_json::Value;
 use stella_core::budget::BudgetGuard;
 use stella_core::event_sender::EventSender;
 use stella_core::ports::ToolExecutor;
-use stella_core::retry::Sleeper;
 use stella_core::{Engine, EngineConfig, TurnCapabilities};
 use stella_protocol::{
     BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult, CompletionUsage,
     MessageRole, Provider, ProviderError, ToolCall, ToolOutput, ToolSchema,
 };
-
-/// A `Sleeper` on tokio's clock. The tool below hangs for an hour, and the
-/// engine's tool timeout is that same clock racing it: a sleeper that
-/// returned at once would time the tool out on its first poll and the turn
-/// this test drops mid-tool would already be over.
-struct TokioSleeper;
-#[async_trait]
-impl Sleeper for TokioSleeper {
-    async fn sleep(&self, duration_ms: u64) {
-        tokio::time::sleep(Duration::from_millis(duration_ms)).await;
-    }
-
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
+use stella_time::test_util::PausedSleeper;
 
 /// Always answers with the same single tool call, so the turn reaches tool
 /// dispatch and parks there.
@@ -108,7 +86,7 @@ impl ToolExecutor for WedgedTool {
 async fn dropping_a_turn_mid_tool_still_leaves_the_partial_history_with_the_caller() {
     let provider = AlwaysCallsATool;
     let tools = WedgedTool;
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
 
diff --git a/crates/stella-core/tests/parallel_dispatch.rs b/crates/stella-core/tests/parallel_dispatch.rs
index 73b2052064..bdc081f6c5 100644
--- a/crates/stella-core/tests/parallel_dispatch.rs
+++ b/crates/stella-core/tests/parallel_dispatch.rs
@@ -25,35 +25,14 @@ use async_trait::async_trait;
 use serde_json::Value;
 use stella_core::budget::BudgetGuard;
 use stella_core::ports::ToolExecutor;
-use stella_core::retry::Sleeper;
 use stella_core::{Engine, EngineConfig, TurnCapabilities, TurnOutcome};
 use stella_protocol::{
     BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult, CompletionUsage,
     Provider, ProviderError, ToolCall, ToolOutput, ToolSchema,
 };
+use stella_time::test_util::PausedSleeper;
 use tokio::sync::mpsc;
 
-/// A `Sleeper` on tokio's clock. The engine's tool timeout is this sleep racing
-/// the dispatch, and the barrier test below needs a parked tool to stay
-/// parked: a sleeper that returned at once would time it out on the first
-/// poll and hand the test a completion it must not see.
-struct TokioSleeper;
-#[async_trait]
-impl Sleeper for TokioSleeper {
-    async fn sleep(&self, duration_ms: u64) {
-        tokio::time::sleep(std::time::Duration::from_millis(duration_ms)).await;
-    }
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// First call: one step carrying two sibling `delegate` calls. Second call: done.
 struct TwoSpawnsThenDone {
     calls: std::sync::atomic::AtomicU32,
@@ -140,7 +119,7 @@ async fn sibling_delegate_calls_in_one_step_execute_concurrently() {
     let tools = BarrierSpawns {
         barrier: tokio::sync::Barrier::new(2),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
@@ -251,7 +230,7 @@ async fn a_mutating_call_between_spawns_keeps_its_barrier() {
     let tools = BarrierSpawnsAndEdit {
         barrier: tokio::sync::Barrier::new(2),
     };
-    let sleeper = TokioSleeper;
+    let sleeper = PausedSleeper;
     let seams = TurnCapabilities::none();
     let engine = Engine::assemble(&provider, &tools, EngineConfig::default(), &sleeper, seams);
     let mut messages = vec![
diff --git a/crates/stella-core/tests/spend_gate.rs b/crates/stella-core/tests/spend_gate.rs
index afe3dd7178..c743be30ae 100644
--- a/crates/stella-core/tests/spend_gate.rs
+++ b/crates/stella-core/tests/spend_gate.rs
@@ -33,12 +33,13 @@ use stella_core::hooks::{
     HookAction, HookExecError, HookExecResult, HookMatcher, HookRunner, Hooks,
 };
 use stella_core::ports::{FallbackResolver, ResolvedFallback, ToolExecutor};
-use stella_core::retry::{ParkPlan, Sleeper, plan_park};
+use stella_core::retry::{ParkPlan, plan_park};
 use stella_core::{Engine, EngineConfig, TurnCapabilities, TurnOutcome};
 use stella_protocol::{
     BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult, CompletionUsage,
     Provider, ProviderError, ToolCall, ToolOutput, ToolSchema,
 };
+use stella_time::test_util::NoopSleeper;
 use tokio::sync::Mutex as TokioMutex;
 use tokio::sync::mpsc;
 
@@ -58,22 +59,6 @@ struct Spend {
     cost_usd: f64,
 }
 
-/// A `Sleeper` that never waits, so a retry ladder costs no test time.
-struct NoopSleeper;
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// A scripted `Provider`: one entry per call, repeating the last entry once
 /// the script runs out. Counts every attempt it is handed.
 ///
diff --git a/crates/stella-core/tests/tool_wall_clock.rs b/crates/stella-core/tests/tool_wall_clock.rs
index fa5a79996d..642e737762 100644
--- a/crates/stella-core/tests/tool_wall_clock.rs
+++ b/crates/stella-core/tests/tool_wall_clock.rs
@@ -26,29 +26,14 @@ use async_trait::async_trait;
 use serde_json::Value;
 use stella_core::budget::BudgetGuard;
 use stella_core::ports::ToolExecutor;
-use stella_core::retry::Sleeper;
 use stella_core::{Engine, EngineConfig, TurnCapabilities, TurnOutcome};
 use stella_protocol::{
     AgentEvent, BudgetMode, CompletionMessage, CompletionRequestRef, CompletionResult,
     CompletionUsage, Provider, ProviderError, ToolCall, ToolOutput, ToolSchema,
 };
+use stella_time::test_util::NoopSleeper;
 use tokio::sync::mpsc;
 
-struct NoopSleeper;
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// First call: one long shell command. Second call: an answer. The turn only
 /// gets there because a refusal is a result it can go on from.
 struct OneShellCallThenDone {
diff --git a/crates/stella-engine/Cargo.toml b/crates/stella-engine/Cargo.toml
index 9fd69f757b..d096a32210 100644
--- a/crates/stella-engine/Cargo.toml
+++ b/crates/stella-engine/Cargo.toml
@@ -18,5 +18,7 @@ serde_json = { workspace = true }
 tokio = { workspace = true, features = ["sync"] }
 
 [dev-dependencies]
+# The shared sleeper doubles (`NoopSleeper`, `PausedSleeper`).
+stella-time = { path = "../stella-time", features = ["test-util"] }
 tokio = { workspace = true, features = ["macros", "test-util"] }
 async-trait = { workspace = true }
diff --git a/crates/stella-engine/src/tests.rs b/crates/stella-engine/src/tests.rs
index 633fa65212..a45c533e33 100644
--- a/crates/stella-engine/src/tests.rs
+++ b/crates/stella-engine/src/tests.rs
@@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
 
 use async_trait::async_trait;
 use serde_json::Value;
+use stella_time::test_util::NoopSleeper;
 use tokio::sync::mpsc;
 
 // Everything through `crate::`, including the three completion types that used
@@ -19,26 +20,10 @@ use tokio::sync::mpsc;
 use crate::{
     AgentEvent, BudgetGuard, BudgetMode, CANCELLED_REASON, CancelToken, CompletionMessage,
     CompletionRequestRef, CompletionResult, CompletionUsage, Engine, EngineConfig, EventSender,
-    MessageRole, Provider, ProviderError, Sleeper, StepOutcome, ToolCall, ToolExecutor, ToolOutput,
+    MessageRole, Provider, ProviderError, StepOutcome, ToolCall, ToolExecutor, ToolOutput,
     ToolSchema, TurnCapabilities, TurnOutcome,
 };
 
-/// A `Sleeper` that records but never actually waits.
-struct NoopSleeper;
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// A scripted provider: one response per call, in order, looping the last
 /// entry once exhausted so a runaway loop fails loudly rather than panicking.
 struct ScriptedProvider {
@@ -171,9 +156,9 @@ fn event_type(event: &AgentEvent) -> String {
         .unwrap_or_default()
 }
 
-/// Context receipts are the one thing a resumed turn re-emits, because the
-/// block registry is a per-turn memo a `Checkpoint` does not
-/// carry (see `stella_core::step`). Compared separately, never mixed in.
+/// Context receipts are the one thing a resumed turn re-emits. The block
+/// registry is a per-turn memo, and a `Checkpoint` does not carry it (see
+/// `stella_core::step`). They are compared on their own, never mixed in.
 fn is_receipt(event: &AgentEvent) -> bool {
     matches!(
         event_type(event).as_str(),
@@ -432,9 +417,9 @@ async fn a_turn_resumed_from_a_checkpoint_emits_the_same_downstream_events() {
         "the downstream event stream must be indistinguishable from the un-interrupted run"
     );
 
-    // The one documented difference, asserted rather than assumed: the block
-    // registry is a per-turn memo the checkpoint drops, so a resumed turn
-    // re-registers the blocks the reference run had already seen.
+    // The one documented difference, asserted here. The block registry is a
+    // per-turn memo the checkpoint drops, so a resumed turn re-registers the
+    // blocks the reference run had already seen.
     let reference_receipts = reference[1].iter().filter(|e| is_receipt(e)).count();
     let resumed_receipts = resumed_events.iter().filter(|e| is_receipt(e)).count();
     assert!(
diff --git a/crates/stella-engine/tests/embedding.rs b/crates/stella-engine/tests/embedding.rs
index be577efde7..9315bcbe03 100644
--- a/crates/stella-engine/tests/embedding.rs
+++ b/crates/stella-engine/tests/embedding.rs
@@ -41,14 +41,15 @@ use std::sync::Arc;
 use std::sync::Mutex;
 
 use async_trait::async_trait;
+use stella_time::test_util::NoopSleeper;
 use tokio::sync::mpsc;
 
 use stella_engine::{
     AbortKind, AgentEvent, BudgetGuard, BudgetMode, CheckpointSink, CompletionMessage,
     CompletionRequestRef, CompletionResult, CompletionUsage, DispatchAdmission, DispatchGate,
-    Engine, EngineConfig, LiveService, Provider, ProviderError, RECALL_MARKER, Sleeper,
-    SteeringRequery, ToolCall, ToolContract, ToolExecutor, ToolOutput, ToolSchema,
-    TurnCapabilities, TurnOutcome, TurnSignal, WaitCall, WaitRequest, admit_dispatch,
+    Engine, EngineConfig, LiveService, Provider, ProviderError, RECALL_MARKER, SteeringRequery,
+    ToolCall, ToolContract, ToolExecutor, ToolOutput, ToolSchema, TurnCapabilities, TurnOutcome,
+    TurnSignal, WaitCall, WaitRequest, admit_dispatch,
 };
 
 /// The one tool the host advertises. Its only job is to make the model's first
@@ -304,22 +305,6 @@ async fn a_host_can_wrap_its_tool_surface_without_dropping_what_the_port_forward
     );
 }
 
-struct NoopSleeper;
-
-#[async_trait]
-impl Sleeper for NoopSleeper {
-    async fn sleep(&self, _duration_ms: u64) {}
-
-    // The floor: a test that asserts on retry timing wants no spread in it.
-    fn now(&self) -> std::time::Instant {
-        std::time::Instant::now()
-    }
-
-    fn jitter(&self, _upper: u64) -> u64 {
-        0
-    }
-}
-
 /// What one consult of the re-query port was shown, owned so the port can keep
 /// it past the borrowed [`TurnSignal`]'s lifetime.
 #[derive(Debug, Clone)]
diff --git a/crates/stella-time/README.md b/crates/stella-time/README.md
index f7430272fa..55e5c50e91 100644
--- a/crates/stella-time/README.md
+++ b/crates/stella-time/README.md
@@ -55,13 +55,12 @@ its own copy, and about thirty test files each kept a double.
 
 `test_util` lives here and not in `stella-core` for the same reason: a
 faithful sleeper double needs the Tokio timer, and `stella-core` may not
-link it. A crate that tests the engine takes this crate as a dev-dependency
-with the feature on; for `stella-core`'s integration tests that is a
-cycle, which cargo allows. That is the tokio / tokio-test shape.
-`stella-core`'s own unit tests are the one place that cannot: a lib's unit
-tests are a second build of the lib, and a dev-dependency that links the
-lib implements the trait for the first. They keep one copy, and
-`tests/one_home.rs` names it and fails on any other.
+link it. `stella-core`'s integration tests take this crate as a
+dev-dependency, which cargo allows in a cycle. That is the tokio /
+tokio-test shape. `stella-core`'s own unit tests keep one copy,
+`tests`, because a lib's unit tests are a second build of the lib
+and a dev-dependency that links the lib implements the trait for the
+first. `tests/one_home.rs` names that copy and fails on any other.
 
 ## God files — do not add lines
 
@@ -78,3 +77,4 @@ it crosses.
   extension bus from `WallClock`.
 - `stella-runtime` stamps wrapper verdicts from `WallClock`.
 - `stella-fleet` paces its CI monitor with `TokioSleeper`.
+- `stella-core` and `stella-engine` test against `test_util`.
diff --git a/crates/stella-time/tests/one_home.rs b/crates/stella-time/tests/one_home.rs
index eb629523d2..32700f0da0 100644
--- a/crates/stella-time/tests/one_home.rs
+++ b/crates/stella-time/tests/one_home.rs
@@ -5,21 +5,22 @@
 //! doubles live here and nowhere else.
 //!
 //! Three crates each kept a wall clock. Two kept a Tokio sleeper. One kept a
-//! sleeper trait of its own. This test reads the shipping tree and fails on
-//! the first copy that comes back. It reads source, which a test of "where
-//! does this live" has to do. `stella-core` would refuse that read. This
-//! crate does not.
-//!
-//! Test files are skipped here: the per-file test doubles are the next
-//! sweep, and it widens this scan to them.
+//! sleeper trait of its own. About thirty test files each wrote the same
+//! no-op double. This test reads the tree and fails on the first copy that
+//! comes back. It reads source, which a test of "where does this live" has
+//! to do. `stella-core` would refuse that read. This crate does not.
 
 use std::fs;
 use std::path::{Path, PathBuf};
 
-/// A `Sleeper` impl in shipping source outside this crate that stays on
-/// purpose, with the reason. Each one is a test double with a shape a shared
-/// double cannot have, kept in the file its one test lives in.
+/// A `Sleeper` impl outside this crate that stays on purpose, with the
+/// reason. Each one has a shape a shared double cannot have.
 const SLEEPER_IMPLS_KEPT: &[(&str, &str)] = &[
+    (
+        "crates/stella-core/src/tests.rs",
+        "the copy the compiler forces: a lib's unit tests are a second build of the lib, and a \
+         dev-dependency that links the lib implements Sleeper for the first",
+    ),
     (
         "crates/stella-core/src/retry.rs",
         "the retry tests' recording sleeper: it logs every requested delay and draws a seeded jitter",
@@ -29,24 +30,12 @@ const SLEEPER_IMPLS_KEPT: &[(&str, &str)] = &[
         "the monitor tests' advancing sleeper: a sleep moves the injected clock instead of waiting",
     ),
     (
-        "crates/stella-core/src/accounted_call.rs",
-        "two doubles beside the accounted-call tests; the test-double sweep retires them",
-    ),
-    (
-        "crates/stella-core/src/driver/dispatch.rs",
-        "a no-op double beside the dispatch tests; the test-double sweep retires it",
-    ),
-    (
-        "crates/stella-core/src/driver/drive.rs",
-        "a no-op double beside the drive tests; the test-double sweep retires it",
+        "crates/stella-core/src/driver/tests/audit_fixes.rs",
+        "the hanging sleeper: it announces its first sleep and then parks forever, for a test that drops a turn mid-backoff",
     ),
     (
-        "crates/stella-core/src/driver/restore.rs",
-        "a no-op double beside the restore tests; the test-double sweep retires it",
-    ),
-    (
-        "crates/stella-core/src/goal.rs",
-        "a no-op double beside the goal tests; the test-double sweep retires it",
+        "crates/stella-core/src/step/tests.rs",
+        "a sleeper on unpaused tokio time: the bound tests race a trickling call against a sleep that has to take time",
     ),
 ];
 
@@ -74,28 +63,20 @@ fn rust_sources(root: &Path) -> Vec<(String, String)> {
         if dir.file_name().is_some_and(|name| name == "stella-time") {
             continue;
         }
-        collect(&dir.join("src"), root, &mut out);
+        for sub in ["src", "tests"] {
+            collect(&dir.join(sub), root, &mut out);
+        }
     }
     out.sort();
     out
 }
 
-/// A test file, by the names `make core-no-io` uses: a `tests/` directory
-/// or a file called `tests.rs`.
-fn is_test_path(path: &Path) -> bool {
-    path.file_name().is_some_and(|name| name == "tests.rs")
-        || path.components().any(|part| part.as_os_str() == "tests")
-}
-
 fn collect(dir: &Path, root: &Path, out: &mut Vec<(String, String)>) {
     let Ok(entries) = fs::read_dir(dir) else {
         return;
     };
     for entry in entries {
         let path = entry.expect("a directory entry").path();
-        if is_test_path(&path) {
-            continue;
-        }
         if path.is_dir() {
             collect(&path, root, out);
         } else if path.extension().is_some_and(|ext| ext == "rs") {
diff --git a/docs/adr/0042-the-engine-reads-time-through-the-sleeper-port.md b/docs/adr/0042-the-engine-reads-time-through-the-sleeper-port.md
index c1aa003266..dcbfd14c3a 100644
--- a/docs/adr/0042-the-engine-reads-time-through-the-sleeper-port.md
+++ b/docs/adr/0042-the-engine-reads-time-through-the-sleeper-port.md
@@ -82,9 +82,9 @@ test that arms none. They live beside the real sources and not in
 `stella-core` may not link. `stella-core`'s integration tests and every
 other crate take it as a dev-dependency, which cargo allows in a cycle.
 That is the tokio / tokio-test shape. `stella-core`'s own unit tests keep
-one copy in `src/tests.rs`, because a lib's unit tests are a second build
-of the lib and a dev-dependency that links the lib implements the trait
-for the first. The compiler forces that copy, and the witness names it.
+one copy, `tests`, because a lib's unit tests are a second build of
+the lib and a dev-dependency that links the lib implements the trait for
+the first. The compiler forces that copy, and the witness names it.
 
 ## Consequences