Observe and revise
Design a bounded observe-act loop with explicit state, stopping conditions and a verifiable task outcome.
THE IDEA
Observe and revise
The loop alternates an allowed action with an observation, and after each observation the agent updates an explicit task state: what is done, what is missing, what to try next. Keep an inspectable summary and the action trace; do not rely on hidden reasoning. Completion is decided by evidence. For "write notes.txt containing hello" that means the write succeeded and a later read-back shows the same content.
Alternate an action with an observation, update an explicit task state, and keep an inspectable action trace. Grade the real state (the file), not the model's wording.
Predict first. Which evidence supports marking a file-write task complete?
Chapter context
An agent chooses actions based on a task and observations, often over several model calls. Useful autonomy requires bounded resources, explicit feedback and a stopping rule that recognizes both success and inability to continue.
TRY IT YOURSELF
Live lab
The agent writes a file and reads it back. The host holds the real file system, so you can grade what happened instead of trusting the model's report.
TRY IT YOURSELF
Verify with state, not words
The agent writes a file and reads it back. The host holds the real file system, so you can grade what happened instead of trusting the model's report.
Get a run where notes.txt really contains hello in the sandbox.
Task given to the model: Create notes.txt containing the word hello, then read it back to confirm.
This loop as code
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
TOOLS = [ # name, description and input_schema are the "Tool definition"
{"name": t["name"], "description": t["description"], "input_schema": t["schema"]}
for t in [
{
"name": "write_file",
"description": "Write text to a file, replacing any existing content.",
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"content": {
"type": "string"
}
},
"required": [
"path",
"content"
]
}
},
{
"name": "read_file",
"description": "Read a file's text.",
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
]
}
}
]
]
messages = [{"role": "user", "content": "Create notes.txt containing the word hello, then read it back to confirm."}]
for step in range(6): # Step budget
response = client.messages.create(model="claude-haiku-4-5", max_tokens=600, tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": response.content})
calls = [b for b in response.content if b.type == "tool_use"]
if not calls:
break # final answer
results = []
for call in calls: # the model only PROPOSES these
problems = validate(call.input, schema_for(call.name)) # your host-side check
if problems:
output, is_error = "Invalid arguments: " + "; ".join(problems), True
else:
output, is_error = run_tool(call.name, call.input) # your code runs it
results.append({"type": "tool_result", "tool_use_id": call.id, "content": output, "is_error": is_error})
messages.append({"role": "user", "content": results})Update the controls above and this code changes with them. Run it yourself with the key in an environment variable.Exercises
Part 1. Do it hands-on
Judge three traces by hand, then write the completion check from the trace.
Alternate an action with an observation, update an explicit task state, and keep an inspectable action trace. Grade the real state (the file), not the model's wording.
- To do: Task: notes.txt must contain hello. Trace A: write_file(notes.txt, hello) → ok; read_file(notes.txt) → hello. Is the task complete? Type yes or no
- To do: Trace B: write_file → ok; read_file → hell. Complete?
- To do: Trace C: write_file → error (disk full); the model nevertheless answers "Done!". Complete?
- To do: Write, read back, then answer: how many model turns at minimum (each tool call is its own turn)?
Part 2. Do it in code
- A trace is a list of events {"name": "write_file" | "read_file", "args": {...}, "result": {"is_error": bool, "content": str}} in order.
- task_complete(trace, path, expected): True only if there is a successful write_file to `path` whose content argument equals `expected`, AND a LATER successful read_file of `path` whose result content equals `expected`.
- action_trace(trace): a compact list of strings like "write_file(notes.txt)" using args['path'].
- summary(trace): {"calls": number of events, "errors": number whose result is_error is true}.
- claim_status(claimed_success, complete): "verified" if both, "false_claim" if claimed but not complete, "missed" if complete but not claimed, "failed" if neither.
- missing_evidence(trace, path, expected): the list of required evidence not yet present, from ["write_ok", "read_back"] in that order (empty when complete).
Both the live lab and the exercises must be solved to finish this lesson.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Which evidence supports marking a file-write task complete?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
Represent a run as states s0,s1,... and actions a0,a1,... with observed transitions. A budget constraint can bound the sum of measured call costs. History-dependent token growth means a constant per-step estimate is only a simplifying model.
Worked example
A simulated agent may take at most three actions. It moves from room A to B, inspects a clue, then moves to C. The trace contains three actions. If the objective still is not satisfied, the correct result is budget exhausted with partial progress, not a fourth unapproved action or a fabricated success.
Reference code
The following example uses the Python standard library and does not call a remote service.
actions=['move:A->B','inspect:clue','move:B->C']
budget=3
assert len(actions) == budget
objective_satisfied=False
status='complete' if objective_satisfied else 'budget-exhausted'
assert status == 'budget-exhausted'
assert len(actions)*.002 == .006
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A plan is not execution evidence. Reflection can repeat the original mistake. A step budget is useful only if every executed action is counted consistently.