How to Steer a Running AI Agent Without Stopping the Loop
To steer a running AI agent, edit .agent/STEERING.md while the loop is going. The agent reads that file at the top of every iteration, before it picks a task. Anything you write there gets handled first, in order, then the agent removes the finished items and goes back to the task list. You change direction without killing the process and without losing the momentum of a warm run.
This is the redirect control inside the larger system for running an AI coding agent overnight. A loop that runs for hours will sometimes pick a task you no longer want, chase a dead end, or need a hotfix you only just noticed. Steering ai agent behavior through a file is how you correct course mid-flight instead of stopping, editing, and starting over.
What steering an AI agent actually means
Section titled “What steering an AI agent actually means”Steering is not a chat message and not a new prompt. It is a single Markdown file, .agent/STEERING.md, that lives next to the rest of the agent state. The Ralph prompt has one job for it. In the “Before Starting” section of .agent/PROMPT.md, the agent is told to check .agent/STEERING.md for critical work, complete the items in sequence, remove them when done, and only proceed to implement tasks if no critical work is pending.
That ordering is the whole mechanism. Because each iteration starts the agent with a fresh context window, the agent rereads the prompt and the steering file every single loop. There is no stale chat history to fight and no need to interrupt the agent mid-thought. You write to a file, and the next clean iteration discovers it.
A steering file looks like a short work order. The default one in the repo handles sandbox setup before any feature work runs:
# Critical Steering Work
## Install and verify dependencies
Run `npm install --ignore-scripts`, then fix native arm64 binaries.
## Main Tasks
Install Playwright system deps, start the dev server, take a screenshot.
---
After you finish this work, exit with message `Steering complete`.You can replace that content with whatever you need the agent to do next. The file is plain text, so you edit it with any tool from any machine that has the working tree, including a shell inside the sandbox.
When to steer a running agent
Section titled “When to steer a running agent”Steer when the loop is healthy enough to keep running but pointed at the wrong thing, or when something urgent jumps the queue. There are three common triggers.
The agent is stuck. You are watching the run and the same failure keeps coming back across iterations. Maybe a test depends on a binary that did not install, or a dev server never came up. The agent is not blocked enough to emit a promise tag, but it is spinning. A steering note that tells it exactly how to fix the environment clears the jam. Spotting this early is the payoff of observability for autonomous coding agents: you see the climbing iteration time and the repeated debugging step before the loop burns ten more iterations.
The agent went the wrong direction. The task list was fine when you wrote it, but the agent picked a task whose approach you now disagree with, or requirements changed since you started the run. You do not want to throw away the work it already shipped. You want it to switch tracks on the next iteration.
You have an urgent fix. A bug landed in production, a dependency needs pinning, or a teammate needs a small change merged into the same branch. The loop is the fastest path to get it done, and you would rather inject the work than wait for the run to finish.
Steering is the wrong tool for two cases. If the agent is fundamentally confused about the goal, fix .agent/tasks.json or the PRD instead, because steering is for one-off interventions, not for rewriting the plan. And if the agent emitted BLOCKED or DECIDE and the loop already exited, you do not steer a stopped process. You answer it and rerun, which I cover below.
How the loop reads STEERING.md every iteration
Section titled “How the loop reads STEERING.md every iteration”The reason steering works without a restart is that the loop already reopens the file on every pass. Each iteration is a clean run of the agent against the prompt, and the prompt routes through the steering check before it ever touches the task list.
flowchart TD
Start["Iteration starts (fresh context)"] --> Read["Read .agent/PROMPT.md"]
Read --> Check{"STEERING.md has critical work?"}
Check -->|"yes"| Steer["Do steering items in sequence"]
Steer --> Remove["Remove finished items from STEERING.md"]
Remove --> Tasks["Pick highest-priority task in tasks.json"]
Check -->|"no"| Tasks
Tasks --> Verify["Run tests, lint, typecheck"]
Verify --> Commit["Commit, emit promise tag"]
Commit --> Next["Next iteration"]
You["You edit STEERING.md mid-run"] -.->|"injected here"| Check
The dotted edge is the part you control. Your edit lands in the file at any moment, and the next iteration picks it up at the diamond. You never have to time it perfectly. If iteration 12 is in flight when you save the file, iteration 13 reads your note. Because the agent removes completed steering items, the file empties itself out and the loop returns to normal task work without you touching it again.
This is why a fresh context per iteration is a feature, not a limitation. A single long session would have buried your note under thousands of tokens of prior reasoning. A loop that reloads its instructions every pass treats the file as the source of truth, so the latest version of .agent/STEERING.md always wins.
Steering versus killing and restarting the loop
Section titled “Steering versus killing and restarting the loop”The blunt alternative is to stop the run, edit something, and start again. Steering beats that on three counts.
You keep warm state. A sandbox that already installed dependencies, built the project, and warmed its caches is expensive to recreate. Ralph runs each agent in a deterministic Docker Sandbox named ralph-<agent>-<dir>-<hash8>, and that sandbox persists between iterations. Killing the loop and recreating the box means paying the setup cost again. Steering reuses the running sandbox.
You keep the run history intact. Every iteration writes a clean transcript to .agent/history/, appends to .agent/logs/LOG.md, and commits per task. A restart starts a new session id and fragments that trail. Steering threads your intervention into the same continuous record, so the morning review reads as one story.
You avoid the gap. When you kill a loop manually, you have to remember to restart it. People walk away and forget. A steering note keeps the loop alive and self-correcting, which is the entire point of an unattended overnight run.
There is one case where stopping is correct: when you want to change how the loop runs, not what it does next. Flags like the iteration count and the agent are set at launch, so switching from ./ralph.sh -n 50 to a different agent with ./ralph.sh --agent codex does require a restart. For everything that is “do this work next,” steer instead.
If you only need to validate a single steering note before trusting it overnight, run one pass with ./ralph.sh --once, confirm the agent did what the file said, then start the full loop.
How to write a steering note that works
Section titled “How to write a steering note that works”A good steering note reads like a task spec: specific, ordered, and verifiable. A bad one reads like a vibe. The agent reads your note with a fresh context, so it has no memory of the hallway conversation in your head. Spell it out.
Write the exact commands and file paths. “Fix the build” is vague. The agent has to guess what broke and what done looks like. Compare:
# Critical Steering Work
## Pin the failing dependency
1. In `package.json`, set `vite` to `5.4.10` exactly.2. Run `npm install --ignore-scripts`.3. Run `npm run build` and confirm it exits 0.4. Commit with message `fix: pin vite to 5.4.10`.
---
After you finish this work, exit with message `Steering complete`.Every line is checkable. The agent knows the version, the commands, the success condition (build exits 0), and the commit message. That is the same standard the loop expects from a real task, and the same reason verification loops matter: a steering note with a verifiable end state lets the agent prove it is done instead of guessing.
Give the agent an exit condition. The default file ends with “After you finish this work, exit with message Steering complete.” That tells the agent the boundary of the steering work so it does not bleed into task work uninvited. Keep that pattern. Without a clear end, the agent may either stop too early or wander.
Keep it small. Steering is for one or a few critical items, not a second task list. If your note grows into a dozen steps across unrelated areas, that is a sign the work belongs in .agent/tasks.json as real tasks with their own specs. Steering is the fast lane, not the highway.
Order matters. The prompt says complete items in sequence. List prerequisites first. If the agent must install a binary before a test can pass, the install goes above the test.
A few quick contrasts:
- Vague: “Make the homepage better.” Specific: “On the homepage hero, change the CTA label to
Start free, verify with a Playwright screenshot saved to.agent/screenshots/.” - Vague: “Tests are flaky, look into it.” Specific: “The test
auth.spec.tsfails on a timeout. Increase the wait in that file to 10 seconds and rerun it until green.” - Vague: “Clean up the deps.” Specific: “Remove
lodashfrompackage.json, replace its three usages insrc/utils/with native equivalents, run the unit tests.”
The pattern is always the same. Name the file, name the command, name the proof.
Combining steering with BLOCKED and DECIDE promises
Section titled “Combining steering with BLOCKED and DECIDE promises”Steering and promise tags solve different halves of the human-in-the-loop problem, and they work together.
Promise tags are how the agent talks to you. When the agent cannot continue, it emits <promise>BLOCKED:reason</promise> and the loop exits with code 2. When it needs a decision it cannot make alone, it emits <promise>DECIDE:question</promise> and exits with code 3. A clean finish is <promise>COMPLETE</promise> with exit code 0, and hitting the iteration cap is exit code 1. Ralph plays a sound and sends a desktop notification on BLOCKED and DECIDE, so you find out the moment a human is actually needed.
Steering is how you talk back. The promise tells you the loop stopped and why. Your answer goes into .agent/STEERING.md, and then you rerun the loop. The next iteration reads your steering note first, handles the unblock or the chosen direction, removes it, and continues with the task list. The flow is tight:
# loop exits 2 with BLOCKED:cannot reach npm registry# you allow the domain from the host, then leave a steering note# in .agent/STEERING.md describing the retry, then:./ralph.sh -n 50For a DECIDE, the agent gave you a question like “Option A vs B.” You pick one in the steering file in plain terms: “Use option A. Implement the REST client, not the GraphQL one. Then continue tasks.” The agent does not relitigate the choice because the steering note is now the instruction, and the fresh context means it reads that note as current truth.
This pairing is the reason an overnight run stays autonomous without being unaccountable. The agent stops itself on the two events that genuinely need a person, notifies you, and waits. You drop a precise note into one file and restart. The promise mechanics behind those exit codes are worth knowing in full if you want the loop to stop on a signal rather than a guess, and the deny-by-default network policy that triggers many BLOCKED exits is enforced by the sandbox, not the agent.
When a steering note itself is not enough, get inside the box. List the running sandboxes and open a shell to debug by hand, then write what you learned back into the steering file:
sbx lssbx exec -it <ralph-sandbox-name> bashPrint the exact name for your project with ./ralph.sh --print-name. Between promise tags that pull you in, a steering file that pushes instructions back, and a sandbox you can open and inspect, you can run a long autonomous loop and still keep both hands on the wheel whenever you want them there.
Frequently asked questions
How do I steer a running AI agent in a Ralph loop?
Edit the .agent/STEERING.md file while the loop is running. The agent reads that file at the top of every iteration, before it picks a task from tasks.json. It completes the steering items in sequence, removes them when done, and then resumes normal task work. Because each iteration starts with a fresh context, you do not have to time the edit precisely. The next iteration picks up whatever the file says.
Do I have to stop the loop to change what the agent is doing?
No. That is the point of steering. Editing STEERING.md injects work into the next iteration without killing the process, so you keep the warm sandbox, the running session, and the per-task git history intact. You only need to restart the loop when you want to change how it runs, such as switching the agent or the iteration count, since those flags are set at launch.
What makes a good steering note?
Specific, ordered, verifiable instructions. Name the exact files and commands, list prerequisites first, and give a clear success condition such as a build exiting 0 or a test going green. End the note with an explicit exit message so the agent knows where steering work stops. Keep it to one or a few critical items. If it grows into a full plan, that work belongs in tasks.json as real tasks instead.
How does steering work together with BLOCKED and DECIDE promises?
Promise tags are how the agent signals it needs you. BLOCKED exits with code 2 and DECIDE exits with code 3, both with a desktop notification. Steering is how you respond. You write the unblock steps or the chosen option into STEERING.md and rerun the loop, and the next fresh-context iteration reads your note first, handles it, removes it, and continues the task list.
Where does the agent look for steering instructions?
In .agent/STEERING.md. The .agent/PROMPT.md file tells the agent in its Before Starting section to check that file for critical work, complete items in sequence, remove them when done, and only proceed to implement tasks if no critical work is pending. So the latest version of STEERING.md is always the source of truth for what the agent does next.