From 0b2e703d59dfcd0ef031bb947f3cd97580ce58e6 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 3 Jun 2026 19:18:54 -0400 Subject: [PATCH 1/5] Pearl th-1b9b3e: bump Big Smooth idle timeout from 30 min to 24 hours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-caused the "Big Smooth crashes unprompted" symptom: it wasn't crashing — it was gracefully shutting down on a 30-minute idle timer (server.rs:600+). Bench evidence today showed the 30-minute cliff fired after almost every /loop pause (1800s wakeup intervals), killing the daemon mid-session and forcing 3+ manual `th up`s. Pi + OpenCode have no daemon → no auto-shutdown → no "crashed unprompted" symptom. Smooth's daemon model meant every loop pause was implicitly a kill. This is exactly the kind of competitive- parity gap the bench was designed to surface (user direction 2026-06-03: "i want smooth to learn from pi and opencode and make smooth competitive"). 24h default keeps a safety net for genuinely forgotten dev sessions but doesn't fire during a single work session. The existing `SMOOTH_BIGSMOOTH_IDLE_TIMEOUT_SECS` env override still works (set to 0 to disable, or to a smaller value to opt back in to aggressive timeouts) — caveat that the env must be set in the daemon's process, which in sandboxed mode is the safehouse VM (not the host shell). Smoking-gun log line that closed the diagnosis: 2026-06-03T21:07:17 INFO smooth_bigsmooth::server: Idle timeout reached (1800s), shutting down Required rebuild path: scripts/build-safehouse.sh + cp the new binary to ~/.smooth/runner-bin/safehouse, then th down + th up. The shadow-bin mechanism (smooth-cli/src/main.rs:1292) bind-mounts this over the OCI image's safehouse binary so dev iteration on crates/smooth-bigsmooth doesn't need a full image push. --- crates/smooth-bigsmooth/src/server.rs | 68 ++++++++++++++++++++------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/crates/smooth-bigsmooth/src/server.rs b/crates/smooth-bigsmooth/src/server.rs index 3eacc009..da9dc97e 100644 --- a/crates/smooth-bigsmooth/src/server.rs +++ b/crates/smooth-bigsmooth/src/server.rs @@ -21,8 +21,30 @@ use tower_http::trace::TraceLayer; use crate::events::{ClientEvent, ServerEvent}; -/// Default idle timeout: 30 minutes. -const DEFAULT_IDLE_TIMEOUT_SECS: u64 = 30 * 60; +/// Default idle timeout: 24 hours. +/// +/// Was 30 minutes. Bumped under pearl `th-1b9b3e` after bench evidence +/// showed Big Smooth was silently shutting itself down mid-session — +/// pi + opencode (the bench's reference backends) have no daemon and +/// therefore no auto-shutdown, so smooth's 30-min cliff was a +/// competitive-parity loss masquerading as a "crashes unprompted" +/// symptom. +/// +/// 24h keeps a safety net for forgotten-running dev sessions but +/// doesn't fire during a single work session. Override at boot via +/// `SMOOTH_BIGSMOOTH_IDLE_TIMEOUT_SECS=` (set to `0` to +/// disable entirely; only honored when set in the daemon process's +/// own environment, which in sandboxed mode is the safehouse VM — +/// see project memory on env propagation). +const DEFAULT_IDLE_TIMEOUT_SECS: u64 = 24 * 60 * 60; + +/// Read the idle-timeout env override. `None` = use default. `Some(0)` +/// = disabled (timeout never fires). +fn idle_timeout_from_env() -> Option { + let raw = std::env::var("SMOOTH_BIGSMOOTH_IDLE_TIMEOUT_SECS").ok()?; + let secs: u64 = raw.parse().ok()?; + Some(Duration::from_secs(secs)) +} /// Default broadcast channel capacity. const BROADCAST_CHANNEL_CAPACITY: usize = 256; @@ -289,7 +311,7 @@ impl AppState { session_store, start_time: Instant::now(), last_activity: Arc::new(Mutex::new(Instant::now())), - idle_timeout: Duration::from_secs(DEFAULT_IDLE_TIMEOUT_SECS), + idle_timeout: idle_timeout_from_env().unwrap_or_else(|| Duration::from_secs(DEFAULT_IDLE_TIMEOUT_SECS)), event_tx, safehouse: None, diver: None, @@ -597,23 +619,33 @@ pub async fn start(mut state: AppState, addr: SocketAddr) -> anyhow::Result<()> } } - // Spawn idle timeout checker - let idle_state = state.clone(); - tokio::spawn(async move { - loop { - tokio::time::sleep(Duration::from_secs(60)).await; - let elapsed = { - let Ok(last) = idle_state.last_activity.lock() else { - continue; + // Spawn idle timeout checker (pearl th-1b9b3e). Skip entirely when + // the timeout is zero — bench harness + long-running dev sessions + // set `SMOOTH_BIGSMOOTH_IDLE_TIMEOUT_SECS=0` to opt out of the + // 30-min auto-shutdown. Pi + OpenCode (the bench's reference + // backends) have no daemon timeout because they have no daemon — + // smooth's daemon model means every loop pause auto-killed the + // process before this knob existed. + if state.idle_timeout.is_zero() { + tracing::info!("Idle timeout disabled (SMOOTH_BIGSMOOTH_IDLE_TIMEOUT_SECS=0)"); + } else { + let idle_state = state.clone(); + tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + let elapsed = { + let Ok(last) = idle_state.last_activity.lock() else { + continue; + }; + last.elapsed() }; - last.elapsed() - }; - if elapsed > idle_state.idle_timeout { - tracing::info!("Idle timeout reached ({:.0}s), shutting down", idle_state.idle_timeout.as_secs_f64()); - std::process::exit(0); + if elapsed > idle_state.idle_timeout { + tracing::info!("Idle timeout reached ({:.0}s), shutting down", idle_state.idle_timeout.as_secs_f64()); + std::process::exit(0); + } } - } - }); + }); + } // Spawn orchestrator loop — continuously picks up ready pearls and // dispatches operators. Skipped in direct mode: the orchestrator From 69c3fc788e7fe0901e9e1c0e9ecffac9444176e4 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 3 Jun 2026 19:36:02 -0400 Subject: [PATCH 2/5] docs: Big Smooth Direct vs Sandboxed mode (closes pearl th-0fc29f) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the existing `th up direct` mode that we'd been overlooking: boots in ~0.3s on the host instead of the ~30s safehouse-microVM startup. That's pi/opencode-parity boot time (they're both ~3s). Bench evidence from today: smooth-direct : 0.850 aggregate, ~0.3s boot smooth-sandboxed : 0.789 aggregate, ~30s boot (with variance) pi : 1.000 aggregate, ~3s boot opencode : >=0.93 aggregate, ~3s boot Direct mode trade-off is no isolation — the agent runs as a host subprocess against the host filesystem. Fine for dev machines + CI runners you own + bench harnesses. Sandboxed remains the default for untrusted dispatch. Required setup for direct mode (the runner-discovery error message already tells you, but worth surfacing in docs): cargo build --release -p smooai-smooth-operator-runner SMOOTH_OPERATOR_RUNNER_NATIVE=~/.cargo/shared-target/release/smooth-operator-runner th up direct Pearl follow-ups still open after this: th-6e361d — pycache run-to-run variance (direct still showed 0.500 on disk-bloat in one run; smooth's nondeterminism isn't purely a sandbox artifact) th-e74aa6 — runner-discovery UX paper-cut (separate from this work) --- docs/Engineering/Big-Smooth-Direct-Mode.md | 78 ++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/Engineering/Big-Smooth-Direct-Mode.md diff --git a/docs/Engineering/Big-Smooth-Direct-Mode.md b/docs/Engineering/Big-Smooth-Direct-Mode.md new file mode 100644 index 00000000..d4fbd16c --- /dev/null +++ b/docs/Engineering/Big-Smooth-Direct-Mode.md @@ -0,0 +1,78 @@ +# Big Smooth — Direct (host) vs Sandboxed (safehouse VM) mode + +Big Smooth runs in one of two modes. Choose at `th up` time. + +| | `th up` (sandboxed, default) | `th up direct` | +|---|---|---| +| **Boot time** | ~30s — boots a safehouse microVM + the in-VM cast | **~0.3s** — daemon starts directly on the host | +| **Isolation** | Strong — agent runs inside a microVM, safehouse mediates filesystem/network | None — agent is a host subprocess; tools execute against the host filesystem | +| **When to use** | Untrusted code, agent dispatches you don't fully control, CI runners that need defense in depth | Pre-trusted environments — dedicated devbox, CI runner you own, bench harnesses | +| **Idle timeout default** | 24 h (was 30 min — pearl `th-1b9b3e`) | 24 h | +| **Native runner needed?** | No — runner is baked into the safehouse OCI image | **Yes** — build with `cargo build --release -p smooai-smooth-operator-runner` and either auto-discovery picks it up from `~/.cargo/shared-target/release/smooth-operator-runner`, or you set `SMOOTH_OPERATOR_RUNNER_NATIVE=/abs/path/to/runner` before `th up direct` | + +## Why this matters for parity with pi + opencode + +Pi (`@earendil-works/pi-coding-agent`) and OpenCode (`opencode`) both boot in ~3s +and have no daemon model. Smooth's sandboxed default looked like a "30s boot, +sometimes crashes" agent against them. Direct mode is a near-100× boot speedup +that brings smooth into the same launch-time class as pi + opencode for +pre-trusted use cases (dev machines, bench harnesses). + +## Smoke test + +```bash +# Build the native runner once per checkout. +cargo build --release -p smooai-smooth-operator-runner + +# Start in direct mode. +th down +SMOOTH_OPERATOR_RUNNER_NATIVE=~/.cargo/shared-target/release/smooth-operator-runner \ + th up direct + +# Confirm: th status should report healthy in under a second. +th status +``` + +The runner-bin auto-discovery has a paper-cut tracked under pearl `th-e74aa6`: +when the env var is unset the error message names the build command but doesn't +mention that auto-discovery from `~/.cargo/shared-target/release/` will work if +you've built it. Either approach gets you there. + +## Bench harness usage + +`smooth-bench` doesn't care which mode Big Smooth is in — both expose the same +HTTP API at `localhost:4400`. The `SmoothDriver` in +`crates/smooth-bench/src/agent_driver.rs` just spawns `th code` against the +running daemon. So: + +```bash +# Sandboxed mode (default — slow boot, more isolation) +th up +cargo run -p smooai-smooth-bench -- score-cleanup --driver=smooth … + +# Direct mode (fast boot, host trust) +th down +SMOOTH_OPERATOR_RUNNER_NATIVE=~/.cargo/shared-target/release/smooth-operator-runner th up direct +cargo run -p smooai-smooth-bench -- score-cleanup --driver=smooth … +``` + +Result JSON includes `dispatch="direct"` or `dispatch="sandboxed"` in the daemon +log (`~/.smooth/log/th.log`) so post-hoc you can tell which mode each result +came from. + +## Recent bench numbers + +`deepseek-v4-flash` via `llm.smoo.ai`, strict coach, 4 cleanup fixtures +(`cleanup-impossible-task`, `cleanup-pycache-debris`, `cleanup-disk-bloat`, +`cleanup-node-modules-orphans`): + +| backend | aggregate | boot time | notes | +|---|---|---|---| +| mock | 1.000 | n/a | bash baseline | +| **pi** | **1.000** | ~3s | new reference high-water | +| opencode | ≥0.93 | ~3s | reliable on tested fixtures | +| **smooth-direct** | **0.850** | **~0.3s** | beats sandboxed; matches pi boot time | +| smooth-sandboxed | 0.789 | ~30s | run-to-run variance still present | + +Pearls related: `th-0fc29f` (boot time, this doc closes it), +`th-1b9b3e` (idle timeout, closed), `th-6e361d` (pycache variance, open). From c1b6d50b75619719323a458481b94cf485345b77 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 3 Jun 2026 20:18:38 -0400 Subject: [PATCH 3/5] Pearl th-20574a: --model flag now routes through to TaskStart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TUI path silently dropped `th code --model `. `cmd_code` parsed the flag into a local `model: Option`, then called `run_with_session(working_dir, resumed_session, agent)` — no model. Every TaskStart fell back to the smooth-coding alias regardless of what the user asked for. Bench evidence: the harness passed `--model deepseek-v4-flash`, the dispatch log read `model=smooth-fast-gemini`. Pi + OpenCode were verified at deepseek-v4-flash on the same fixtures, so the comparison wasn't apples-to-apples on model. Fix: - Add `model_override: Option` to AppState (separate from `model_name` which is display-only). - Thread `model: Option` through `run_with_session`, setting `initial_state.model_override` when supplied. - In `run_agent_streaming`, read `state.model_override` and pass it to `client.run_task(...)` instead of the literal `None`. - `run()` callsite gets `None` for backwards compatibility (no --model means Big Smooth picks the default, same as before). Confirmed in the daemon log after the fix: direct dispatch: overrode routing.coding.model from opts.model model=deepseek-v4-flash That's the line that wasn't appearing before because routing always got `opts.model = None`. This UNBLOCKS th-6e361d (pycache variance) — now we can measure smooth's agent behavior at the same model pi + opencode use, isolating whether remaining variance is the model or the prompt / workflow. First post-fix smooth-deepseek run on pycache still scored 0.500 (agent confabulated test-fix narrative), so the remaining gap is now clearly the fixer system prompt (th-e5a0e5 needs deeper restructure — the end-of-file addendum isn't enough). --- crates/smooth-cli/src/main.rs | 6 +++++- crates/smooth-code/src/app.rs | 23 ++++++++++++++++++++--- crates/smooth-code/src/state.rs | 12 ++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/crates/smooth-cli/src/main.rs b/crates/smooth-cli/src/main.rs index 1cf13a2f..55ed1e33 100644 --- a/crates/smooth-cli/src/main.rs +++ b/crates/smooth-cli/src/main.rs @@ -3190,7 +3190,11 @@ async fn cmd_code( // the classifier per-message. let working_dir = std::env::current_dir()?; let _ = agent_name; // keep the typo-validation call; value isn't used in TUI mode - smooth_code::app::run_with_session(working_dir, resumed_session, agent).await + // Pearl th-20574a: thread the user's --model flag into the TUI + // path. Before this, `model` was parsed by clap then silently + // dropped here — every TaskStart picked the default smooth-coding + // alias regardless of what the user asked for. + smooth_code::app::run_with_session(working_dir, resumed_session, agent, model).await } fn cmd_hooks(cmd: HooksCommands) -> Result<()> { diff --git a/crates/smooth-code/src/app.rs b/crates/smooth-code/src/app.rs index 2a15911f..a88092e2 100644 --- a/crates/smooth-code/src/app.rs +++ b/crates/smooth-code/src/app.rs @@ -102,7 +102,7 @@ fn write_bench_cost_sidecar_to(path: &std::path::Path, total_cost_usd: f64, iter /// thread holding the lock). #[allow(clippy::unused_async)] // async required for caller ergonomics and tokio::spawn inside pub async fn run(working_dir: PathBuf) -> anyhow::Result<()> { - run_with_session(working_dir, None, None).await + run_with_session(working_dir, None, None, None).await } /// Run the TUI, optionally preloading a persisted session. @@ -118,7 +118,7 @@ pub async fn run(working_dir: PathBuf) -> anyhow::Result<()> { /// # Errors /// Same as [`run`]. #[allow(clippy::unused_async)] -pub async fn run_with_session(working_dir: PathBuf, resume: Option, agent: Option) -> anyhow::Result<()> { +pub async fn run_with_session(working_dir: PathBuf, resume: Option, agent: Option, model: Option) -> anyhow::Result<()> { tui_debug(format!("app::run start, cwd={}", working_dir.display())); // TTY pre-flight. If stdin or stdout isn't a TTY, the TUI will enter @@ -213,6 +213,13 @@ pub async fn run_with_session(working_dir: PathBuf, resume: Option`. When + /// `Some(_)`, every `TaskStart` dispatched to Big Smooth carries + /// this value verbatim in its `model` field; Big Smooth's routing + /// then resolves it against the configured providers (a smooth-* + /// alias OR a concrete model id like `deepseek-v4-flash`). Pearl + /// `th-20574a` — before this field existed, the CLI's `--model` + /// flag was silently dropped on the TUI path and every run used + /// the default smooth-coding alias regardless of what the user + /// asked for. `None` (the default) preserves the legacy behavior + /// of letting Big Smooth pick. + pub model_override: Option, /// Active lead role name (`fixer` / `mapper` / `oracle` / `heckler`). /// Flows into every `TaskStart` so the runner applies the right /// clearance set; rendered on the status bar so the user can see @@ -385,6 +396,7 @@ impl AppState { scroll_offset: 0, user_scrolled: false, model_name: "claude-sonnet-4".to_string(), + model_override: None, agent_name: "fixer".to_string(), agent_pinned: false, verbose: false, From 10929a1b1c9868cea51601e5fb7eab410ab36841 Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 3 Jun 2026 20:18:52 -0400 Subject: [PATCH 4/5] pearl: close th-20574a --- .smooth/dolt/pearls/.dolt/noms/journal.idx | Bin 1287885 -> 1288954 bytes .../noms/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv | Bin 26569120 -> 26696693 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.smooth/dolt/pearls/.dolt/noms/journal.idx b/.smooth/dolt/pearls/.dolt/noms/journal.idx index 707f945149a4f873a91ac35575b96f63c9c35b85..cf912715e34d70405a1eefa9116c35b77a62e575 100644 GIT binary patch delta 1125 zcmXZbdrVVT90%}UDJZQ#L0uW!P!I!dC}xqz91fyG1!T|)tPB^?iHIPyn^<8IFOO4U ztc#JCyfKrP+R}lYWl$!Ru46QmM=e8y2n{koBW$1!reTL3?myp~eD3e&oZs)9-rmq< zLudw~Au~ir=EwrAL+gb1n}EfTxv`A{xkmd$52+4*&BihApqtF;Oh+T?mXF+u^sUqn3oP+`~qRQr7|KrGP9R zc_-DLKE-KIQ~4Ve!vwQAcwMz{=){p;gg->4&z{fB)f-{ z=CrixBhxzw)?6~pw0|b@<+iFQ!E2HG`QWt0RD$&-rjc%PNY<6jev_x@ z_YqI z{zw9%nE3b#zBVgMIp`AQLvtmSvJsqW5VSoJ&Dhk`oSW4eHTFRd2>$dd&PgafZnY<^ zqLJDf^5wbkf3F9th&IgvSaM)6@#S*0_m6tF)~$7nvVpbrw&DXtzW^0IC2WtwXR9U7 z*-Xck&0JF1K8G(wAg~Vjb-DH2=8JqDPx~6g<^)wd)8V}@InHW|+hscu#_9X;)%F0u z`^T3}{hn#3dAah){=atDy~wT^4;mD#R$|m_#vy^bISEN8b}M(;cKCG;N!Kyga2%%^ zm@O%D$p+8;myMNEy8Dy(2Ci2AuEsG5k)19{F@^S_%lk6)b~nbt33kupPin!dr>ivQ qBblSN;n~}z4$-$C5$#z$y%ONvC>)b7{D3a$`Mr~+`P~Gyng0VE&zl_p delta 47 zcmV~$$rV6A6acVi-x>5ZH94df6=>rhHRB}t-W-uHg;J%~Xmxsn(PXw*ZIa#boN@oI C!4R_m diff --git a/.smooth/dolt/pearls/.dolt/noms/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv b/.smooth/dolt/pearls/.dolt/noms/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv index 1ee5961af7c6da1114df070de3515d5315c6f799..85b317ac9ccc22d6c359aff916dfa764d06126cc 100644 GIT binary patch delta 40047 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≀>n>QH4g3jwDgGGG6qNsvGX>XFbGz?{vj$b>?%@~is9*Q! z;8DHMdG4&imO;Z_KkK`{8hh&pb!6A#eg}TGXwJfJOa5&6zUKRD&w=Ov$p8gypBy)+ z*4dvNH>g(Jn~$eYJbT5<60@Zbio0ai_s8zOdHV6^x37Kec>dyT?Tx!y+M-oG`?g)U zGU#tt{nf{ngrbijVr}i1Mofh3@P8xzC*t1)%IJ@1&u=3@*aCcZCcf`y6U|iFICaur z&ydb>@}frUN;RJF3my82Uufgk8(XGLx~au9PMW68?6#Z!Zdh*e*h(DeYVAirrfN;W z?P@FS;NXuwnDcg%vm3gVwxMR))88U55KXz_t}GRj-g81+wOb! zce%ef)q-f+bpDwSY)RG)J-O6=@!IC|HqrBLz2$|gTh=^&!^exRKBMiX;k|D7!`~cs a-8lSjUl_F2Iro2#M^!0e!CS99Oa2q^@v$oa delta 970 zcmV~$1)L5B0LJ0ptK-AzI^9mk>F#c)yN;8m^YA|ouV$LlY&vF}u7z$JvyDyLbZuhK z^LpQ{KGwglqQril%*WysX#?4QJE@Kr5e?#K}~8= zn>y5`9`$KJLmJVTCN!lP&1pePTG5&?+R&DEw5J0d=|pF`(3Ng2 zTfSpI2RO(f4s(Q~9OF1AILY_?z>oaIDNb{Svz+5R7r4kJE^~#eT;n=F^9wh)$*U<30~~$Ri%}gunTRr#$01FL=o-Uh{^xyyHFp@*f}g$R|F>$dcp8 zapib&d^v%fP);N#mXpXy<$#<_PA;dAgXENQDmk^BMoufIlhex?av8a-Tuv@8 zSCA{pmE_8D6}hThO|CB2kZa1d~2)VyJKprR$k_XE} zoqvX-@7@@+5h(j2tabk*CTr@-%t6JVUlTQ=TQy TmgmTG<$3aad4UcrRBZVF)&7U? From 50153d4365749a09a9b3c6306af03296b333911c Mon Sep 17 00:00:00 2001 From: Brent Rager Date: Wed, 3 Jun 2026 20:25:00 -0400 Subject: [PATCH 5/5] =?UTF-8?q?fixer.txt:=20add=20Hard=20Rule=20#0=20?= =?UTF-8?q?=E2=80=94=20do=20the=20thing=20the=20user=20asked=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strong counter-bias against the agent pivoting to "fix the failing tests" on tasks that have nothing to do with tests. Standalone defensible improvement (the rule itself is unambiguous), but empirically NOT sufficient on its own to stop the pivot: smooth on cleanup-pycache-debris @ deepseek-v4-flash + Hard Rule #0: "I need to fix the failing tests. Since no test files were found, I will create a test file tests/test_util.py … fixed the failing tests. Test Results: 1 passed, 0 failed." Score: 0.500. Agent literally created and ran an invented test. This means the test-fix orientation is multi-layer: - The fixer.txt system prompt is heavy on test guidance (still correct for actual test-fix tasks, just badly weighted) - coding_workflow.rs's CODING phase may enforce a structure that expects tests - The model has been trained to pattern-match "Test Results" markers as a goal state Hard Rule #0 stays as one piece of the fix. The deeper surgery is filed as a new pearl: needs either a general-purpose agent persona that doesn't go through coding_workflow.rs's test-centric path, or a workflow-level branch on "is this actually a test task?" classified from the user's first message. --- .../src/cast/prompts/fixer.txt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/smooth-operator/src/cast/prompts/fixer.txt b/crates/smooth-operator/src/cast/prompts/fixer.txt index 50fb5db7..32def8b0 100644 --- a/crates/smooth-operator/src/cast/prompts/fixer.txt +++ b/crates/smooth-operator/src/cast/prompts/fixer.txt @@ -1,3 +1,23 @@ +You are a general-purpose technical agent working inside a sandboxed workspace. You can write code, edit files, run shell commands, run tests, perform filesystem cleanup, investigate issues, answer questions — whatever the user actually asks for. + +## Hard rule #0: DO THE THING THE USER ASKED FOR + +This rule beats every other rule in this document. + +The user's message — taken at face value, on its own merits — defines your task for this turn. Period. + +- If the user says "delete __pycache__ dirs", your job is to delete __pycache__ dirs. NOT to run tests, NOT to look for failing tests, NOT to invent tests that aren't there. +- If the user says "tell me what this regex does", your job is to explain a regex. NOT to refactor it, NOT to write tests for it. +- If the user says "clean up the tmp/ directory", your job is filesystem cleanup. NOT a code-review of nearby files. + +**Do NOT pivot.** Do NOT introduce a task the user didn't ask for. Do NOT say "I will now fix the remaining test failures" unless the user's literal request was to fix test failures. If you find yourself drafting a sentence like "I will now [do something the user didn't ask for]", stop and re-read the user's first message. + +Test-running, test-fixing, and test-writing guidance appears further down in this prompt. Apply it ONLY when the user's request involves tests. For non-test tasks (cleanup, refactor, explanation, investigation, ops), skip the test sections entirely — they don't apply. + +This rule exists because the model that runs this prompt has been observed to pattern-match the prompt's many test-related sections and pivot to "let me fix the tests" on tasks that have nothing to do with tests. That's a bug, not a feature. The user asked for something specific. Do that. + +--- + You are a coding agent working inside a sandboxed workspace. You can write code, edit files, run commands, and run tests. You can also just answer questions about code — analytical / advisory mode is fine when that's what the user is actually asking for. ## When the user's message starts with `## Skill: `