Agent Planning, Vision, and Interfaces¶
Summary¶
This chapter extends the agent with planning and perception. It covers task planning and replanning, vision-language models, local and cloud agents, MCP and ROS 2 bridges, and calling web APIs with safe key handling. After this chapter, you will be able to build an agent that sees a scene and plans a multi-step task.
Concepts Covered¶
This chapter covers the following 26 concepts from the learning graph:
| Concept | Concept Impact Score |
|---|---|
| HTTP API | 31 |
| Tool Server | 24 |
| Agent Architecture | 23 |
| Task Planning | 15 |
| Agent Planner | 10 |
| Vision-Language Model | 7 |
| JSON Requests | 6 |
| Learned Policy Executor | 5 |
| Requests Library | 5 |
| Object Localization | 5 |
| Planner-Executor Split | 4 |
| API Key Handling | 4 |
| Image-to-Arm Coordinates | 4 |
| Task Decomposition | 4 |
| Failed Grasp Recovery | 4 |
| Local Agent | 3 |
| Cloud Agent | 3 |
| Replanning | 3 |
| Model Context Protocol | 2 |
| Asking a Human | 2 |
| Edge AI Computer | 1 |
| Latency and Cost | 1 |
| Privacy Considerations | 1 |
| MCP Server | 1 |
| ROS 2 Bridge | 1 |
| Scene Description | 1 |
Prerequisites¶
This chapter builds on concepts from:
- Chapter 1: Setting Up Python for Robotics
- Chapter 11: Moving the Arm: Trajectories, Grippers, and Teleoperation
- Chapter 13: Logging, Testing, Simulation, and ROS 2
- Chapter 14: Cameras, Perception, and Learning from Demonstration
- Chapter 15: AI Agents, Tools, and OpenClaw Skills
Now I Can See and Plan!
In Chapter 15 an agent could move me through safe tools. In this chapter it learns to look at the table, break "put the block in the bin" into steps, and cope when a grasp fails. You will also wire the tools to the world with web APIs, MCP and ROS 2. Let's move it!
An agent that can only follow a request like "move to x equals 0.2" is a voice-controlled joystick. The agents that people find exciting do more. They look at the scene, work out where the objects are, decide on a sequence of steps that reaches a goal, notice when a step fails, and try something else or ask for help. Each of those abilities adds a new way to go wrong, and so each needs the same two things that Chapter 15 built: a clear division of labor, and checks in code.
The chapter has four parts. The first is the architecture: how perception, planning, checking and execution fit together, and why the planner and the executor are kept apart. The second is seeing: how a vision-language model describes a picture, and how its answer becomes coordinates for the arm, using the camera mapping of Chapter 14. The third is recovery: what an agent should do when a grasp fails, or when it cannot tell what is wanted. The fourth is interfaces: how tools reach an agent over a network, with a web API, with the Model Context Protocol, and with ROS 2, and where the agent should run. Every piece runs in the lab on your computer, with no network, no key and no arm, using a pretend vision model and the fake arm.
The Architecture of an Agent System¶
Agent Architecture¶
An agent architecture is the way the parts of an agent system are arranged and connected. The arrangement matters more than the choice of model, because it decides what the model can and cannot do. The one in this chapter has six parts, and each does one job.
| Part | Job | Made of |
|---|---|---|
| Perceive | Turn camera pictures into a list of objects with positions | A camera, a vision model, and checks (Chapter 14 and this chapter) |
| Plan | Turn a goal and the scene into a list of steps | A language model, the agent planner |
| Check | Reject any plan that breaks a rule, before anything moves | Plain code: the tool schemas of Chapter 15 |
| Execute | Run the steps one at a time and look at each result | Plain code, the executor |
| Act | Do one step on the arm | The tools of Chapter 15, or a learned policy |
| Recover | Decide what to do when a step fails | Retry, replan, or ask a person |
Two of the six parts hold a model, and four are ordinary code. That ratio is deliberate. The parts that carry safety, the check, the executor, the tools and the recovery, are programs that you can read and test, and the models sit behind them.
The Planner-Executor Split¶
The planner-executor split is the central design rule. The planner decides what to do and in what order. The executor does it, one step at a time, and checks what happened. They are different programs with different powers. The planner can propose anything, and it never touches the arm. The executor can touch the arm, and it does only what a checked plan says, in the order that it says, and it reads each result before going on. The planner's output is data (a list of steps), and so it can be printed, saved, tested, validated and refused. This is the same idea as Chapter 15's doorway, one level higher: the doorway for single commands is the tool layer, and the doorway for whole plans is the checked plan.
A Plan Is Data, and Data Can Be Checked
A language model that moves the arm directly is hard to test, because you cannot know what it will do next. A language model that writes a plan is easy to test, because the plan sits there as a list that code can inspect before one motor turns. Whenever you can turn a decision into data, you gain a place to put a safety check.
Research on robots that use language models points the same way. The SayCan system (Ahn and colleagues, 2022) combines what a language model suggests with value functions of the robot's own skills, so that the plan is grounded in what the robot can actually do. Code as Policies (Liang and colleagues, 2022) has a model write Python code that calls the robot's functions. ReAct (Yao and colleagues, 2022) has a model alternate between reasoning and acting so that it can use what it learns from the world. In each, the model's output is a program or a plan over a fixed set of skills, which is what the tools of Chapter 15 are.
Task Planning and Task Decomposition¶
Task planning is working out a sequence of actions that gets from the present state to a goal. Task decomposition is its main technique: break a goal into smaller steps, each one small enough to be a single tool call. The goal "put the red block in the bin" decomposes into ten steps. The plan below is exactly what the lab's planner produces for a block at (0.22, 0.089) m.
| Step | Tool | Why |
|---|---|---|
| 1 | get_status |
Look before moving, as the skill's first rule says |
| 2 | open_gripper |
Open before approaching, or the fingers hit the block |
| 3 | move_to_pose above the block |
Approach from above (Chapter 11) |
| 4 | move_to_pose down to the block |
The fingers surround it |
| 5 | close_gripper |
Grasp, and read whether anything is held |
| 6 | move_to_pose above the block |
Lift |
| 7 | move_to_pose above the bin |
Carry |
| 8 | move_to_pose down into the bin |
Lower |
| 9 | open_gripper |
Release |
| 10 | go_home |
Clear of the table |
The agent planner is the part that produces such a list. In the lab it is a short function, plan_pick_and_place, so that everything runs offline. In a real system it is a language model that receives the goal and the scene description, and the tool schemas, and answers with the same list as JSON. Because the answer is data, the first thing the program does with it is validate_plan: every step is checked against its tool's schema, and a plan with an invented tool or a value out of range is refused as a whole, with each fault named by its step number, before any step runs. The rule from Chapter 11 for waypoint lists applies again: check the whole list before anything moves.
Diagram: Plan Checker¶
Run the Plan Checker MicroSim fullscreen
Plan Checker
Type: microsim
sim-id: plan-checker
Library: p5.js
Status: Specified
Bloom Level: Analyze
Bloom Verb: examine
Learning Objective: The learner will examine eight pick-and-place plans written as lists of tool calls and name the problem with each, or say that the plan is valid, with at least 7 of 8 correct on the first attempt.
Prerequisites: task planning, task decomposition, the planner-executor split, the plan of ten steps, tool schemas and their ranges (all defined in the section "The Architecture of an Agent System" above this block and in Chapter 15).
Evidence of Mastery: For each of eight plans the learner chooses one of six outcomes and commits. A choice is correct when it matches the Outcome column in Content. Mastery is 7 of 8 correct on the first attempt. Reading the model plan in Explore mode is exploration, not evidence.
Misconceptions: (1) A plan is fine if each step is individually legal. (The order matters: closing the gripper before the approach, or lifting without checking the grasp, are order faults.) (2) A plan that the model wrote needs no checking. (A plan is data that should always be checked as a whole.) (3) The executor can fix a bad plan as it goes. (It should refuse a bad plan before anything moves.)
Instructional Rationale: An Analyze-level examine objective asks the learner to break a plan into its steps and test each against rules and against its neighbors. Each plan has at most one fault, so the learner must look for the specific rule that is broken.
Content:
The model plan, shown in Explore mode, is the ten-step plan of the chapter. The six outcomes: "Valid plan", "Missing status check", "Gripper closed on approach", "Value out of range", "No grasp check before lifting", "Uses a tool that does not exist". Eight plans in this fixed order, each a short list of steps:
| # | Plan | Outcome | Why (shown as feedback) |
|---|---|---|---|
| 1 | get_status, open_gripper, move above the block, move down, close_gripper, check holding_something, move up, go_home | Valid plan | Every rule is met and the order is safe. |
| 2 | open_gripper, move above the block, move down, close_gripper, check holding_something, move up | Missing status check | The first step should be get_status, so that the plan starts from what the arm really reports. |
| 3 | get_status, move above the block, move down, close_gripper, check holding_something, move up | Gripper closed on approach | The gripper was never opened, so the fingers would hit the block on the way down. |
| 4 | get_status, open_gripper, move above the block with z = 0.01, move down, close_gripper, check holding_something | Value out of range | z = 0.01 is below the minimum of 0.02 m. |
| 5 | get_status, open_gripper, move above the block, move down, close_gripper, move up | No grasp check before lifting | The plan lifts without reading whether the gripper holds something. |
| 6 | get_status, open_gripper, move above the block, wave_hello, move down | Uses a tool that does not exist | There is no tool called wave_hello. |
| 7 | get_status, open_gripper, move above the block, move down, close_gripper, check holding_something, move above the bin, open_gripper, go_home | Valid plan | The grasp is checked and the order is safe. |
| 8 | get_status, open_gripper, move above the block with x = 0.40, move down, close_gripper, check holding_something | Value out of range | x = 0.40 is above the maximum of 0.28 m. |
Provenance: The ten-step plan, the tools and the ranges are from the chapter and Chapter 15. The plans are illustrative and written for this sim.
Rules: Each plan has at most one fault, and exactly one correct outcome. The checks, in order: every tool exists; every value is inside its range; the first step is get_status; the gripper is opened before the first move down; a close_gripper step is followed by a check of holding_something before any lift.
Learner Activity:
- In Explore mode the learner reads the model plan and the five checks.
- The learner switches to the eight plans. Plan 1 is shown.
- The learner chooses an outcome and commits.
- The sim shows whether the choice was correct and the Why text, then moves on. After plan 8 it shows the score.
Feedback: Eight plans, fixed order, one attempt each. Correct: "Correct:
Starting State: Explore mode with the model plan listed and the prompt "What is wrong with this plan, if anything?" ready for the first plan.
Chapter Anchors: The chapter's plan has ten steps, starts with get_status, opens the gripper before the approach, and checks the grasp before lifting. The ranges are x 0.10 to 0.28 m and z 0.02 to 0.07 m. The sim has eight plans and mastery is 7 of 8.
The Learned Policy Executor¶
A step of a plan does not have to be a single tool call. A learned policy executor is an executor that, for a certain kind of step, hands control to a trained policy (Chapter 14) for a short time. A plan could read "move above the block, then run the grasp policy for up to 10 seconds, then check the grip". The planner works at the level of goals ("grasp the block"), the policy does the delicate motion that is hard to program, and the executor puts a time limit around the policy and checks the result when it returns. The same safety rules apply to the policy's actions as to any other (Chapter 14's warning), and the executor's check after the policy is exactly the grasp check of this chapter. Teaming a planner with learned skills in this way is the idea behind SayCan.
Seeing with a Vision-Language Model¶
Vision-Language Models¶
A vision-language model (VLM) is a language model that can also take pictures as input: it interprets and generates information from both images and text. You send it a picture and a question, such as "list the blocks on the table and where they are", and it answers in words, or in JSON if you ask. Through a web API, the picture travels as part of the message. In Anthropic's Messages API an image is a content block of type image with a source that holds the picture as base64 text and its media type, and the text question comes after it ("images before text" works best). The example below is the shape of such a call. It needs a key and a network, and so it is shown and not run:
Pictures cost. A provider charges for a picture in tokens, and for Anthropic's models the count is about \( \lceil w / 28 \rceil \times \lceil h / 28 \rceil \) for a picture of width \( w \) and height \( h \) pixels, up to a limit that depends on the model. A 640 by 480 picture costs \( 23 \times 18 = 414 \) tokens. A 1920 by 1080 one is shrunk to about 1456 by 819 first and costs about 1560, nearly four times as much, and the provider advises reducing a picture when you do not need the detail. A camera frame is almost always bigger than the model needs. The limits, the formula and the model names change, so read the provider's current documentation.
Open-weights models can run on your own computer. Examples that were current when this was written are SmolVLM-Instruct (about 2 billion numbers, an Apache 2.0 license, and about 5 GB of GPU memory according to its card) and Qwen2.5-VL-3B-Instruct, whose license is not Apache 2.0, so read it before you use the model in a class. The catalog of such models turns over fast.
Scene Descriptions and Object Localization¶
A scene description is the written summary of what the camera sees, in a fixed form that the planner can use. The simplest useful form is a JSON list of objects, each with a label and a position. It is the contract between perception and planning: perception promises to deliver that form, and planning promises to use only that form. Object localization is finding where an object is in the picture, and a VLM can return a bounding box, the corners \( [x_1, y_1, x_2, y_2] \) of a rectangle around the object in pixels, with the origin at the top left and \( y \) increasing downward. Ask for absolute pixels, since the provider's guidance is that models do not do well with normalized coordinates, and remember that the coordinates refer to the picture as the model saw it: if the picture was resized, scale the answer back.
Treat every box as a claim. The provider's own documentation says that a model's coordinates are approximate and should be verified before you rely on them, that small objects lose precision, and that counts of objects can be off. So the lab's check_detection function tests each box before it is believed: the box must lie inside the picture, it must be about the size of a block, and the point it gives must fall inside the area that the arm's tools allow. A hallucinated box, in the lab a "yellow block" whose box is off the edge of the picture, is rejected before it can become a target.
Image-to-Arm Coordinates¶
Image-to-arm coordinates is the step that turns a box into a point for the arm, and you built it in Chapter 14. Take the middle of the box, \( ((x_1 + x_2)/2, (y_1 + y_2)/2) \), and convert it with the pixel-to-world homography that the four markers give. The result is \( (x, y) \) in metres in the base frame, which the planner puts into a move_to_pose step. In the lab the three blocks come out within 1.4 to 2.5 mm of their true positions, even though the pretend model's boxes are a few pixels off, because a few pixels are a millimetre or two at this camera height. On a real camera the error will be larger, so measure it, as Chapter 14 advised.
A Model's Coordinates Are a Guess Until You Check Them
A vision model can give a confident box around something that is not there, or put a real object a hand's width from where it is. Always check the box (inside the picture, a believable size, inside the arm's allowed area), and for anything that matters, confirm with a second look or ask a person before the arm moves.
When Something Goes Wrong¶
Replanning and Failed Grasp Recovery¶
Replanning is making a new plan from the present situation when the old one stops working. It differs from retrying: a retry repeats the same step, and a replan builds a different list, because something changed. A refused tool call is a good example. When move_to_pose says "x is outside 0.1 to 0.28", repeating it is pointless, and a new plan with a corrected x is the answer. Failed grasp recovery is the most common recovery on a real arm, and it follows a short routine. The close_gripper tool reports whether it holds something (an empty gripper closes fully, and a held object stops it, Chapter 11). If it holds nothing, open the gripper, go back to the approach and try once more, since a block may simply have moved a little. If the second try fails as well, stop trying and ask. The lab's executor does just that.
Asking a Human¶
Asking a human is a recovery action, and often the right one. An agent should ask in three situations. It cannot do what was requested (the tools do not allow it). It cannot tell what was meant (two red blocks, or "put it over there"). Or it has tried the allowed recoveries and failed. A good question is specific and gives the person something to do: "I could not pick the block up after two tries. Please look at the table and tell me what to do" is better than "error". The rules of thumb in the table help to choose.
| Situation | Best next action | Why |
|---|---|---|
| A grasp finds nothing, the first time | Retry the approach once | The block may have moved slightly |
| A grasp finds nothing, the second time | Ask a human | The retry has failed, so something else is wrong |
| A tool refuses a value and states the range | Replan with a value inside the range | The refusal says exactly how to fix it |
| The same refusal happens twice | Ask a human | The agent is not making progress |
| The status says the arm is stopped | Ask a human, and wait | Only a person can clear a stop |
| The scene has two objects that match the request | Ask a human | A guess could move the wrong one |
| The first look finds no objects | Retry the look once | The picture may have been blurred |
| The user says stop | Stop the arm | Nothing is more important |
Diagram: Failure Recovery Chooser¶
Run the Failure Recovery Chooser MicroSim fullscreen
Failure Recovery Chooser
Type: microsim
sim-id: failure-recovery-chooser
Library: p5.js
Status: Specified
Bloom Level: Evaluate
Bloom Verb: recommend
Learning Objective: The learner will recommend the best next action for each of eight situations an agent meets, choosing among retry once, replan, ask a human and stop the arm, with at least 7 of 8 correct on the first attempt.
Prerequisites: replanning, failed grasp recovery, asking a human, the stop tool, the table of situations (all defined in the section "When Something Goes Wrong" above this block).
Evidence of Mastery: For each of eight situations the learner chooses one of four actions and commits. A choice is correct when it matches the Best action column in Content. Mastery is 7 of 8 correct on the first attempt. Reading the guidance in Explore mode is exploration, not evidence.
Misconceptions: (1) The agent should keep retrying until it works. (After one retry, repeated attempts waste time and can cause harm.) (2) Ambiguity should be settled by a best guess. (A wrong guess can move the wrong object, so ask.) (3) A stopped arm can be restarted by the agent. (Only a person can clear a stop.)
Instructional Rationale: An Evaluate-level recommend objective asks the learner to choose a course of action against criteria and justify it. The four actions differ in risk and cost, so the learner must weigh them against each situation.
Content:
The four actions: "Retry once", "Replan", "Ask a human", "Stop the arm". Eight situations in this fixed order:
| # | Situation | Best action | Why (shown as feedback) |
|---|---|---|---|
| 1 | The grasp check says nothing is held, on the first attempt. | Retry once | The block may have moved a little, so one more try is reasonable. |
| 2 | The grasp check says nothing is held, on the second attempt. | Ask a human | The retry failed, so something else is wrong. |
| 3 | move_to_pose is refused: "x = 0.35 is outside the allowed range 0.1 to 0.28". | Replan | The refusal says how to fix the plan, so build a new one with x inside the range. |
| 4 | get_status says that the arm is stopped. | Ask a human | Only a person can clear a stop, and the agent should wait. |
| 5 | The scene description lists two red blocks, and the request was "pick up the red block". | Ask a human | A guess might move the wrong block. |
| 6 | The first look at the table returns no objects. | Retry once | The picture may have been blurred or dark, so look again. |
| 7 | The same refusal comes back twice in a row for the same step. | Ask a human | The agent is not making progress. |
| 8 | The user types "Stop!" while the arm is moving. | Stop the arm | Stopping is always the priority. |
Provenance: The actions and reasons are from the chapter section "When Something Goes Wrong". The situations are illustrative and written for this sim.
Rules: Each situation has exactly one correct action. "Retry once" applies to a first failure that may be due to chance. "Replan" applies when an error message says how to change the plan. "Ask a human" applies to repeated failure, ambiguity and a stop. "Stop the arm" applies to a user's request to stop.
Learner Activity:
- In Explore mode the learner reads the table of situations and actions.
- The learner switches to the eight situations. Situation 1 is shown.
- The learner chooses an action and commits.
- The sim shows whether the choice was correct and the Why text, then moves on. After situation 8 it shows the score.
Feedback: Eight situations, fixed order, one attempt each. Correct: "Correct:
Starting State: Explore mode with the four actions listed and the prompt "What should the agent do next?" ready for the first situation.
Chapter Anchors: The chapter's recovery routine retries a missed grasp once and then asks a person, replans after a refusal that states a range, and asks a human for a stop or for two matching objects. The sim has eight situations and mastery is 7 of 8.
Interfaces: Web APIs, MCP, and ROS 2¶
HTTP APIs, JSON Requests, and the Requests Library¶
An HTTP API is a way for one program to ask another to do something over a network, using the same protocol as the web. A client sends a request, a method such as GET or POST, an address, headers and sometimes a body, and the server sends back a response with a status code and usually a body. JSON requests carry their data as JSON in the body, which is the same format as the tool arguments of Chapter 15, so a tool call is naturally a POST. The status code is a number that says how it went, and its first digit is the useful part: 2xx means success, 4xx means that you sent something wrong (so fix the request), and 5xx means that the server had a problem (so retry later, or report it).
The requests library is Python's usual client (pip install requests). A call looks like requests.post(url, json=body, headers=headers, timeout=5). The json= argument turns a dictionary into a JSON body, headers carries things such as the key, response.json() turns the answer back into a dictionary, and response.raise_for_status() raises an HTTPError for any 4xx or 5xx. The timeout argument is not optional in practice. The library's own documentation says that nearly all production code should use it, and without one a request to a server that has gone quiet waits forever, which, for a program that moves an arm, is the failure of Chapter 4's lesson again. Also check the status code before you read the body: a failed call can still return JSON, which holds the explanation.
| Status | Meaning | What the client should do |
|---|---|---|
| 200 | It worked | Use the answer |
| 400 | The request was malformed or invalid | Fix the request (the body says how) |
| 401 | The key is missing or wrong | Fix the key |
| 403 | The key is fine but is not allowed to do this | Ask for permission |
| 404 | No such address or tool | Fix the address or the name |
| 409 | A conflict with the state of the thing (the arm is stopped) | Wait; ask a person |
| 429 | Too many requests | Slow down, then retry |
| 500 | The server failed | Retry after a wait, then report it |
| 529 | The service is overloaded (Anthropic's code) | Retry later |
Tool Servers¶
A tool server puts the arm's tools behind an HTTP API, so that any client on the network, whether an agent, a script or a web page, can use them through the same doorway. The lab's server uses only Python's standard library. It offers GET /tools (the schemas) and POST /call/<tool> (run a tool with a JSON body). It does not add any new safety logic: every call goes through call_json and RobotSession.call, so the bounds, the validation and the sticky stop of Chapter 15 apply exactly as before. The server only translates the result into the right status code, 200 for success, 400 for an invalid call, 404 for an unknown tool and 409 for a stopped arm. Keeping the safety in the tool layer, and not in the server, means that the same rules hold whichever way a request arrives, which is the reason to build it in that order.
API Key Handling¶
An API key is a secret string that proves who is calling, and for a cloud model it also means that someone is paying. API key handling is the set of habits that keeps it secret. The server in the lab requires the key in an X-API-Key header, compares it in a way that does not leak timing (hmac.compare_digest), and answers 401 if it is missing or wrong. The habits for the keys themselves are these, and they follow the guidance that model providers publish:
- Never put a key in your code or in a file that goes into Git. Read it from an environment variable with
os.environ["NAME"], which is how Anthropic's library findsANTHROPIC_API_KEYby itself. - For a project, keep keys in a
.envfile, load it with thepython-dotenvpackage (load_dotenv()), and put.envin.gitignorebefore you create the file. - Use a separate key for each purpose, set spending limits, and rotate keys regularly.
- Turn on secret scanning for your repository: GitHub scans for leaked credentials, and LeRobot's own pre-commit checks include a secret scanner called gitleaks.
- If a key has ever been in a public place, even for a minute, treat it as stolen and replace it.
The lab's check_secrets.py is a small version of such a scanner. It looks for strings that look like keys, ignores lines that read the environment, and checks that a .env file is listed in .gitignore.
Ignore .env Before You Create It
Add .env to .gitignore first, then create the file. The other order is how keys end up in public repositories: one git add . is all it takes, and Git remembers it even after you delete the file.
Diagram: HTTP Status Reader¶
Run the HTTP Status Reader MicroSim fullscreen
HTTP Status Reader
Type: microsim
sim-id: http-status-reader
Library: p5.js
Status: Specified
Bloom Level: Understand
Bloom Verb: interpret
Learning Objective: The learner will interpret eight HTTP status codes returned by the arm's tool server or a model provider by choosing what the client should do, with at least 7 of 8 correct on the first attempt.
Prerequisites: HTTP API, status code, requests library, API key (all defined in the sections "HTTP APIs, JSON Requests, and the Requests Library" and "API Key Handling" above this block).
Evidence of Mastery: For each of eight responses the learner chooses one of six actions and commits. A choice is correct when it matches the Action column in Content. Mastery is 7 of 8 correct on the first attempt. Reading the status table in Explore mode is exploration, not evidence.
Misconceptions: (1) Any error means that the server is broken. (A 4xx means that the request was wrong.) (2) A 401 means the request body is wrong. (It means the key is missing or wrong.) (3) A 429 should be retried at once. (The client should slow down first.)
Instructional Rationale: An Understand-level interpret objective asks the learner to explain what a message means. The status code's first digit separates the client's faults from the server's, so the learner must connect each code to the right party and the right remedy.
Content:
The six actions: "Use the answer", "Fix the request", "Fix the key or the permission", "Fix the address or the name", "Slow down and retry later", "Server problem: retry after a wait, then report". Eight responses in this fixed order:
| # | Response | Action | Why (shown as feedback) |
|---|---|---|---|
| 1 | 200 from POST /call/get_status | Use the answer | The call worked. |
| 2 | 400 with the message "x = 0.35 is outside the allowed range 0.1 to 0.28" | Fix the request | The request had an invalid value, and the message says how to correct it. |
| 3 | 401 from GET /tools | Fix the key or the permission | The X-API-Key header is missing or wrong. |
| 4 | 403 from a model provider with a valid key | Fix the key or the permission | The key is valid but is not allowed to do this. |
| 5 | 404 from POST /call/wave_hello | Fix the address or the name | There is no tool with that name. |
| 6 | 429 from a model provider | Slow down and retry later | The client sent too many requests. |
| 7 | 500 from a model provider | Server problem: retry after a wait, then report | The failure is on the server's side, and a retry after a wait often works. |
| 8 | 529 from a model provider | Slow down and retry later | The service is overloaded, so retry after a wait. |
Provenance: The meanings of the codes are from the chapter's status table, which follows the Anthropic API's documentation of its errors (401 for a problem with the key, 429 for rate limits, 500 for a server error to retry with backoff, 529 for overload) and the lab's tool server (400, 404 and 409). The responses are illustrative and written for this sim.
Rules: Each response has exactly one correct action. A 2xx is success. A 4xx is the client's fault, and the action depends on the code. A 5xx is the server's fault. A 429 and a 529 are answered by waiting.
Learner Activity:
- In Explore mode the learner reads the status table.
- The learner switches to the eight responses. Response 1 is shown.
- The learner chooses an action and commits.
- The sim shows whether the choice was correct and the Why text, then moves on. After response 8 it shows the score.
Feedback: Eight responses, fixed order, one attempt each. Correct: "Correct:
Starting State: Explore mode with the status table listed and the prompt "What should the client do?" ready for the first response.
Chapter Anchors: The chapter's table lists 200, 400, 401, 403, 404, 409, 429, 500 and 529, and states that 4xx is the client's fault and 5xx the server's. The sim has eight responses and mastery is 7 of 8.
The Model Context Protocol and MCP Servers¶
The Model Context Protocol (MCP) is an open standard for connecting AI applications to tools and data, and the tool server above is a homemade version of the same idea. In MCP a host (the AI application) starts clients, each of which connects to a server that offers tools, resources and prompts. The messages are JSON-RPC, and the two usual transports are stdio (the client starts the server as a program and talks to it on its standard input and output) and streamable HTTP. The specification read for this book was dated 2026-07-28. The point of a standard is that a tool written once works with any client that speaks it.
An MCP server for the arm is short with the official Python SDK, because the SDK builds the tool schema from the function's type hints. The SDK's package is mcp, and in version 2 its server class is called MCPServer. (In version 1 it was FastMCP, in mcp.server.fastmcp, and old tutorials use that name, which fails on version 2, so check the version that you installed.) The lab's arm_mcp.py registers four of the arm's tools, with the bounds written as type hints (Annotated[float, Field(ge=0.10, le=0.28)]), and the SDK turns them into the same minimum and maximum that Chapter 15 wrote by hand. The SDK checks the arguments before your function runs and refuses a value out of range with a technical message, so a real design catches that message, or keeps the friendlier validation of Chapter 15 inside the function as well. Two cautions come from the specification itself. Tools "represent arbitrary code execution and must be treated with appropriate caution", so a host should get a person's consent before it runs one. And a server that uses the stdio transport must never print to standard output, because that channel carries the protocol's messages, so write logs to the standard error stream or to a file.
ROS 2 Bridges¶
A ROS 2 bridge connects the agent's tools to the ROS 2 world of Chapter 13, and it is another doorway, with the same rules. One node wraps the arm's tools: it subscribes to a topic of commands, runs each one through the same RobotSession.call, and publishes the result and the arm's state as a sensor_msgs/JointState message. In the other direction, an agent running outside ROS can call the tools through the tool server or MCP, and the bridge makes the arm appear to the rest of a ROS system, such as MoveIt or a camera node, as an ordinary participant. The reBot-DevArm's published ROS 2 packages target the Humble release and live in separate repositories. The design rule is the one of the whole chapter: whatever the route in, the call arrives at the same checked tools.
Where the Agent Runs¶
Local and Cloud Agents¶
A local agent runs on a computer that you control, with a model that runs there too, and a cloud agent uses a model that runs on a provider's computers and is reached over the internet. Each has a place. A cloud model is usually more capable, needs no special hardware, and costs money for each use, and it needs a network that works. A local model is private and costs nothing per use, and it is limited by the computer's memory and speed. Running a model locally has become simple: Ollama is an open-source program that downloads a model and serves it on your own computer, at http://localhost:11434 by default, with a command such as ollama run <model name> and an HTTP API that takes JSON requests, so the tool-calling loop of this chapter works against it in the same way.
An edge AI computer is a small computer built to run models on the robot itself. Examples are NVIDIA's Jetson Orin Nano Super Developer Kit (67 trillion operations per second at 8-bit precision and 8 GB of memory, according to NVIDIA) and a Raspberry Pi 5 with the AI HAT+ accessory (13 or 26 trillion operations per second). Prices of such boards have moved a great deal with the cost of memory: the Jetson was announced at $249 and a trade report in July 2026 said that NVIDIA had raised it to $399, so check a current price before you plan a class set. A model has to fit the board's memory, which means small models, and small models are less reliable at planning than the large ones, which argues for keeping the planner's job simple and the checks strict.
Latency, Cost, and Privacy¶
Latency and cost are the price of a cloud agent. Every turn of the agent loop is a request over the network, so a task of ten tool calls is ten round trips, each taking a second or more, and a picture adds tokens, which add both time and money (the formula above). Three habits cut both: shrink pictures to what the model needs, keep the tools' answers short (Chapter 15's table), and give the model a whole plan to write in one go instead of a call per step. The planner-executor split helps again, since a plan is written once and executed locally, with no model in the loop for each step.
Privacy considerations matter because a camera sees whatever is in front of it. A picture sent to a cloud model leaves your computer and is handled under the provider's rules. A dataset pushed to the Hugging Face Hub (Chapter 14) is public unless you mark it private. A camera in a classroom or a bedroom may capture people who did not agree to it. The practical rules are to know where each picture goes, to point cameras at the work area and not at people, to use a local model when the scene is sensitive, and to keep the logs of Chapter 13 free of keys and of pictures that you do not need.
| Local agent | Cloud agent | |
|---|---|---|
| Where the model runs | Your computer or an edge board | The provider's computers |
| Capability | Limited by memory and speed | Usually higher |
| Cost | The hardware, then nothing per use | A charge for each use |
| Network | Not needed | Required |
| Privacy | Pictures stay with you | Pictures go to the provider |
| Delay per turn | Depends on the hardware | A network round trip plus the model's time |
Pick the Smallest Setup That Works
A class of twelve does not need a cloud model for every wiggle of the gripper. A local model for the planner, a strict executor, and a cloud model only for the occasional hard question is a sensible shape. Choose by what must stay private and what the network will allow.
Lab: Perceive, Plan, Check, and Execute¶
In this lab you will build the parts of the chapter and join them into one run, entirely on your computer: a pretend vision model, a plan checker and executor with recovery, a tool server that you call with requests, an MCP server for the arm, and a secret scanner. You need no key, no network and no arm. The new libraries are requests and mcp. You will extend the arm-lab project and reuse the tools, the vision code and the fake arm.
Step 1. Activate the project and install the libraries. The pin on mcp matters, because the server class was renamed in version 2.
Step 2. Write the pretend world and the planner. WorldSession is the RobotSession of Chapter 15 with an object that close_gripper can find or miss. plan_pick_and_place is the agent planner, and Step is one line of a plan. validate_plan checks every step against its tool's schema and names each fault by its step number. execute_plan is the executor: it validates first, runs the steps in order, looks at each result, and handles a missed grasp by retrying once and then asking. Create armlab/world.py and armlab/planner.py:
Step 3. Write the perception module. MockVLM plays the part of the vision-language model: it finds the real blocks in the synthetic picture of Chapter 14 and returns their boxes as JSON text, a few pixels off, and it can add an invented object. parse_detections pulls the JSON out of the answer and checks its structure, and check_detection rejects a box that is outside the picture, the wrong size, or pointing outside the arm's allowed area. Create armlab/perception.py:
Step 4. Run the perception. Create perception_demo.py:
1. The model's answer (text, with JSON inside)
Here is what I see: {"objects": [{"label": "red block", "box": [204, 200, 239, 235]}, {"label": "green block", "box": [301, 224, 341, 260]}, {"label": "blue block", "box": [435, 275, 468, 312]}, {"label": "yellow block", "box": [650, 300, 700, 350]}]}
2. Parse it, then check each detection before believing it
red block accepted at (0.220, +0.089) m, 1.4 mm from the truth
green block accepted at (0.198, -0.001) m, 2.0 mm from the truth
blue block accepted at (0.152, -0.118) m, 2.5 mm from the truth
yellow block REJECTED: the box is not inside the picture
3. Answers that are not JSON are an error, not a crash
ValueError: the answer has no JSON in it
ValueError: objects must be a list
ValueError: each object needs a label and a box of four numbers, but got {'label': 'red block'}
ValueError: the JSON is not in the expected form: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Three blocks were accepted, each within a few millimetres of the truth, and the invented yellow block was rejected because its box lies outside the picture. The three bad answers in item 3 each raise a ValueError with a reason, so a model that answers in the wrong form cannot crash the program or slip through.
Step 5. Run the whole architecture. The script perceives the red block, plans, checks the plan, and executes it three ways: a plan with two faults is refused as a whole, a plan with the block present is carried out, and a plan with the block missing fails its grasp check twice and ends by asking a person. Create architecture_demo.py:
1. Perceive: ask the pretend model where the red block is, and check the answer
red block at (0.220, +0.089) m, problems: none
2. Plan: break 'put the red block in the bin' into steps, and check the whole plan first
1. get_status {} # look before moving
2. open_gripper {'percent': 100} # open before approaching
3. move_to_pose {'x': 0.22, 'y': 0.089, 'z': 0.07, 'speed_dps': 60} # above the block
4. move_to_pose {'x': 0.22, 'y': 0.089, 'z': 0.03, 'speed_dps': 60} # down to the block
5. close_gripper {} # grasp
6. move_to_pose {'x': 0.22, 'y': 0.089, 'z': 0.07, 'speed_dps': 60} # lift
7. move_to_pose {'x': 0.2, 'y': -0.1, 'z': 0.07, 'speed_dps': 60} # above the bin
8. move_to_pose {'x': 0.2, 'y': -0.1, 'z': 0.03, 'speed_dps': 60} # down into the bin
9. open_gripper {'percent': 100} # release
10. go_home {} # clear of the table
problems found before any move: none
3. A bad plan is rejected as a whole
plan rejected before any move: step 4: there is no tool called 'wave_hello' step 5 (move_to_pose): z = 0.01 is outside the allowed range 0.02 to 0.07 (height above the table, meters). Choose a value inside the range.
result: refused
4. Execute: the block is there, and the grasp check passes
step 1 get_status look before moving -> ok
step 2 open_gripper open before approaching -> ok
step 3 move_to_pose above the block -> ok
step 4 move_to_pose down to the block -> ok
step 5 close_gripper grasp -> ok
step 6 move_to_pose lift -> ok
step 7 move_to_pose above the bin -> ok
step 8 move_to_pose down into the bin -> ok
step 9 open_gripper release -> ok
step 10 go_home clear of the table -> ok
result: done
5. Execute: the block has gone, so the grasp check fails, the executor retries once and then asks a person
step 1 get_status look before moving -> ok
step 2 open_gripper open before approaching -> ok
step 3 move_to_pose above the block -> ok
step 4 move_to_pose down to the block -> ok
step 5 close_gripper grasp -> ok
grasp check: nothing is held (attempt 1 of 2)
step 2 open_gripper open before approaching -> ok
step 3 move_to_pose above the block -> ok
step 4 move_to_pose down to the block -> ok
step 5 close_gripper grasp -> ok
grasp check: nothing is held (attempt 2 of 2)
asking a person: I could not pick the block up after two tries. Please look at the table and tell me what to do.
result: asked a person
Look at item 3. The bad plan had an invented tool and a value out of range, and the executor named both faults by step number and moved nothing: the arm never left home. In item 5 the executor did what it was told to do for a missed grasp, and not one step more. It tried again from the approach, found nothing again, and asked. That is the planner-executor split working: the plan came from one place, the checks and the recovery from another.
Two Programs, Two Jobs
The planner only wrote a list, and the executor only ran it and looked at the results. Neither had to be clever about safety, because the doorway under both of them was. When you add a new skill to the agent later, add it as a new tool and a new check, and leave the rest alone.
Step 6. Put the tools behind a web API. The tool server uses only the standard library. It requires an X-API-Key header, offers GET /tools and POST /call/<tool>, and turns each result into an honest status code. The script starts it in a background thread on a free port, with a key that comes from the environment (or a random one if the variable is not set), and uses it with requests. Create armlab/toolserver.py and server_demo.py:
1. Without a key, with a wrong key, and with the right key
no key: 401
wrong key: 401
right key: 200 -> ['close_gripper', 'get_status', 'go_home', 'move_to_pose', 'open_gripper', 'stop']
2. Calls, and the status code that each one gets
POST /call/get_status -> 200 status report
POST /call/open_gripper -> 200 gripper opened to 50 percent
POST /call/move_to_pose -> 400 x = 0.35 is outside the allowed range 0.1 to 0.28 (forward from the base, mete
POST /call/wave_hello -> 404 unknown tool 'wave_hello'. The tools are: get_status, move_to_pose, open_gripp
POST /call/stop -> 200 stopped: torque is off. Only a person can clear a stop.
POST /call/go_home -> 409 the arm is stopped. Only a person can clear a stop, so tell the user and wait.
3. raise_for_status turns a 4xx or 5xx into an exception that the program can handle
HTTPError: 409 Client Error: Conflict
4. A timeout stops a program from waiting forever for a server that is not there
the call ended with a requests exception instead of hanging: True
The status codes carry the meaning: 401 for a bad key, 400 for a value out of range, 404 for an invented tool, and 409 for a stopped arm, which raise_for_status turned into an HTTPError that a program can catch. The message for the 400 is the same sentence that the agent read in Chapter 15, because the server only passes it on. The last item shows a timeout at work, since without one that call could wait forever.
Step 7. Offer the tools over MCP. arm_mcp.py registers four tools with the MCP SDK, and the bounds are in the type hints. The script looks at the server from the inside, without starting a transport: it lists the tools, shows the schema that the SDK wrote for z, and calls a tool well and badly. Create armlab/arm_mcp.py and mcp_demo.py:
1. The tools that an MCP client would see: ['get_status', 'move_to_pose', 'go_home', 'stop']
2. The schema of z, written by the SDK from the type hint: {'type': 'number', 'minimum': 0.02, 'maximum': 0.07}
required parameters: ['x', 'y', 'z']
3. A good call: moved to (0.2, 0.1, 0.05) at up to 60.0 degrees per second
4. A call beyond the limit is refused before the function runs: Error executing tool move_to_pose: 1 validation error for move_to_poseArguments Input should be less than or equal to 0.28 [type=less_than_equal, input_value=0.35, input_type=float]
The schema that the SDK wrote from a type hint carries the same minimum and maximum as the one that you wrote by hand in Chapter 15, and the call beyond the limit was refused before the arm's code ran. To connect a real MCP client, you run python -m armlab.arm_mcp, which uses the stdio transport, and give the client that command. Check the client's documentation for how it starts a server. Remember that the SDK's refusal is technical, and that the checks in RobotSession are the ones that must always hold.
Step 8. Scan for secrets. check_secrets.py looks for lines that look like keys, and checks that a .env file is ignored. The demo builds a temporary project with a deliberate mistake and shows the scanner catching it. Create check_secrets.py and secrets_demo.py:
1. A project that reads its key from the environment
findings: 0
2. The same project after someone pastes a key into the code and creates a .env file
.env:1: looks like a key or password
bad.py:1: looks like a key or password
.env is ignored by Git: False
3. After adding .env to .gitignore: True
The scanner flagged both the key pasted into the code and the .env file, and it reported that .env was not protected until a .gitignore listed it. A scanner that runs before every commit (as LeRobot's does with gitleaks) catches the mistake while it is still on your computer.
Step 9. Record your work.
git add .
git commit -m "Add planning, a pretend VLM, a tool server, an MCP server, and a secret scanner"
git log --oneline
Check Your Work¶
Your project is complete when all of these are true:
python perception_demo.pyaccepts three blocks and rejects theyellow blockwiththe box is not inside the picture.python architecture_demo.pyshowsplan rejected before any movefor the bad plan,result: donefor the good one andresult: asked a personfor the missed grasp.python server_demo.pyshows the status codes 401, 200, 400, 404 and 409.python mcp_demo.pylists four tools and shows the schema ofzwith a minimum of 0.02 and a maximum of 0.07.python secrets_demo.pyfinds nothing in the clean project and two findings in the other.- Earlier scripts and
python -m pytest -qstill pass. git log --onelineshows a sixteenth commit.
Challenge: Rate-Limit the Server¶
A server that anyone with the key can call as fast as they like is at the mercy of a runaway agent. Add a limit to make_server that answers 429 if more than 5 calls arrive in any one second, with a message that says how long to wait. Test it by sending 10 quick requests with requests and counting the 200s and the 429s.
Click to see one solution
Keep a list of the times of recent calls, drop the ones older than a second, and refuse when the list is full. Add the list and the check to the handler's do_POST, before the call is made:
In do_POST, after the key check, add: if too_fast(): return self._reply(429, {"error": "more than 5 calls in one second. Wait one second and try again."}). Ten quick get_status requests give five 200s and five 429s. The client's correct answer to a 429 is the one in the status table: slow down, and retry. Chapter 17 treats rate limiting as one of the layers that stops a runaway agent.
Summary and Key Takeaways¶
You can now build an agent that sees a scene, plans, checks, and acts, and connect its tools to the world.
- An agent architecture has six parts: perceive, plan, check, execute, act and recover, with models only in the first two. The planner-executor split keeps the model's plan as data that code can check. Task planning by task decomposition turns a goal into steps, and the agent planner writes the list. A learned policy executor can hand a step to a trained policy for a short time.
- A vision-language model answers questions about a picture. A scene description is the fixed form of that answer, and object localization returns boxes in pixels. Every box is a claim to check, and image-to-arm coordinates convert a checked box with the homography of Chapter 14.
- Replanning builds a new plan from the present situation. Failed grasp recovery retries the approach once. Asking a human is the right action for repeated failure, for ambiguity and for a stop.
- An HTTP API carries JSON requests, with status codes that say whose fault a failure is. The requests library needs a
timeoutandraise_for_status. A tool server exposes the tools without changing their safety checks. API key handling means environment variables, a.gitignorefor.env, separate keys and secret scanning. - The Model Context Protocol standardizes how clients reach tools, and an MCP server can be built from type hints (in version 2 of the SDK it is
MCPServer). A ROS 2 bridge is one more doorway into the same checked tools. - A local agent keeps pictures at home, and a cloud agent is more capable and costs money and time. An edge AI computer runs small models on the robot. Latency and cost grow with every turn and every picture, and privacy considerations start with where each picture goes.
My Agent Can See, Plan, and Cope!
You just built an agent that checks what its vision model says, plans in data, refuses a bad plan before it moves, recovers from a missed grasp by asking, and reaches its tools over a web API and MCP with keys kept out of the code. Every doorway is a safety layer in the making, and Chapter 17 puts them together. Let's move it on!
Self-Check Questions¶
Test yourself before you move on. Click each question to reveal an answer.
1. Why is a plan written as data safer than a model that calls tools directly?
Data can be inspected before anything happens. The whole plan can be validated, each fault can be named by its step, and a bad plan can be refused before one motor turns. A model that calls tools directly leaves no moment to check.
2. A VLM returns the box [650, 300, 700, 350] for a 640 by 480 picture. What should the program do?
Reject it. The box lies beyond the right edge of the picture (x from 650 to 700, in a picture 640 pixels wide), so it cannot be an object in the picture. A model's boxes are claims that must be checked, and this is a hallucination.
3. How many tokens does a 640 by 480 picture cost on the formula in the chapter, and what does shrinking a 1920 by 1080 picture to that size save?
\( \lceil 640/28 \rceil \times \lceil 480/28 \rceil = 23 \times 18 = 414 \) tokens. A 1920 by 1080 picture costs about 1,560, so the smaller picture needs about a quarter of the tokens, which also means a lower cost and a shorter wait.
4. A request to the tool server returns 401. A second returns 400. What does each tell the client to do?
A 401 means that the key is missing or wrong, so fix the key. A 400 means that the request was invalid, so read the message (it names the parameter and the allowed range) and fix the request.
5. Why does the tool server not check the bounds itself?
The bounds are in the tool layer (RobotSession.call), and the server passes every call to it. Putting the safety there means that the same rules hold whether a call arrives by command line, web API, MCP or ROS 2, and a new doorway cannot forget a check.
6. An agent's second attempt to grasp a block also finds nothing held. What should it do and why?
Ask a human. One retry covers the chance that the block moved a little. After a second failure, something else is wrong, such as a missing block or a miscalibrated camera, and more attempts waste time and may cause harm.
Chapter 17 collects these doorways into a full safety design for agent-controlled arms, and shows how to test it.