- name
- agentbuilder
- description
- Use this skill when creating, editing, debugging, reviewing, or documenting Standard Agents or AgentBuilder projects. Apply it for work on agents, prompts, models, tools, hooks, threads, APIs, subagents, provider setup, model selection, environment variables, and spec-aligned architecture, even if the user only says things like "build an agent", "write a prompt", "choose a model", or "fix my AgentBuilder app" without explicitly naming Standard Agents.
# AgentBuilder Skill
This guide is a living record. It is not part of the Standard Agent Specification — it exists to help humans and coding agents *use* the spec to build effective agents. It is updated regularly and not bound to any spec version.
> Commands like `pnpm exec agents …` are placeholders. Use whichever package manager (`npm`, `pnpm`, `yarn`, `bun`) the project actually uses.
For deeper reference material that this skill links to, see:
- `agents/agents/AGENTS.md` — agent definition reference (created by `agents scaffold`)
- `agents/prompts/AGENTS.md` — prompt and tool config reference
- `agents/tools/AGENTS.md` — tool-writing patterns and `ThreadState` examples
- `agents/models/AGENTS.md` — recommended model list (authoritative)
- `agents/hooks/AGENTS.md` — hook reference
- Canonical TypeScript types (full signatures): read `node_modules/@standardagents/spec/dist/` in your project, or browse `packages/spec/src/` on GitHub at https://github.com/standardagents/agentbuilder
- Specification: https://standardagentspec.org/llms.txt
- Builder docs: https://docs.standardagentbuilder.com/llms.txt
> If the `agents/*/AGENTS.md` files don't exist in your project yet, run `pnpm exec agents scaffold` to create them.
---
## Before you write any code
Most failed Standard Agent projects fail in the same six ways. Read this section first and answer all six questions explicitly before creating any code. Each links to the deeper section that explains *how*.
**1. Architect the agent graph before picking types.**
List the domains the system touches. Determine the tree (coordinator → domain agents → sub-domain agents). *Then* decide what each node is. Do not start by writing one agent and bolting tools onto it. → [Architecture & decomposition](#architecture--decomposition)
**2. `dual_ai` is the default. `ai_human` is the exception.**
`ai_human` is correct only when the thread itself is the chat surface (chat UI, website widget, AgentBuilder admin, direct API chat). For Slack, email, SMS, Discord, webhooks, polled inboxes, schedulers, or any other mediated channel, the human is just a tool target — the agent is `dual_ai`. The whole graph being `dual_ai` is fine and often cleaner. → [Interaction type](#interaction-type-dual_ai-is-the-default)
**3. Every `dual_ai` agent must have explicit session boundaries.**
Name its `sessionStop` tool. Name its `sessionFail` tool. Set a finite `maxSessionTurns`. Give side_b a real, non-redundant job. If you cannot say in one sentence what tool call ends the session, the agent is not designed yet. → [Session boundary discipline](#session-boundary-discipline)
**4. Pick models from `current-models` only.**
Never invent model strings from memory. Run `pnpm exec agents current-models`, choose by category, then run `pnpm exec agents available-models --provider=<name>` to confirm the exact string. → [Model selection](#model-selection)
**5. Research every third-party API before writing the tool.**
Fetch the official current docs. Confirm auth, base URL, endpoint paths, payload shapes, rate limits, error codes. Do not write a tool from memory of how an API "usually" works. APIs change. → [API research checklist](#api-research-checklist)
**6. Check `ThreadState` before adding any dependency.**
Before reaching for S3, Redis, an external cron, a queue service, or even `node:fs`, confirm the framework does not already provide the capability via `ThreadState`. It almost always does. → [ThreadState first](#threadstate-first)
---
## What is a Standard Agent?
In the Standard Agent paradigm, agents are the atomic unit of an AI system, and it is the *composition* of many domain-specific agents that produces efficacy. Standard Agents can be effective using small and cheap models — but small and cheap models suffer from poor tool discernment when presented with a broad, undifferentiated set of tools. Decomposition is what makes them work.
A "gmail" agent will outperform a "google apps" agent. A higher-level "communications" coordinator composes the gmail, slack, and SMS agents. A still-higher "personal assistant" coordinator composes communications, research, scheduling, and finance. This is fractal: the same shape scales up to teams, departments, and entire products. These agent-graphs solve real industry problems — progressive tool discovery, model/prompt tuning, context dilution, task resumability, and compaction.
Just because subagents *can* compose complex behavior doesn't mean every step needs one. A subagent is a two-sided conversation where each side may take multiple steps per turn. Sometimes you only need a feature of a different model — generating an image, rewriting a paragraph in a more eloquent voice. For those, use a **subprompt** (a single LLM step exposed as a tool), not a subagent. Use a **subagent** when you need iteration, QA, reflection, or long-lived addressable behavior.
When a subagent does QA on another model's output, prefer a *different provider* on the reviewing side. Same-lab models tend to rate their own output too generously.
### Example graph
```
personal_assistant_coordinator (dual_ai, top-level reasoning model)
├── communications_coordinator (dual_ai, resumable, explicit parent communication, note: children are explicit because they receive inbound messages and should filter out noise before returning)
│ ├── gmail_agent (dual_ai, resumable, immediate, explicit parent communication)
│ ├── slack_agent (dual_ai, resumable, immediate, explicit parent communication)
│ └── sms_agent (dual_ai, resumable, immediate, explicit parent communication)
├── research_assistant (dual_ai, resumable, explicit parent communication)
│ ├── browser_use_agent (dual_ai, resumable, implicit parent communication)
│ ├── google_drive_agent (dual_ai, resumable, implicit parent communication)
│ └── notes_agent (dual_ai, resumable, implicit parent communication)
├── scheduling_coordinator (subprompt)
└── finance_ops_coordinator (dual_ai)
```
Note: no `ai_human` anywhere. The "human" enters via the gmail/slack/sms tool calls. The whole graph is autonomous.
## The Standard Agent stack
The spec defines an agent as a composition of:
- **Providers** — how to talk to LLM providers and model variants
- **Models** — named model definitions referencing a provider
- **Prompts** — system instructions plus the tools, subprompts, and subagents available at that step
- **Tools** — functions the agent can call to interact with the world (and other agents)
- **Agents** — bind it all together: name, type, sides, session bindings
- **Hooks** — custom code that intercepts lifecycle events (history filtering, message injection, tool result transforms)
- **Effects** — custom code scheduled to run later
- **Threads** — runtime instances of an agent, each with its own state, message history, and filesystem
- **Endpoints** — built-in and custom HTTP endpoints exposed by a thread
---
## Architecture & decomposition
**Procedure (do this in order, every time):**
1. List the domains the system actually touches.
2. Draw the tree on paper or in a comment block. Coordinators on top, leaf agents at the bottom.
3. Pick interaction types. (Default `dual_ai`. Use `ai_human` only at chat-surface entry points.)
4. Name session boundaries for every `dual_ai` node.
5. Pick a model category for every node.
6. List the third-party APIs each leaf needs and queue them for research.
7. Map each capability to a `ThreadState` primitive before writing any custom plumbing.
Only then start writing files.
### Mega-agent smell test
If a single agent has **more than ~8 tools across unrelated domains**, decompose it. Decomposition is rarely wrong; flattening usually is.
Other smells that mean "decompose now":
- One prompt has tools from two clearly different worlds (e.g., `send_email` and `query_postgres` and `generate_image`).
- The system prompt is trying to teach the model when to use which tool by writing rules in English. Rules-in-prose are coordinator logic; promote them to a coordinator that picks subagents.
- A model keeps calling the wrong tool because two tools have similar names or overlapping descriptions. Different domains, different agents.
- The agent's prompt is over ~150 lines. That's almost always context dilution — split.
### Worked example: wrong vs. right
**Wrong** — one mega-agent with tools from unrelated domains:
```
personal_assistant (ai_human)
tools: send_gmail, read_gmail, search_ebay_listings, place_ebay_order,
track_ebay_shipment, create_calendar_event, list_calendar_events,
post_slack_message, read_slack_channel, query_stock_price,
execute_stock_trade, search_recipes, generate_image
```
Gmail, eBay, calendar, Slack, stock trading, recipes, image gen — seven unrelated worlds in one tool list. The model picks `send_gmail` when it meant `post_slack_message`. It calls `execute_stock_trade` with eBay listing IDs. The system prompt grows 80 lines of rules trying to teach it which tool belongs to which world. Every bug fix breaks two unrelated flows.
**Right** — coordinator + domain subagents:
```
personal_assistant_coordinator (dual_ai, reasoning model)
├── gmail_agent tools: send_gmail, read_gmail
├── ebay_agent tools: search_ebay_listings, place_ebay_order,
│ track_ebay_shipment
├── calendar_agent tools: create_calendar_event, list_calendar_events
├── slack_agent tools: post_slack_message, read_slack_channel
├── trading_agent tools: query_stock_price, execute_stock_trade
└── content_helpers subprompts: search_recipes, generate_image
```
`personal_assistant_coordinator` runs a reasoning model and decides which domain owns the next step. Each leaf is a fast tool-calling model with a tight tool set it can actually discern between. A fix to the eBay flow can't break Gmail.
### Coordinators
Coordinators provide two things flat agents can't:
1. **Inter-domain communication.** A `coding_coordinator` can ask `research_agent` to investigate a library, then hand the findings to `bash_agent` to run commands. The bash agent never needed to know the research agent existed.
2. **Filtering.** A `gmail_agent` can filter spam well on its own. But the higher-level objectives of the organization — "what email actually matters to the CEO of the sprinkler hose company" — belong to a coordinator above it. The coordinator filters again before escalating.
### Graph depth tradeoffs
Deeper graphs add latency and create more inter-agent communication that can fail. But they also enable parallelism and isolation. A `marketing_communications_agent` can have a `social_media_agent` which has `twitter_agent`, `linkedin_agent`, and `facebook_agent` as children. That subtree handles posting workflows entirely without involving the top coordinator. Aim for depth that matches the natural hierarchy of the work, not depth for its own sake.
---
## Interaction type: `dual_ai` is the default
> **`dual_ai` is the default agent shape. `ai_human` is the exception**, used only when the thread itself is the chat surface — chat UI, website widget, AgentBuilder admin, or an API the user is talking to directly.
For **Slack, email, SMS, Discord, webhooks, polled inboxes, schedulers, or any other mediated channel**, the human is just a tool target. The agent on the framework side is `dual_ai`. The whole graph being `dual_ai` is fine — often cleaner.
### The single decision
Ask: **"Is the human typing directly into this thread?"**
- **Yes** → the top-level agent is `ai_human`.
- **No** → the top-level agent is `dual_ai`. The human enters the graph via a tool somewhere (e.g., a `send_slack_message` call inside a slack subagent).
Do not default to `ai_human` just because a human is eventually involved. Ask where messages physically arrive.
### The two shapes
**Shape A — chat surface (`ai_human` at top):**
```
website_chatbot (ai_human) ← user types in a chat widget
tools: search_products, lookup_order, escalate_to_human
```
**Shape B — mediated (`dual_ai` everywhere):**
```
slack_research_assistant (dual_ai, reasoning model)
├── slack_agent (dual_ai, resumable) ← messages arrive via tool calls,
│ not as thread messages
└── research_agent (dual_ai)
```
In Shape B, side_a of `slack_research_assistant` plans the work and dispatches subagents. Side_b reviews and decides when the work is done. The "user" is a Slack channel reachable through `slack_agent`'s tools.
### Handoffs (a special case of `ai_human`)
When an `ai_human` agent calls another `ai_human` agent as a tool, the runtime does **not** spawn a new thread — it changes which prompt "owns" the existing thread. This is a *handoff*. Useful for stepwise human flows: an `onboarding_agent` collects information, then hands off to a `scheduling_agent` that books a meeting. The user keeps talking to the same thread.
### Subprompts vs. subagents vs. handoffs
- **Subprompt** — one LLM step exposed as a tool. Use to switch models for a focused task: image generation, polished writing, JSON extraction.
- **Subagent** — full child thread with its own `ThreadState`. Use when you need iteration, QA, reflection, or long-lived addressable behavior. Always `dual_ai`.
- **Handoff** — `ai_human` → `ai_human`, swaps prompt ownership of the same thread. Use for stepwise human-driven flows.
A subagent can be:
- **Blocking + non-resumable** — the parent waits, the child runs once, returns, and is gone (tool-call style)
- **Blocking + resumable** — the parent waits but the child remains addressable for future calls
- **Non-blocking + non-resumable** — fire-and-forget one-shot
- **Non-blocking + resumable** — long-lived addressable child the parent can re-message later (e.g., a Slack monitor that lives as long as the parent)
---
## Session boundary discipline
This section is a hard rule, not a suggestion. **Every `dual_ai` agent in the graph must explicitly define its session boundaries.** This is what makes whole-graph `dual_ai` safe.
### Required for every `dual_ai` agent
- **`sessionStop` tool binding** — names the tool call that ends the session with a result. Side_a or side_b invokes it when the work is done. Common patterns: `report_findings`, `submit_to_parent`, `final_answer`, `done`.
- **`sessionFail` tool binding** — names the tool call that ends the session with a failure the parent should see. Common patterns: `escalate_blocked`, `report_unresolvable`, `give_up_with_reason`.
- **`maxSessionTurns`** — a finite integer sized to the realistic upper bound for the task. Never omit this. A research subagent might be 20; an asset QA loop might be 6; a tight reflection loop might be 3.
- **A real job for side_b** — reflection, QA, judging, alternative-perspective driving, devil's advocate. If side_b is "another instance of side_a," you don't have a `dual_ai` agent — collapse it to a single-side prompt or a coordinator pattern.
- **Cross-provider QA** — when side_b reviews side_a's output, pick a model from a *different provider* than side_a. Same-lab models systematically over-rate their own work.
### The smell test
> If you cannot articulate in one sentence what tool call ends the session, the agent is not designed yet.
Examples that pass:
- "Side_a calls `submit_video_assets` once side_b approves the renders."
- "Side_b calls `report_findings` after the research turn count exceeds 5 or it judges the topic exhausted."
- "Either side calls `escalate_to_parent` if the customer policy question is unresolvable."
Examples that fail:
- "It just stops when it's done." (How does the runtime know?)
- "Whichever side decides." (Decides by calling *what*?)
### Why this matters
A `dual_ai` agent with no `sessionStop`, no `sessionFail`, and no `maxSessionTurns` will burn turns until it hits a runtime cap, then fail in a way the parent can't interpret. Coding agents that have been bitten by this once will start defaulting to `ai_human` to "feel safer" — and then we're back to mega-agents and chat-surface confusion. Boundary discipline is what keeps `dual_ai` the default.
---
## Model selection
**Rule: never write a model string the user did not request and `current-models` did not produce.**
### Required workflow
1. Run `pnpm exec agents current-models` to see the curated category list (e.g. `extra_reasoning`, `fast_tool_calls`, `writing`, `image_generation`, `tiny`).
2. Choose the category that fits the role (table below).
3. Run `pnpm exec agents available-models --provider=<name>` to confirm the exact provider model string.
4. Define the model in `agents/models/<name>.ts` using `defineModel`.
If you have multiple configured providers, pass `--provider=<name>` explicitly.
### Role → category mapping
| Role | Category | Notes |
|---|---|---|
| Top-level coordinator | `extra_reasoning` | The discerning brain. Don't skimp here. |
| Domain subagent (gmail, slack, etc.) | `fast_tool_calls` | High volume, narrow tool set, latency matters. |
| Eloquent text generation | `writing` | Use as a subprompt from a fast-tool-calling agent. |
| Image generation | `image_generation` | Use as a subprompt. |
| QA / reviewer side_b | `extra_reasoning` from a *different provider* than side_a | Same-lab QA is biased. |
| Cheap classification, tagging, routing | `tiny` | Where speed and cost dominate quality. |
### Fallback strategy
Define more than one model per category, on different providers. The model `name` should describe the *use case*, not the provider, so a fallback can substitute transparently:
```
agents/models/extra_reasoning.ts → primary (provider A)
agents/models/extra_reasoning_fallback.ts → secondary (provider B)
```
Prompts and agents reference `extra_reasoning`; if the primary provider is down, the fallback is one rename away.
For the authoritative list of which models currently fill each category, see `agents/models/AGENTS.md` in your project (created by `agents scaffold`). Do not embed the list here — it drifts.
---
## API research checklist
This is the single biggest gap in most coding-agent-built tools. **Do not write a `defineTool` that touches a third-party API from memory.** APIs change. Auth flows change. Endpoints get deprecated. Read the current docs every time.
### Before writing the tool
1. **Fetch the official current docs.** Use WebFetch, the read-website skill, or a browser. Confirm:
- Auth method (bearer, OAuth, signed request, API key in header vs. query)
- Base URL and current API version
- Endpoint paths and HTTP methods
- Request payload shape (and required vs. optional fields)
- Response payload shape (success and error)
- Rate limits and `Retry-After` handling
- Pagination model (cursor, offset, link header)
- Idempotency rules — does retrying duplicate side effects?
2. **Confirm SDK availability and Workers compatibility.** Is there a first-party JS/TS SDK? Does it run in Cloudflare Workers (no Node built-ins, no `fs`, no native modules, no long-lived sockets)? If not, use `fetch` directly. Most provider SDKs are not Workers-compatible out of the box.
3. **Identify required secrets.** Declare them as `secret` variables, never `text`. Secrets must never be referenced in prompt text or returned to the model.
4. **Map error modes explicitly.** Each gets handled, not ignored:
- `401` / `403` — auth failed. Surface a clear message; do not retry blindly.
View on GitHub