Anatomy of an Agent for Multi-Agent Work: Prompt, Tools, Loop, Memory

A
Author
22 Oct, 2026 4 min read 0 views

Every agent in a multi-agent system is built from the same four parts: a system prompt that defines its job, a set of tools it may call, a loop that keeps going until the job is done, and some memory of what has happened so far. If you can wrap these four parts in one small class, you can build teams by composing instances of it.

The four parts

System prompt

The prompt states the role, the goal, the output format and the rules. In a team, it should also say who the agent works for and what it should return. Keep it focused on one job.

Tools

Tools are functions the model can request. Each has a name, a description and a JSON schema for its input. A worker might have a search tool; a reviewer might have none. Fewer tools mean fewer wrong choices.

Loop

The loop calls the model, runs any requested tools, appends the results, and repeats until the model returns a final answer without a tool request. Always add a maximum number of turns.

Memory

Within a run, memory is the message list. Across runs it might be a database. For subagents, the message list is usually discarded after the run, and only the final answer is passed back.

A reusable Agent class

The code below implements all four parts with the Anthropic SDK. The tools argument maps tool names to Python functions plus their schemas. The loop checks stop_reason for tool_use.

import json
import anthropic

client = anthropic.Anthropic()

class Agent:
    def __init__(self, name, system, tools=None, model="claude-sonnet-5-5", max_turns=8):
        self.name = name
        self.system = system
        self.tools = tools or {}   # {"tool_name": (schema_dict, python_function)}
        self.model = model
        self.max_turns = max_turns

    def run(self, task: str) -> str:
        messages = [{"role": "user", "content": task}]
        schemas = [schema for schema, _ in self.tools.values()]
        for _ in range(self.max_turns):
            kwargs = dict(model=self.model, max_tokens=4000,
                          system=self.system, messages=messages)
            if schemas:
                kwargs["tools"] = schemas
            response = client.messages.create(**kwargs)
            messages.append({"role": "assistant", "content": response.content})

            if response.stop_reason != "tool_use":
                return "".join(b.text for b in response.content if b.type == "text")

            results = []
            for block in response.content:
                if block.type == "tool_use":
                    _, fn = self.tools[block.name]
                    try:
                        output = str(fn(**block.input))
                        is_error = False
                    except Exception as exc:
                        output, is_error = f"Tool error: {exc}", True
                    results.append({"type": "tool_result",
                                    "tool_use_id": block.id,
                                    "content": output,
                                    "is_error": is_error})
            messages.append({"role": "user", "content": results})
        return "Stopped: reached the turn limit."

add_schema = {
    "name": "add",
    "description": "Add two numbers.",
    "input_schema": {
        "type": "object",
        "properties": {"a": {"type": "number"}, "b": {"type": "number"}},
        "required": ["a", "b"],
    },
}

calculator = Agent("calc", "You do arithmetic. Always use the add tool.",
                   tools={"add": (add_schema, lambda a, b: a + b)})
print(calculator.run("What is 1234 + 8766?"))

Running this should print an answer that contains 10000. The model requests the add tool, your function runs, the result goes back as a tool_result, and the model writes the final reply.

Why a class matters for teams

Once an agent is an object with a run(task) method that takes a string and returns a string, every multi-agent pattern in this course becomes ordinary code: call one agent, pass the result to another, run several in threads, wrap one inside a tool. The class hides the model details, so you can change the model or add logging in one place.

Design notes

  • Give each agent a unique name. You will need it for logs and traces.
  • Return strings or small structured objects from run. Return values are your inter-agent protocol.
  • Return tool errors to the model with is_error so it can recover instead of crashing the loop.
  • Keep the turn limit low for subagents. A worker that needs thirty turns is probably doing too much.

Later posts reuse this shape. If you prefer a framework, the same ideas map onto the Agent Frameworks topic.

Stateless calls, stateful agents

The Messages API is stateless. The model remembers nothing between calls; the only memory is the list of messages you send. This is why the messages list is the heart of the class above, and why a subagent's memory disappears when run returns. That is a feature in multi-agent work: a fresh subagent starts clean, with no leftover assumptions from earlier tasks. If you do want an agent to remember something across runs, you must store it yourself and put it back into the prompt.

What you should log per agent

Even a tiny agent deserves a few log lines, because in a team you will need to know which agent did what. At minimum record the agent name, the task text, the number of turns used, the tools called, the token usage from response.usage, and the final output. Add a timestamp and a run id when you start composing agents. These records are the raw material for tracing, cost accounting and evaluation in later posts.

Exercise

Extend the class so that run returns an object with the answer, the number of turns and the total input and output tokens. Then create two agents with different system prompts and compare their token use on the same task. You will start to see how prompt and tool choices change cost, which matters when you multiply by many agents.

Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.