A terminal is a window; a shell is the program reading your lines; a command is a program the shell launches. Once those three things stop blurring together, every AI coding agent's tool call reads as exactly what it is — a command, running in a place you can inspect. Two simulations below make it concrete: a safe fake terminal you click through, and a live pipeline you can watch data move through.
"Terminal" gets used loosely for three different layers that sit inside one another. Keeping them separate is most of this guide.
bash, zsh, or similar. It reads the line you typed, works out what it means (is cd a builtin? is ls a program somewhere on disk? did you use a | or a >?), and acts on it. The shell is what actually has a concept of a current directory, variables, and history.ls, the shell asks the operating system to start a brand-new process running the ls program, waits for it to finish, and shows you what it printed. The shell itself is also a process — the one your terminal window is attached to.a window; reads keystrokes, draws text; no idea what a file or a variable is
parses your line, tracks cwd/variables/history, launches child processes for real work
Every child process the shell launches inherits a copy of the shell's environment and starts in the shell's current directory — then goes its own way. When ls finishes, it vanishes; the shell was there before it and is still there after. That asymmetry — one long-lived interpreter, many short-lived workers — is the whole architecture, and it's exactly the architecture an AI coding agent sits inside: the agent's tool-call runner behaves like a shell issuing one child process per action, and each of those children is a normal, inspectable process like the ones above.
Everything you type at a shell prompt breaks down the same way: a program name, followed by arguments, separated by spaces. ls -la /tmp is the program ls given two arguments, -la and /tmp; the program itself decides what those mean; the shell's only job was splitting the line and finding ls.
Typing ls doesn't invoke magic — the shell has an ordered list of directories, the PATH environment variable, and it checks each one in turn for a file named ls that's marked executable. First match wins; if none match, you get command not found. This is also why the same command name can mean two different programs on two machines: whichever one PATH finds first, runs.
When a process starts, the operating system hands it three open channels by default: stdin (input, usually your keyboard), stdout (normal output), and stderr (errors and diagnostics, kept separate on purpose so you can silence one without losing the other). Your terminal window happens to be what stdout and stderr are connected to when you run something interactively — but that connection is just a default, and the next two sections are about rewiring it.
When a process finishes, it hands the operating system a small integer, its exit code. 0 means success; anything else (1–255) means some kind of failure, and the specific number is program-defined. The shell stores the last one in a variable ($? in bash) so the next command — or a script, or an agent — can react to whether the previous step actually worked, instead of guessing from the text it printed.
Try it below. The chips run realistic commands against a small, entirely fake filesystem and process list — nothing here touches a real machine, so click freely. The narration line explains the actual mechanism behind each result, not just what got printed.
Not every command is a separate program. cd has to be a shell builtin: a child process cannot change its parent's working directory, so if cd ran as a subprocess, the directory change would vanish the instant it exited. echo, history, and parts of job control are builtins for similar reasons. ls, cat, ps, and git are real files on disk, found via PATH.
Arguments are just strings. The shell does no interpretation of what -la or a filename "means" — it hands the program an array of strings and the program's own argument parser decides. This is why quoting matters: rm file with spaces.txt is four arguments, not one, unless you quote it.
Two operators do all of the rewiring from the last section, and neither one is implemented by the programs involved — the shell does it before the program ever starts running.
> redirects a process's stdout away from the terminal into a file. When you ran echo "status: shipped" > status.txt above, echo printed exactly the same bytes it always does — it has no idea whether it's writing to a screen or a file. The shell opened status.txt first and quietly swapped what file descriptor 1 (stdout) points at, before echo ever executed. >> does the same thing but appends instead of overwriting; 2> redirects stderr specifically; < does the same trick in reverse, feeding a file into stdin.
| — the pipe — connects one process's stdout directly to a second process's stdin, through a small buffer the kernel manages. cat access.log | grep error | wc -l starts three independent programs simultaneously, each unaware the others exist: cat just writes bytes to whatever stdout is; grep just reads whatever stdin is and writes matches to whatever stdout is; wc -l just counts lines arriving on stdin. The shell is the only party that knows the whole chain — it wired stdout→stdin twice and let the kernel move the bytes.
This is the single idea that explains why the command line composes so well: small programs that only promise to read stdin and write stdout can be chained into arbitrarily long pipelines nobody had to design in advance. Watch it happen:
cat access.log | grep error | wc -lNotice the last panel: $? after a pipeline reflects only the last command's exit status by default — wc -l here, which essentially never fails. If grep had crashed, a naive script checking $? would still see success. This is exactly the class of bug that bites automated tooling, agents included, which is why careful shells and scripts turn on pipefail to make the whole pipeline fail if any stage does.
Every process the shell launches is short-lived and forgets everything when it exits — so where does continuity come from? Two places: the filesystem, which outlives every process and is the one thing all of them agree on, and the environment, a set of key-value strings each process inherits from its parent at the moment it's launched.
Every process has a current working directory (cwd) — a piece of state the operating system tracks per-process, not a file. pwd, from section 02, just asks for it. Paths come in two flavors: absolute paths start from the filesystem root (/home/user/project/notes.txt) and mean the same file no matter where you're standing; relative paths (notes.txt, ../sibling/) are resolved against the cwd, so the same relative path can point at two different files depending on where a command runs. A huge share of "it works on my machine" bugs — human or agent — are a relative path resolved against an unexpected cwd.
Environment variables are the process equivalent of ambient configuration: PATH (where to look for programs, section 02), HOME (your home directory), SHELL (which shell is running), and application-specific ones like API_KEY or NODE_ENV all travel this way. The inheritance is strictly one-directional and copy-on-launch: a child process gets a snapshot of its parent's environment at the moment it starts, can change its own copy freely, and none of that ever flows back up. This is why "I set a variable in one terminal tab" doesn't appear in another, and why a script that does export FOO=bar and then calls another program successfully passes it down — but a script that just runs FOO=bar without export keeps it to itself.
Together, cwd and environment are the entire piece of state a shell session carries between commands — everything else about "what happened" has to be read back off the filesystem or a process's own output, which is precisely the property the next two sections lean on.
An AI coding agent that edits code, runs tests, and fixes bugs needs a way to actually do things to a project, not just talk about them. The terminal is the obvious substrate for that, for reasons that have nothing to do with nostalgia:
Every action is already a command. "Read this file," "run the tests," "install this package," "check what changed" all have exact, unambiguous shell equivalents (cat, npm test, npm install, git diff). An agent doesn't need a bespoke API for each of these — the shell already is the universal API for a filesystem and a set of programs, and it predates the agent by decades.
Everything is inspectable text. A command's output is stdout and stderr — plain text you, or the agent, can read and reason about. There's no opaque binary state to decode; the same signal a human debugging over SSH sees is the signal the agent sees.
Every action is scriptable and repeatable. Because a command is just a program plus arguments, the exact same action can be re-run byte-for-byte, chained with others via pipes, or wrapped in a script that runs it on a schedule. An agent's plan is naturally a sequence of commands, because that sequence is also directly executable — nothing has to be translated from "intent" into "action" a second time.
This is the detail worth sitting with: when a coding agent's framework logs a "tool call" like run_command("npm test"), that is not a metaphor or a simplified view of what happened — it is, literally, a program name and its arguments, run by a shell, producing stdout, stderr, and an exit code, exactly like every example in sections 02 and 03. Understanding cat access.log | grep error | wc -l means you already understand the mechanical shape of every tool call a coding agent makes. The agent isn't running in some parallel, agent-specific universe — it's running in the same shell, on the same filesystem, subject to the same PATH and the same exit codes you now know how to read.
The practical consequence is that agents need a real, durable place to run those commands — not a browser tab that dies when a laptop sleeps. A shell process that outlives your connection to it is what a persistent terminal session provides, and reaching one on a machine that isn't your laptop is what SSH is for. Both are extensions of exactly the model in section 01: still a shell, still processes, just running somewhere that stays on.
An agent's chat message — "I ran the tests and fixed the bug" — is a claim. The commands it actually ran are a fact, and because of everything above, that fact is sitting in plain text: the shell's history, the process list while something was running, the exit code of each step, and whatever files changed on disk. Reading the trail instead of trusting the summary is the same discipline as git log and git diff from the git guide — don't take "it's done" on faith; look at what actually landed.
Concretely, four things are worth checking after any agent turn, all of which you now have the vocabulary for: did the command it says it ran actually exit 0, not just print something reassuring? Did it run in the directory you think it did, or did a relative path resolve somewhere else? Did a background process it started (say, a dev server) actually stay running, or silently die? And does history — or an equivalent log — show the commands in the order the summary implies, with nothing skipped?
None of this requires special agent-monitoring tooling — it requires the same terminal literacy from sections 02–04, pointed at the agent's session instead of your own. That's also why an agent's terminal being attachable matters as much as it being persistent: a session you can drop into mid-task, scroll back through, and read command-by-command turns "trust the summary" into "verify the trail," which is the more defensible habit whether the operator is a person or a model.
An agent reports "tests passed." The chat log shows it ran npm test. What's the fastest way to actually confirm this?
TermRoam gives every agent — and you — a real, persistent shell on a server that doesn't sleep when your laptop does, with full scrollback so the command trail from section 06 is never lost the moment a tab closes. You can attach to the same session from a phone or a desktop mid-task, read exactly what ran, and trust the exit codes over the recap. It's the terminal this guide describes, not a simulation of it — running somewhere built to stay on.
See TermRoam →