← writing

Elements of Agentic System Designa map of the code around a model

system design9 min

A magnifying glass enlarges the execution mechanism inside an open architecture notebook.

Elements of Agentic System Design maps ten behaviors people attribute to AI agents to the code that produces them. I wrote it while building an agent runtime because I needed to decide which capabilities the runtime should own and which ones applications should supply.

Motivation

That boundary kept moving. Memory could live in the runtime or in application storage. A planner could be a built-in primitive or another prompt. Tool execution could belong to the model harness, a plugin system, or the application itself. A feature list did not help because every feature could plausibly sit on either side.

I started writing a book to force the decision. Each chapter needed working examples, and each example needed real code. If I could not explain a behavior without assigning it to a component, I had not found the boundary yet.

I kept the book independent from Idyllic. The examples could use my runtime, but the explanations had to begin with language models and ordinary software. Otherwise I would have documented one framework instead of producing a map I could use to design it.

Breaking down "intelligence"

I started with a narrow description of a language model: it takes text and returns text. It does not retain your last message, know who you are, or affect anything outside its response.

The surrounding program creates those behaviors.

  • An agent remembers your name because the program loads your name into the prompt.
  • It searches the web because the program parses its output, matches a tool name, runs a function, and sends the result back in another prompt.
  • It works overnight because a scheduler starts it and reconstructs its context from stored data.

The model still reads text and writes text in every case. Storage, loops, schedulers, parsers, policies, and function calls turn those responses into a system.

Most references organize that system by technique: retrieval, function calling, planning, caching, and multi-agent coordination. Those categories match libraries and papers. They do not match the problem a developer brings to a debugger. A developer says, "The agent forgot what I told it," or, "It keeps wandering off."

So I worked backward from each observed behavior to the code that could have produced it.

"It seems to..."The program actually...
Remember what I saidincludes conversation history in the next prompt
Have long-term memorystores data and retrieves selected records into context
Do things in the worldparses structured output and dispatches a function
Think step by stepruns several model calls and passes state between them
Plan before actingasks for a plan, then executes each step
Check its own workruns a separate verification call and retries on failure
Have multiple expertsroutes work among prompts with different instructions
Work while I sleepstarts from a timer or external event
Learn from experiencestores outcomes and retrieves them during later work

When an agent forgets a fact, I can inspect the context assembled for that call and the storage and retrieval code that supplied it. I do not have to search a catalogue of techniques and guess which chapter contains the symptom.

Repeated samples pass through a check that keeps successful results and retries failures.
Repeated attempts become a search when a check determines what to keep.

The ten elements

The behavior-to-code pass produced ten elements:

#ElementWhat it describesWhere I look in the code
1Contextinformation available during one model calltoken budget and context construction
2Memorystored information retrieved into later contextsstorage and retrieval
3Agencyconversion of model output into effectsparser, policy, and execution boundary
4Reasoningchains, loops, and branches across model callscall structure and computation between calls
5Coordinationcommunication and sequencing between reasoning processesexecution flow and data flow
6Artifactsshared persistent statetyped objects, operations, and lifecycle
7Autonomytriggers and ownership of the main loopschedulers, event handlers, and context reconstruction
8Evaluationmeasurement of successquality signals and scoring functions
9Feedbacksignals that steer current worksignal sources and injection points
10Learningfeedback stored for future workextraction, storage, and update pipeline

Each element had to point to a code address. If I could not say where a developer would implement or debug it, I left it out or folded it into another element.

Consider a research agent that keeps reading material nobody requested. The complaint can come from four places:

Context
  The task description permits a broad search.

Reasoning
  The loop never checks whether the current question still serves the task.

Agency
  The search tool has no scope parameter, so the model cannot request a narrow search.

Evaluation
  Nothing checks whether the agent used the documents it retrieved.

Those diagnoses lead to changes in different files. The map does not choose one without evidence. It gives me the four places to inspect before I spend a week rewriting the prompt.

One complaint about wandering leads to four possible implementation locations: context, reasoning, agency and evaluation.
A single symptom can have several causes in different files.

Agency

Tool calling almost became an element because every model SDK presents it as a core capability. It only covers one path from model output to an application effect.

A tool-calling API standardizes three parts:

PartWhat the application does
Formatasks the model for structured output instead of parsing prose
Vocabularygives the model a closed set of names and schemas
Routertranslates the selected name and arguments into a function call

The model only produces a string. delete_all_records remains inert until application code parses it, finds a registered function, checks the policy, and runs it.

StageExample
Model output{"tool": "delete_all_records"}
Application boundaryparse, look up, authorize, dispatch
Effectdelete rows, write a file, or send an email

Application code creates agency in the middle row. The registry and policy also determine which strings can become effects. If the dispatch table has no deletion function, the model cannot delete anything through that path.

Application code parses text and checks policy before allowing an effect.
Application code decides whether model output can cause an effect.

That gives me three debugging questions when an agent takes a bad action:

  1. Did the model select a bad action from the instructions and context it received?
  2. Did the execution layer authorize an action it should have blocked?
  3. Did I expose a capability or policy that made the outcome possible?

The same classification covers plugin protocols and skills. A plugin protocol extends the functions available to the router. A skill loads procedural instructions into context. Both matter, but neither needs a new element because the ten elements already identify the code involved.

Editing the map

My first outline borrowed levels from science: philosophy, physics, chemistry, engineering, and applications. I removed that structure once the examples showed that it separated principles from the code that used them. I introduced each principle where it affected a design choice instead.

The element list also changed as I built examples. I applied the code-address test to every edit:

EditReason
Renamed proactivity to autonomyProactivity describes how the behavior feels. Autonomy identifies triggers and ownership of the main loop.
Folded grounding into contextGrounding changes which facts the program places in the prompt.
Folded planning into reasoningPlanning uses one particular arrangement of model calls.
Renamed semantic objects to artifactsThe broader name covers any shared typed state used for coordination.
Separated feedback from learningFeedback changes current work. Learning stores a change for later work.

Two names collapsed when they led to the same code. One name split when it led to two different implementations. I generated more examples whenever a boundary remained unclear and watched for the same code appearing across them.

Two names converge on one implementation address, while one name branches to two addresses.
Implementation locations determine where the taxonomy merges or splits.

Externalization

Three elements move information out of one model call so another call can use it:

Memory     stores context for one agent to retrieve later
Artifacts  store shared state that several agents can change
Learning   stores feedback that changes future behavior

They use the same broad operation and serve different consumers. Memory reconstructs what one agent should know. Artifacts coordinate work around a shared object. Learning changes how later work runs.

Evaluation, feedback, and learning form another dependency:

Evaluation measures the result.
Feedback uses that measurement to change the current task.
Learning stores the change for future tasks.

A system can measure a bad result and continue unchanged. It can also repair the current task and repeat the same mistake tomorrow. Keeping the three elements separate tells me which connection the implementation lacks.

Evaluation feeds the current task and stores learning that can affect a future task.
Feedback steers the current task, while learning preserves a signal for future tasks.

Writing the book

I gave every chapter the same four sections so a reader could apply each element in the same order:

### Introduction
### Demystification
### Design Considerations
### The Reframe

The introduction names the behavior. Demystification traces it to code. Design Considerations covers the implementation choices. The Reframe converts a complaint about the agent into a change a developer can make.

For context, the reframes look like this:

Before: "Why does the AI keep forgetting things?"
After:  "I need to load the relevant history into context on each call."

Before: "The model is hallucinating."
After:  "I left out the facts needed to answer, so the model filled the gaps."

Before: "The AI's personality is inconsistent."
After:  "The system prompt or reconstructed context changed between calls."

I wrote each reframe so the reader would know which code to open.

Diagrams

My first diagrams repeated the prose. I replaced them with diagrams that locate the component responsible for a behavior.

The tool-calling diagram gives the parser and policy boundary more space than the model and tool because that code decides whether text causes an effect. The continuity diagram shows what the program stores between calls and what it reconstructs for the next one. The identity diagram holds the conversation history constant while it changes the model, then holds the model constant while it changes the history.

The diagrams let me point at the component that caused the observed behavior.

Context reconstruction

I used conversation continuity to test the map. On every turn, the application assembles a new context from stored messages and other data. The model receives that context as text. It does not carry the previous call inside itself.

This changes how I debug several common failures:

  • If an agent loses a fact, I check whether storage retained it and whether retrieval placed it in the next prompt.
  • If an agent contradicts an earlier answer, I compare the two contexts.
  • If its personality changes, I compare its instructions, retrieved history, and model configuration.

I can also test where continuity comes from:

Keep the history and replace the model. The character mostly remains.
Keep the model and replace the history. The character changes.

The application produces continuity by preserving state and reconstructing context. I can inspect both operations in the logs and compare the results across runs.

A comparison holds history fixed while changing the model, then holds the model fixed while changing context.
The proposed experiment varies the model and the assembled context separately.

Shipping the map

The map targets people who build frameworks, languages, SDKs, and agent platforms. They decide where memory lives, who owns the execution loop, how tools cross the policy boundary, and which state agents share.

Building Effective Agents, 12-Factor Agents, and agentic design patterns teach construction patterns. Elements gives a developer a way to take an existing system apart: identify which elements it implements, find them in the repository, and see what is missing.

I ship the framework as a Claude Code skill:

---
name: intelligence-designer
description: Analyze and design agentic AI systems using the Elements of Agentic
  System Design framework. Use when asked to analyze an agent architecture,
  understand how an agentic system works, or design a new agent system.
argument-hint: <system or question to analyze>
allowed-tools: Read, Grep, Glob, WebFetch
---

The skill receives four read-only tools. It can inspect a repository and apply the map, but it cannot change the code. That keeps analysis separate from implementation and lets a developer review the diagnosis before acting on it.

Harness engineering

I initially called the field agentic system design. A LangChain post led me to the term harness engineering, which HumanLayer and others were already using for the code around a model. I adopted the common term because it named the same work and saved readers from translating another private vocabulary.

The ten elements still describe the decisions inside a harness. A model, a system prompt, and a tool list do not provide memory across sessions, autonomous execution, shared state, evaluation, or learning. Storage, schedulers, execution policies, context builders, and feedback pipelines provide them.

I now use the elements to settle the boundary that started the project. When an agent appears to remember, plan, act, coordinate, or learn, I trace the behavior to one of those mechanisms, find the file that owns it, and decide whether it belongs in the runtime or in the application.