Understand AGENTS.md for Your First Agent
Understand AGENTS.md for Your First Agent

Most people building their first agent on OpenClaw hit the same wall. They get excited, spin up a new project, start writing tool definitions and prompt logic⦠and then they notice a file sitting in the root of every OpenClaw project template called AGENTS.md.
They open it. They see a bunch of structured text β some YAML-looking blocks, some natural language instructions, some sections they don't recognize. And they close it. They figure they'll come back to it later, or that it's just documentation, or that it's optional.
It's not optional. It's the single most important file in your agent project, and misunderstanding it (or ignoring it) is the number one reason first-time agent builders end up with agents that burn through tokens, hallucinate tool names, get stuck in loops, and generally behave like unsupervised interns with a credit card.
Let me walk you through exactly what AGENTS.md is, how to read it, how to configure it, and how to use it to build agents that actually do what you want.
What AGENTS.md Actually Is
Think of AGENTS.md as the constitution for your agent. It's a structured configuration file that tells the OpenClaw runtime everything it needs to know about how your agent should behave β its identity, its capabilities, its constraints, and its decision-making strategy.
Unlike a system prompt (which is just a blob of text you hope the LLM follows), AGENTS.md is parsed and enforced by the OpenClaw runtime. When you define a tool limit of 3 calls per turn in AGENTS.md, that's not a suggestion to the model. That's a hard constraint the runtime enforces before the LLM ever sees the request.
This is a critical distinction. System prompts are suggestions. AGENTS.md is law.
Here's a minimal AGENTS.md that actually works:
# Agent: OrderStatusBot
## Identity
role: customer-support
model: gpt-4
description: Looks up order status for customers using their order ID or email.
## Tools
- search_orders: Look up order by ID or customer email
- get_customer_info: Retrieve customer profile details
## Constraints
max_tool_calls: 3
max_turns: 5
cost_limit_usd: 0.25
timeout_seconds: 15
## Behavior
tone: friendly, concise
fallback: "I can only help with order status. Let me connect you with a team member for other questions."
That's it. That's a fully functional agent configuration. Let's break down each section.
The Anatomy of Every AGENTS.md File
Identity Block
## Identity
role: customer-support
model: gpt-4
description: Looks up order status for customers using their order ID or email.
The role field isn't decorative. OpenClaw uses it to scope permissions and logging. If you're running multiple agents (say, a researcher and a writer), the role determines which tools each agent can access and how delegation works between them.
The model field is your LLM selection. OpenClaw is model-agnostic β you can use gpt-4, gpt-3.5-turbo, claude-3-sonnet, llama-3, or even a locally hosted model if you've configured the endpoint. More on that in a second.
The description is deceptively important. It's not just for your benefit β the OpenClaw runtime injects this into the agent's system context. A vague description produces a vague agent. Be specific. "Looks up order status for customers using their order ID or email" is infinitely better than "Helps customers with their orders."
Tools Block
## Tools
- search_orders: Look up order by ID or customer email
- get_customer_info: Retrieve customer profile details
Each tool listed here must correspond to a tool definition in your project (either a Python function decorated with @Tool or a tool config file in your /tools directory). The descriptions after the colon aren't optional fluff β they're fed directly to the LLM as part of the tool schema.
This is where most beginners make their first major mistake. They write tool descriptions like:
- search_orders: Searches orders
And then wonder why the agent calls the tool with malformed parameters or uses it in the wrong context. Your tool description in AGENTS.md should answer three questions: What does this tool do? When should the agent use it? What does it expect?
Better:
- search_orders: Look up an order's current status. Use when customer provides an order ID (format: ORD-XXXXX) or email address. Returns order status, shipping info, and estimated delivery date.
That extra sentence or two saves you dozens of failed tool calls and wasted tokens.
Here's how the corresponding Python tool definition looks in OpenClaw:
from openclaw import Tool
from pydantic import BaseModel, Field
class OrderSearchInput(BaseModel):
query: str = Field(description="Order ID (e.g., ORD-12345) or customer email address")
@Tool(
name="search_orders",
retry_on_fail=True,
fallback_description="Use this when customer asks about order status, shipping, or delivery"
)
def search_orders(input: OrderSearchInput) -> str:
# Your actual database query logic here
order = db.find_order(input.query)
if not order:
return f"No order found for '{input.query}'. Ask customer to verify their order ID or email."
return f"Order {order.id}: Status={order.status}, Shipped={order.ship_date}, ETA={order.eta}"
Notice the Pydantic model with Field(description=...). OpenClaw automatically generates JSON schemas from these type annotations. The LLM gets a clean, explicit contract for how to call your tool. No guessing, no hallucinated parameter names.
Constraints Block
This is where AGENTS.md becomes genuinely powerful β and where it diverges most from the "just use a system prompt" approach.
## Constraints
max_tool_calls: 3
max_turns: 5
cost_limit_usd: 0.25
timeout_seconds: 15
forbidden_patterns:
- "refund \$[0-9]{3,}"
- "policy change"
- "override"
Every single one of these is enforced at the runtime level. Let me repeat that because it matters: the LLM cannot violate these constraints. It's not being asked to limit itself to 3 tool calls. The runtime literally stops executing after 3 tool calls and returns whatever the agent has at that point.
max_tool_calls: 3 β Hard cap on tool invocations per run. This is your primary defense against the "$50 in API calls from a runaway loop" scenario that haunts every agent developer.
max_turns: 5 β Maximum back-and-forth reasoning steps. An agent that hasn't figured out the answer in 5 turns probably isn't going to figure it out in 50.
cost_limit_usd: 0.25 β Budget ceiling for the entire session. OpenClaw tracks token usage in real time and halts execution if you're about to exceed this.
timeout_seconds: 15 β Wall-clock time limit. Essential for user-facing agents where a 60-second wait is unacceptable.
forbidden_patterns β Regex patterns applied to the agent's output. If the agent tries to offer a $500 refund when it shouldn't, the runtime catches it before it reaches the user. This is the guardrail that lets you sleep at night.
Behavior Block
## Behavior
tone: friendly, concise
fallback: "I can only help with order status. Let me connect you with a team member for other questions."
strategy: sequential
memory: summarize
The fallback message is what the agent returns when it hits a constraint wall or encounters a request outside its scope. Without this, the agent will try to wing it, and "winging it" for an LLM means confident hallucination.
The strategy field controls how the agent approaches multi-step tasks:
sequentialβ One tool call at a time, each informed by the previous resultparallelβ Execute independent tool calls simultaneouslyplan_firstβ Generate a plan, get it approved, then execute
For most first agents, sequential is what you want. It's predictable, easy to debug, and the trace output reads like a logical narrative.
The memory field determines how the agent handles context window management:
summarizeβ Compresses older messages to free up tokenssliding_windowβ Keeps the N most recent turnssemanticβ Keeps the turns most relevant to the current queryfullβ Keeps everything until you hit the limit (then fails)
For a customer support bot, sliding_window is usually fine. For a research agent processing long documents, summarize or semantic can be the difference between success and context-window-exceeded errors.
The Part Everyone Skips: Tracing and Debugging
Add this to your AGENTS.md:
## Observability
trace: true
log_format: structured
cost_tracking: true
Now when you run your agent, you get output like this:
π€ Planning: Customer wants order status. Need order ID or email.
π₯ Input: "Where's my order ORD-78421?"
π§ Tool: search_orders("ORD-78421") β "Order ORD-78421: Status=Shipped, Shipped=2026-01-15, ETA=2026-01-20"
β
Response: "Your order ORD-78421 has shipped! It went out on January 15th and should arrive by January 20th."
π° Cost: $0.03 | Tokens: 847 (input: 612, output: 235) | Duration: 2.1s
This isn't a nice-to-have. This is how you figure out why your agent is doing what it's doing. Without tracing, debugging an agent is like debugging a microservice by reading the HTTP status codes and nothing else.
When something goes wrong β and it will β the trace is what tells you whether the problem is in your tool description, your constraints, your model selection, or the LLM just having a bad inference.
A Complete, Real-World AGENTS.md
Let me put it all together. Here's an AGENTS.md for a research assistant agent that actually holds up in production:
# Agent: ResearchAssistant
## Identity
role: researcher
model: gpt-4
description: Searches the web, reads articles, and produces concise summaries on a given topic. Focuses on factual information from credible sources. Always cites sources.
## Tools
- web_search: Search the web for recent information on a topic. Use for broad queries or finding relevant URLs. Input: search query string. Output: list of URLs with snippets.
- read_article: Fetch and extract the main content from a URL. Use after web_search to read promising results. Input: URL string. Output: article text (truncated to 3000 tokens).
- check_facts: Cross-reference a claim against multiple sources. Use before including any statistical claims. Input: claim string. Output: confidence score and supporting/contradicting sources.
## Constraints
max_tool_calls: 8
max_turns: 10
cost_limit_usd: 1.00
timeout_seconds: 60
forbidden_patterns:
- "I think|I believe|In my opinion"
## Behavior
tone: neutral, academic, concise
fallback: "I wasn't able to find reliable information on that topic. Could you narrow your question or provide more context?"
strategy: plan_first
memory: semantic
## Observability
trace: true
log_format: structured
cost_tracking: true
And the corresponding Python setup:
from openclaw import Agent
agent = Agent.from_agents_md("./AGENTS.md")
# Cost estimation before running
estimate = agent.estimate_cost("What are the latest developments in CRISPR gene therapy for sickle cell disease?")
print(f"Estimated cost: ${estimate.cost_usd:.2f} ({estimate.total_tokens} tokens)")
# Run the agent
result = agent.run("What are the latest developments in CRISPR gene therapy for sickle cell disease?")
print(result.content)
print(f"Actual cost: ${result.cost_usd:.2f}")
print(f"Sources: {result.citations}")
That Agent.from_agents_md() call is doing the heavy lifting. It parses your AGENTS.md, wires up the tools, sets the constraints, configures tracing β everything. One line.
Testing Your Agent Without Going Broke
One of the smartest things you can do after writing your AGENTS.md is test it without burning through API credits:
# Mock mode: deterministic responses, no API calls
agent = Agent.from_agents_md("./AGENTS.md")
agent.config.mock_mode = True
result = agent.run("Where's my order ORD-78421?")
# Returns a predictable mock response, lets you test flow/constraints
# Record mode: save a real session for replay
agent.config.record_session = "tests/fixtures/order_lookup.json"
result = agent.run("Where's my order ORD-78421?")
# Replay mode: use recorded session for CI/CD
agent = Agent.from_recording("tests/fixtures/order_lookup.json")
result = agent.run("Where's my order ORD-78421?")
assert "ORD-78421" in result.content
assert result.tool_calls_count <= 3
assert result.cost_usd < 0.50
You write real integration tests that actually assert on agent behavior β tool call count, cost, content patterns β without making a single API call after the initial recording. This is how you put agents in CI/CD pipelines without your CFO sending you a concerned email.
Common Mistakes I See Every Week
1. Too many tools. If your agent has access to 15 tools, it's going to get confused about which one to use. Start with 2-3. Add more only when you have a specific use case that demands it.
2. No constraints. Running an agent without max_tool_calls and cost_limit_usd is like giving someone your credit card and saying "just buy what you need." Set limits. Tighten them later as you learn your agent's actual behavior patterns.
3. Vague descriptions. "Helps with stuff" is not a description. "Retrieves current stock prices for US equities using ticker symbols (e.g., AAPL, MSFT). Returns price, daily change, and volume." is a description.
4. Ignoring the fallback. Without a fallback, your agent will try to answer things it shouldn't. The fallback is your safety net for out-of-scope requests.
5. Starting with multi-agent setups. You don't need three agents coordinating when one agent with the right tools will do the job. Multi-agent architectures are powerful but they multiply complexity, cost, and debugging difficulty. Get one agent working perfectly first.
Skip the Setup: Felix's OpenClaw Starter Pack
If you've read this far and you're thinking "this is great but I don't want to write all this from scratch" β I get it. Configuring tools, writing good descriptions, setting up tracing, getting the constraints rightβ¦ it's not hard per se, but it's a lot of small decisions that are easy to get wrong the first time.
Felix's OpenClaw Starter Pack on Claw Mart is the best $29 shortcut I've seen. It includes pre-configured AGENTS.md templates for the most common agent patterns (research assistant, customer support, data analyst, code reviewer), along with pre-built tool definitions, testing fixtures, and a project structure that just works out of the box. Instead of spending a weekend figuring out the right forbidden_patterns regex or the optimal memory strategy for your use case, you start with a working configuration and customize from there. It's the difference between building a house from lumber and customizing a floor plan.
Where to Go From Here
You've got the mental model now. Here's your action plan:
-
Start a new OpenClaw project and open the generated
AGENTS.md. Read every line before changing anything. -
Define one agent with 2-3 tools. A customer support bot, a research assistant, a data lookup tool β pick something concrete and small.
-
Set aggressive constraints.
max_tool_calls: 3,cost_limit_usd: 0.25,max_turns: 5. You can always loosen them. You can't un-spend tokens. -
Turn on tracing from day one.
trace: true. Read the output. Understand what your agent is actually doing, not what you think it's doing. -
Record your first successful session and write a test against it. Now you have a regression test for your agent's behavior.
-
Iterate on tool descriptions. This is the highest-leverage improvement you can make. Better descriptions = better tool selection = fewer wasted calls = lower cost = happier users.
The AGENTS.md file isn't boilerplate. It's the blueprint. The agents that work well in production are the ones where someone spent time getting this file right. The ones that burn money and hallucinate are the ones where someone skipped it and went straight to writing Python.
Don't skip it. Read it. Understand it. Configure it deliberately. Everything else follows from there.