Skip to content
The AI Agent Factory

14.9 How today's tools realize it

Approved by
Panaversity ·
39 min read

In everyday life. Three office buildings all take deliveries. In the first, the front desk calls you before any parcel comes up. In the second, the doors stay locked, and you let each driver in yourself. In the third, drivers walk straight in unless you post your own rule at the door. In every building, you read the label before you sign for the parcel.

What you will learn. How to practice each lesson of this chapter in your own agent. Also, where Claude Code, Codex and OpenCode differ when they install a package, reach a server on your own computer, keep it running and review a change, as of October 30, 2026.

Why it matters. Each product stops at a different place before it installs a package or reaches a server. Claude Code is the front desk: it asks before each install. Codex is the locked door: its sandbox keeps the network off until you approve. OpenCode is the open door, until you set its rules. The parcel is the package. Your part does not change: you decide, you predict, the agent runs it, and you read what really happened.

How to use this page. Each section is the practice for one lesson, and that lesson links to it. Each is a short exercise, ten to fifteen minutes. Most steps are the same in every agent, so they are written once. Where the three products differ, the section ends with tabs: pick your tool once, and every strip on this page follows. Each lesson already showed its own block's output, so most exercises change one thing in a copy first, and you predict the new result. That is the Modify step of PRIMM-AI+. Before you approve a command, read what the agent says it will do, and say no to anything you do not understand. Concept 12.7 covers the products in full.

Practice: modules

From Concept 14.1. Find out what a module shares with other files, mark where data comes in, then install the project's packages from its lock files. Fifteen minutes, and a few more the first time, for the setup.

The first time, set up once. The rest of this page uses this folder and these tools.

  1. Download brightline-lab-ch14.zip, and unzip it.
  2. Open its concept-blocks/ch14 folder in your agent. Set the agent to ask before it acts: Manual in Claude Code, Ask for approval in Codex. In OpenCode, add the rules its tab at the end of this section describes. The exercises start in this folder, unless a step says otherwise.
  3. You set up Node.js and uv in 13.2. If this computer does not have them, the download's field-guide-code-toolchain.md has the install steps.

The exercise.

  1. Send: "Please copy 14-1.ts to try-14-1.ts and 14_1.py to try_14_1.py. In each copy, also import TERMS_DAYS from the due-date module, and print it on a new last line. Don't run them yet."
  2. Open brightline-ap/src/due-date.ts and brightline-ap/py/brightline_ap/due_date.py in a text editor, not in the agent. Is TERMS_DAYS marked export? Write in your note what each copy will print now.
  3. Send: "Please run try-14-1.ts with Node and try_14_1.py with uv run python, and show me what each prints. Don't explain them yet. I want to check my guesses first." Expect node try-14-1.ts, then uv run python try_14_1.py.
  4. Send: "Please look in ../../brightline-ap/src for the lines that read a file, with readFileSync, and the functions whose names end in FromRow, which turn rows into records. Show me each one, and don't change anything." Those are this program's edges, as 14.1 says. Mark them in your note.
  5. Open brightline-ap/package.json and brightline-ap/py/pyproject.toml in the text editor. In your note, list each package they name, and answer the three questions from 14.1 for each one.
  6. Send: "Please copy the brightline-ap folder into a new folder beside it, practice-14, and put the copy under git with one commit." Expect a copy command, then git init, git add . and git commit. Then open practice-14 in your agent, and send: "Please install this project's packages from its lock files, with npm ci here and uv sync in py/. Before each command, tell me which packages it will install." Compare the names with your note before you approve. Expect npm ci, then uv sync. Most later exercises work in practice-14, so the lab still starts clean.

What you should see. Node prints nothing. It stops with a SyntaxError, because due-date.ts does not provide an export named TERMS_DAYS, and Node loads every import before it runs any line. Python prints the usual line, then the five terms with their days, {'Due on receipt': 0, 'Net 15': 15, ...}, because Python shares every top-level name. Three files read a file: src/list-vendors.ts, src/due-by-friday.ts and src/reconcile.ts. Two functions turn rows into records: invoiceFromRow and vendorFromRow. In practice-14, npm reports 4 packages added. Then uv lists each package it installed: pyright, pytest and ruff, and the packages they need. So there are more names than in your note, because a lock file holds the exact version of those too.

How each agent asks before it runs a command or installs a package. In these tabs, a sandbox is not the throwaway machine of 12.3. It is a set of limits on your own computer, like a contractor who works in your house but may change only one room, and asks before going out for supplies.

An install, such as npm ci, npm install zod or uv add pydantic, is a shell command, so Claude Code asks before each one in Manual and Accept edits modes.1 Read the package name in the prompt before you approve. Anthropic's documentation describes no step that checks a package before it is installed. The permission prompt, your deny rules2 and, if you turn it on, the sandbox's list of allowed domains are the controls. A local session has no network limit by default. With the sandbox on, the allowed domains start empty.3 Cloud sessions default to a Trusted network level, which allows package registries such as npm and PyPI.4 Use Manual for this page.

In the workspace-write sandbox, network access is off by default, and Codex asks for approval before a command that needs the network, such as npm install.5 So expect it to ask before npm ci and uv sync, which download packages. Cloud environments have an internet access setting with a package-managers preset.6 Use Ask for approval for this page, as in 13.10.

OpenCode runs npm and uv with your own rights.7 Before your first message in a folder, set edits and the shell action to ask, as in Lab 12, so each command and each install waits for you.8 Do it again in practice-14, before you ask for the installs. OpenCode does not sandbox the agent. Packages it installs and servers it starts run with your rights. Its security policy says to run it in a Docker container or virtual machine if you need isolation.9

Practice: types you write down

From Concept 14.2. Write the terms as a type, try three calls of your own, and see what the type checker and Node each do with them. Ten minutes.

  1. Open practice-14 in your agent, and send: "Please create exercises/try-14-2.ts. In it, write the five terms from src/due-date.ts as a literal type, Terms. Add a function, days, that takes Terms and returns its number of days from a small table, with no other check. Don't add any calls yet."
  2. In your note, write three calls to days of your own. In one, use an allowed term in the wrong case. In another, use terms Brightline never uses, written with a cast, such as as Terms. In the third, pass a string variable that holds allowed terms, as text read from a file would. For each call, predict whether the type checker refuses it.
  3. Send: "Please add my three calls to try-14-2.ts, and print each result. Then run npm run typecheck, and don't fix anything." Then paste your calls. Expect npm run typecheck.
  4. Predict what Node prints for each call. Then send: "Now please run try-14-2.ts with Node, and show me what it prints. After that, delete the file, so the type checker passes again." Expect node exercises/try-14-2.ts, then a command that deletes the file.

What you should see. The type checker names two of your lines: the wrong case, and the string variable, even though its text is allowed. The checker knows only that the variable holds a string, not which one. It accepts the cast. Node runs all three calls anyway, because it removes the types first. It prints undefined for the wrong case and for the cast, and the right number of days for the string.

Practice: classes and objects

From Concept 14.3. Read a class's methods before you run code that uses it, then change what counts as the same vendor. Ten minutes.

  1. Open brightline-ap/src/invoice-book.ts in a text editor, and list the methods of InvoiceBook in your note. Then open 14-3.ts, which calls three of them, and compare. Predict what it prints today, before the lab adds anything.
  2. Open concept-blocks/ch14 in your agent again, and send: "Please run 14-3.ts with Node and show me what it prints. Don't explain it yet. I want to check my guess first." Expect node 14-3.ts.
  3. Send: "Please copy 14-3-equality.ts to try-14-3.ts. In the copy, change the line that makes b to const b: Vendor = a;. Then copy 14_3_equality.py to try_14_3.py, and change @dataclass to @dataclass(eq=False) in that copy. Don't run them yet." eq=False tells @dataclass not to write __eq__.
  4. Write in your note what each copy will print now, line by line.
  5. Send: "Please run try-14-3.ts with Node and try_14_3.py with uv run python, and show me what each prints. Don't explain them yet." Expect node try-14-3.ts, then uv run python try_14_3.py.

What you should see. 14-3.ts stops with TypeError: book.openTotalCents is not a function, because the class has no such method until Task 3 adds it. The TypeScript copy prints true true, then the vendor, because a and b are now one object. The Python copy prints False Vendor(id='V005', name='Hocking Valley Pallets'), then False. Without __eq__, == asks whether two objects are the same object, and @dataclass still wrote __repr__.

Practice: data models and schemas

From Concept 14.4. Decide the rule for an amount, add the two schema libraries, and see what each one does with values you chose. Fifteen minutes.

  1. In your note, write the rule for an invoice amount in your own words. Then pick five amounts to test it with. Use one written as text, such as "482500", and the same text with spaces around it. Add a negative amount, an amount with a fraction of a cent, and INV-8301's amount with one zero missing, 48250. For each, write whether your rule should accept it.
  2. Open practice-14 in your agent, and send: "Please add the package zod with npm, and pydantic with uv add in py/. Ask before each command." Ask the three questions from 14.1 again, and approve only these two names. Your agent asks as the tabs in Practice: modules say. Expect npm install zod, then uv add pydantic.
  3. Send: "Please write exercises/try-14-4.ts with a Zod schema, and exercises/try_14_4.py with a Pydantic model, each for one field, the amount, following my rule. Check each of my five values with both, and print what each one returns, or why it refuses. Don't run them yet." Then paste your rule and your values. Read each rule line. Does it say what your note says?
  4. Predict what each library does with each value. Then send: "Please run both files and show me what they print." Expect node exercises/try-14-4.ts, then uv run python ../exercises/try_14_4.py from py/.
  5. Before you commit, run your agent's review, as the tabs below say. Then send: "Please show me git status, then commit only package.json, package-lock.json, py/pyproject.toml and py/uv.lock." Expect git status, then git add and git commit.

What you should see. Zod refuses both text values, the negative and the fraction, and accepts 48250. Pydantic turns both text values into 482500, refuses the negative and the fraction, and also accepts 48250. Neither can see the missing zero, because a schema checks shape, not meaning. The commit holds the two package lists and both lock files, so a clean copy installs the same versions.

How each agent reviews a change before the commit:

Run /code-review before the commit.10

/review reports findings on your uncommitted changes or your branch, without changing files.11

OpenCode's version 2 documentation describes no review command. Read its review panel or git diff instead.12

Practice: errors as design

From Concept 14.5. Watch today's code stop at the first bad row, watch a catch block hide every problem, and decide what the report should say. Fifteen minutes.

  1. Open practice-14 in your agent. Read src/reconcile.ts in a text editor. Then predict in your note what it does with data/invoices-messy.csv, whose four problems 14.5 lists. Does it stop, and where? Which problems does it never report?
  2. Send: "Please run npm run reconcile -- data/invoices-messy.csv, and show me everything it prints. Don't explain it yet. I want to check my guess first." Expect npm run reconcile -- data/invoices-messy.csv.
  3. Send: "Please copy src/reconcile.ts to exercises/try-14-5.ts. In the copy, put the dueDate call in a try and catch that skips the row on any error. Don't run it yet." Predict what the copy prints for the same file.
  4. Send: "Please run the copy on data/invoices-messy.csv, and tell me whether it ends with a failure code." Expect node exercises/try-14-5.ts data/invoices-messy.csv.
  5. Decide in your note, as the person who owns the export: should a run on this file stop at the first problem, or collect them all? Then write the report line you want for INV-8306, with its line, its field and what is allowed.

What you should see. The real run stops at INV-8306, on line 7, with Error: Unknown terms: Net 10. The message names no line and no invoice. The code never looks at the amount on line 5, the date on line 11 or the vendor on line 14. The copy prints 0 mismatches in data/invoices-messy.csv, with no failure code. The file has four problems, and the report says nothing is wrong.

Practice: JSON and REST APIs

From Concept 14.6. Start the sample API, ask it for a page that does not exist, and see what code does when it reads the body without checking the status code. Fifteen minutes.

  1. Open practice-14 in your agent. Choose a made-up key, such as a word and a number. It is made up, so it is safe to type in a chat. Send: "Please copy .env.example to .env, and put my made-up key in it, [your key]. Then show me git status." .env must not appear in the list, because git ignores it.
  2. Send: "Please start the sample API with npm run api, in the background, so you can keep working. Tell me how you will stop it later." Expect npm run api. The tabs below say how each agent keeps it running.
  3. Send: "Please copy 14-6.ts from the lab's concept-blocks/ch14 folder to exercises/try-14-6.ts, and change page=2 to page=4 in the copy. Don't run it yet." Use the table in 14.6 to write in your note which status code page 4 will get, and what a client should do then. Then predict each line the copy prints.
  4. Send: "Please run try-14-6.ts with the key from .env, and show me what it prints. Don't explain it yet." Expect node --env-file=.env exercises/try-14-6.ts. Then ask: "What did the body of that answer say?"
  5. Send: "Please stop the sample API now, and show me that nothing is still running."

What you should see. 400 application/json, because the open invoices fill only three pages. Then a TypeError, Cannot read properties of undefined (reading 'length'). The body holds an error, page must be a whole number from 1 to 3, not a page of invoices, and the code read it as a page anyway. A client acts on the status code before it reads the body. At the end, nothing is still running.

How each agent reaches a server on your own computer and keeps it running. In these tabs, a sandbox is not the throwaway machine of 12.3. It is a set of limits on your own computer, like a contractor who works in your house but may change only one room, and asks before going out for supplies.

A local session has no network limit by default. With the sandbox on, a local test server needs an exception: the allowLocalBinding setting on macOS, or excludedCommands on Linux, where a direct connection to localhost does not reach servers on the host.3 Claude Code can run a long-running command, such as a development server, in the background while it keeps working. You can send a running command to the background with Ctrl+B. /tasks lists background tasks.13 Its web fetch tool refuses localhost, so to call a local API it runs a command such as curl instead.14

OpenAI's security page says that when its network proxy is on, local and private addresses such as localhost are blocked unless you allow them. The proxy is off by default.5 So check here whether your Codex session can reach the sample API, before the lab needs it, and allow it in your settings if it cannot. Codex can run commands in background terminals. /ps lists them with their recent output, and /stop stops them.15

OpenCode has no network limit.7 A shell command has your full file, process and network authority, so it can reach localhost and the internet alike.7 The shell tool can start a command in the background, which returns at once and tells the session when the command ends. Its documentation names development servers as the use.7 The key here is made up. With a real key, remember that OpenCode can read any file your account can.

Practice: async and await

From Concept 14.7. Decide which answers deserve another try, then watch your rule meet a busy server and a wrong key. Fifteen minutes.

  1. In your note, decide for each of these answers whether a client should try again, and how many times: 400, 401, 429 and 503.
  2. Open practice-14 in your agent, and send: "Please start the sample API in the background, as before." Expect npm run api. The tabs in Practice: JSON and REST APIs say how each agent keeps it running.
  3. Send: "Please write exercises/try-14-7.ts. It asks the sample API for /vendors with the key from .env, then with a wrong key. Give every call a two-second deadline, try again only as my note says, and print the status code of every try. Don't run it yet." Then paste your note. Read the lines that decide whether to try again. Do they say what your note says? Is every fetch awaited?
  4. Predict the status codes each part prints. Then send: "Please run try-14-7.ts with the key from .env, and show me what it prints. Don't explain it yet." Expect node --env-file=.env exercises/try-14-7.ts.
  5. Send: "Please stop the sample API, and show me that nothing is still running."

What you should see. With your key, a 503, then a 200 if your note tries a 503 again. The first /vendors call after the API starts gets a 503 on purpose, and the next one works. If your note never tries a 503 again, your key gets only the 503, and no vendors. With the wrong key, one 401, or two if your note tries a 401 again. The second try changes nothing, because the key is still wrong.

Practice: reading SDK-shaped code

From Concept 14.8. Stop the made-up SDK's loop early, and predict what its generator fetches and when the client closes. Ten minutes.

  1. Open practice-14/exercises/sdk-shaped/ap-sdk.ts in a text editor. Find the wrapper function, the async generator, the for await loop and await using.
  2. Open practice-14 in your agent, and send: "Please copy exercises/sdk-shaped/ap-sdk.ts to exercises/try-14-8.ts. In the copy, make #get print each path it asks for, and stop the for await loop after the first invoice, with break. At the end, call main with the key from the environment. Don't run it yet."
  3. In your note, predict every line it prints. How many requests go to /vendors? How many pages does it fetch? Does client closed print?
  4. Send: "Please start the sample API in the background, then run try-14-8.ts with the key from .env, and show me what it prints. Don't explain it yet." Expect npm run api, then node --env-file=.env exercises/try-14-8.ts.
  5. Send: "Please stop the sample API, and show me that nothing is still running."

What you should see. Two requests to /vendors, because the first answer was the API's 503 and the wrapper tried once more. Then 8 vendors, one request for page 1 and one invoice, INV-8301 482500. Last comes client closed. The generator fetches the next page only when the loop asks for more, and await using closed the client when the block ended, even after break.

The differences that change a decision

  1. Reaching localhost. OpenCode and a local Claude Code session reach it freely. Claude Code with its sandbox on, and Codex with its network proxy on, need an exception for it. Check before a task that starts a local server.
  2. Installing a package. Codex blocks the network by default and asks. Claude Code asks before an install in Manual and Accept edits modes. OpenCode asks only if your permission rules say so. In every case, you approve the exact package name.
  3. Keeping a server running. All three can run a command in the background. Before the task ends, check what is still running and stop it: /tasks in Claude Code, /ps and /stop in Codex, the process list for OpenCode.

What stays the same on every product?

Four things do not change, whichever product you use.

  1. A schema at the edge and tests against the API's documented behavior hold, whichever agent wrote the client.
  2. Every new package is a trust decision you make by name, and its exact version is in a committed lock file.
  3. Keys live in the environment, never in a file git tracks.
  4. A server an agent starts is stopped when the task ends.

What it costs

Claude Code comes with the Pro, Max and Ultra plans,16 and with Team and Enterprise seats that include it, as of November 1, 2026.17

Codex is on every ChatGPT plan, including Free.18

OpenCode is free under the MIT license, at version 2.2.5 on October 30, 2026.19 The tool is free, but you pay for the model you connect.

Check yourself

Question 1 / 8

0 answered

In Claude Code's *Manual* mode, what happens when the agent wants to run npm install zod?

  1. Permission modes, Anthropic.↑
  2. Configure permissions, Anthropic.↑
  3. Sandboxing, Anthropic.↑↑2
Owner
Panaversity