# System Architecture

Course: [AI Agents](https://www.devtutorials.ai/learn/ai-agents) · Section: Agent Architecture
Source: https://www.devtutorials.ai/learn/ai-agents/architecture/system-architecture

> The parts inside an agent program: the perception-action cycle, the runtime, inference engine, tool registry and state store, and the input, reasoning, execution and monitoring layers.

Think about a restaurant.

A waiter takes your order and makes sure the request is clear and valid. The chef decides how the food should be prepared.

The kitchen staff use tools such as stoves, ovens, and knives. A manager oversees the restaurant and makes sure everything runs smoothly and on schedule.

Each person has a specific responsibility. Together, they turn a customer's request into a completed meal.

An agent application works in a similar way. It is made up of several parts, and each part has a specific job.

**The main idea:** An agent works in a loop. A small program manages that loop using four main components: a runtime, an inference engine, a tool registry, and a state store.

## A Real-World Example

In Lesson 1, the agent fixed the row count bug. It looked like a single intelligent action, but four different components were working together behind the scenes:

1. The **runtime** managed the loop and tracked the number of steps.
2. The **inference engine** asked the agent's brain to decide the next action.
3. The **tool registry** managed the available tools and executed tools such as `run_tests` and `read_file`.
4. The **state store** saved information from each step so the agent could remember what had happened.

When you build your own agent, you create these four components. Every agent framework includes these same concepts, even if different names are used.

**In simple words:** An agent application is a loop supported by four helpers: one runs the process, one thinks, one performs actions, and one remembers information.

## What You Will Learn

In this lesson, you will learn:

1. What happens during one complete cycle of the agent loop.
2. The four main runtime components and their responsibilities.
3. The four layers of an agent: input, reasoning, execution, and monitoring.
4. How to build a simple agent runtime using these components.

The **Agent Lab** at the bottom of this page uses these same components. In this lesson, you will intentionally break some of them to see what happens. You will remove a tool from the registry and provide an empty goal to the input layer. The code examples use plain JavaScript to demonstrate the same concepts.

## Perception-Action Cycle

The **perception-action cycle** describes one complete turn of the agent loop.

**Perception** means collecting information. **Action** means performing a task.

In Lesson 1, you learned the loop as **look, think, act, and check**. The perception-action cycle is the same process viewed from inside the application.

It contains four steps.

### Input Processing

**Input processing** means validating and preparing incoming information before the agent uses it. The input may come from a user or from a tool result.

For example, input processing can:

- Remove extra spaces.
- Reject an empty goal.
- Shorten input that is excessively large.

Allowing invalid input to pass through this step can lead to confusing problems later in the process.

```js runnable title="Clean and check the input first"
function processInput(text) {
  const clean = text.trim();
  if (clean === "") return { ok: false, reason: "the goal is empty" };
  if (clean.length > 60) return { ok: true, goal: clean.slice(0, 60) + "..." };
  return { ok: true, goal: clean };
}

const inputs = [
  "  Make every test pass  ",
  "",
  "Fix the bug on the ticket page where the row count text is wrong and add a test",
];

for (const text of inputs) {
  const result = processInput(text);
  console.log(result.ok ? "OK: " + result.goal : "REJECTED: " + result.reason);
}
```

Expected output:

```text
OK: Make every test pass
REJECTED: the goal is empty
OK: Fix the bug on the ticket page where the row count text is w...
```

The first goal contains extra spaces, but input processing cleans it before the agent uses it. The empty goal is rejected before anything else runs. The long goal is shortened to a safe length before it reaches the agent.

Now try an empty goal in the Agent Lab.

The run stops at step 1 with the message **"the goal is empty"**.

The agent's brain is never called because the problem is detected immediately. This saves processing time and, in real applications, can also reduce cost.

### State Updates

A **state update** is the process of saving the result of each step into the agent's memory.

After each update, the state reflects the current situation.

For example, after `run_tests` finishes, the state changes from: "Tests have not been run" to "2 tests are failing".

Keeping the state updated helps the agent make decisions using the latest information.

### Action Selection

**Action selection** is the reasoning stage you learned about in Lesson 2. The agent reviews the updated state and chooses the next action that should be performed.

### Environment Feedback

**Environment feedback** is the result returned after an action is completed. This feedback becomes new input for the next cycle of the agent loop.

Because of this, the cycle keeps repeating:

- Feedback creates new input.
- New input leads to a new decision.
- The decision leads to a new action.

The cycle continues until a stopping condition is reached.

## Runtime Components

Inside an agent application, four components work together to make the cycle run.

### Agent Runtime

The **agent runtime** is responsible for running and managing the agent loop.

It:

- Starts the agent run.
- Counts and tracks steps.
- Calls the other components in the correct order.
- Stops the loop when the task is complete or a limit is reached.

You can think of the runtime as the restaurant manager. The manager does not cook the food, but they ensure that every task happens in the correct order and at the correct time.

### Inference Engine

The **inference engine** is the component that communicates with the model.

**Inference** means asking a trained AI model to generate a response.

The inference engine:

- Creates the message sent to the model.
- Sends the request.
- Receives the response.
- Converts the response into the next action.

In the Agent Lab, the scripted brain or the live model operates through this component.

### Tool Registry

The **tool registry** is the collection of tools that the agent is allowed to use.

Each tool entry contains:

- A name
- A description
- Required inputs
- The code that performs the action

The tool registry has two important responsibilities:

1. It tells the model which tools are available.
2. It allows only registered tools to be executed.

```js runnable title="A tool registry: register, list and run"
function createRegistry() {
  const tools = {};
  return {
    register(name, description, run) {
      tools[name] = { description, run };
    },
    list() {
      return Object.keys(tools);
    },
    call(name, input) {
      if (!tools[name]) return "ERROR: no tool named " + name;
      return tools[name].run(input);
    },
  };
}

const registry = createRegistry();
registry.register("read_file", "Read one file", (path) => "text of " + path);
registry.register("run_tests", "Run every test", () => "3 passed, 0 failed");

console.log("Tools: " + registry.list().join(", "));
console.log(registry.call("run_tests"));
console.log(registry.call("delete_file", "web/format.js"));
```

Expected output:

```text
Tools: read_file, run_tests
3 passed, 0 failed
ERROR: no tool named delete_file
```

The registry runs `run_tests` because it is registered. It refuses `delete_file` because nobody registered it. The registry acts as a list of allowed tools.

Now see what happens when a tool the agent needs is missing from the registry.

The agent asks to use `run_tests`, but the registry reports that the tool does not exist.

A well-designed agent stops and explains what it needs. It does not guess a solution when it cannot verify the result. Open the **Tools** tab to see the smaller registry.

### State Store

The **state store** is where the agent keeps its memory. It stores the goal, every action taken, and every result returned during the run.

The inference engine reads information from the state store when preparing the next request. The runtime writes new information to the state store after every step.

```js runnable title="A state store keeps every step"
function createStateStore(goal) {
  const steps = [];
  return {
    add(action, result) {
      steps.push({ n: steps.length + 1, action, result });
    },
    last() {
      return steps[steps.length - 1];
    },
    summary() {
      return "Goal: " + goal + " | steps: " + steps.length;
    },
  };
}

const store = createStateStore("Every test passes");
store.add("run_tests", "1 passed, 2 failed");
store.add("read_file", "found the wrong word");

console.log(store.summary());
console.log("Last step: " + store.last().action + " -> " + store.last().result);
```

Expected output:

```text
Goal: Every test passes | steps: 2
Last step: read_file -> found the wrong word
```

**Easy Way to Remember:** The **runtime** runs the process, the **inference engine** makes decisions, the **tool registry** provides access to tools, and the **state store** keeps track of information. In simple terms: run, think, act, and remember.

## Build a Tiny Runtime

Now let's put all four components together.

The example below shows a complete but very small agent runtime. The "brain" uses a few fixed rules instead of AI, so the result is always the same every time you run it.

```js runnable title="Four parts, one tiny agent runtime"
const world = { text: "80,000 row" };

const registry = {
  run_tests: () => (world.text === "80,000 rows" ? "pass" : "fail"),
  read_file: () => world.text,
  write_file: (text) => { world.text = text; return "saved"; },
};

const store = [];

function infer(history) {
  const last = history[history.length - 1];
  if (!last) return { tool: "run_tests" };
  if (last.result === "pass") return { finish: true };
  if (last.tool === "run_tests") return { tool: "read_file" };
  if (last.tool === "read_file") return { tool: "write_file", input: last.result + "s" };
  return { tool: "run_tests" };
}

for (let step = 1; step <= 6; step++) {
  const action = infer(store);
  if (action.finish) { console.log("Step " + step + ": finish"); break; }
  const result = registry[action.tool](action.input);
  store.push({ tool: action.tool, result });
  console.log("Step " + step + ": " + action.tool + " -> " + result);
}
```

Expected output:

```text
Step 1: run_tests -> fail
Step 2: read_file -> 80,000 row
Step 3: write_file -> saved
Step 4: run_tests -> pass
Step 5: finish
```

Find the four components in the code:

- The **runtime** is the `for` loop. It counts the steps and stops after 6 iterations.
- The **inference engine** is `infer`. It reads the history and decides the next action.
- The **tool registry** is the `registry` object.
- The **state store** is the `store` list.

In a real agent, `infer` is usually replaced with a call to an AI model. The other three components often remain very similar.

## Architecture Layers

Another way to understand an agent is through four **layers**.

A layer is a group of components that share the same responsibility. Each layer communicates only with the layers directly connected to it.

### Input Layer

The **input layer** receives the goal and the results returned by tools. It validates, cleans, and prepares this information before it is used, just like the `processInput` example shown earlier.

### Reasoning Layer

The **reasoning layer** decides what the agent should do next. It contains the inference engine and reads information from the state store to make decisions.

### Execution Layer

The **execution layer** performs the selected action. It contains the tool registry.

This layer:

- Verifies that the requested tool exists.
- Checks that the inputs are valid.
- Enforces boundaries and access rules, such as allowing changes only within the `web/` folder.

### Monitoring Layer

The **monitoring layer** observes everything the agent does.

It records information such as:

- Which tool was used
- Whether the action succeeded or failed
- How long the action took

When an agent behaves unexpectedly, the monitoring log is usually the first place to investigate.

In the Agent Lab, the **Trace** tab represents the monitoring layer.

```js runnable title="A monitoring layer sums up a run"
const log = [
  { tool: "run_tests", ok: true },
  { tool: "read_file", ok: true },
  { tool: "write_file", ok: false },
  { tool: "write_file", ok: true },
  { tool: "run_tests", ok: true },
];

const errors = log.filter((entry) => !entry.ok).length;
const used = [...new Set(log.map((entry) => entry.tool))];

console.log("Steps: " + log.length);
console.log("Errors: " + errors);
console.log("Tools used: " + used.join(", "));
```

Expected output:

```text
Steps: 5
Errors: 1
Tools used: run_tests, read_file, write_file
```

Three numbers can quickly tell a story. The agent completed 5 steps. One write operation failed, so the agent tried again.

| Layer | Responsibility | Component | Example in Agent Lab |
|---|---|---|---|
| Input | Validate and prepare incoming information | Input checks | An empty goal is rejected |
| Reasoning | Decide the next action | Inference engine, state store | The reasoning shown for each step |
| Execution | Perform actions safely | Tool registry, boundary checks | The Tools tab |
| Monitoring | Track and record activity | Log | The Trace tab |

Run the full agent one more time. Then open the **Trace** tab. The information shown there is the log created by the monitoring layer.

Never skip the monitoring layer. Without logs, it can be difficult to understand why an agent failed. With logs, you can identify the exact step where the problem occurred.

## Common Mistakes

| Mistake | Incorrect Understanding | Correct Understanding | What Can Go Wrong |
|---|---|---|---|
| No input validation | "The model can handle any input." | Validate and clean the input before sending it to the model. | Empty, invalid, or poorly formatted goals can waste model calls and create confusing results. |
| Running any tool requested by the model | "If the model asks for a tool, just run it." | Execute only tools that are registered and approved in the tool registry. | The agent may try to use tools that do not exist or tools that should never be allowed. |
| No state store | "The model will remember everything." | Store the results of every step and provide them again when needed. | The agent forgets previous actions and repeats the same work. |
| One large function for everything | "It is easier to put everything in one place." | Keep the runtime, inference engine, tool registry, and state store as separate components. | The system becomes difficult to test, maintain, update, and debug. |
| No monitoring or logging | "It worked before, so it will work again." | Record and monitor every step of the agent's activity. | When something fails, it becomes difficult to find the cause of the problem. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- One complete cycle of an agent is called the **perception-action cycle**: input, state update, action, and feedback.
- An agent application is built using four main components: the **runtime**, **inference engine**, **tool registry**, and **state store**.
- The **runtime** manages the loop, the **inference engine** decides what to do next, the **tool registry** provides access to approved tools, and the **state store** keeps track of information and results.
- The four architecture layers are **input**, **reasoning**, **execution**, and **monitoring**.
- Always validate input before sending it to the model, allow only registered tools to run, and record every step using logs.

Next: **State Machines**, the named states an agent moves through and the rules for moving between them.
