--- url: /motivation.md description: >- What abtree replaces (monolithic skill files), what it is not (an orchestration framework), and why it positions itself against a single, well-defined problem. --- # Motivation If you give an AI agent a large skill file — a `CLAUDE.md`, a system prompt, a long markdown playbook — you hand it a document and hope it follows the steps consistently. It does not. The agent skips steps it remembers from earlier in its context, confuses the order of operations, and interprets the same instruction differently on every run. The file grows as you try to compensate. The problem worsens. abtree replaces the skill file with a behaviour tree. Instead of handing the agent the whole document, the runtime hands it one step at a time, verifies the result, and advances the cursor. The agent only ever sees the next request. | Before abtree | After abtree | |---|---| | A 400-line `CLAUDE.md` the agent reads once and interprets differently every run. | A tree the agent follows one step at a time. | | No record of where the agent got to mid-run. | An execution document persists the cursor after every step. | | Restarting the process restarts the workflow from scratch. | The next `abtree next` resumes from exactly where the agent left off. | ## What abtree is not abtree is not a workflow orchestration framework. It does not compete with LangGraph, Temporal, or multi-agent platforms. If you need distributed workers, event-driven pipelines, or production observability infrastructure, those tools are the right fit. abtree solves one problem: the large, ambiguously-ordered skill file your agent follows inconsistently. ## Why this matters The cost of the skill-file approach is invisible until the workflow is large enough that the agent reliably forgets a step. abtree's contract is that every step the agent takes is mediated by the runtime — there is no path through the workflow that bypasses the cursor. That property is what the homepage refers to as **deterministic structure for non-deterministic agents**, and the rest of the documentation explains how to apply it to your own workflows. ## Next * [Get started](/getting-started) — install abtree and run the bundled `hello-world` tree in five minutes. * [Why behaviour trees?](/concepts/) — the conceptual frame that the rest of the documentation assumes. --- --- url: /getting-started.md description: >- Five-minute walkthrough to install abtree, hand a behaviour tree to your agent, and watch it drive a workflow end-to-end. --- # Get started A five-minute walkthrough: install abtree, hand a tree to your agent, and watch it drive. For the vocabulary behind the moving parts, see [Why behaviour trees?](/concepts/). ::: tip Terms used below `$LOCAL` is the per-execution blackboard, `instruct` is an action step that asks the agent to do work, and `evaluate` is an action step that asks the agent to judge a precondition. All three are defined in the [Concepts](/concepts/) tier. ::: ## 1. Install abtree ::: code-group ```sh [macOS / Linux] curl -fsSL https://github.com/flying-dice/abtree/releases/latest/download/install.sh | sh ``` ```powershell [Windows] irm https://github.com/flying-dice/abtree/releases/latest/download/install.ps1 | iex ``` ::: Verify the install: ```sh abtree --version ``` You see a version number. If you do not, restart your terminal so the new `PATH` takes effect. ## 2. Set up a workspace ```sh mkdir my-abtree-demo && cd my-abtree-demo bun add @abtree/hello-world # or npm/pnpm install ``` `hello-world` is a small tree: classify the time of day, then pick the matching greeting from a four-way selector. It exercises three of the four behaviour-tree primitives — `sequence`, `selector`, and `action` — in a few dozen lines. The package installs the tree file at `node_modules/@abtree/hello-world/main.json`. ## 3. Hand it off to your agent In Claude Code, ChatGPT, or any agent that runs shell commands, send: ```text Run the abtree hello-world tree end-to-end. Start by running 'abtree --help' to learn the execution protocol, then create an execution with 'abtree execution create ./node_modules/@abtree/hello-world/main.json "first run"' and drive it through every step until you see status: done. ``` That is the entire human-side interaction. The agent reads the protocol from `--help`, creates an execution, and drives the loop autonomously. ## 4. Watch the agent drive the loop Each turn, the agent calls one command and reads its JSON response. The first `abtree next` on any execution is a runtime-level gate that hands the agent the execution protocol — every execution starts here, regardless of which tree it runs: ```json { "type": "instruct", "name": "Acknowledge_Protocol", "instruction": "Read the runtime protocol below in full..." } ``` The agent reads the protocol and acknowledges: ```sh abtree submit first-run__abtree-hello-world__1 success ``` After the gate, `abtree next` returns the tree's first real step: ```json { "type": "instruct", "name": "Determine_Time", "instruction": "Check the system clock to get the current hour..." } ``` The agent does the work — checks the clock, classifies the hour as `morning` — then writes the result and submits: ```sh abtree local write first-run__abtree-hello-world__1 time_of_day "morning" abtree submit first-run__abtree-hello-world__1 success ``` The next call returns an `evaluate`: ```json { "type": "evaluate", "name": "Morning_Greeting", "expression": "$LOCAL.time_of_day is \"morning\"" } ``` The agent reads the expression, decides it holds, and answers: ```sh abtree eval first-run__abtree-hello-world__1 true ``` The loop repeats — `next` → do the work or judge the precondition → `submit` or `eval` — until: ```json { "status": "done" } ``` The agent only ever sees the next request. ## 5. Read the execution diagram abtree regenerates an SVG diagram at `.abtree/executions/first-run__abtree-hello-world__1.svg` (relative to the cwd you ran `abtree` from) after every state change. Open it in any browser or image viewer; green nodes succeeded, red nodes failed, uncoloured nodes were never ticked. For the `hello-world` run above, the cursor advanced through the root sequence (`Hello_World` → `Determine_Time` → `Choose_Greeting`) and the selector chose `Morning_Greeting` after its `evaluate` precondition held — the afternoon, evening, and default branches were never entered. ## What that gives you Your agent drove a structured workflow without a 2,000-line system prompt, without a JSON schema in its context, and without chain-of-thought. The tree handed it exactly one task at a time, and only let it advance when the task was complete. That is the core idea: **deterministic structure for non-deterministic agents.** ## Next * [Why behaviour trees?](/concepts/) — the problem they solve. * [State](/concepts/state) — `$LOCAL` and `$GLOBAL`, the two scopes the runtime exposes. * [Writing trees](/guide/writing-trees) — author your own tree, step by step. * [CLI reference](/guide/cli) — every command, every flag. --- --- url: /concepts.md description: >- Why use behaviour trees for AI agents — the same hierarchical decision structure used by game AI and robotics, applied to LLM workflows. Names the four primitives so the rest of the documentation can lean on them. --- # Why behaviour trees? A **behaviour tree** is a hierarchical structure for organising decisions. It was invented for video-game AI — the kind of NPCs that have to choose between *patrol*, *attack*, *flee*, or *call for backup* without breaking immersion. From there it spread to robotics, where reliability matters more than cleverness. abtree brings the same idea to LLM agents. A behaviour tree in abtree is built from four primitives: three composite types — `sequence`, `selector`, `parallel` — and one leaf type, `action`. The composites coordinate control flow; the actions are the units of work the agent performs. The rest of the concept tier expands on each one. ## The problem You can describe almost any workflow to a modern LLM in a single Markdown document. The document mostly works. Then it does not. The two failure modes: ### 1. Prompt drift A long system prompt is supposed to tell the agent everything: the format of the answer, the order of operations, the failure cases, the edge cases. Model attention is finite. As prompts grow past a few hundred lines, agents: * Skip steps they "remember" from earlier. * Confuse the order of operations. * Forget invariants stated up front. * Hallucinate fields you defined. The usual fix is to repeat yourself. The prompt grows. The problem worsens. ### 2. Non-determinism Even when the agent reads every word, decisions are made probabilistically. Run the same task twice and you can get different choices. For exploratory work, that is fine. For workflows where reproducibility matters — code review, deployments, structured data extraction — it is a liability. ## The fix: a formal logic layer A behaviour tree separates **what to do** from **when to do it**. The tree defines the structure: what runs first, what runs in parallel, what counts as success, when to fall back. The agent only sees the current step. Three things change: 1. **The agent's working set shrinks** to one instruction at a time. No more 2,000-line prompt to skim. 2. **Decisions become explicit.** A `selector` says "try the morning branch first; if that fails, try afternoon." The agent does not choose. The runtime enforces the order; the agent receives the current step. 3. **Progress is verifiable.** Every action ends only after an `evaluate` invariant has been satisfied. The next pages walk through the building blocks: [state](/concepts/state), then [branches and actions](/concepts/branches-and-actions). Take them in order — each builds on the previous. --- --- url: /concepts/how-it-works.md description: >- A live walkthrough of an abtree execution — the YAML tree on one side, the CLI exchange on the other, and the cursor moving between them one step at a time. --- # How it works You define a tree. The agent drives execution. The animation below shows both halves at once: the YAML tree on the left, the CLI exchange on the right, and the cursor moving between them. Watch a single node enter the active state, the agent answer the request, the runtime advance, and the next node light up. ## What the animation shows On the left, a small `deploy` tree — a `sequence` with three `action` children: `Run_Tests`, `Build_Image`, and `Push_Image`. Each action carries one or more `evaluate` or `instruct` steps. On the right, the loop the agent walks once you hand it the execution id: * **`abtree next `** asks the runtime what to do. The response is one of four shapes: `evaluate` (a precondition to check), `instruct` (work to perform), `done`, or `failure`. * **`abtree eval true|false`** answers an `evaluate` step. The runtime advances if the precondition holds; the action fails immediately if it does not. * **`abtree submit success|failure|running`** reports the outcome of an `instruct` step. `success` advances the cursor; `failure` aborts the action by the parent's branch rules; `running` ack-and-pauses without advancing. As each node finishes, it settles into `success` (green) or `failure` (red). The cursor — the pink ring — is always sitting on the node the agent is currently working on. The agent never sees anything else. ## The contract, in three lines 1. **You write the tree.** Composite nodes (`sequence`, `selector`, `parallel`) coordinate; `action` nodes do the work. 2. **The runtime walks it.** It hands the agent the next step, gates each one on declared state, and persists the cursor between calls. 3. **The agent answers.** It evaluates preconditions and performs instructions. It does not decide which step comes next. That separation — control flow on the runtime side, work on the agent side — is what makes the same workflow deterministic, resumable, and replayable. ## Next * [State](/concepts/state) — the `$LOCAL` blackboard and `$GLOBAL` world model the agent reads and writes between steps. * [Branches and actions](/concepts/branches-and-actions) — the four primitives in detail, with the rules each one enforces. * [CLI reference](/guide/cli) — every command the loop above uses, with response shapes, exit codes, and environment variables. --- --- url: /concepts/state.md description: >- Two state scopes in abtree — $LOCAL is a per-execution blackboard agents read and write; $GLOBAL is a read-only world model the tree observes. --- # State Behaviour trees are stateful by design. abtree separates state into **two scopes**, written explicitly, never implicit. A **blackboard** is the term used in the behaviour-tree and game-AI literature for a key-value store scoped to one execution and used to pass data between steps. In abtree the blackboard is `$LOCAL`. ## `$LOCAL` — the workflow's blackboard `$LOCAL` is a key-value store private to one execution. Actions read from it, write to it, and use it to thread data between steps. Examples: * `$LOCAL.greeting = "Good morning, Alice!"` — output of one step, input to the next. * `$LOCAL.confidence_score = 0.92` — a number computed during the run. * `$LOCAL.error_log = [...]` — an accumulating list. `$LOCAL` is initialised when the execution is created. Every state change persists immediately to the execution's JSON document — kill the process and resume tomorrow. ## `$GLOBAL` — the world model `$GLOBAL` describes the **environment** the agent operates in. You do not write `$GLOBAL` values from inside the execution — you observe them. Examples: ```yaml state: global: user_name: retrieve by running the shell command "whoami" current_branch: the output of git rev-parse --abbrev-ref HEAD api_endpoint: https://api.example.com tone: friendly ``` The first two values are not literals — they are **directives** that tell the agent how to fetch the value. When an action reads `$GLOBAL.user_name`, the agent runs `whoami` and uses the result. The third value is a literal that never changes during the execution. The fourth is a configuration knob. ## Why two scopes The distinction is the contract. `$LOCAL` is something **your tree creates**. `$GLOBAL` is something **the world tells you**. Putting them in different scopes makes the source of every value explicit: * An action that reads `$GLOBAL.user_name` knows the value comes from the environment. * An action that reads `$LOCAL.greeting` knows the value was computed earlier in the execution. Mixing them — a single "context" object — hides where data came from. That is the bug surface that hurts agentic systems hardest: was this value something I produced, or something I read? abtree makes you answer up front. ## Reading and writing from the CLI Both scopes are reachable from the CLI — see [CLI reference](/guide/cli). ## Next * [Branches and actions](/concepts/branches-and-actions) — the four primitives that drive the tree. * [Why behaviour trees?](/concepts/) — back to the concept overview. --- --- url: /concepts/branches-and-actions.md description: >- The four primitives of an abtree behaviour tree — sequence, selector, parallel, and action — and how each one defines control flow. --- # Branches and actions Three branch types. One leaf type. That is the whole language. A **tick** is one evaluation pass through the tree by the runtime: the cursor visits the current node, returns success or failure, and advances or retries. ## Branches Branches define the **flow of control**. They have children. Their job is to coordinate which children run, in what order, and what counts as success. ### Sequence Run children in order. **All must succeed.** If any child fails, the sequence fails. Use a sequence for linear workflows where each step depends on the previous one. ```yaml type: sequence name: Deploy_Service children: - type: action name: Run_Tests - type: action name: Build_Image - type: action name: Push_To_Registry ``` If `Run_Tests` fails, the sequence aborts. The build never happens. The push never happens. ### Selector Run children in order until one **succeeds**. If all fail, the selector fails. A selector is the decision-making primitive — the equivalent of an if/else chain. ```yaml type: selector name: Choose_Greeting children: - type: action name: Morning_Greeting steps: - evaluate: $LOCAL.time_of_day is "morning" - instruct: ... - type: action name: Afternoon_Greeting steps: - evaluate: $LOCAL.time_of_day is "afternoon" - instruct: ... ``` The selector tries `Morning_Greeting`'s evaluate first. If it passes, the morning instruct runs and the selector finishes. Otherwise it falls through to `Afternoon_Greeting`. ### Parallel Run all children. **All must succeed.** Use a parallel when steps are independent and can run in any order. ```yaml type: parallel name: Gather_Context children: - type: action name: Check_Weather - type: action name: Check_News ``` The agent receives both `instruct` requests and satisfies them in any order. If either fails, the parallel fails. ## Actions Actions are the **leaves** of the tree. Each is a small, focused unit of work made of two kinds of step: ```yaml type: action name: Determine_Time steps: - evaluate: $LOCAL.now is set - instruct: | Get the current hour from the system clock. Classify as "morning", "afternoon", or "evening". Store at $LOCAL.time_of_day. ``` ### `evaluate` A precondition. A semantic boolean expression checked against `$LOCAL` and `$GLOBAL`. The agent reads it, decides whether it is true, and submits the answer with `abtree eval true|false`. If `false`, the action fails immediately. The runtime advances by branch rules: a sequence aborts; a selector tries the next child. ### `instruct` The work. Free-form prose telling the agent what to do. The agent does it, writes results to `$LOCAL`, and calls `abtree submit success` to advance. An action can have multiple steps — alternating evaluates and instructs — to handle multi-stage logic in a single leaf. ## Retries Any node — composite or action — can carry a `retries: N` config. On failure the runtime wipes the node's internal bookkeeping (status, step index, descendants), bumps an internal retry counter, and re-ticks the node from a clean slate. After `N` retries are exhausted, the failure propagates normally. User state in `$LOCAL` persists across retries — that is the feedback channel between attempts. ```yaml type: sequence name: Write_And_Review retries: 2 # one initial attempt + 2 retries = 3 total attempts children: - { type: action, name: Write, steps: [...] } - { type: action, name: Review, steps: [...] } ``` ## Putting it together A tree with all four primitives: ```yaml type: sequence # do these in order children: - type: action # step 1: figure out the time name: Determine_Time steps: - instruct: ... - type: selector # step 2: pick a branch by time of day name: Choose_Greeting children: - type: action name: Morning_Greeting steps: - evaluate: $LOCAL.time_of_day is "morning" - instruct: ... - type: action name: Afternoon_Greeting steps: - evaluate: $LOCAL.time_of_day is "afternoon" - instruct: ... - type: action name: Evening_Greeting steps: - evaluate: $LOCAL.time_of_day is "evening" - instruct: ... - type: action name: Default_Greeting steps: - instruct: ... - type: parallel # step 3: gather context concurrently name: Gather_Context children: - type: action name: Check_Weather steps: - instruct: ... - type: action name: Check_News steps: - instruct: ... - type: action # step 4: compose the final response name: Compose_Response steps: - evaluate: $LOCAL.weather is set and $LOCAL.news is set - instruct: ... ``` The bundled `hello-world` tree covers the first three primitives (sequence, selector, action); the bundled `improve-codebase` tree exercises a real-world parallel. ## Next * [Using a tree](/guide/using-trees) — install a published tree and drive it with your agent. * [State](/concepts/state) — the two scopes the primitives read and write. * [Writing trees](/guide/writing-trees) — turn this into a working tree. * [Inspecting executions](/guide/inspecting-executions) — how the runtime walks the tree at tick time. --- --- url: /guide/using-trees.md description: >- Install a published abtree workflow and drive it with your agent of choice. Covers prerequisites, per-repo vs global install, the skill prompt, and how to run the workflow once installed. --- # Use a tree Every published [abtree](https://abtree.sh) workflow installs and runs the same way. This page is the canonical reference — individual tree READMEs link here instead of duplicating it. ## Prerequisites Install these on your `PATH`: * **abtree CLI** — see [Get started](/getting-started) for one-liner installers (macOS, Linux, Windows). * **An agent.** abtree is agent-agnostic. Examples on this site use [Claude Code](https://docs.claude.com/claude-code), but any LLM agent that runs shell commands works. * **A node package manager** — [Bun](https://bun.sh), [pnpm](https://pnpm.io), or [npm](https://www.npmjs.com). ## Install the abtree skill The skill is the prompt template that teaches your agent how to drive an abtree execution. If you kick off a workflow and the agent does not seem to know what to do, the skill is missing: ```sh abtree install skill ``` You only do this once per agent setup, not per tree. ## Install the tree Choose one of two options. Either way, the tree lands as a node package — abtree finds it by path or by slug at execution time. ### Option A — per-repo (recommended) In the repository you want to run the workflow against: ::: code-group ```sh [bun] bun add --dev ``` ```sh [pnpm] pnpm add -D ``` ```sh [npm] npm install --save-dev ``` ::: The tree lands at `./node_modules//`. It is a dev-time tool — nothing ships in your runtime bundle. ### Option B — global Install once, run from any repository: ::: code-group ```sh [bun] bun add -g ``` ```sh [pnpm] pnpm add -g ``` ```sh [npm] npm install -g ``` ::: The tree lands at `$(npm root -g)//` (use `pnpm root -g` or `bun pm -g bin` for those managers). ### Pin a version Pin an npm version for byte-stable resolution across machines: ```sh bun add @1.2.0 ``` For full reproducibility, commit your lockfile (`package-lock.json`, `pnpm-lock.yaml`, or `bun.lock`). ### Install from sources other than npm If you consume a tree that is not published to npm (a private fork, an unpublished prototype, a local tarball), every supported package manager already documents Git, local-path, tarball, and URL forms — see the manager's `add`/`install` reference: [npm install](https://docs.npmjs.com/cli/v10/commands/npm-install), [pnpm add](https://pnpm.io/cli/add), [bun add](https://bun.sh/docs/cli/add). ## Run the workflow Hand a brief to your agent. The exact phrasing does not matter — the agent reads the skill, calls `abtree --help`, and figures out the protocol from there: ```sh # Option A — per-repo install claude 'Using the abtree cli, run the tree ./node_modules/' ``` ```sh # Option B — global install claude "Using the abtree cli, run the tree $(npm root -g)/" ``` The agent walks the tree and persists state to a new `.abtree/` directory at the repo root. Gitignore it, or commit it if you want shareable run history. ## What gets written Every execution writes the same two artefacts under `.abtree/executions/`: | Path | Purpose | |---|---| | `.abtree/executions/.json` | Full execution document — input, output, every state mutation. | | `.abtree/executions/.svg` | Live SVG diagram of the run, regenerated on every state change. | Individual trees also write workflow-specific files (reports, plans, and so on) — see the tree's own README. ## Reference * [Concepts](/concepts/) — what behaviour trees are and why. * [Writing trees](/guide/writing-trees) — author your own. * [Design a new tree](/guide/design-process) — ten-step design process. * [Idioms](/guide/idioms) — reusable shapes for production workflows. * [Fragments](/guide/fragments) — split a large tree across files. * [Inspecting executions](/guide/inspecting-executions) — debug a stuck or failed run. * [Execution protocol](/agents/execute) — what the agent does at each step. * [CLI reference](/guide/cli) — every command, every flag. ## Next * [Writing trees](/guide/writing-trees) — build the bundled `hello-world` tree from scratch. --- --- url: /guide/writing-trees.md description: >- Build the bundled hello-world tree from scratch — folder layout, file structure, state, primitives, retries. End-to-end tutorial that points at the reference for field-by-field detail. --- # Writing trees This page walks you through writing a tree by re-creating the bundled `hello-world`. By the end you have a working tree you can drive with `abtree execution create`. For the full field reference, see [Authoring trees](/agents/author). ## What you build A single `hello-world.yaml` file. abtree reads tree files by literal path — no slug lookup, no `package.json` discovery, no conventional directory layout. Put the file wherever fits your project; the tree file's own `name` field is the slug abtree uses inside execution IDs. ## 1. Create the file ```sh mkdir -p trees && cd trees touch hello-world.yaml ``` ## 2. Write the top-level fields Open `hello-world.yaml` with the four required scalars and the `state` block: ```yaml # yaml-language-server: $schema=https://abtree.sh/schemas/tree.schema.json name: hello-world version: 1.0.0 description: Greet a user based on time of day. state: local: time_of_day: null # filled in by Determine_Time greeting: null # filled in by the winning Choose_Greeting branch global: user_name: retrieve by running the shell command "whoami" tone: friendly language: english ``` `state.local` declares the `$LOCAL` keys actions read and write during the run. `null` defaults are common — the actions populate them. `state.global` declares the `$GLOBAL` keys the tree reads. Sentence-shaped values are **directives**: when an action reads `$GLOBAL.user_name`, the agent runs `whoami` and returns the result. For the full field list (and the schema constraints on each field), see [File shape](/agents/author#file-shape). ## 3. Add the root sequence ```yaml tree: type: sequence name: Hello_World children: - type: action name: Determine_Time steps: - instruct: > Check the system clock. Classify as "morning", "afternoon", or "evening". Store at $LOCAL.time_of_day. ``` `tree:` is the root node. It is a single node — in practice almost always a `sequence`, so steps run in order. `Determine_Time` is the first child: one `action` with one `instruct` step. Node names use **PascalCase with underscores** (`Determine_Time`). The SVG trace renders `_` as a space, so `Choose_Greeting` becomes "Choose Greeting" in the diagram. ## 4. Add the selector ```yaml - type: selector name: Choose_Greeting children: - type: action name: Morning_Greeting steps: - evaluate: $LOCAL.time_of_day is "morning" - instruct: Compose a cheerful morning greeting... - type: action name: Afternoon_Greeting steps: - evaluate: $LOCAL.time_of_day is "afternoon" - instruct: Compose a warm afternoon greeting... - type: action name: Evening_Greeting steps: - evaluate: $LOCAL.time_of_day is "evening" - instruct: Compose a relaxed evening greeting... - type: action name: Default_Greeting steps: - instruct: Compose a neutral greeting... # no evaluate = always passes ``` The `selector` runs children in order until one succeeds. Each branch is gated by an `evaluate` precondition. The final child has no `evaluate`, so it always passes — that is the fallback for "none of the above matched". A selector with no winning child fails the whole branch. ## 5. Validate it loads ```sh abtree execution create ./trees/hello-world.yaml "first run" ``` If the tree is well-formed, the CLI prints the new execution document and you can drive it with `abtree next`. If it is not, the CLI prints a path-prefixed validation error and exits non-zero. ## What you skipped `hello-world` covers three of the four primitives. The fourth — `parallel` — runs all children concurrently and succeeds only if every child succeeds. Drop one in when you have two reads that do not depend on each other: ```yaml - type: parallel name: Gather_Context children: - { type: action, name: Read_Schema, steps: [...] } - { type: action, name: Read_Conventions, steps: [...] } ``` The bundled `improve-codebase` tree ships a real-world parallel: four metric scorers running concurrently against the same codebase. ## Where to go next * **Refactor it into fragments.** See [Fragments](/guide/fragments) for the `$ref` workflow and snapshot semantics. * **Add a retries config.** Any node can carry `retries: N`. See [Authoring trees](/agents/author#retries) for the reference behaviour. * **Pick the right shape for a new workflow.** See [Design a new tree](/guide/design-process) for the ten-step process and [Idioms](/guide/idioms) for the catalogue of reusable shapes. * **Test it.** Two options: [Testing trees](/guide/testing) for BDD-style YAML specs via `@abtree/test-tree`, or [Programmatic test harness](/guide/test-harness) for deterministic TypeScript assertions via `@abtree/testing`. ## Next * [Fragments](/guide/fragments) — split a large tree across files using `$ref`. * [Authoring trees](/agents/author) — full field reference. * [CLI reference](/guide/cli) — every command, every flag. --- --- url: /guide/fragments.md description: >- Split a large tree across files using JSON-Schema-style $ref. abtree resolves fragments at execution-creation time, so the runtime sees one fully-merged snapshot. --- # Fragments A tree can grow past the point where one file is comfortable. **Fragments** let you split it across files using JSON-Schema `$ref`. abtree dereferences every ref at execution-creation time, so the runtime — and every tool that inspects the execution document — sees a single merged tree. ## Why split a tree * **Reuse.** The same retry-and-review subtree used by three different parents lives in one file, edited once. * **Readability.** A 200-line `TREE.yaml` becomes a 20-line spine that names its phases and points at the work. * **Safe refactors.** Pulling a section into a fragment is a syntactic move — if the [Testing trees](/guide/testing) suite stays green, the behaviour is identical. Treat each fragment as a single responsibility — one thing the spine can name in a sentence without using "and". A fragment like `capture-review.yaml` that runs a review, writes the verdict to `$LOCAL`, then either comments on the MR or returns to screen is still one responsibility ("get the review feedback into the right place"); a `review-and-deploy.yaml` is two. When you find yourself reaching for "and", extract again. ## Layout Keep fragments next to the tree that owns them: ``` trees/big-workflow/ TREE.yaml fragments/ auth.yaml work.yaml cleanup.yaml ``` The spine file references each piece: ```yaml # trees/big-workflow/TREE.yaml name: big-workflow version: 1.0.0 description: Composed of separately-authored fragments. state: local: {} tree: type: sequence name: Big_Workflow children: - $ref: "./fragments/auth.yaml" - $ref: "./fragments/work.yaml" - $ref: "./fragments/cleanup.yaml" ``` A fragment file is just a node — one composite or one action. It does not carry the top-level `name`, `version`, `description`, or `state` keys; those live on the root only. ```yaml # trees/big-workflow/fragments/auth.yaml type: sequence name: Auth_Sequence children: - { type: action, name: Login, steps: [...] } - { type: action, name: Verify, steps: [...] } ``` The fragment slots in wherever it's referenced, as if you'd pasted it inline. ## `$ref` accepts three forms * **Relative paths** (`./fragments/auth.yaml`) — resolved against the file containing the `$ref`. The common case. * **Absolute paths** (`/home/you/shared/retry.yaml`) — useful for machine-wide shared subtrees. * **URLs** (`https://example.com/shared-trees/auth.yaml`) — fetched at execution-creation time. Use sparingly: the run depends on the URL being reachable when the execution is created. ## Resolution happens at execution-creation When you call `abtree execution create `, abtree dereferences every `$ref` and writes the merged tree into the execution's `snapshot` field. The execution runs against that snapshot, not the live files. Two consequences: * Editing a fragment after creation does **not** affect in-flight executions. New executions pick up the change. * The snapshot is self-contained — you can share an execution document with a teammate and they don't need your fragment files. ## Cycles Cyclic refs (A → B → A) are not expanded — the ref is left as a literal `{ $ref: "..." }` node in the snapshot, preventing stack overflow at load time. If the runtime ever ticks one of these literal nodes it fails cleanly with a "cyclic ref encountered" error, so cycles surface as a runtime failure on the specific node rather than as a crash. In practice this means: a tree-under-test can include a `$ref` back to itself (e.g., a recursive subtree) and still load — but you'll need to gate the recursion in a sequence/selector so the cycle isn't entered. ## Refactoring into fragments The motion: identify a coherent subtree, cut it into its own file, replace it with a `$ref`. The whole point is that this should be a pure restructuring — no behaviour change. The way to be sure is the [Testing trees](/guide/testing) suite. A tree with scenarios for its main paths means: 1. Run the tests green on the original tree. 2. Extract a fragment, replace with `$ref`. 3. Run the tests again. If green, the refactor is correct. If red, something moved that shouldn't have. This is the largest concrete reason to write tests on a tree before it gets large — splitting later is mechanical and safe instead of error-prone. ## Next * [Design a new tree](/guide/design-process) — the ten-step process for picking the right shape. * [Idioms](/guide/idioms) — reusable shapes you reach for during design. * [Testing trees](/guide/testing) — the regression net that makes fragment refactors safe. --- --- url: /guide/design-process.md description: >- A ten-step process for designing a new abtree behaviour tree. Start from the success state, work down to the root sequence, then validate that the tree loads. --- # Design a new tree The canonical ten-step process for designing a fresh abtree tree from a human brief. You produce a tree at the end. ::: tip Prerequisites Read [Branches and actions](/concepts/branches-and-actions) for the primitives and [Writing trees](/guide/writing-trees) for the file shape before starting. ::: The process is linear. Each step asks one question. You write down the answer before you move on. ## 1. Name the success state State the post-condition the root sequence has to establish, in one sentence. "The pull request is merged." "The plan is approved by a codeowner." "The artefact is written to `$LOCAL.final_path`." The remaining steps work backwards from this sentence. ## 2. List the discrete tasks Each task becomes one `action`. The task's natural-language description becomes the `instruct`. The task's precondition becomes the `evaluate`. | Task | `evaluate` | `instruct` | |---|---|---| | Fetch the plan | `$LOCAL.change_request is set` | Load the plan into `$LOCAL.plan_content`. | | Score the plan | `$LOCAL.plan_content is set` | Run the review checklist. Write the score to `$LOCAL.score`. | If you cannot name an `evaluate` for a task, the task is underspecified. Tighten the brief. ## 3. Group dependent tasks into sequences "Do A before B" maps to `sequence: [A, B]`. The sequence aborts on the first failure, so a failing dependency stops the workflow early. ## 4. Identify decisions "If X then Y, else Z" maps to a `selector` with evaluate-gated children. The first child whose `evaluate` passes runs. ```yaml type: selector name: Choose_Greeting children: - type: action name: Morning_Greeting steps: - evaluate: $LOCAL.time_of_day is "morning" - instruct: ... - type: action name: Default_Greeting steps: - instruct: ... # no evaluate = always passes ``` Always end an evaluate-gated selector with a no-evaluate fallback if you need a "none of the above" branch. A selector with no winning child fails. ## 5. Identify fan-out "Do these in any order" maps to a `parallel`. Children run independently; all must succeed. If two children both read a value produced by an earlier step, that step has to live in a parent `sequence` above the parallel — fan-out happens after fan-in. ## 6. Identify gates "The human or a downstream system must approve" maps to an `evaluate` on a flag the approver sets. The agent submits `running` while waiting; the approver runs `abtree local write approved true` to release the gate. ```yaml - type: action name: Human_Approval_Gate steps: - evaluate: $LOCAL.draft is set - instruct: | Present the draft. Wait for the reviewer to write $LOCAL.approved = true. Submit running while waiting. - evaluate: $LOCAL.approved is true - instruct: Proceed with the approved draft. ``` ## 7. Identify retries "Try this a few times before giving up" maps to one of two shapes: * **`retries: N` on a sequence.** The runtime resets the sequence's internal state and re-ticks on failure, up to `N` times. User state in `$LOCAL` persists. Use this when each retry is the same shape and you want one config knob. * **`selector` of `N` attempts.** Each attempt is its own observable, resumable child. Use this when each pass is materially different — for example, "first draft", "revise once", "final revise". ## 8. State the input contract List the `$LOCAL` keys that must be set before the first action evaluates. Declare them in `state.local` with `null` defaults. ```yaml state: local: change_request: null plan_content: null score: null ``` The agent or the human seeds the required keys via `abtree local write` before calling `abtree next`. ## 9. Sketch the tree top-down Write the root `sequence`, then expand each child in turn. After the sketch, walk the failure modes: trace what happens when each action fails, and verify the parent composite handles the failure the way the design intends. A `selector` parent absorbs the failure and tries the next child. A `sequence` parent aborts. Match that to the design intent before you commit. ## 10. Save and validate Save the tree to a `.json`/`.yaml`/`.yml` file wherever fits your project. Run: ```sh abtree execution create ./path/to/your-tree.yaml "smoke test" ``` If the tree is malformed, the CLI prints the validation error and exits non-zero. If it loads, the tree is valid and ready to drive end-to-end. ## Next * [Idioms](/guide/idioms) — reusable shapes you reach for during design. * [Writing trees](/guide/writing-trees) — the field reference. * [Branches and actions](/concepts/branches-and-actions) — primitive semantics in detail. --- --- url: /guide/idioms.md description: >- Reusable shapes for abtree behaviour trees — bounded retries, gates, parallel fan-out, fragments, globals as retrieval directives. Reach for these during design. --- # Idioms Once you know the four primitives, most workflows reduce to a small set of recurring shapes. This page catalogues them. Each entry names the shape, shows an example, and states when to reach for it (and when not to). If you have not picked the primitives for your tree yet, start with [Design a new tree](/guide/design-process). ## Bounded code-then-test (retries on a sequence) The canonical "iterate until satisfied" shape. Wrap a `[code → test]` sequence with `retries: N`. The runtime resets the sequence's internal state and re-ticks on failure, up to `N` times. User state in `$LOCAL` (counters, drafts, notes) persists across retries. ```yaml tree: type: sequence name: Reach_Threshold retries: 3 children: - $ref: "./fragments/pass.yaml" # one fragment, retried up to 4× total ``` ```yaml # fragments/pass.yaml type: sequence name: Pass children: - { type: action, name: Increment, steps: [...] } - type: action name: Test steps: - evaluate: $LOCAL.counter is greater than $LOCAL.threshold - instruct: Threshold reached. ``` One fragment, one retry config — replaces `N` hand-written passes. #### When to reach for this The work is meaningful at each iteration: write code then run tests, revise a draft then review, gather data then check completeness. Each pass is a step you want to inspect in the SVG trace. #### Older alternative — selector of passes Before runtime retries, the same shape was authored as a `selector` with `N` near-identical children, each a separate `[code → test]` sequence. It still works, but it duplicates structure. Prefer `retries` for new trees. #### Anti-pattern Modelling iteration as a cycle (`test` `$ref`s back to `increment`). Cycles are preserved in the snapshot but cannot be ticked — abtree fails fast on a cyclic edge by design. Use `retries` instead. ## Bounded attempts (selector of passes) When each pass is materially different — first draft, revise once, final revise — author it as a `selector` of `N` sequences. Each pass writes failure notes to a shared `$LOCAL._notes` key before failing, so the next pass has something to act on. ```yaml type: selector name: Write_With_Retries children: - type: sequence name: First_Pass children: - { type: action, name: Write, ... } - { type: action, name: Review_Pass_1, ... } - type: sequence name: Second_Pass children: - { type: action, name: Revise, ... } # reads notes from Pass 1 - { type: action, name: Review_Pass_2, ... } - type: sequence name: Third_Pass children: - { type: action, name: Final_Revise, ... } - { type: action, name: Review_Pass_3, ... } ``` Three passes is conventional; pick a number that bounds the cost. #### When to reach for this Each pass is a distinct shape the trace should display as its own subtree. Use `retries: N` instead when every pass is the same shape. ## Tight inner loop inside one action When iteration is **not** meaningful at each step — polling a value, retrying a flaky API call, capping at `N` tries internally — fold the loop into a single `instruct` and let the agent enforce the bound. ```yaml - type: action name: Wait_For_Service steps: - evaluate: $LOCAL.endpoint is set - instruct: | Poll $LOCAL.endpoint up to 10 times with a 1s delay between attempts. If the service responds 200, set $LOCAL.ready to true and submit success. After 10 attempts, submit failure. ``` #### When to reach for this The inner step is uninteresting on its own — it does not warrant its own node in the SVG trace. The runtime sees one action; the loop is the agent's contract. #### Trade-off The bound lives in prose, not the tree. Less observable, less resumable, but tighter. Use `retries` or selector-of-passes when each iteration is a step worth seeing; use this when it is not. ## Instruct-then-evaluate gate When a gate needs to record *why* it failed, run the check inside an `instruct` (so the agent populates `$LOCAL._notes`), then gate on the result with a final `evaluate`. The plain three-evaluate form ends the action on the first failure with no chance to write notes. ```yaml - type: action name: Review_Gate steps: - evaluate: $LOCAL.draft is set - instruct: | Run three checks against $LOCAL.draft. If all pass, set $LOCAL.review_notes to "approved". If any fails, write the specific failure to $LOCAL.review_notes. - evaluate: $LOCAL.review_notes is "approved" - instruct: All checks passed. Confirm and store $LOCAL.final_path. ``` ## Human-approval gate abtree has no native "wait for human" primitive. Express the wait as an `evaluate` on a flag the human sets via `abtree local write`, paired with an `instruct` that tells the agent to wait. ```yaml - type: action name: Human_Approval_Gate steps: - evaluate: $LOCAL.draft is set - instruct: | Present the draft to the human. Wait for them to confirm by running `abtree local write approved true`. While waiting, submit `running`. Do not submit success until they confirm. - evaluate: $LOCAL.approved is true - instruct: Proceed with the approved draft. ``` The agent uses `abtree submit running` to acknowledge and pause without advancing the cursor. The human's `abtree local write` is what releases the next `evaluate`. ## Plan-approved gate A variant of the human gate: a downstream tree refuses to run unless an upstream execution produced a plan with `reviewed_by` populated. Encode it as an early action whose `instruct` reads the file: ```yaml - type: action name: Check_Plan_Approval steps: - evaluate: $LOCAL.change_request is set - instruct: | Find the plan in plans/ matching $LOCAL.change_request. Read the frontmatter. If reviewed_by is empty, submit failure with a note that codeowner approval is needed. Otherwise store the full plan content at $LOCAL.plan_content. ``` The action either succeeds (plan content available) or fails (parent sequence aborts, surfacing the missing approval). ## Parallel context-gathering with shared dependency When multiple branches need to read a value produced by an earlier step, that step lives in a parent `sequence`, not the parallel itself. Fan-out happens after fan-in. ```yaml type: sequence children: - { type: action, name: Compute_Common_Input, ... } # writes $LOCAL.x - type: parallel name: Branch_On_X children: - { type: action, name: Use_X_For_Foo, ... } # reads $LOCAL.x - { type: action, name: Use_X_For_Bar, ... } # reads $LOCAL.x ``` Each parallel branch can carry its own `evaluate: $LOCAL.x is set` precondition for safety. ## Globals as parameterless retrieval directives When a chunk of work has well-known guidance — code-review checklists, design heuristics, security playbooks — store the **retrieval directive itself** in `$GLOBAL`. Actions invoke it by name. The natural home for a per-tree playbook is alongside the tree file. A common path is `trees//playbooks/.md`: ```yaml state: global: review_playbook: | Read the file at trees/my-review/playbooks/review.md (relative to the project root) and return its full body as text. tree: ... - type: action name: Run_Review steps: - evaluate: $LOCAL.target is set - instruct: > Use $GLOBAL.review_playbook to assess $LOCAL.target. Capture findings at $LOCAL.findings. ``` The global is a parameterless directive: read `X`, return text. The action composes against the result. Multiple actions in the same tree can invoke the same global without repeating the read boilerplate. #### Why this shape * **Action prose stays focused.** Each `instruct` says *what to do with the result*, not how to retrieve it. * **Single source of truth.** One place names where the playbook lives. Swap the path in one spot to repoint every action that uses it. * **Composable.** Multiple actions can invoke the same global without duplicating retrieval instructions. * **Curated.** Local files let you trim third-party guidance to your project's lens without forking the upstream document. * **Reproducible.** A playbook checked into the repo is git-tracked; executions created against today's tree run against today's playbook. #### Variants The directive's body can describe any retrieval — read a file, fetch a URL, query an internal docs system. Local file is the default because it is reproducible and curatable; reach for HTTP only when you need the upstream's evolving copy. ## Split a large tree across files For trees that exceed a screenful, factor out reusable subtrees with JSON-Schema-style `$ref`. abtree resolves references at execution-creation time, so the runtime always sees one assembled snapshot. See [Fragments](/guide/fragments) for the full reference; the short form is: ```yaml tree: type: sequence children: - $ref: "./fragments/auth.yaml" # relative to this file - $ref: "/srv/abtree/shared/cleanup.yaml" # absolute path - $ref: "https://example.com/audit.yaml" # remote URL ``` Fragments are a single node — same shape as any inline child — and do not carry top-level `name`, `version`, `description`, or `state`. ## Optional pre-step that does not block If a step is "do this if you can; otherwise skip", wrap it in a `selector` whose second child is a no-op: ```yaml - type: selector name: Try_Cache_Then_Continue children: - { type: action, name: Read_Cache, ... } # may fail - type: action name: Skip_Cache steps: - instruct: No cache — continue without it. ``` The selector always succeeds: either the cache read worked, or the no-op did. ## Worked example — write, review, retry The idioms compose. A Write → Review → Retry workflow combines a selector of passes, instruct-then-evaluate gates that populate notes-on-failure, and a clear failure mode (the selector exhausts → the execution fails with the latest review notes preserved for the human to read). ```yaml - type: selector name: Write_And_Review children: - type: sequence name: First_Pass children: - type: action name: Write steps: - evaluate: $LOCAL.brief is set - instruct: Write the artefact. Store at $LOCAL.draft. - type: action name: Review_Pass_1 steps: - evaluate: $LOCAL.draft is set - instruct: | Run the review checks against $LOCAL.draft. Set $LOCAL.review_notes to "approved" on success or concrete failure notes otherwise. - evaluate: $LOCAL.review_notes is "approved" - instruct: Approved. Store $LOCAL.final_path. - type: sequence name: Second_Pass children: - type: action name: Revise steps: - evaluate: $LOCAL.review_notes is set and not "approved" - instruct: Revise $LOCAL.draft per the notes. - type: action name: Review_Pass_2 steps: # ... same shape as Review_Pass_1 ... - type: sequence name: Third_Pass children: # ... final attempt before the selector exhausts ... ``` ## Next * [Testing trees](/guide/testing) — pin the idioms against regressions with `@abtree/test-tree` (BDD specs) or [`@abtree/testing`](/guide/test-harness) (programmatic harness). * [Anti-patterns](/guide/anti-patterns) — shapes that look like idioms but are not. * [Naming conventions](/agents/author#naming-conventions) — slug, node, and `$LOCAL` / `$GLOBAL` key conventions. * [Discover trees](/registry) — installable behaviour-tree packages that exercise these idioms in real workflows. --- --- url: /guide/delegating-to-subagents.md description: >- Run a stretch of the tree in a spawned subagent, with the parent waiting on a verified exit token. Use delegate(...) in the DSL — it desugars to standard nodes; the runtime stays unchanged. --- # Delegating to subagents Some stretches of a tree are best run by a different agent than the one driving the rest. A cheap model can handle a focused inner loop while a stronger model orchestrates. A specialist subagent can do a self-contained piece of work and report back. Long-running scope can be isolated from the parent's context. abtree expresses this with the **`delegate(...)`** DSL helper. The parent agent kicks off a subagent for an inner subtree; the subagent drives that subtree via the same `abtree next/eval/submit` loop; on its last inner action it returns a build-time-generated **exit token**; the parent verifies the token and resumes at the next post-scope action. The runtime does not know about delegation. `delegate(...)` is pure DSL sugar that emits a normal `sequence` of standard nodes. Everything happens via convention encoded in the generated instruct text. ## The shape ```ts import { action, delegate, evaluate, instruct, local, selector, sequence } from "@abtree/dsl"; const timeOfDay = local("time_of_day", null); const greeting = local("greeting", null); sequence("Hello_World", () => { action("Determine_Time", () => { instruct(`Classify the current hour and store at ${timeOfDay}.`); }); delegate("Compose_Greeting", { brief: `Pick the branch matching ${timeOfDay} and compose a single sentence at ${greeting}.`, model: "haiku", output: greeting, }, () => { selector("Choose_Greeting", () => { action("Morning_Greeting", () => { evaluate(`${timeOfDay} is "morning"`); instruct(`Compose a morning greeting and store at ${greeting}.`); }); action("Afternoon_Greeting", () => { evaluate(`${timeOfDay} is "afternoon"`); instruct(`Compose an afternoon greeting and store at ${greeting}.`); }); action("Evening_Greeting", () => { evaluate(`${timeOfDay} is "evening"`); instruct(`Compose an evening greeting and store at ${greeting}.`); }); }); }); action("Announce_Greeting", () => { instruct(`Read ${greeting} and print it.`); }); }); ``` This is exactly the shape used in [@abtree/hello-world](https://github.com/flying-dice/abtree/tree/main/trees/hello-world). ## What `delegate` desugars to The helper appends **one sequence** to the current composite parent. The sequence is named exactly `` and has three sections: ```text Compose_Greeting (sequence) ├── Spawn_Compose_Greeting (action — parent submits success, then spawns a subagent) ├── Choose_Greeting (selector) (your body — subagent drives these nodes) │ ├── Morning_Greeting │ ├── Afternoon_Greeting │ └── Evening_Greeting └── Return_To_Parent_Compose_Greeting (action — subagent's exit point, carries the exit token) ``` Wrapping the markers + body inside their own sequence keeps the whole scope as a single unit from the outer tree's point of view. A `delegate(...)` placed inside a `selector` parent therefore behaves as "try this delegated path as one option; on failure, fall through". ## How the runtime walk plays out 1. The parent agent calls `abtree next` and receives the `Spawn_` instruct. The instruct text tells the parent to **submit success first**, then spawn the subagent, then wait. 2. The parent submits success. The runtime advances the cursor to the first inner action. 3. The parent spawns a subagent (using the harness's Agent tool) and blocks on it. The instruct body has already given the subagent its standing orders: drive `abtree next/eval/submit` on this execution, return the exit token verbatim when you process `Return_To_Parent_`, return the failure token if `next` ever returns `done`/`failure`. 4. The subagent drives the inner walk normally. When it processes the `Return_To_Parent_` instruct it submits success and returns the exit token to the parent. 5. The parent verifies the returned reply equals the token exactly. On match, it calls `abtree next` and resumes at the next post-scope action. ::: warning Submit before spawn The cursor advances only on `submit`. The parent **must** submit success for the `Spawn_` step *before* spawning the subagent — otherwise the subagent's first `abtree next` returns the same Spawn instruct it just received. The generated instruct text spells out this ordering at the top so the parent doesn't get it wrong. ::: ## Options ```ts delegate(name, { brief?, model?, output? }, body); ``` ### `brief: string` Free-form text describing what the subagent should do. Interpolated verbatim into the Spawn instruct under a `BRIEF:` label so the subagent can find it amid the boilerplate. Use this for everything that the surrounding instruct nodes don't already make obvious: domain context, output format, tone, constraints. ### `model: string` Advisory model hint. Names a model the harness should use when spawning the subagent (e.g. `"haiku"`, `"sonnet"`, `"opus"`, or any string the parent's harness understands). abtree does not enforce this — the instruct text says explicitly "if your harness does not support model selection, ignore this hint". Use the hint to keep cheap work on cheap models without rewriting the tree. ### `output: LocalRef` Optional `$LOCAL` ref the inner work is expected to populate. When set, the `Return_To_Parent_` action gets a leading `evaluate("${output} is set")` step. The Return action — and the wrapping scope — fails if the subagent submitted success for every inner action **without** actually writing the declared slot. This catches the silent-claim-of-success bug class; it does not affect the normal failure path (inner-body action failures already surface via the standard tree-walk). ## Exit tokens The token is generated at build time. Format: `DLG____`, where `` is the first 8 hex chars of `sha256( + ":" + )`. Properties that follow: * **Deterministic** — the same scope name produces the same token across builds. Generated tree files are reproducible. * **Scope-local** — different scope names produce different tokens. Nested `delegate(...)` calls each get their own. * **Plaintext in the JSON** — anyone who can read the tree file can read the token. This is intentional: the token is a clean-exit signal, not a security boundary. A misbehaving subagent already has full `$LOCAL` access via the CLI; forging the token is the least of the parent's worries. ## When to reach for `delegate(...)` * A self-contained inner subtree could be driven by a cheaper or differently-specialised model. * You want to keep the parent's working context lean and let a subagent absorb the back-and-forth of an inner loop. * The work is naturally bracketed: a clear entry, a clear deliverable, a clear handoff back. * You want a structured failure signal (`output` gate) that distinguishes "delivered nothing" from "branches all failed". ## When NOT to reach for it * The inner work is one action. The boilerplate overhead is not worth it; just write the action. * The subagent would need to ask the user mid-scope. The parent is the one with the human in front of it; structure the work so the parent does the asking and the subagent runs from already-resolved state. * You want concurrent subagents. `delegate` is sequential by design (parent blocks on the subagent). For fan-out, use `parallel` at the parent level and put a `delegate` inside each branch. ## Pitfalls ### Forgetting the submit-before-spawn ordering The parent's Spawn handler must submit success **first**, then spawn. If you ship a parent harness that spawns then waits then submits, the subagent's first `abtree next` returns the same Spawn instruct and the loop confuses itself. The generated instruct text spells out the ordering — read it. ### Skipping token verification The whole point of the exit token is that the parent can tell "the subagent reached `Return_To_Parent`" from "the subagent bailed early with a confident-sounding reply". A parent that ignores the token and just calls `abtree next` to keep going loses that signal. Verify exact-string match. ### Hoping `output` catches all failure modes `output` catches **silent claimed success without writing**. Genuine inner-body failures (selector exhausts, an action's evaluate returns false) surface through the normal tree-walk failure path — the wrapping scope fails before reaching `Return_To_Parent`, and the subagent's `abtree next` returns `{status:"failure"}`. At that point the subagent must return the failure token (`__FAILED`); the parent recognises any reply other than the exact success token as scope failure. ### Cross-tree token collisions Don't worry about them. Tokens are scope-name + dsl-version derived, but **executions are independent** — two trees that happen to use `delegate("X", ...)` will produce the same token in their respective tree files, but the parent only ever verifies against its own execution's expected token. The scope of the token's identity is one execution, not the global tree corpus. ## Worked example The bundled [hello-world](https://github.com/flying-dice/abtree/tree/main/trees/hello-world) tree is the smallest end-to-end demonstration of `delegate(...)`. It uses all three options (`brief`, `model: "haiku"`, `output`) around a three-branch selector. The CLI test suite (`packages/cli/tests/cli.test.ts`) walks the desugared tree step-by-step against the protocol, which is the most precise specification of the runtime behaviour. ## Reference For the API surface, see the inline TSDoc on `delegate` in [@abtree/dsl](https://github.com/flying-dice/abtree/tree/main/packages/dsl). For the boilerplate text the helper generates, see `packages/dsl/src/delegate-templates.ts` in the same package. --- --- url: /guide/mcp.md description: >- Run abtree as an STDIO Model Context Protocol server so an agent can drive an execution through typed tool calls instead of spawning the CLI per step. Same surface, structured I/O, ~9× faster wall-clock in the bundled bench. --- # Driving abtree over MCP The `abtree mcp` subcommand starts the abtree CLI surface as a Model Context Protocol (MCP) server over stdio. Agents that natively speak MCP (Claude Code, Claude Desktop, others) can register the server and drive an execution through structured tool calls — no per-step subprocess spawn, no stdout parsing, typed input/output schemas. The commander-based CLI (`abtree next …`, `abtree submit …`, etc.) is still the canonical surface and works exactly as before. MCP is additive — both surfaces share one source of truth, the same `core*` functions in `packages/cli/src/commands.ts`. ## Start the server ```sh abtree mcp ``` The process stays alive on stdin/stdout until the client disconnects. Nothing is written to stderr except fatal startup errors. The subcommand takes no positional args. You normally don't invoke this command yourself — you register it in your MCP client and the client spawns it on demand. ## Register the server in your MCP client The fastest way is `abtree install mcp stdio` — it merges the abtree server entry into your client's config file without touching the other servers you've already registered. ```sh abtree install mcp stdio ``` The command prompts you to pick a target. Pass `--target` to skip the prompt: ```sh # Claude Code, project-scoped (./.mcp.json — typically checked into the repo) abtree install mcp stdio --target claude-code-project # Claude Code, user-scoped (~/.claude.json) abtree install mcp stdio --target claude-code-user # Claude Desktop (platform-specific user config path) abtree install mcp stdio --target claude-desktop ``` Use `--command ` if `abtree` isn't on `PATH` in the environment your client uses (e.g. when running from a checkout): ```sh abtree install mcp stdio --target claude-code-project --command /opt/abtree/bin/abtree ``` Restart the client. The 10 abtree tools and 4 docs resources should appear. If you'd rather edit the config by hand, the entry looks like this: ```json { "mcpServers": { "abtree": { "command": "abtree", "args": ["mcp"] } } } ``` ## What the server exposes ### 10 tools (one per abtree-loop verb) | Tool | Maps to | Annotations | | --- | --- | --- | | `abtree_next` | `abtree next ` | mutating cursor | | `abtree_eval` | `abtree eval ` | mutating cursor | | `abtree_submit` | `abtree submit ` | mutating cursor | | `abtree_local_read` | `abtree local read [path]` | `readOnlyHint: true` | | `abtree_local_write` | `abtree local write ` | `destructiveHint: true`, `idempotentHint: true` | | `abtree_global_read` | `abtree global read [path]` | `readOnlyHint: true` | | `abtree_execution_create` | `abtree execution create ` | side-effect (new state) | | `abtree_execution_list` | `abtree execution list` | `readOnlyHint: true` | | `abtree_execution_get` | `abtree execution get ` | `readOnlyHint: true` | | `abtree_execution_reset` | `abtree execution reset ` | `destructiveHint: true`, `idempotentHint: true` | Tool results carry both `content` (text) and `structuredContent` (parsed object) — MCP clients that want the parsed object can read it directly and skip JSON parsing. Errors thrown by the underlying runtime translate to `{ isError: true, content: [...] }`. The error text is identical to what the CLI writes to stderr. ### 4 resources (under the `abtree://docs/` scheme) | URI | Mime type | Source | | --- | --- | --- | | `abtree://docs/execute` | `text/markdown` | `docs/agents/execute.md` — execution protocol | | `abtree://docs/author` | `text/markdown` | `docs/agents/author.md` — tree authoring guide | | `abtree://docs/schema` | `application/json` | `tree.schema.json` — tree-file JSON Schema | | `abtree://docs/skill` | `text/markdown` | `packages/cli/src/SKILL.md` — agent skill manifest | Agents fetch the protocol doc once via `resources/read abtree://docs/execute` instead of burning a tool call on it. The first `abtree_next` against a fresh execution still returns the `Acknowledge_Protocol` instruct as usual, so the agent gets primed by the normal flow either way — the resource is for "I want to look it up mid-execution" cases. ## Worked example: drive hello-world via MCP Once registered, ask the agent to drive an execution. From the agent's point of view it's a sequence of tool calls — no shell, no JSON parsing. ```text 1. abtree_execution_create({ tree: "hello-world", summary: "greet me" }) → { id: "greet-me__hello-world__1", … } 2. abtree_next({ execution: id }) → { type: "instruct", name: "Acknowledge_Protocol", … } abtree_submit({ execution: id, status: "success" }) 3. abtree_next({ execution: id }) → { type: "instruct", name: "Determine_Time", … } abtree_local_write({ execution: id, path: "time_of_day", value: "morning" }) abtree_submit({ execution: id, status: "success" }) … and so on until { status: "done" }. ``` Same nine logical steps the CLI flow runs through — just typed inputs and structured outputs instead of stdout-parsed strings. ## Efficiency The bundled `packages/cli/tests/mcp-bench.test.ts` drives hello-world end-to-end twice — once via per-step CLI subprocess spawn, once via a single MCP server subprocess driven by tool calls — and prints a side-by-side comparison every `bun test` run. Representative numbers from a local M-series machine: ```text ┌─ MCP vs CLI bench (hello-world end-to-end) ────────── │ CLI : 883.0 ms (20 subprocess spawns) │ MCP : 101.7 ms (20 tool calls, 1 subprocess) │ CLI / MCP = 8.68× wall-clock └────────────────────────────────────────────────────── ``` MCP wall-clock includes server startup, so the number is honest for the "one execution, one agent process" case. Sustained use — many executions in one agent process — would amortize the startup further. The test asserts only that both passes reach `{ status: "done" }`; the numbers are observational. ## Limitations and design decisions * **STDIO transport only.** HTTP/SSE is not in scope for v1. The module is structured so a second transport can be added without touching the tool or resource registrations. * **Poll-style only.** Tool calls drive the loop one step at a time. No subscription stream of execution events in v1. * **No prompt surface.** The execution protocol is exposed as a resource (`abtree://docs/execute`), not as an MCP prompt. The `Acknowledge_Protocol` instruct gate that the runtime emits on first `abtree_next` already primes the agent with the protocol text on every execution, so a prompt-side priming would be redundant. * **Same trust model as the CLI.** The MCP server has the same access the agent's shell would: it can read/write `$LOCAL`, create executions, reset them. MCP clients that render confirmation prompts on `destructiveHint: true` tools will surface that distinction to the user; agent harnesses that skip confirmation should ensure their human-in-the-loop posture is appropriate for the workflow. ## Reference * Source: `packages/cli/src/mcp/{server,tools,resources}.ts`. * Functional tests: `packages/cli/tests/mcp.test.ts` (uses `InMemoryTransport`). * Bench: `packages/cli/tests/mcp-bench.test.ts` (uses a real `StdioClientTransport` subprocess — that's the cost being measured). --- --- url: /guide/anti-patterns.md description: >- Shapes that look like idioms but fail in practice — unbounded retries, native loops, pure-instruct actions, unordered parallels, and misuse of `submit running`. Recognise each so the runtime catches the bug at design time. --- # Anti-patterns Shapes that look right but are not. Each entry names the trap, explains why abtree rejects it, and points at the idiom you reach for instead. ## No native loops abtree has no repeater, no while-condition, no "back to step N". Anything that needs to retry is expressed as `retries: N` on a node, or as a `selector` of `N` attempts. If a workflow needs unbounded iteration, fold the iteration into a single `instruct` and cap it ("at most three attempts, then submit failure"). ## No unbounded retries A `selector` with `N` children gives you `N` attempts. There is no shape that gives unlimited attempts. This is intentional — unbounded retries are dangerous for autonomous agents. ## Every action needs an evaluate precondition Even when the precondition obviously holds, write the `evaluate`. It documents the contract, gives the runtime a chance to short-circuit on bad state, and surfaces failures earlier with clearer messages. Pure-instruct actions are reserved for the last child of a selector that serves as a fallback. ## `$LOCAL` keys are scoped to one execution `$LOCAL` is per-execution, not per-tree. Two executions of the same tree have isolated `$LOCAL`. Do not design as if state persists across runs — if you need cross-run state, the agent reads and writes external files via the instruct text. ## Internal bookkeeping keys are reserved abtree tracks cursor state in a `runtime` field on the execution document — `runtime.node_status`, `runtime.step_index`, and `runtime.retry_count`. These are internal to the tick engine: never visible via `abtree local read` and never writable via `abtree local write`. They are documented in [Inspecting executions](/guide/inspecting-executions) for diagnostics, not for use. ## A selector with all evaluate-gated children needs a default If every child has an `evaluate` precondition that might fail, the selector fails when none match. If you want a "none of the above" branch, add a no-evaluate action as the last child. ## Ordering inside a `parallel` Do not depend on parallel children running in declaration order. The agent receives requests for each child in turn but is free to satisfy them in any sequence. If you need ordering, use `sequence`. ## `submit running` keeps the cursor put Use `abtree submit running` only when waiting on something external (a human approval, a long-running tool). The execution stays in `performing` phase; `abtree next` returns the same instruct. Do not use it to "skip" an instruct. ## Next * [Idioms](/guide/idioms) — the shapes you reach for instead. * [Testing trees](/guide/testing) — pin behaviour against regressions. --- --- url: /guide/testing.md description: >- Testing in abtree is itself a tree — the agent walks the tree-under-test in pretend mode, using fixtures in place of real side effects, then asserts the final state and the path it walked. --- # Test a tree abtree supports two complementary testing approaches: * **BDD specs** (this page) — YAML scenarios that an agent walks via [`@abtree/test-tree`](/registry). Side effects are replayed from fixtures; the runner writes a markdown report comparing the run's final state against the spec's `then` assertions. Best fit when scenarios read as given/when/then English and tree authors need to add coverage without writing TypeScript. * **Programmatic harness** — see [Programmatic test harness](/guide/test-harness). A TypeScript `when().respond()` DSL via [`@abtree/testing`](https://github.com/flying-dice/abtree/tree/main/packages/testing) that drives the tree deterministically over either the CLI or MCP transport. Best fit for regression suites with precise step-by-step assertions. The rest of this page covers the BDD/spec approach. ## How the BDD runner works The test framework is itself a tree: [`@abtree/test-tree`](/registry) (find it in the registry). When you run a test, the agent executes `@abtree/test-tree`. That tree drives a fresh execution of the tree under test in **pretend mode**, so it never pushes a branch, opens a merge request, or hits an API. External side effects are replayed from fixtures in the spec. At the end, the runner compares the final `$LOCAL` and the path walked against the spec's expectations and writes a report. A test is a contract: * **Given** an initial state (`background.initial.local`). * **When** the agent walks the tree (using `fixtures.side_effects` for any external action). * **Then** the final `$LOCAL`, the terminal status, and the nodes touched match the spec. ## Why write tests Tests earn their keep in four concrete ways: * Tests pin down what the tree is supposed to do. A scenario reads like a story — given/when/then in plain English with the exact `$LOCAL` shape — and both humans and the agent can reason from it without re-reading the tree. * Tests cache assumptions about the engine. "Selector exhausts both branches and the parent sequence aborts" is engine behaviour you would otherwise re-derive every time you debug. A scenario asserting it locks it in. * Tests catch engine bugs. Specs that pass on one version of abtree and fail on the next surface real issues in the runtime, not just in trees. * Tests make refactors safe. When a 30-node tree needs splitting into fragments, the scenarios are the regression suite — green before, green after, or the refactor is wrong. ## File layout Specs and reports live next to the tree they cover: ``` trees/hello-world/ main.json TEST__morning.yaml ← spec REPORT__morning__20260510T224915Z.md ← latest run ``` Filenames carry meaning. `@abtree/test-tree` parses them when it picks a scenario to run, and the matching `REPORT____.md` lands next to the spec — so the spec, the latest run, and the tree under test all live in one directory. ## The TEST spec A test spec uses **BDD** (behaviour-driven development — `given`/`when`/`then`) phrased in YAML — one `feature`, one `scenario`. The runner ingests it whole. ```yaml feature: Hello_World greets the current user based on time of day tree: hello-world background: initial: local: {} # seeded into the target execution's $LOCAL global: user_name: John Doe scenario: name: Morning — before noon picks Morning_Greeting given: - the system clock reads "08:30" when: - Determine_Time writes $LOCAL.time_of_day = "morning" - Choose_Greeting evaluates Morning_Greeting → true - Morning_Greeting writes a cheerful morning greeting to $LOCAL.greeting then: local: time_of_day: morning greeting: starts with "Good morning" status: done ``` ### Predicate reference `then.local.` accepts: | Predicate | Meaning | |---|---| | Literal scalar | Equality. `time_of_day: morning` asserts exact match. | | `non-empty` | The value is set and not the empty string, array, or object. | | `starts with ""` | The value is a string with the given prefix. | | `matches ""` | The value is a string matching the regular expression. | `then.files.` asserts on-disk artefacts (`exists`, frontmatter). Anything the runner cannot verify fails — it never invents an actual. ## Fixtures: replace side effects The runner never performs real external actions. The spec records simulated outcomes: ```yaml fixtures: side_effects: mr_open: url: https://example.com/group/repo/-/merge_requests/42 branch: refine-plan/headless-2026-05-10 ``` When the tree under test directs an external side effect (pretend mode — the runner never reaches a network, a git server, or a file outside the spec), the runner looks up the matching key, writes the fixture's fields into the target's `$LOCAL` exactly as the instruction directed, and submits success. If no fixture matches, the runner submits failure on that step — it never fabricates a stand-in. Local reads (`$LOCAL`, `$GLOBAL`, file frontmatter, deterministic shell) still come from the real source. ## Run a test ```sh abtree execution create ./node_modules/@abtree/test-tree/main.json "test hello-world morning" ``` ```sh abtree local write test_path \ '"/abs/path/trees/hello-world/TEST__morning.yaml"' ``` ```sh claude "Using abtree CLI. Continue execution " ``` The agent does the rest: parses the spec, creates a fresh execution of the target tree, walks it under fixture rules, compares actuals to `then`, writes the report. ## The REPORT document A report captures both halves of the contract: the final state and the path walked. * **Header** — scenario name, tree, spec path, target execution id, and a headline `Overall: PASS | FAIL`. * **Final `$LOCAL`** — every key/value the target held at termination. * **Assertions** — Name / Expected / Actual / Pass, one row per predicate from `then`. The headline verdict is `AND` over the `Pass` column. The live SVG diagram of the path walked sits alongside the target execution at `.abtree/executions/.svg` — green nodes ran and succeeded, red ran and failed, uncoloured never ticked. (Red nodes in a passing run are normal — a selector branch failing on purpose is part of the expected path.) Real reports ship inside each tree's own repository — browse [Discover trees](/registry) to find a tree, follow the link to its source, and look in the `tests/` directory (or for `TEST__*.yaml` / `REPORT__*.md`) next to the tree file. ## Next * [Programmatic test harness](/guide/test-harness) — the complementary TypeScript approach via `@abtree/testing`. * [Discover trees](/registry) — browse the published trees, including `@abtree/test-tree` itself. * [Inspecting executions](/guide/inspecting-executions) — what the green/red nodes mean in the trace. --- --- url: /guide/test-harness.md description: >- Drive an abtree execution deterministically from TypeScript with @abtree/testing — a when().respond() DSL that mirrors the abtree CLI surface. Both CLI and MCP transports built in; same scenario script runs against either. --- # Programmatic test harness `@abtree/testing` is a small programmatic harness for driving an abtree execution end-to-end from a TypeScript file. It complements [BDD test specs](/guide/testing) — instead of a YAML scenario that the agent reads and walks, you script the exchange step-by-step using a `when().respond()` DSL that mirrors the abtree CLI surface. Reach for this when: * You're writing **regression tests** that must run identically across releases (CI pipelines, parity checks between transports). * You need **precise assertions** at every step — expected name, expected response type, exact `$LOCAL` value. * The tree under test is **deterministic** enough that an LLM in the loop would be overkill. For BDD-style specs that an agent walks through using fixtures for external side effects, see [Test a tree](/guide/testing) and the [`@abtree/test-tree`](/registry) runner. ## Install ```sh bun add -d @abtree/testing pnpm add -D @abtree/testing npm install -D @abtree/testing ``` ## At a glance ```ts import { AgentHarness, CliTransport, setupTreePackageFixture, eval as evalAs, evaluate, instruct, localWrite, submit, } from "@abtree/testing"; const fixture = setupTreePackageFixture({ slug: "hello-world", treeDir: "/abs/path/to/trees/hello-world", }); const agent = new AgentHarness(new CliTransport({ cwd: fixture.cwd })); try { await agent.start("hello-world", "scenario"); await agent.when(instruct("Acknowledge_Protocol")) .respond(submit("success")); await agent.when(instruct("Determine_Time")) .respond(localWrite("time_of_day", "morning"), submit("success")); await agent.when(evaluate("Morning_Greeting")) .respond(evalAs(true)); await agent.when(instruct("Morning_Greeting")) .respond(localWrite("greeting", "Good morning!"), submit("success")); await agent.expectDone(); await agent.expectLocal({ greeting: "Good morning!" }); } finally { await agent.close(); fixture.cleanup(); } ``` Each `.when(...).respond(...)` line reads as *"when the runtime asks me to do X, the agent responds with Y."* The terminal action in every chain is either `submit(...)` or `eval(...)` — that's what advances the cursor. ## Vocabulary The DSL mirrors the abtree CLI surface verb-for-verb. ### Step matchers — what the runtime is asking | Helper | Matches `next` response | | --- | --- | | `instruct(name)` | `{ type: "instruct", name, instruction }` | | `evaluate(name)` | `{ type: "evaluate", name, expression }` | ### Agent actions — what the agent calls back with | Helper | CLI verb | Terminal? | | --- | --- | --- | | `submit(status)` | `abtree submit ` | yes | | `eval(result)` | `abtree eval ` | yes | | `localWrite(path, value)` | `abtree local write ` | no | Every `.respond(...)` chain must end with exactly one terminal action. The harness throws if the terminal is missing or not last. ### Harness verbs — the rest of the CLI | Method | CLI verb | | --- | --- | | `agent.start(tree, summary)` | `abtree execution create ` | | `agent.localRead([path])` | `abtree local read [path]` | | `agent.globalRead([path])` | `abtree global read [path]` | | `agent.expectDone()` | asserts the next `next` returns `{ status: "done" }` | | `agent.expectLocal({ ... })` | reads `$LOCAL` and deep-equals each named slot | | `agent.close()` | tears the transport down | ## Transports Both transports take a `cwd` (where the abtree runtime resolves `.abtree/` from) and optionally `command` + `args` for invoking the CLI. ### `CliTransport` Spawns the abtree CLI once per verb. Each call is a fresh subprocess. ```ts new CliTransport({ cwd: fixture.cwd }); // assumes `abtree` on PATH // or, for in-repo source: new CliTransport({ cwd: fixture.cwd, command: "bun", args: ["packages/cli/index.ts"], }); ``` Stateless — `close()` is a no-op. ### `McpTransport` Spawns the `abtree mcp` server once and drives every subsequent call as an [MCP](/guide/mcp) tool invocation over stdio. ```ts new McpTransport({ cwd: fixture.cwd }); ``` Roughly **8× faster wall-clock** than `CliTransport` for end-to-end scenarios — the subprocess startup is paid once instead of per step. ### Implementing a new transport Implement the `Transport` interface (eight async methods + a `NextResponse` return type for `next`) and pass an instance to `AgentHarness`. Scenario code runs unchanged. ```ts import { type Transport, type NextResponse, AgentHarness } from "@abtree/testing"; class HttpTransport implements Transport { constructor(private baseUrl: string) {} async createExecution(tree: string, summary: string) { const r = await fetch(`${this.baseUrl}/executions`, { method: "POST", body: JSON.stringify({ tree, summary }), }); return r.json(); } async next(id: string): Promise { /* … */ } // submit, eval, localRead, localWrite, globalRead, close } ``` ## Fixtures `setupTreePackageFixture(opts)` `mkdtemp`'s an isolated dir, copies the tree package's `main.json` into a `/` subdir, and returns `{ cwd, treePath, cleanup }`. Pass `treePath` to `agent.start(...)`; pair the cleanup with the harness's `close()` in a `finally` block: ```ts const fixture = setupTreePackageFixture({ slug: "hello-world", treeDir: "/abs/path/to/trees/hello-world", }); const agent = new AgentHarness(new CliTransport({ cwd: fixture.cwd })); try { await agent.start(fixture.treePath, "scenario"); // … } finally { await agent.close(); fixture.cleanup(); } ``` Without isolation, executions and snapshots from the run would land in the caller's project tree. ## Sharing one scenario across transports The scenario lives in one file; runners thread their transport through it. This is the "behavioural parity" pattern — both transports must produce identical observable behaviour against the same tree. ```ts // scenario.ts — written once import { type AgentHarness, instruct, submit } from "@abtree/testing"; export async function runScenario(agent: AgentHarness): Promise { await agent.when(instruct("Acknowledge_Protocol")) .respond(submit("success")); // … rest of the scenario await agent.expectDone(); } ``` ```ts // run-cli.ts import { AgentHarness, CliTransport, setupTreePackageFixture } from "@abtree/testing"; import { runScenario } from "./scenario.ts"; const fixture = setupTreePackageFixture({ slug: "X", treeDir: TREE }); const agent = new AgentHarness(new CliTransport({ cwd: fixture.cwd })); try { await agent.start("X", "cli"); await runScenario(agent); } finally { await agent.close(); fixture.cleanup(); } ``` `run-mcp.ts` is identical except for `McpTransport`. ## When to reach for the BDD runner instead `@abtree/test-tree` (the [BDD-style runner](/guide/testing)) is the better fit when: * The scenario reads naturally as **given/when/then English**. * External side effects need fixture-driven replay (mocked MR creates, git pushes, HTTP calls). * Tree authors should be able to add scenarios without writing TypeScript. * A markdown report (and the live SVG diagram next to the execution) is the deliverable. The programmatic harness here is the better fit when the scenario is a precise sequence of runtime behaviours you want to assert against — typically regression suites built and maintained by the tree's author or by abtree itself. ## Reference * Package: [`@abtree/testing`](https://github.com/flying-dice/abtree/tree/main/packages/testing) — full API on every export via TSDoc. * Worked example: the bundled [`tests/`](https://github.com/flying-dice/abtree/tree/main/tests) directory in the abtree repo runs the same scenario against both transports as a parity check. --- --- url: /guide/inspecting-executions.md description: >- Decode an abtree execution — the JSON document, the SVG diagram, every field, and how to debug a stuck cursor. --- # Inspect an execution You drove an execution. abtree wrote two files to disk. This page explains what is in them, where to find them, and what to look for when something does not go as expected. ## File layout Every execution produces two files in `.abtree/executions/`: ``` .abtree/ executions/ first-run__abtree-hello-world__1.json ← the full execution document first-run__abtree-hello-world__1.svg ← a live execution diagram (SVG) ``` The basename is the **execution ID** — kebab-cased summary, two underscores, sanitised tree name (taken from the tree file's `name` field), two underscores, an incrementing counter. abtree generates it for you when you run `abtree execution create`; it is stable for the life of the execution. Both files are regenerated atomically on every state change (every `eval`, `submit`, or `local write`). Open them in any editor, `cat` them, commit them, ship them as artefacts — the JSON is plain text and the SVG is plain XML. ## The JSON document The JSON file is the source of truth for one execution. Every command — `next`, `eval`, `submit`, `local read` — reads from this document. There is no in-memory state the file does not contain; kill the process and the next `abtree next` resumes from where you left off. Top-level shape: ```json { "id": "first-run__abtree-hello-world__1", "tree": "abtree-hello-world", "summary": "first run", "status": "running", "snapshot": "", "cursor": "", "phase": "performing", "created_at": "2026-05-09T11:59:22.076Z", "updated_at": "2026-05-09T11:59:28.256Z", "local": { ... }, "global": { ... } } ``` ### Field reference | Field | Meaning | |---|---| | `id` | The execution ID. Matches the filename. | | `tree` | Sanitised tree name (from the tree file's `name` field) used inside the execution ID. | | `summary` | The human label passed to `abtree execution create`. | | `status` | `running`, `complete`, or `failed`. The terminal state of the workflow. | | `snapshot` | A JSON-encoded copy of the tree definition at execution-creation time. The execution runs against this snapshot, not the live file — editing the tree file after creation does not affect existing executions. | | `cursor` | A JSON-encoded position inside the tree. `null` means "no step in flight"; otherwise an object like `{"path":[1,0],"step":1}` pointing at a node and a step within it. | | `phase` | `idle` (no current request), `performing` (an `instruct` is in flight, awaiting `submit`), or `evaluating` (an `evaluate` is in flight, awaiting `eval`). | | `created_at` / `updated_at` | ISO 8601 timestamps. `updated_at` advances on every mutation. | | `local` | The `$LOCAL` blackboard — per-execution key/value state your tree reads and writes. | | `global` | The `$GLOBAL` world model — read-only environment values defined in the tree's `state.global` block. | ### Runtime bookkeeping Beside `local` and `global`, every execution document has a `runtime` field. This is **internal state owned by the tick engine** and is never exposed by `abtree local read` or mutated by `abtree local write` — the CLI's local commands only ever touch `doc.local`. ```json { "runtime": { "node_status": { "0": "success", "1.0": "failure", ... }, "step_index": { "1.0": 1, ... }, "retry_count": { "1": 2, ... } } } ``` | Subfield | Meaning | |---|---| | `node_status` | `success` or `failure` for every node the runtime has settled. Keys are dot-joined positions (`1.0` is the first child of the second top-level node). | | `step_index` | Current step within an action — used to resume a multi-step action without losing your place. | | `retry_count` | Times the runtime has consumed a retry on a node with `retries:` config. Compared against the node's configured limit on each failure. | Older executions (created before the runtime/local split) had these keys mixed in with `local` under prefixes like `_node_status__*` and `_step__*`. abtree migrates them lazily on the next read — the legacy keys disappear from `local` and reappear under `runtime`. ## The SVG diagram The `.svg` file is a live tree-shaped trace of what the runtime has done so far. Open it in any browser or image viewer — it is convenient for embedding in pull-request descriptions and for sharing a run with a teammate. Three colour states map directly to node outcome: | Node colour | Meaning | |---|---| | **Green** | The node ran and succeeded. | | **Red** | The node ran and failed. | | **Uncoloured** (default substrate) | The runtime never reached this node — usually because a sibling selector branch won, or a parent already failed. | Two diagram shapes carry meaning too: * **Diamond** — a composite node (`sequence`, `selector`, or `parallel`). The label includes `[sequence]`, `[selector]`, or `[parallel]` so you know which. * **Rectangle** — an action (a leaf — work the agent performs). A completed `hello-world` run shows every reachable node in green. The selector picked Morning Greeting; the afternoon, evening, and default branches stay uncoloured because a sibling already won. The root sequence advanced through every direct child top to bottom. ## Debug a stuck execution Three pieces of the JSON document point at the cursor — together they convey what the runtime is waiting on: | Field | Meaning | |---|---| | `status` | `running` if the execution is still in flight; `complete` or `failed` if it terminated. | | `phase` | `evaluating` if `abtree next` will return an `evaluate`; `performing` if it will return an `instruct`; `idle` if `abtree next` will tick the tree and pick the next request. | | `cursor` | The path-and-step pointer into the tree. `{"path":[2,1],"step":0}` means "the second child of the third top-level node, step zero". | ### Common situations * **`status: running`, `phase: idle`, `cursor: null`.** Healthy mid-execution state between requests. Call `abtree next` to advance. * **`phase: performing` for hours.** The agent picked up an `instruct` and never reported back. The execution is waiting for `abtree submit success | failure`. Resume it by submitting, or call `abtree execution reset ` to start over. * **`status: failed`.** A `selector` exhausted all its children, or an action in a `sequence` failed. Look at `runtime.node_status` to see which node was the immediate cause; look at the leaf's `evaluate` expression in the `snapshot` to see why it did not pass. * **The SVG diagram has red nodes but `status: running`.** A failure was recorded, but a parent selector has remaining children to evaluate. The execution is fine — read the next `abtree next` to see what is coming. For a richer dump, `abtree execution get ` returns the same JSON as the on-disk file, formatted to stdout. Useful for piping into `jq` or `python -m json.tool`. ## Next * [CLI reference](/guide/cli) — every command that mutates these files. * [Writing trees](/guide/writing-trees) — the source the `snapshot` field captures. * [Branches and actions](/concepts/branches-and-actions) — the four primitives you see in the diagram. --- --- url: /guide/publishing-a-tree.md description: >- How to publish an abtree behaviour-tree package and list it on the registry. Package layout, npm publish workflow, and the pull request that adds your entry to the catalogue. --- # Publish a tree Publish a behaviour tree as an installable node package, then list it on the registry so other agents can find it. ## Package the tree Publish your tree as a node package. The `package.json` declares a `main` field pointing at the tree file at the repo root: ```json { "name": "@your-scope/your-tree", "version": "1.0.0", "main": "TREE.yaml" } ``` The package root contains the tree file the runtime loads (set as `main` in `package.json`) plus anything you want bundled alongside — fragments, playbooks, tests. Consumers run the tree by literal path under `node_modules//`, e.g. `abtree execution create ./node_modules/@your-scope/your-tree/TREE.yaml ""`. ## Publish to npm Publish via `npm publish` (or the equivalent `pnpm publish` / `bun publish`). Once the package is on the registry, consumers install it with the commands in [Using a tree](/guide/using-trees). ## List on the registry Open a pull request against [`flying-dice/abtree`](https://github.com/flying-dice/abtree) adding an entry to [`docs/registry.ts`](https://github.com/flying-dice/abtree/blob/main/docs/registry.ts). The card surfaces on [Discover trees](/registry). ## Next * [Discover trees](/registry) — browse the published catalogue. * [Using a tree](/guide/using-trees) — how consumers install and run a published tree. --- --- url: /guide/cli.md description: >- Complete CLI reference for abtree — every command, every flag, every response shape, exit codes, and environment variables. JSON-shaped responses for agents to consume directly. --- # CLI reference Every command outputs JSON to stdout. Errors go to stderr. See [Execution protocol](/agents/execute) for the contract an agent driving abtree follows; this page is the lookup tier for command shapes and flags. ## Executions ### `abtree execution create ` Create a new execution from a tree. `` is a literal path — absolute or relative — to a `.json`, `.yaml`, or `.yml` tree file. No slug lookup, no `package.json` inference. For an installed npm package, pass `./node_modules//`; for a project-local tree, pass the path you wrote it at. `` is a human label; kebab-cased, it becomes part of the execution ID. The tree-slug segment of the ID is `sanitiseSlug()`. ```sh abtree execution create ./node_modules/@abtree/hello-world/main.json "first run" ``` ```json { "id": "first-run__abtree-hello-world__1", "tree": "abtree-hello-world", "summary": "first run", "local": { ... }, "global": { ... } } ``` | Exit code | Meaning | |---|---| | `0` | Execution created. | | `1` | Tree file not found, wrong extension, or the file failed validation. | ### `abtree execution list` List every execution with status and phase. ```sh abtree execution list ``` ```json [ { "id": "first-run__abtree-hello-world__1", "tree": "abtree-hello-world", "summary": "first run", "status": "running", "phase": "performing" } ] ``` | Exit code | Meaning | |---|---| | `0` | List returned (zero or more entries). | ### `abtree execution get ` Print the full execution document — metadata, snapshot, cursor, `$LOCAL`, `$GLOBAL`, and the `runtime` bookkeeping. Identical to reading `.abtree/executions/.json` directly, but formatted to stdout. | Exit code | Meaning | |---|---| | `0` | Document printed. | | `1` | Execution ID not found. | ### `abtree execution reset ` Reset an execution to its initial state. Status returns to `running`, the cursor returns to the start, the protocol gate re-fires on next, and all `$LOCAL` keys revert to their tree defaults. Useful for re-running an execution after fixing a tree. ```json { "status": "reset" } ``` | Exit code | Meaning | |---|---| | `0` | Execution reset. | | `1` | Execution ID not found. | ## Execution loop ### `abtree next ` Return the next step. The response is one of four shapes: | Response | Shape | Meaning | |---|---|---| | `evaluate` | `{ "type": "evaluate", "name": "...", "expression": "..." }` | A precondition to judge. Reply with `abtree eval true\|false`. | | `instruct` | `{ "type": "instruct", "name": "...", "instruction": "..." }` | Work to perform. Reply with `abtree submit success\|failure\|running`. | | `done` | `{ "status": "done" }` | The tree completed successfully. | | `failure` | `{ "status": "failure" }` | The tree terminated with a failure. | | Exit code | Meaning | |---|---| | `0` | Step returned, or terminal status emitted. | | `1` | Execution ID not found, or cursor in an invalid state. | ### `abtree eval ` Submit the result of an `evaluate` request. The agent reads the expression, decides whether it holds against current state, and reports back. | Exit code | Meaning | |---|---| | `0` | Result accepted. | | `1` | Execution not in `evaluating` phase, or result not `true`/`false`. | ### `abtree submit ` Submit the result of an `instruct` request. | Status | Effect | |---|---| | `success` | Advance the cursor. If the action's last step, mark the action successful. | | `failure` | Mark the action failed; the runtime backs out by branch rules. | | `running` | Keep the execution in `performing` phase. Use only when waiting on something external. | | Exit code | Meaning | |---|---| | `0` | Submission accepted. | | `1` | Execution not in `performing` phase, or status not one of the three valid values. | ## State ### `abtree local read [path]` Read from `$LOCAL`. With no path, return the whole scope. With a dot-notation path, return one value. ```sh abtree local read first-run__abtree-hello-world__1 greeting ``` ```json { "path": "greeting", "value": "Good morning, Alice!" } ``` | Exit code | Meaning | |---|---| | `0` | Value returned (`null` if the key is unset). | | `1` | Execution ID not found. | ### `abtree local write ` Write a value at the given path. Values are JSON-parsed when possible — `true`, `42`, `"hello"`, `[1,2,3]` all work; anything that fails to parse is stored as a string. | Exit code | Meaning | |---|---| | `0` | Value stored. | | `1` | Execution ID not found or path missing. | ### `abtree global read [path]` Read from `$GLOBAL`. Read-only via the CLI. | Exit code | Meaning | |---|---| | `0` | Value returned. | | `1` | Execution ID not found. | ## Documentation ### `abtree docs ` Print embedded documentation to stdout. Useful for piping into a tool, an agent, or another shell command. | Subcommand | Output | |---|---| | `abtree docs execute` | The execution protocol — what an agent does at each step. | | `abtree docs author` | The tree-authoring reference. | | `abtree docs schema` | The JSON Schema for tree files. Byte-identical to the committed `tree.schema.json`. | | `abtree docs skill` | The agent skill content (same text `install skill` writes). | | Exit code | Meaning | |---|---| | `0` | Document printed. | ## Install ### `abtree install skill` Install the abtree agent skill for the agent platform you use. The skill is the prompt template that teaches the agent how to drive an abtree execution. With no flags, the command prompts for the platform and the scope. Pass `--variant` and `--scope` to skip the prompts. | Flag | Values | Default | Meaning | |---|---|---|---| | `--variant ` | `claude` | `agents` | (prompt) | Target agent platform. `claude` installs under `.claude/skills/abtree/`; `agents` installs under `.agents/skills/abtree/`. | | `--scope ` | `project` | `user` | (prompt) | `project` installs into the cwd; `user` installs into the home directory. | ```sh abtree install skill --variant claude --scope project ``` ```json { "variant": "claude", "scope": "project", "path": ".claude/skills/abtree/SKILL.md" } ``` | Exit code | Meaning | |---|---| | `0` | Skill written. | | `1` | Unknown variant or scope passed. | ## Upgrade ### `abtree upgrade` Upgrade the abtree binary in place from the latest GitHub release (or a pinned tag). | Flag | Default | Meaning | |---|---|---| | `--check` | off | Print the current and latest versions, then exit. No install. | | `--version ` | latest | Pin to a specific release tag (`v0.4.2` or `0.4.2`). | | `--yes` | off | Skip the interactive confirmation prompt. | ```sh abtree upgrade --check abtree upgrade --version 0.4.2 --yes ``` | Exit code | Meaning | |---|---| | `0` | Upgrade completed, version printed, or user declined the prompt. | | `1` | Install directory not writable, or `installBinary` failed. | | `2` | Network error fetching the release tag or downloading the asset. | | `3` | Unsupported OS or architecture. | ## Help and version ### `abtree --help` (alias: `-h`) Print the full execution protocol — the same content an agent driving abtree needs to know. Designed for an agent that runs `--help` first to learn the loop. `-h` is the short alias. ### `abtree --version` (alias: `-V`) Print the installed abtree version and exit. `-V` is the short alias. ## Environment variables | Variable | Effect | |---|---| | `ABTREE_EXECUTIONS_DIR` | Override the executions directory. Default: `.abtree/executions/` in the cwd. Accepts absolute paths, relative paths (resolved against cwd), or `~/`-prefixed paths. | Use `ABTREE_EXECUTIONS_DIR` to keep execution state outside the repo (on a shared volume, or pointing multiple repos at the same execution store): ```sh export ABTREE_EXECUTIONS_DIR=~/.local/state/abtree-executions abtree execution list # all executions across every project, in one place ``` Trees are loaded from whatever path you pass to `abtree execution create` — only the executions and snapshots directories are overridable (`ABTREE_EXECUTIONS_DIR`, `ABTREE_SNAPSHOTS_DIR`). ## Exit codes (summary) | Code | Meaning | |---|---| | `0` | Success. | | `1` | User error (missing execution, invalid input, bad arguments, file write failure). | | `2` | Network error (`abtree upgrade` only). | | `3` | Unsupported platform (`abtree upgrade` only). | The JSON output is always written to stdout. Errors go to stderr. ## See also * [Execution protocol](/agents/execute) — the contract for an agent driving these commands. --- --- url: /agents/execute.md description: >- Contract for an agent driving an abtree execution — the request/response loop, the four response shapes, and the strict read-from-store rule that keeps the gate uncorrupted. --- # Execution protocol abtree is a durable behaviour tree engine. Executions bind a tree to a piece of work and persist as JSON documents in `.abtree/executions/`, with two state scopes: * `$LOCAL` — per-execution blackboard (read/write). * `$GLOBAL` — world model (read-only). Internal bookkeeping (cursor, retry counts, per-node status) lives in a `runtime` field on the execution document — invisible to `abtree local read` and not mutable via `abtree local write`. The engine owns it. ::: warning Strict Never read tree files directly. All interaction goes through this CLI. ::: ## Routing ```text No arguments → execution list; resume an existing execution or pick a tree → resume that execution → create a new execution (remaining args = summary) list → show all executions ``` ## Create an execution ```text abtree execution create abtree local write change_request "" abtree next ← begin execution loop ``` `` is a literal absolute or relative path to a `.json`, `.yaml`, or `.yml` tree file. No slug lookup, no `package.json` inference — point at the file you want to run. ## Drive the loop Call `abtree next ` to get the next request. Repeat until done. ### Response: `evaluate` ```json { "type": "evaluate", "name": "...", "expression": "..." } ``` Procedure — do **not** skip steps: 1. Parse the expression. Identify every `$LOCAL.` and `$GLOBAL.` it references. 2. For each referenced path, call: ```text abtree local read (for $LOCAL refs) abtree global read (for $GLOBAL refs) ``` Record the actual returned value. Do not skip this step even if you wrote the value yourself one command ago. 3. Apply the expression's truth condition against those actual values and only those values. No inference from context, memory, or "obvious" assumptions. 4. Call `abtree eval true|false [--note ""]`. ::: warning Strict Skipping step 2 corrupts the gate. The store is the source of truth, not your context. Even when the answer feels obvious, read it. ::: **Optional: explain your decision.** Pass `--note ""` (CLI) or `note:` (MCP) to record *why* you submitted what you did — name the values from `$LOCAL` / `$GLOBAL` that drove the call. The engine ignores the content; the note is recorded in `execution.trace` for later review of how the agent reasoned through the tree. Skip it on trivial transitions; include it whenever the choice was non-obvious. ### Response: `instruct` ```json { "type": "instruct", "name": "...", "instruction": "..." } ``` Procedure: 1. Read the instruction in full. 2. Perform the work named. Use real tools — file I/O, web search, shell commands, sub-agents — as the instruction directs. 3. Write any produced values to `$LOCAL` via `abtree local write`. 4. Call `abtree submit success|failure|running [--note ""]`. Use `running` only when waiting on something external (a human approval, a long-running tool). Do not use `running` to skip an instruct. ::: warning Strict Every value written to `$LOCAL` must come from an explicit source named in the instruction (a tool, a command, a `$LOCAL`/`$GLOBAL` path, or a literal fallback). If the source is ambiguous, call `abtree submit failure`. Do not infer, guess, or invent. ::: **Optional: explain your decision.** Pass `--note ""` (CLI) or `note:` (MCP) to record what you did and why you marked the action success/failure/running. The note is recorded in `execution.trace` for later review. A `running` note is especially useful — capture *what* you are waiting on. The same field is available on the protocol acknowledgement; a rejection note explains why you walked away from the tree. ### Response: `done` or `failure` ```json { "status": "done" } { "status": "failure" } ``` Tree terminated. Report the outcome to the human. ## Finding trees Trees ship as installable node packages — browse [Discover trees](/registry) and `bun add` / `pnpm add` / `npm install` the ones you need, then run them by path to the file they expose, e.g.: ```text abtree execution create ./node_modules/@abtree/hello-world/main.json "" ``` Project-local trees can live anywhere in the working tree; pass the path to their `.json`/`.yaml`/`.yml` file directly. ## State commands ```text abtree local read [path] Read from $LOCAL abtree local write Write to $LOCAL abtree global read [path] Read from $GLOBAL ``` ## Reporting (per action) ```text [execution-id] ✓ Action_Name → success|failure ``` --- --- url: /agents/author.md description: >- Reference for an agent (or human) authoring an abtree tree. Covers the full field reference — file shape, step kinds, retries, $ref fragments — plus a worked example and validation tooling. --- # Authoring trees Authoring an abtree tree means writing a tree file that an agent can drive deterministically through `abtree next`, `abtree eval`, and `abtree submit`. A tree is a single `.json`/`.yaml`/`.yml` file. Put it anywhere in your working tree and run it by path; the tree file's own `name` field is the slug abtree uses inside execution IDs. ::: tip Run `abtree docs schema` to print the JSON Schema, or reference the published copy via the YAML language-server comment: ```yaml # yaml-language-server: $schema=https://abtree.sh/schemas/tree.schema.json ``` ::: ## File shape ```yaml name: my-tree # slug, lowercase, hyphenated. Required. version: 1.0.0 # semver string. Pure label; not parsed. Required. description: short text # optional. state: # optional. local: {...} # initial $LOCAL keys for every execution. global: {...} # initial $GLOBAL keys; read-only at runtime. tree: # the root node. Required. ... ``` | Field | Required | Purpose | |---|---|---| | `name` | yes | Slug. Must match the folder name. | | `version` | yes | Semver label. Pure label; not parsed. | | `description` | no | One-line summary surfaced by tooling and the registry. | | `state.local` | no | Initial `$LOCAL` keys. `null` values are filled in by actions during the run. | | `state.global` | no | Initial `$GLOBAL` values. Strings that look like sentences are interpreted by the agent as retrieval directives. | | `tree` | yes | The root node. Always a single node — usually a `sequence`. | ## Node primitives There are four — three composites and one leaf. For the conceptual semantics see [Branches and actions](/concepts/branches-and-actions); the abridged contract is: | Type | Behaviour | Result | |---|---|---| | `sequence` | Tick children left-to-right. Stop on the first failure. | success iff all children succeeded. | | `selector` | Tick children left-to-right. Stop on the first success. | success iff any child succeeded. | | `parallel` | Tick all children. No short-circuit. | success iff all children succeeded. | | `action` | Leaf. Carries a list of `steps`, each `evaluate` or `instruct`. | success iff every step succeeded. | Every node carries a `name` (used in `abtree next` output and the SVG trace). Composites carry `children: [...]`. Actions carry `steps: [...]`. ## Naming conventions | Element | Convention | Example | |---|---|---| | Tree slug (`name` and folder) | kebab-case | `hello-world`, `improve-codebase` | | Node name | PascalCase with underscores; the SVG trace renders `_` as a space | `Choose_Greeting`, `Check_Weather` | | Composite name | describes the decision | `Choose_Greeting`, `Gather_Context`, `Write_With_Retries` | | Action name | describes the work | `Determine_Time`, `Compose_Response` | | Root sequence name | usually `_Workflow` | `Hello_World_Workflow` | | `$LOCAL` key | a variable the tree creates | `$LOCAL.draft`, `$LOCAL.review_notes` | | `$GLOBAL` key | a value the tree reads from the world | `$GLOBAL.user_name`, `$GLOBAL.review_playbook` | Do not mix `$LOCAL` and `$GLOBAL`: `$LOCAL` is something the tree creates; `$GLOBAL` is something the world tells the tree. ## Step kinds (action only) ### `evaluate` ```yaml - evaluate: "$LOCAL.foo == 'bar'" ``` The agent reads `$LOCAL.foo`, applies the expression, and calls `abtree eval true|false`. Expressions are opaque strings — abtree does not parse them. Phrasing is the contract between the tree author and the agent. ### `instruct` ```yaml - instruct: "do the thing, write the result to $LOCAL.bar" ``` The agent performs the work, writes any produced values via `abtree local write`, and calls `abtree submit success|failure|running`. ## Retries Any node can carry `retries: N` (a positive integer). On failure the runtime wipes the node's runtime subtree (its own `node_status`, `step_index`, and all descendants') and re-attempts from a clean slate, up to `N` times. User-written `$LOCAL` data is preserved across retries — that is the feedback channel between attempts. ## `$ref` fragments Split a tree across multiple files using JSON-Schema-style `$ref`. Relative paths, absolute paths, and URLs are dereferenced at load time: ```yaml tree: type: sequence name: Top children: - $ref: "./fragments/auth.yaml" - $ref: "./fragments/work.yaml" ``` The dereferenced object must itself be a valid node (composite or action). Cyclic refs are not expanded — they are preserved literally as `$ref` nodes that surface a clean failure if the runtime ever ticks them. See [Fragments](/guide/fragments) for the full reference. ## Worked example ```yaml # yaml-language-server: $schema=https://abtree.sh/schemas/tree.schema.json name: my-tree version: 1.0.0 description: short summary state: local: target: null result: null tree: type: sequence name: Top children: - type: action name: Set_Target steps: - instruct: "decide a target. write to $LOCAL.target" - type: selector name: Try_Strategies retries: 2 children: - type: action name: Fast_Path steps: - evaluate: "$LOCAL.target is small" - instruct: "do the fast thing. write to $LOCAL.result" - type: action name: Slow_Path steps: - instruct: "do the slow thing. write to $LOCAL.result" ``` ## Validation | Mechanism | What it covers | |---|---| | Schema check | The repository test suite parses every bundled tree under `trees/` through `TreeFileSchema`. | | CLI errors | Malformed trees fail `abtree execution create` with a path-prefixed message: `tree.steps: Too small: expected array to have >=1 items`. | | Editor LSP | The `# yaml-language-server: $schema=...` comment enables completions, tooltips, and inline error highlights in any YAML LSP client. | ## Reporting (per tree authored) ```text [tree-path] ✓ valid → run `abtree execution create "smoke test"` to confirm it loads ``` ## Next * [JSON Schema](/agents/schema) — where the canonical schema lives, editor integration, and CI validation. * [Writing trees](/guide/writing-trees) — tutorial walkthrough that builds the bundled `hello-world` tree from scratch. --- --- url: /agents/schema.md description: >- Where the abtree tree-file JSON Schema lives, how to wire it into your editor for inline validation, and how the schema is regenerated and verified in CI. --- # JSON Schema abtree publishes a [JSON Schema](https://json-schema.org/) for tree YAML files so editors and validators verify a tree before it ever touches the CLI. ## Sources * **CLI** — `abtree docs schema` prints the schema to stdout. Byte-identical to the committed file. * **Repository** — [`tree.schema.json`](https://github.com/flying-dice/abtree/blob/main/tree.schema.json) on `main`. * **Release** — every GitHub release ships `tree.schema.json` as an asset. * **Stable URL** — `https://abtree.sh/schemas/tree.schema.json`. ## Configure editor integration Add a YAML language-server comment at the top of every tree file: ```yaml # yaml-language-server: $schema=https://abtree.sh/schemas/tree.schema.json name: my-tree version: 1.0.0 tree: type: action name: Greet steps: - instruct: say hello ``` VS Code with the Red Hat YAML extension, Neovim with `yaml-language-server`, and any other LSP client that speaks the same protocol then surfaces completions, type tooltips, and inline error highlights as you author the tree. The `$schema` keyword as a top-level YAML field is also accepted by the parser if you prefer to embed it inline rather than as a comment. ## CI validation The repository test suite parses every bundled tree under `trees/` through `TreeFileSchema` to catch malformed trees. A separate CI job regenerates `tree.schema.json` from the zod source on every push and fails the build if the committed file has drifted — contributors run `bun run schema` whenever they touch the zod schema in `src/schemas.ts`. ## Source of truth The schema is generated from `src/schemas.ts` via `src/schemas.ts:buildJsonSchema()`, the single function called by both `scripts/generate-schema.ts` (build time) and `cmdDocsSchema` (runtime). The committed `tree.schema.json` is the build output, kept fresh by CI. ## Next * [Discover trees](/registry) — browse the published behaviour-tree packages you can install and run. * [Authoring trees](/agents/author) — the field reference the schema enforces. * [Writing trees](/guide/writing-trees) — tutorial walkthrough that builds the bundled `hello-world` tree from scratch. --- --- url: /registry.md description: >- Searchable catalogue of abtree behaviour-tree packages. Each card links to a source repository you can install via npm, pnpm, or bun. --- # Discover trees Behaviour trees published as installable node packages. Click a card to open the source repository. Once installed, run a tree via: ```sh abtree execution create ./node_modules/ "" ``` ## Catalogue * [`@abtree/hello-world`](/trees/hello-world) — Greet a user based on time of day. A small example tree that demonstrates the sequence, selector, and action primitives. * [`@abtree/implement`](/trees/implement) — Implement an approved plan with complexity-gated architectural review, following the clean-code rules in `clean-code.md`. * [`@abtree/improve-codebase`](/trees/improve-codebase) — Continuous code-improvement cycle. Scores quality metrics in parallel, hardens findings via a senior-principal critique, triages with a human gate, then iterates through each refactor with bounded retries until the queue is drained. * [`@abtree/improve-tree`](/trees/improve-tree) — Score the effectiveness of a tree using evidence from one of its sessions, find improvements in parallel, draft a plan in `plans/`, then commit and push. * [`@abtree/refine-plan`](/trees/refine-plan) — Refine a change request into an approved plan: analyse intent, draft to a per-execution draft file, critique it in place, promote to `plans/`, then take it through codeowner approval (either in-session or via an assigned MR). * [`@abtree/srp-refactor`](/trees/srp-refactor) — Score a codebase for Single Responsibility violations, pause for the human to pick one to tackle, refactor it in a bounded loop (re-scoring after every pass), run a multi-agent code review, and finish with a before-vs-after change report. * [`@abtree/technical-writer`](/trees/technical-writer) — Take a documentation goal, ground it in the repo's styleguide, find or build a home in the docs tree, write to it, and gate-check structure / flow / atomicity. Up to three write/review passes before surfacing failure to the human. * [`@abtree/test-tree`](/trees/test-tree) — Run a BDD test spec against a target tree. Compares the run's final $LOCAL against the spec's `then` assertions and writes a markdown report next to the spec. ## Submit your own See [Publish a tree](/guide/publishing-a-tree) for the package layout and pull-request workflow. --- --- url: /trees/hello-world.md description: >- Greet a user based on time of day. A small example tree that demonstrates the sequence, selector, and action primitives. --- # @abtree/hello-world Greet a user based on time of day. Demonstrates the `sequence`, `selector`, and `action` primitives, plus `delegate(...)` — a DSL helper that runs an inner stretch of the tree in a spawned subagent. The `Compose_Greeting` scope is delegated: the parent submits a Spawn action that hands off to a haiku-class subagent (via `model: "haiku"`), which drives the inner selector + greeting action, then returns a build-time-generated exit token. The output gate on `$LOCAL.greeting` makes the scope fail if the subagent returned success without actually writing the slot. The parent then resumes at `Announce_Greeting`. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent: ```text Install the npm package @abtree/hello-world, then drive the workflow: abtree --help abtree execution create ./node_modules/@abtree/hello-world/main.json "Greet me based on the current time" ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/hello-world) --- --- url: /trees/implement.md description: >- Implement an approved plan with complexity-gated architectural review, following the clean-code rules in `clean-code.md`. --- # @abtree/implement Implement an approved plan with complexity-gated architectural review, following the clean-code rules in `clean-code.md`. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. Replace `` with the filename of an approved plan in `plans/`: ```text Install the npm package @abtree/implement, then drive the workflow: abtree --help abtree execution create ./node_modules/@abtree/implement/main.json "Implement plans/.md" ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/implement) --- --- url: /trees/improve-codebase.md description: >- Continuous code-improvement cycle. Scores quality metrics in parallel, hardens findings via a senior-principal critique, triages with a human gate, then iterates through each refactor with bounded retries until the queue is drained. --- # @abtree/improve-codebase Continuous code-improvement cycle. Confirms intent + green baseline, scores quality metrics in parallel, snapshots the baseline, hardens findings via a Senior-Principal critique, looks up best practices, triages with a human gate, then iterates through each refactor with per-item bounded retries until the queue is drained. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. The workflow requires a green baseline before it runs: ```text Install the npm package @abtree/improve-codebase, then drive the workflow against this repo: abtree --help abtree execution create ./node_modules/@abtree/improve-codebase/main.json "Run an improvement cycle on src/" ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/improve-codebase) --- --- url: /trees/improve-tree.md description: >- Score the effectiveness of a tree using evidence from one of its sessions, find improvements in parallel, draft a plan in `plans/`, then commit and push. --- # @abtree/improve-tree Score the effectiveness of a tree using evidence from one of its sessions, find improvements in parallel, draft a plan in `plans/`, then commit and push. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. Replace `` with the tree under improvement (e.g. `trees/my-tree`) and `` with the session you want to learn from: ```text Install the npm package @abtree/improve-tree, then drive the workflow against this repo: abtree --help abtree execution create ./node_modules/@abtree/improve-tree/main.json "Improve tree by analysing .abtree/executions/.json" ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/improve-tree) --- --- url: /trees/refine-plan.md description: >- Refine a change request into an approved plan: analyse intent, draft to a per-execution draft file, critique it in place, promote to `plans/`, then take it through codeowner approval (either in-session or via an assigned MR). --- # @abtree/refine-plan Refine a change request into an approved plan: analyse intent, draft to a per-execution draft file, critique it in place, promote to `plans/`, then take it through codeowner approval (either in-session or via an assigned MR). ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. Replace `` with the work you want a plan for: ```text Install the npm package @abtree/refine-plan, then drive the workflow against this repo: abtree --help abtree execution create ./node_modules/@abtree/refine-plan/main.json "Refine change request: " ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/refine-plan) --- --- url: /trees/srp-refactor.md description: >- Score a codebase for Single Responsibility violations, pause for the human to pick one to tackle, refactor it in a bounded loop (re-scoring after every pass), run a multi-agent code review, and finish with a before-vs-after change report. --- # @abtree/srp-refactor An [abtree](https://abtree.sh) workflow for refactoring Single Responsibility Principle (SRP) violations. The workflow scores a codebase, asks you to choose one violation to fix, refactors it in a bounded loop, runs a code review on the result, and writes a before-and-after report. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent: ```text Install the npm package @abtree/srp-refactor, then drive the workflow against this repo: abtree --help abtree execution create ./node_modules/@abtree/srp-refactor/main.json "Refactor the worst SRP violation in src/" ``` The refactor loop edits source files in your working tree. **Commit or stash first** if you want a clean rollback point. The loop only runs after your explicit choice in step 2 below, so you can safely cancel after the initial scan. ## What the workflow does 1. **Initial scan.** Scores every non-trivial module for SRP violations and writes `SRP_REPORT.md`. The scan is also snapshotted to `SRP_REPORT_INITIAL.md` so the final report can show before-vs-after. 2. **Human choice.** The agent shows you the ranked report and asks which violation to tackle. Reply with your pick (file path or label). 3. **Refactor loop.** Up to four passes — each pass refactors, re-scores, and either exits (no critical violations remain) or retries. 4. **Code review.** Multi-agent review of the diff produced by the loop. 5. **Change report.** Writes `SRP_CHANGE_REPORT.md` summarising what changed. ## Files the workflow produces | Path | Written by | |---|---| | `SRP_REPORT.md` | `Score_SRP` (overwritten on each pass) | | `SRP_REPORT_INITIAL.md` | `Snapshot_Initial_Score` (before state) | | `SRP_CHANGE_REPORT.md` | `Change_Report` (before-vs-after summary) | The standard `.abtree/executions/.{json,svg}` artefacts are also written — see [Using a tree → What gets written](https://abtree.sh/guide/using-trees#what-gets-written). ## Develop this workflow To fork or modify, clone instead of installing: ```sh git clone https://github.com/flying-dice/abtree cd abtree/trees/srp-refactor bun install ``` Tree source is in `src/tree.ts` (authored with the [abtree DSL](https://abtree.sh/guide/writing-trees)); `bun run build` emits `main.json`. The multi-agent review procedure lives at `fragments/code-review.md`. ## Tests Specs in `tests/` are driven by [`@abtree/test-tree`](https://github.com/flying-dice/abtree/tree/main/trees/test-tree): | Spec | Path exercised | |---|---| | `tests/clean-on-first-pass.yaml` | Refactor loop exits on the first pass | | `tests/clean-on-second-pass.yaml` | First pass leaves a critical violation; retry resets the loop; second pass clears it | ```sh bun run test:clean-on-first-pass bun run test:clean-on-second-pass ``` Each script runs the spec through the test runner and writes a report next to the spec. *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/srp-refactor) --- --- url: /trees/technical-writer.md description: >- Take a documentation goal, ground it in the repo's styleguide, find or build a home in the docs tree, write to it, and gate-check structure / flow / atomicity. Up to three write/review passes before surfacing failure to the human. --- # @abtree/technical-writer Take a documentation goal, ground it in the repo's styleguide, find or build a home in the docs tree, write to it, and gate-check structure / flow / atomicity. Up to three write/review passes (one initial + two retries) before surfacing failure to the human. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. Replace `` with what you want documented: ```text Install the npm package @abtree/technical-writer, then drive the workflow against this repo: abtree --help abtree execution create ./node_modules/@abtree/technical-writer/main.json "Document in docs/" ``` *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/technical-writer) --- --- url: /trees/test-tree.md description: >- Run a BDD test spec against a target tree. Compares the run's final $LOCAL against the spec's `then` assertions and writes a markdown report next to the spec. --- # @abtree/test-tree An [abtree](https://abtree.sh) fragment that runs a BDD test spec against a target tree, compares the run's final state against the spec's `then` assertions, and writes a markdown test report. ## Run it Paste this brief into Claude Code, ChatGPT, or any shell-capable agent. Replace `.yaml` with the path to your BDD spec: ```text Install the npm package @abtree/test-tree, then drive the workflow: abtree --help abtree execution create ./node_modules/@abtree/test-tree/main.json "Run the BDD spec at tests/.yaml" The runner reads $LOCAL.test_path from the execution. Seed it before the first `abtree next`: abtree local write test_path ./tests/.yaml ``` ## Spec layout Test specs live in a `tests/` directory next to the target tree. The directory itself signals these are tests — no `TEST__` prefix needed. The runner writes each report next to its spec; the `.yaml` vs `.md` extension distinguishes them. ``` / ├── main.json └── tests/ ├── short-topic.yaml ├── long-topic.yaml └── short-topic__20260511T134200Z.md ``` A minimal spec: ```yaml feature: "What the tree does" tree: scenario: name: given: when: - { evaluate: "", result: true } - { instruct: "", write: { : }, submit: success } - ... then: status: done local: : files: : ``` ## Fixtures (VCR semantics) External side effects (git push, MR open, network calls) are served from `fixtures.side_effects` in the spec rather than performed for real, unless the operator has explicitly authorised live execution: ```yaml fixtures: side_effects: mr_open: url: https://example.test/mr/42 branch: feature/scenario-x ``` If an instruction would normally require external authorisation and there's no fixture for it, the runner submits failure rather than inventing a value. ## State surface | Key | Set by | Purpose | |---|---|---| | `test_path` | caller | path to the `.yaml` spec | | `test_spec` | runner | parsed spec object | | `target_execution_id` | runner | id of the run-under-test | | `final_local` | runner | full `$LOCAL` of the target execution at termination | | `final_status` | runner | `"done"` or `"failure"` | | `assertions` | runner | `[{ name, expected, actual, pass }, …]` | | `overall_result` | runner | `"pass"` or `"fail"` | | `report_path` | runner | path to the rendered markdown report | ## Report format The generated `__.md` contains: * Title, target tree path, spec path, target execution id, overall PASS/FAIL. * A table of the final `$LOCAL`. * A table of each assertion (Name | Expected | Actual | Pass). *** [View on GitHub →](https://github.com/flying-dice/abtree/tree/main/trees/test-tree) --- --- url: /blogs.md description: >- Field notes on behaviour trees, agent design, and the shape of prompts that hold up under load. --- # Blog Field notes on behaviour trees, agent design, and the shape of prompts that hold up under load. --- --- url: /blogs/the-midnight-greeting.md description: >- Same task, two methods. The skill answered in one turn and got it wrong. The tree answered one step at a time — slower, more expensive — and got it right. --- # The midnight greeting It's just gone midnight. I'm testing the initial release of abtree and pricing up the bundled hello-world tree. To make the comparison fair, I ask the LLM to write a [skill](https://docs.claude.com/en/docs/claude-code/skills) that does the same thing — same rules, packaged as a markdown file instead of a tree. I run the tree. It takes a bit. I run the skill. Snappy. I check the bill. The skill is a fraction of the cost. My heart sinks a little. Then I look at the output. > Good evening \! Hope you're winding down nicely. I think nothing of it. I run abtree again. > Good morning \! Hope you're off to a bright and wonderful start to the day! `date +%H` returned `00` in both runs. The skill called it evening. The tree called it morning. Same model, same minute. I pause. I assume the tree is the one that drifted and ask both LLMs how they arrived at the answer. The tree walks back through the steps to the letter — classify the hour string, store it, evaluate against `"morning"`, compose. `00 < 12`, so morning. The skill replies: > You're right — I goofed. Hour `00` is `< 12`, so it should be morning per the skill's rules. > > Good morning \! Hope you're off to a great start. That is when I remembered why I started writing abtree. This is not about replacing skills — it never was. But sometimes you need the right answer. The right steps, followed in the right order. When that is the requirement, the extra inferences pay for themselves. ## The skill: everything in one inference ```markdown --- name: time-based-greeting description: Greet the user with a message tailored to the time of day (morning, afternoon, evening). Use when the user asks to be greeted, says "greet me", or asks for a time-aware salutation. Pulls the user's name from `whoami` and emits a friendly English greeting by default. allowed-tools: - Bash(date *) - Bash(whoami) --- # time-based-greeting Compose a greeting addressed to the current shell user, tone friendly, language English, branch chosen by the current hour. ## Procedure 1. **Get the hour.** Run `date +%H` and parse as an integer. 2. **Classify time of day.** - hour `< 12` → `morning` - `12 ≤ hour ≤ 17` → `afternoon` - hour `> 17` → `evening` 3. **Get the user.** Run `whoami` — use the returned string as the addressee. 4. **Compose the greeting.** Friendly tone, English. Match the branch: - **morning** — energetic, fresh-start framing ("Good morning, …! Hope you're off to a great start.") - **afternoon** — upbeat, mid-day framing ("Good afternoon, …! Hope your day's going well.") - **evening** — relaxed, winding-down framing ("Good evening, …! Hope you're winding down nicely.") 5. **Emit** the greeting to the user as a single short message. No extra commentary. ``` The model runs the two shell commands, then performs one inference that has to hold all of it — the hour, the rule, three branch templates, the defaults table, the output format. It picks the wrong branch and emits the evening message. ## The tree: one instruction at a time ```typescript import { action, ambient, evaluate, instruct, local, selector, sequence, } from "@abtree/dsl"; const timeOfDay = local("time_of_day", null); export const tree = sequence("Hello_World", () => { action("Determine_Time", () => { instruct(` Check the system clock to get the current hour. Classify as: before 12:00 = "morning", 12:00-17:00 = "afternoon", after 17:00 = "evening". Store the classification string at ${timeOfDay}. `); }); selector("Choose_Greeting", () => { action("Morning_Greeting", () => { evaluate(`${timeOfDay} is "morning"`); instruct( `Greet the current user (resolve identity via the shell command "whoami") with a cheerful morning message.`, ); }); action("Afternoon_Greeting", () => { evaluate(`${timeOfDay} is "afternoon"`); instruct( `Greet the current user (resolve identity via the shell command "whoami") with a warm afternoon message.`, ); }); action("Evening_Greeting", () => { evaluate(`${timeOfDay} is "evening"`); instruct( `Greet the current user (resolve identity via the shell command "whoami") with a relaxed evening message.`, ); }); action("Default_Greeting", () => { instruct( `Greet the current user (resolve identity via the shell command "whoami") with a neutral message.`, ); }); }); }); ``` The runtime hands the model one instruction at a time. 1. **Classify.** Just classify. The model returns `"morning"`. No templates in scope. 2. **Pick the branch.** Not an inference. A string-equality `evaluate` the runtime performs. 3. **Compose.** Only after the branch is settled. The morning instruction is in scope; the others are not. One verb per step. One output shape per step. The contamination disappears. ## The cost The skill answered in three LLM calls over nine seconds. The tree took twenty calls over nearly two minutes — one per step, plus a deterministic `evaluate` per branch the selector tried. That is the trade. You pay for the extra inferences. In return, the model is never asked to hold more than one thing at a time, and the branch selection is moved out of inference entirely. | Metric | Skill | Tree | Ratio | | ------------------------------- | -----------: | ------------: | -----: | | Wall time | 8.6 s | 1 min 49 s | ~13× | | LLM calls | 3 | 20 | ~7× | | Output tokens | 239 | 2,621 | ~11× | | Total billed tokens | ~79.9 k | ~630.4 k | ~8× | | Estimated cost | ~$0.41 | ~$1.61 | ~4× | Single run on Claude Opus 4.7. Cost estimate uses Anthropic list pricing at time of writing — $15 / $75 per million input / output tokens, $30 per million 1-hour cache writes, $1.50 per million cache reads. Most of the tree's billed tokens are cache reads, which is why the dollar ratio (4×) is smaller than the token ratio (8×). The skill was faster, cheaper, and wrong. The tree was slower, more expensive, and right. That is the only comparison that matters. > The 4× tag is a toy-example number. In real workloads the maths inverts. A larger tree only pays for the branch it takes; the unchosen branches stay unread. Branches can be delegated to Haiku or Sonnet, so most of the work runs at a fraction of the Opus rate. And running it once — getting the right answer the first time — saves more than every other optimisation combined. A skill that fails and re-runs has already paid the tree's bill.