harnessengineering llm ai agents ai/agenticai
Steering the Running Loop
Core Idea
Steering keeps a long-running agent on course by turning fresh events, rules, and plans into short, recent messages that stay in the model’s attention.
Long-running agents can drift because the model only knows what is currently in its context.
Problems include:
- files changing while the agent is working,
- old rules fading because of context rot,
- long plans being forgotten halfway through execution.
The solution is steering: the harness injects fresh guidance into the running conversation so the model can adjust without restarting.
long-running agents need a way for the harness to update their attention while they work.
1. The injection mechanism
Steering works by appending new machine-generated guidance near the bottom of the context, usually alongside the next user-side message.
A typical reminder might say:
<system-reminder>
src/app.py changed on disk. Re-read it before editing.
</system-reminder>The tags are just a convention indicating that the message came from the harness rather than directly from the user.
Appending reminders instead of changing the system prompt has three advantages:
- Caching stays intact. Earlier cached context does not need to be rebuilt because guidance is appended to the append-only conversation.
- Recency improves attention. Recent instructions are easier for the model to notice than ones buried far back.
- Events can be handled naturally. Something happens between rounds, so the harness adds the information to the next round.
Main idea: steering adds timely information at the end of the context, where it is cheap and visible.
2. Steering should use a queue
Production systems should not have every component independently inserting text into the conversation.
Instead, they should maintain a central reminder queue.
Different systems can enqueue events such as:
- file changes,
- permission changes,
- memory updates,
- mode changes.
Before the next model call, the queue is drained into the outgoing message in a predictable order.
This makes it possible to know exactly:
- what guidance the model received,
- when it received it,
- why it was inserted.
Main idea: treat steering as an organized internal messaging system, not scattered prompt manipulation.
3. What production harnesses steer with
Common steering messages include several types.
File-change notices
If a file changes after the model read it, the harness tells the model that its copy is stale and that it should read the file again.
This is different from actually preventing unsafe edits. A reminder informs the model; a deterministic guardrail can separately block the action.
Rule refreshers
Important standing rules can be repeated near the moment when they matter rather than relying on very old context.
Session context
Persistent project memory can be injected as labeled machine context at the beginning of a session.
Mode changes
If the user changes operating mode—for example, switching into planning mode—the harness can inject that change without rebuilding the system prompt.
Main idea: translate changes in the program’s environment into short textual updates the model can understand.
4. Events in the body become sentences in the array
The harness interacts with:
- files,
- permissions,
- UI state,
- external processes,
- memory,
- user actions.
The model itself only sees text.
Steering is therefore the bridge between these two worlds:
Real-world event
↓
Harness detects it
↓
Reminder queue
↓
Text inserted into context
↓
Model adjusts behaviorMain idea: steering converts machine events into language the model can reason about.
5. The todo list: self-steering
A particularly useful steering mechanism is a persistent todo list.
The model receives a tool such as todo_write and uses it to store its plan outside the conversation.
For example:
[done] Inspect authentication code
[done] Find failing test
[in progress] Fix retry handling
[todo] Run full test suite
[todo] Update documentationWhenever the list changes, the harness injects the latest version back into the conversation.
This means the model’s plan no longer depends on remembering tokens it generated tens of thousands of tokens ago.
Instead: model creates plan → harness stores it → harness keeps showing it back
Main idea: externalizing the plan prevents it from fading out of attention.
6. The whiteboard analogy
The todo mechanism does not make the model inherently more intelligent.
It gives the model something closer to an external whiteboard.
The important information is:
- stored outside the model,
- kept short,
- repeatedly brought back into view when relevant.
The same mechanism can also power a progress UI for the user.
Main idea: good harnesses improve reliability by keeping important state visible rather than expecting the model to remember everything.
7. OpenAI’s developer role
The chapter describes OpenAI’s developer role as a more explicit way for an application to provide guidance to the model.
Instead of encoding harness instructions inside something like:
<system-reminder>...</system-reminder>the application can use a dedicated message role for application-level guidance.
The conceptual pattern is still the same:
Harness decides something the model should know
↓
Send fresh guidance
↓
Model sees it in contextBut the chapter emphasizes two limitations.
First, message roles influence model behavior; they should not be treated as a hard security mechanism.
Second, having a dedicated role does not eliminate the actual engineering work. The harness still needs:
- event detection,
- a queue,
- ordering,
- decisions about when guidance should be injected.
Main idea: APIs can standardize the message format, but the harness still has to decide what to communicate and when.
8. Restraint: don’t over-steer
Too many reminders create their own problem.
Every reminder:
- consumes tokens,
- adds noise,
- competes for attention.
If the model sees warnings constantly, they may become background noise.
The chapter recommends two rules:
Trigger reminders from events
Good:
The file changed. Re-read it.Poor:
Remember to be careful.repeated every round.
Repeated reminders indicate a design problem
If the same instruction has to be injected constantly, it probably belongs somewhere more permanent, such as:
- the system/developer instructions,
- a tool description,
- a deterministic rule in the harness.
Main idea: steering should be rare, specific, and triggered by meaningful changes.
Background Work and Time
Core Idea
A long-running agent should sleep while nothing requires reasoning, let cheap software watch the world, and wake the model only when an event makes its attention valuable again.
Everything built so far is synchronous: the model calls a tool and waits for it to finish.
That breaks down for slow work such as:
- long builds,
- large test suites,
- CI runs,
- deployments,
- scheduled checks.
Polling is especially wasteful because it uses expensive model calls just to ask whether something has finished.
The solution has two parts:
- Let work continue after the current tool call ends.
- Let external events wake the model later.
Separate the model’s reasoning from the waiting.
1. Background tasks
Long-running commands should be able to run independently of the agent loop.
The harness maintains a task registry containing things such as:
- task ID,
- command,
- status,
- buffered output,
- exit code.
A background command returns immediately:
task b1 startedinstead of waiting for the process to finish.
The model can then continue doing other work.
Slow processes should become managed background jobs rather than blocking tool calls.
2. Tools for background work
A minimal background-task system needs three operations.
Start
run_command(..., run_in_background=true)
Starts the process and immediately returns a task ID.
Inspect
task_output(task_id)
Returns:
- output collected so far,
- current status,
- possibly the exit result.
Stop
task_stop(task_id)
Terminates a task that is no longer needed.
3. Completion becomes a steering event
When a background process finishes, the harness should not make the model continuously check it.
Instead, completion enters the steering system from Chapter 8:
task b1 finished with exit code 0The harness adds that notification to the reminder queue.
If the agent is already running, the message appears in the next round.
If the agent is asleep, the event can start a new model turn.
4. Prevent polling by design
Models may naturally try things like:
sleep 30
check status
sleep 30
check statusThis wastes API calls.
A better harness can explicitly block foreground waiting and tell the model to use:
- background tasks,
- notifications,
- scheduled wakeups.
This demonstrates an important principle: tool design shapes model behavior.
5. Monitors: wake on a condition
Once the harness can wake the model, it can do so when a condition becomes true.
Examples:
- CI finishes,
- a log contains
ERROR, - a file changes,
- a deployment completes.
A cheap process watches the condition, while the expensive model does nothing.
The pattern becomes:
cheap body watches
↓
condition happens
↓
wake expensive brain6. Schedules: wake at a time
The same mechanism can trigger on time rather than an external condition.
Examples:
- check something in one hour,
- summarize issues every morning,
- inspect deployment status every few hours.
The harness stores the scheduled time and invokes the model when it arrives.
There are two important kinds of scheduled work:
New scheduled job
Wake into a fresh context with a task.
Session follow-up
Resume an existing conversation later.
These are different because they give the model different context.
7. Self-scheduling
The model itself can be allowed to decide when it should wake again.
For example:
schedule_wakeup("10 minutes")The model can then end its current turn instead of sitting around waiting.
Later, the harness resumes it.
Conceptually this resembles asynchronous programming:
do some work
await external progress
resume laterThe model can also choose sensible intervals based on the task instead of checking constantly.
8. From chat loop to event-driven agent
Earlier, the model ran because the user sent a message.
Now many things can start a turn:
- user messages,
- background task completion,
- file changes,
- timers,
- CI events,
- webhooks,
- monitored conditions.
The architecture changes from:
User → Modelto something closer to:
User ───────────┐
Task finished ──┤
Timer ──────────┤
File changed ───┼→ Harness → Model
Webhook ────────┤
Other event ────┘9. Control the cost of wakeups
Every time the harness wakes the model, it creates another API call.
Therefore, frequent checks can become expensive very quickly.
Better strategies include:
- prefer event-driven triggers,
- use realistic polling intervals,
- use cheaper models for simple checks,
- keep wakeup context small,
- avoid waking when nothing meaningful changed.
10. Notifications must be trustworthy
The model may make decisions based on messages such as:
CI passedor:
task b1 completed successfullyTherefore, these notifications must accurately reflect the underlying system.
If the watcher crashes or status is uncertain, the harness should report the failure rather than inventing certainty.
Steering events become part of the model’s perceived world, so false notifications can cause incorrect actions.
11. Background work must be visible and stoppable
Users should be able to inspect:
- which jobs are running,
- what they are doing,
- when they started,
- their status.
They should also be able to cancel them.
Invisible autonomous processes make systems difficult to trust.
12. Persistent state becomes necessary
A wakeup may happen hours later, after the harness process has restarted.
Therefore, important state cannot exist only in RAM. Persistent wakeups make this close to durable execution: the harness can resume work after a process restart.
The harness needs to persist things such as:
- conversation/session state,
- background task metadata,
- scheduled wakeups,
- pending notifications.
This reinforces an earlier principle:
sessions are data that must survive process restarts.
Main idea: once agents operate across time, persistence stops being optional.