Skip to Content
Behavior Trees

Behavior Trees

Behavior trees (BTs) are the execution engine behind every agent in AgentLoop. They pair deterministic control flow with intelligent, model-driven decision nodes — neural, symbolic, and learned — so agent behavior stays reproducible and auditable while still adapting to the work in front of it.

This page explains the core concepts: ticking, the blackboard, status, and the node types you can use. When you are ready to write one, head to Authoring Behavior Trees; for the exact file format, see Behavior Tree JSON.

What a behavior tree does

A behavior tree is a hierarchical structure that defines how an agent carries out work. The agent runs a simple control loop: it ticks the tree repeatedly, evaluating one node at a time. Each tick advances the tree by a single step, and the node it lands on returns a status. The tree keeps ticking until it reaches a terminal result.

Agents run their trees in one of two modes:

  • Reactive — task-handling agents (such as the engineer or QA tester) wait at a blocking action until work arrives, process it to completion, then loop back to wait.
  • Proactive — the orchestrator runs on a timer, continuously scanning for work and assigning tasks without waiting for an external signal.

The blackboard

The blackboard is a key-value store shared by every node in a tree. Nodes read from it to make decisions and write to it to record results. It holds three broad kinds of state:

  • Input context — the task description, codebase analysis, comments, and other inputs.
  • Intermediate results — LLM outputs, analysis results, and feature flags produced mid-run.
  • Control state — retry counts, validation flags, and status changes.

For example, one node might write an LLM’s code changes to implementation, a later condition might read validationFailed to decide whether to retry, and a utility node records its scoring decisions to _utilitySelectorDecisions.

Status

Every node returns one of three statuses on each tick:

StatusMeaning
SUCCEEDEDThe node finished successfully; the parent moves on to its next child.
FAILEDThe node failed; the parent reacts according to its own type.
RUNNINGThe node is still working; the tree stays on it for the next tick.

How a parent reacts to a child’s status depends on the parent’s type — this is what gives a tree its shape:

  • Sequence runs children left to right, failing immediately on the first failure and succeeding only when all children succeed.
  • Selector runs children left to right, succeeding immediately on the first success and failing only when all children fail.
  • Retry repeats a child up to N times on failure, succeeding if any attempt succeeds.
  • Parallel runs children concurrently, with the final result determined by its variant.

Node types

Behavior tree nodes fall into four groups: control nodes that shape flow, leaf nodes that do work, LLM nodes that add reasoning, and utility/logic nodes for scoring and symbolic reasoning.

Control nodes

TypeWhat it does
rootEntry point. Holds a single child; every tree has exactly one.
sequenceRun children left to right; fail on the first failure, succeed when all succeed.
selectorRun children left to right; succeed on the first success, fail when all fail.
parallel / race / allRun children concurrently; result depends on the variant’s semantics.
retryRepeat a child up to N times if it fails.
repeatRepeat a child N times unconditionally.
flip / inverterInvert the child’s result (success becomes failure and vice versa).
succeedAlways return success.
failAlways return failure.
waitPause for a fixed duration.
branchReference and splice in a named subtree.
lottoPick a random child, optionally weighted.
forEachIterate a child over a collection from the blackboard, updating the current item and index keys each pass.

Leaf nodes

TypeWhat it does
actionCall a registered function (for example, commit changes or run validation). Returns success or failure based on the handler.
conditionEvaluate a boolean predicate synchronously. Returns success if true, failure if false.

LLM nodes

These nodes add reasoning. They are transformed into standard nodes when the tree is loaded, so they tick and propagate status like any other node.

TypeWhat it does
llm-actionCall the LLM to generate structured output or take an action.
llm-conditionAsk the LLM a yes/no question and branch on the answer.
llm-selectorLet the LLM route to one of several named branches.
llm-sequenceLet the LLM filter or reorder a set of steps.

Utility and logic nodes

TypeWhat it does
utility-selectorPick a child by scoring children from blackboard state, using static or learned weights instead of fixed priority.
logicRun a deterministic query against the rules/policy engine — a rule schema and facts; succeeds when the query has at least one solution.

LLM nodes are rewritten into plain action, condition, selector, or sequence nodes at load time. This keeps the running tree fully deterministic in shape — only the LLM’s answers introduce variation, and every call is recorded for replay.

Utility behavior trees

A utility-selector replaces a static-priority selector with scoring. Rather than always trying children in a fixed order, it scores each child from features in the blackboard and picks based on those scores.

It works in a few steps:

  1. Extract features from the blackboard using dot-notation paths, such as task.complexity or agent.recentFailures.
  2. Score each child, either from inline static weights (a deterministic argmax) or from a referenced model artifact applied to the features.
  3. Select a child by mode: max takes the highest score, distribution samples from the scores, and threshold-then-random picks randomly among children within a threshold of the top score.
  4. Apply an inertia bonus to the currently-running child to reduce needless flipping between options.
  5. Record the decision to the blackboard for debugging and training.

Because the utility-selector remains a native composite node rather than an action wrapper, the BT visualizer still sees the correct tree topology and snapshots capture which child was selected.

{ "type": "utility-selector", "name": "ChooseStrategy", "mode": "max", "featuresFrom": ["task.complexity", "agent.recentFailures"], "weightsRef": "strategy-model@v1", "fallbackChild": 2, "children": [ { "type": "sequence", "name": "DeepRefactor", "children": [] }, { "type": "sequence", "name": "QuickPatch", "children": [] }, { "type": "action", "name": "Escalate", "call": "EscalateToHuman" } ] }

Static weights give you a fully deterministic argmax, while a model reference and the distribution or threshold-then-random modes introduce sampling. Choose static weights when you need a node’s choice to be reproducible across runs.

Putting it together

A typical branch combines control, leaf, and LLM nodes — for example, retrying an LLM implementation step and committing only once it succeeds:

{ "type": "sequence", "children": [ { "type": "action", "call": "LoadContext" }, { "type": "retry", "attempts": 3, "child": { "type": "llm-action", "name": "ImplementTask", "prompt": "Implement the following task: {{taskDescription}}", "contextKeys": ["taskDescription"], "outputKey": "implementation" } }, { "type": "action", "call": "CommitChanges" } ] }

To understand where behavior trees sit in AgentLoop’s broader design — how deterministic control flow, LLM reasoning, and symbolic logic combine — read The Neurosymbolic Loop.

Last updated on
AgentLoop — Multi-agent loops you can see and control