Skip to content

The .agent Format

A .agent file is a small, indentation-based document (2 spaces, never tabs) — a strict superset of TOON. You describe what the agent is; toac emits the how.

How it works

Real logic (what a tool actually does) lives in plain TypeScript, in a co-located <agent>.tools.ts.

Keys

KeyReqFormMeaning
agentyesidentifierthe agent's name (also the export + filename)
modelyesstringa Claude model id, e.g. claude-opus-4-7
descriptionnostringone line on what it does
inputsnoinputs[N]{name,type}: + N rowstyped call parameters
toolsnotools[N]: a,b or tools[N]{name,input}:tools, implemented in <agent>.tools.ts (see Tools)
promptyesprompt: | + blockthe instruction prompt
outputsnooutputs[N]{name,type}: + N rowstyped structured result
systemnosystem: | + blocksystem prompt (defaults to the description)
usesnouses[N]: a,bsub-agents wired in as tools via asTool()
maxTurns / retriesnonumbertool-use turn cap / model-call retries
maxContextTokensnonumbersoft per-turn context budget; elides old tool results over it
temperaturenonumber 0–1sampling temperature (omit for the default)

Counts are checked

A header's count must match its rows: inputs[2]{...} has exactly two rows; tools[2]: a,b lists exactly two names. The explicit [N] lengths are what make the format reliable for LLMs to author.

Types

string, number, boolean, an enum of literal values (draft|final), or a quoted object type like "{title:string;score:number}". Append [] for an array (string[], or "{...}[]"). Read object fields with {inputs.x.field} or, in a loop, {item.field}.

Enums compile to TypeScript string-literal unions and z.enum([...]) — the literal set reaches the model through the schema, which makes structured outputs markedly more reliable than a free string:

agent
outputs[2]{name,type}:
  verdict,approve|reject|escalate
  reason,string

A trailing ? on a field name marks it optional — the generated TypeScript types it field?: and the zod schema gets .optional(). Omitted optionals interpolate as empty, iterate as an empty list (so {:else} renders), and test false in {#if}:

agent
inputs[2]{name,type}:
  topic,string
  audience?,string
agent
inputs[3]{name,type}:
  sources,"{title:string;url:string}[]"
  detailed,boolean
  audience,string

Tools

Tools take one of two forms. Bare names list the tools; each is a full defineTool (description, schema, body) in the co-located <agent>.tools.ts:

agent
tools[2]: web_search,fetch_page

Typed tools declare each tool's input type right in the .agent file, so the file owns the schema. toac then generates the Zod schema and a typed defineTool, and <agent>.tools.ts supplies only the run body — type-checked against the declared input:

agent
tools[2]{name,input}:
  web_search,"{query:string}"
  fetch_page,"{url:string}"
ts
// researcher.tools.ts — just the bodies
import type { WebSearchInput } from "./researcher";
export const web_search = (i: WebSearchInput) => search(i.query);

Add a third column for a model-facing description: tools[N]{name,input,description}:. A tool's input must be an object type.

A complete example

A kitchen-sink agent using loops, conditionals, object fields, and destructuring — all type-checked:

agent
agent: brief
model: claude-opus-4-7
description: Summarize sources for an audience, optionally in detail.
inputs[3]{name,type}:
  sources,"{title:string;url:string}[]"
  detailed,boolean
  audience,string
prompt: |
  Write a brief for {inputs.audience}.
  {#each inputs.sources as {title, url}, i}
  {i}. {title} — {url}
  {:else}
  No sources provided.
  {/each}
  {#if inputs.detailed}
  Include a thorough analysis section.
  {:else}
  Keep it to a single paragraph.
  {/if}
outputs[1]{name,type}:
  brief,string

Try it live in the playground, or see the template constructs in detail in Prompt Templates.

Released under the MIT License.