The Agent
An agent is an entity that interacts with an environment at any meaningful level. A person going about their day is an agent in this sense, and so is a thermostat holding a room at temperature or a self-driving car making its way through traffic.
All of them perceive their environment through sensors and act on it through actuators. Because each action feeds the next observation, perceiving and acting fold into one continuous cycle, a perceive-reason-act loop, that lasts as long as the agent continues to interact. The agents you will build all adopt the same cycle and are set apart only by how much they can perceive and how far their actions reach.
The three parts on the loop
A single trip around the loop runs through three parts, each with a plain name and a distinct job.
Perception gathers what the agent can know, reasoning chooses what to do, and action changes the environment. Perception and action often come from an existing interaction model, whether it controls a vacuum, web automation, a game entity, or a humanoid. The reasoning step can change without changing that boundary, which separates capability from reach.
Deep · ConceptsExplore the loop and classic architectures?
Open this section when you want concrete examples and the fuller architecture taxonomy.
- Perception gives the agent everything it knows about its environment. A thermostat reads a single number: the current temperature. A self-driving car fuses cameras, radar, and a map into a model of the road. Whatever perception captures is all the agent has to work from, since nothing downstream ever touches the world directly. A gap in perception becomes a gap the rest of the agent cannot see around.
- Reasoning turns a perception into a decision, which can vary widely between agents. A thermostat compares the temperature against a target and stops there, while a chess engine searches millions of moves and a self-driving car runs a learned policy. The agents you build here reason by calling a language model, and almost everything the course adds later (tools/memory/planning) hangs off this single step.
- Action changes the environment for itself and other participants. An action can be as small as flipping a relay or as involved as steering a car. Later pages call code exposed to the model a tool, because from the model's side it is just another available action. Calling a tool means invoking that exposed code as the action. It also marks the outer edge of an agent's reach, because however sharp the reasoning, the agent affects the world only through its action space.
Classic agent designs differ at the decision step. Russell and Norvig's Artificial Intelligence: A Modern Approach arranges them as a spectrum, from a bare reflex agent up to a learning agent that improves its own rules. This course starts with rigid logic, then adds a language model and engineered context to move toward a model-based, goal-oriented loop. Later modules keep that loop running over changing state, which is the pattern learning agents build on.
Applied · ReflexRun the smallest rule-based agent loop?
An agent with no language model
The smallest decision function is a single if
statement. Here it drives a one-dimensional track bounded by walls at positions 0
and 9, which leaves the whole agent about as stripped down as one can get. Press Run
and watch the position number bounce between those two walls as the loop runs.
Two things worth examining in the code above:
- The environment (wall positions, current
position) lives in
state.env, while the agent's intrinsics (its direction) live instate.agent. Keeping them separate means a different agent with different intrinsic state can operate in the same environment cleanly. - The reasoning step is one
ifstatement. Replacing it with something more powerful, whether a decision tree, a neural network, or a language model, only requires changing what sits between perception and action. The loop structure, the state layout, and the action step all remain identical.
What changes when reasoning calls an LLM
You call the chat-completions interface like any other function: hand it a list of messages and a model name, and it hands back one assistant message plus metadata.
chat(). It is the course-provided wrapper
you run live a little further down this page. The snippet below previews its
call shape and the fields it returns, so the names are familiar by the time
you reach the runnable cell.
const reply = await chat({
model: "nvidia/nemotron-3-nano-30b-a3b",
messages: [{ role: "user", content: "Hi" }],
});
// reply.choices[0].message → { content, reasoning_content }
// reply.choices[0].finish_reason → "stop" | "tool_calls" | "length"
Three facts about that function shape how agents built on top of it have to work:
- Calls do not share state. Between requests the server holds nothing for you. If you want the model to remember the previous turn, you send that turn again. The "memory" of a conversation is a list of messages your code maintains and re-sends each time.
- The output has two channels. Reasoning models
return their working notes in
reasoning_contentand their final answer incontent. You inspect those working notes during development, while the next stage of the loop reads and acts on the answer incontent. - finish_reason is the loop's exit condition.
"stop"means the model has written its final answer."tool_calls"means it wants your code to run a named function and report the result back."length"means the token budget ran out before the model finished. A loop that drives an LLM-based agent is built entirely around reading this one field and deciding what to do next.
Deep · BuildCompare where APIs keep conversation state?
This course uses the
Chat Completions API
throughout: you keep the conversation as a messages[] array in your own code and resend
the full array on every turn. The newer
Responses API
can hold the thread server-side instead, so you send only the new input and a reference to the prior
turn. Either way the model stays stateless and unchanged between calls. The conversation lives outside
it, in state your code or the server keeps.
A real call, in your browser
The cell below sends one chat-completion to the model. It needs an NVIDIA API key, though the free tier covers every exercise. You enter it once, and the course then keeps it in your browser and reuses it on every page so you never paste it twice.
Press Run to send a single request. It stands entirely on its own, with no loop wrapped around it and nothing from an earlier turn carried along.
Deep · WireInspect the raw HTTP request and response?
What chat() actually sends
chat() is a convenience wrapper around one ordinary HTTP
POST in the OpenAI-compatible wire format. That request carries three things:
- the endpoint to send to,
- an
Authorizationheader carrying your key, - and a JSON body with
modelandmessages.
The cell below makes that call by hand and shows both sides. Open request to
see what leaves your browser with the key redacted, and raw response to see
the full object chat() hands back.
Every endpoint in this course speaks this same format, including the OpenClaw agent runtime in Module 3.
- Your request may go through a proxy. If you selected the
learning management system/iframe setting when you put in your API key, your
request does not hit
build.nvidia.comdirectly. That endpoint does not return the cross-origin (CORS) headers a browserfetchrequires, so the call is routed through a small pass-through proxy that adds them. The proxy also adds a course attribution header so DLI can identify traffic from this resource. The lesson sends your API key with that request without storing it or substituting another key. - Streaming is wrapped in a helper.
chatStream()sends the same chat request with streaming enabled, then parses the returned event stream into answer text, reasoning text, usage metadata, and the final done signal. The helper also connects those pieces to the lesson panel and Stop button, so the cell can focus on the concept instead of the streaming plumbing. You can open the helper source from the cell menu to inspect exactly what it does.
Applied · LoopCompare maze reasoning inside one fixed loop?
Swapping the reasoning step while the loop stays fixed
You now have both halves of an agent, and the stateless chat() function above will pair nicely
with the previous reflex agent's perceive-reason-act loop. Before
wiring the model into that loop, it helps to watch the decision step use routines
that are not language models first.
Run the nodes in order. Each puts a different decision rule into the same maze-solving loop:
- The constant "always east" rule reaches the goal on a straight corridor, then stalls at the first wall in the maze, since a fixed heading cannot turn.
- DFS dives and backtracks out of dead ends.
- BFS fans out in rings before walking the shortest path.
- A* biases those same rings toward the goal.
chat() call you just ran can fill that
same step. It changes two edges: your code renders perception as text, and the
model returns a tool call that your code applies. The next section zooms in on those
two edges.
Using a language model to choose maze moves
The maze loop still samples the world and applies one validated move. Only the
decision step changes. Instead of DFS, BFS, or A*, the model reads the rendered maze
and asks your code to call choose_direction.
Run the cells in this order, then vary one control at a time:
- Run the engine node first. It exports the maze utilities and one shared controller,
state.runMaze, that walks corridors automatically and calls the model only at junctions. - Run the editable maze-agent node below.
DIRECTIVEis the core system instruction, and the options object controls the model, maze size, text map, image attachment, move history, coordinate trail, and repeated tool schema. - Start with the working defaults. Then turn on
includeImageto test the rendered maze, switchmodelto compare models, or setadvancedto grow the maze after the simple case works.
choose_direction gives the
model the same contract but enforces it through a JSON Schema, so the loop reads a
typed value instead of parsing free prose. With it, a language model can take the
decision step that BFS and A* held a moment ago.
The loop, drawn locally
Place the chat() call inside the loop that re-sends history, and a single
turn of any LLM agent in this course takes the same four-stage shape. Name the stages
now, because every security and reliability question later traces back to one of these
four arrows.
- Locally-sample. Your code reads a slice of the environment, which might be the maze cell and its valid moves, or the latest user message, or the contents of a file. It never reaches for the whole world at once, because all the turn requires is the narrow piece of state the model needs to act on this time around.
- Locally-perceive. That slice is rendered into a full
engineered context: the system prompt, the running
messages[]history, prior tool results, and the slice you just sampled. All of it is plain text. For an LLM agent, perception is context engineering. - Locally-act. The model maps that text to more text: an
intent signal, ideally a typed tool call like
{ "move": "east" }rather than free prose. The intent only requests an action, and it never performs one. Your code reads it and applies one bounded update to the environment (doMove(...)), validating it first. - Locally-impact. The update changes the world. Nothing persisted inside the model. The only durable state is the environment and the context your code will rebuild. The next turn's locally-sample reads the changed world, and the cycle repeats.
Look closely at the middle of that list. The perceive-to-act step is a single stateless text-to-text mapping in which a full engineered context goes in and an intent signal comes back. The model only transforms text, so perceiving the world and turning an intent into a real change remain your code's job on both sides of that mapping.
That leaves the green box below as the only piece you did not write. An agent therefore has just two choices: what you feed into its context, and what you do with the intent it returns.
Before you continue
Each question is answerable by editing a knob in the LLM-maze cell above and rerunning it.
- Take the text away from the multimodal model.
Set
includeText:falseandincludeImage:true, then rerun. The model now sees only the rendered picture of the maze. Does it still reach the goal, and does it take more decisions than the text-plus-image run did? - Remove the strategy hint from the DIRECTIVE. Delete the line that says to prefer unvisited branches over visited cells, then rerun the same cell. Count the decisions against the run with the hint in place. How much of the model's competence came from that one sentence?
- Swap the decision model.
Change
model:"vision"tomodel:"super"and rerun. The loop, maze, perception, and action do not change. Only the model making the move does. Does the larger text model reach the goal in fewer decisions?
Try it · the model as a pure function
The smallest artifact in the course is a single chat() call with no tools
and no memory wrapped around it. Each message you send stands on its own, running the
bare stateless function that the loop will later wrap. Type something below and watch
the reply stream back.