JJoeven

Curriculum/Agent Architectures

Anatomy of an Agent

Six named parts: assembler, model, parser, executor, memory, and stop. The loop is the product.

beginner20 min1 / 24

An agent is not a prompt. It is not a chatbot with extra adjectives in the system message. It is a loop with named parts. If you cannot point to each part in code, you have a notebook cell that worked once.

People use the word “agent” for three different things: a model that is allowed to call tools, a product that acts in the world, and a loop that will not stop until a predicate says so. This track is the third. The model is one function inside the loop. The product is what you get when that loop is named, tested, and budgeted.

A chatbot answers in one shot. An agent takes several shots, and between shots the world can change: a ticket is fetched, a search returns hits, a refund is refused. That is why the loop exists. Tokens without a loop are a draft. A loop without named parts is a furnace you cannot debug.

This lesson names the six parts and shows them in one small Python loop. Later lessons zoom in. Do not skip the names. Debugging is “which part failed,” not “the model is dumb.”

Six parts, six jobs

PartJobIf it is missing
Context assemblerBuild the prompt from goal, memory, observations, tool docsThe model guesses from vibes
ModelMap context → next text (thought, tool call, or answer)You have a script, not an LLM agent
ParserTurn model text into a typed decisionHallucinated JSON becomes a bug
ExecutorRun tools with timeouts, schemas, and permissionsThe model talks but never acts
MemoryStore what happened so the next turn is not amnesiaThe loop forgets its own mistakes
StopSuccess check, budget, or human handoffYou bought an infinite token furnace

These names are not vendor names. LangGraph nodes, “tool calling” APIs, and notebook cells are costumes on these six. If a teammate cannot find should_stop, you do not have an agent you can operate.

Six named parts of the loop
AssembleModelParseExecuteMemoryStop

Assemble, model, parse, execute, remember, stop. Name the box that failed.

Six named parts of the loop

Write the names in code as functions, even if each function is ten lines. A single run_agent that inlines JSON parsing, HTTP, and a while-True is a demo. You cannot stub the model if it is glued to json.loads.

The assembler

The assembler is the only writer of what the model sees. It chooses the goal, a window of recent events, the tool schemas that are legal right now, and any retrieved notes. It is a budget officer, not a concat of the week.

If the model saw a tool, the assembler advertised it. If the model saw an old error, the assembler left it in the window. “The model copied a bad call” is often “the assembler kept the bad call in context.” The next lesson is that budget in detail. Here, know the job: nothing reaches the model except through this function.

The model

The model maps context to text. In production that is an HTTP call. In Joeven try-it boxes we use a fake model: a function that returns the same shape a real API would. Architecture should not depend on a vendor. If your loop only works with one SDK, you do not have an architecture. You have a client.

The model does not run tools. It does not parse its own JSON. It does not decide that the loop is done unless you treat a finish name as data that stop will read. Keep that split even when a vendor wraps tools inside the API. Your code still has to validate, execute, and stop.

A fake model is not a toy. It is how you test the other five parts without paying for tokens. Return the same keys a real response would: raw text, or a structured tool call you still parse. Do not special-case “we are in a notebook.”

The parser

The model emits text — sometimes JSON, sometimes markdown fences, sometimes a paragraph that looks like a tool call. The parser turns that into a typed decision: a name and arguments, or an error. Unknown names do not run. Broken JSON does not run. The parser fails closed.

No parser means you are one hallucinated name away from looking up tools in globals(). Never eval model text as Python. The tools track owns the dispatcher allowlist. This track owns the fact that the loop must parse before it executes. A later lesson is the immune system in full.

The executor

The executor looks up the tool by name, validates arguments, enforces timeouts, and returns a short observation. Long HTML dumps belong in storage, not back in the prompt. The executor is the only place the world changes. If there is no log line, it did not happen.

The model may write “I refunded the order.” That sentence is not a refund. The executor running refund with blessed args is a refund. Hide HTTP status codes from the loop when you can; return a small dict. The loop should see ok or error, not a stack trace.

Memory

Memory is not “the context window.” It is a store the assembler reads: last actions and observations, maybe a rolling summary, maybe retrieved notes. Without it, step 4 cannot see that step 2 already searched. Amnesia looks like a dumb model. It is usually a missing append.

Scratchpad, summary, retrieve-on-demand, and profile are different stores. Mixing them into one vector soup is a later lesson’s warning. For anatomy: append what happened, then assemble from that store. Do not hope the vendor “remembers.”

Stop

Stop is a contract: success, budget, or handoff. Without it you bought a furnace. Hitting a cap must return cannot: step budget (or money, or time), not a confident guess. The prompting track wrote stop rules in text. Here they live in code.

Stop is not the model saying “I am done.” Stop is your predicate reading the decision, the observation, the step count, and maybe a human flag. If the only stop is “the model called finish,” a stuck search loop will never finish — unless a budget fires.

One step is a pipeline

Each step is the same pipeline:

  1. Assemble context from goal, memory, and legal tools.
  2. Call the model.
  3. Parse the text.
  4. If parse failed, record the error as an observation — or stop.
  5. Execute only if the parser blessed a tool.
  6. Append action and observation to memory.
  7. Ask stop. Break or continue.

When a run fails, ask which part failed. Wrong tool choice can be assembler (bad docs) or model (bad reasoning). Crash on launch_nukes is a parser miss. Double refund is an executor or stop miss. Repeating the same search is memory or stop. Name the part in the ticket.

Why the split is how you test

Unit-test the parser without paying for tokens. Feed it fences, trailing commas, unknown names. Replay traces through the executor without calling the model. Swap models without rewriting tools. That is the point of names.

You do not need a framework to get this split. Six functions and a for loop are enough. Frameworks help when you add checkpoints and graphs. They do not replace the names. If a library hides the parser, you still own fail-closed. If it hides stop, you still own the cap.

What this track is not

Multi-agent — specialists talking to each other — is the next track. Evals as a discipline, and production serving, come later. This track is the loop: six parts, ReAct, plan-and-execute, reflection, typed state, human-in-the-loop, and jobs. Stay here until the loop is obvious in code.

A swarm will still have these six parts per specialist. If you cannot draw one loop, you cannot operate five.

Common mistakes

  • One blob of Python that calls the vendor SDK and prints.
  • No should_stop.
  • Letting the model “execute” by writing a story.
  • Treating vendor tool-calling as if the parser and executor disappeared.
  • Dumping the whole transcript every step and calling that memory.
  • Asking “is the model smart enough?” before asking which part failed.
Live PythonOpen full playgroundpython
Output
Run to execute this in your browser. Nothing is sent to a server.

The fake model first calls add, then finish. Memory grows by an action and an observation each step. Stop fires on finish. Point at each function. Change the window in assemble (memory[-6:]) and you are already in the next lesson. Cap max_steps at 1 and stop fires as a budget. That is the whole product in miniature.

This demo parser trusts the CALL name {...} grammar. A real parser must fail closed on junk. We keep the grammar tiny so you can see the six calls. Do not ship raw.split as your immune system.

How agents use this

Name the six functions in your codebase. If a teammate cannot find should_stop, you do not have an agent you can operate. LangGraph nodes and vendor tool calls are costumes on these six parts.

When you debug, name the part in the ticket: “parser rejected unknown_tool,” not “the agent acted weird.” The rest of this track is those parts under load: budget the assembler, fail the parser closed, stop the furnace, then dress the loop as ReAct or a plan.

Check your understanding

Which component turns model text into a typed tool call?