Behavior Tree JSON
This page is the format reference for the JSON that defines an agent’s behavior tree. It covers where the file lives, the top-level structure, the fields every node shares, and the fields specific to each node type. For the concepts behind these trees, see Behavior Trees.
You usually do not hand-write this JSON. The chat surface and LLM-powered tools generate and edit behavior trees for you, then you refine them. See Authoring Behavior Trees for the recommended workflow.
Where it lives
Each agent type has one behavior tree file:
.agentloop/agents/<type>/<type>.bt.jsonFor example, a custom reviewer agent reads .agentloop/agents/reviewer/reviewer.bt.json. The file is plain JSON, so you can version it with the rest of your project. Editing it changes how that agent thinks the next time it runs.
Top-level structure
The file is a single JSON object. The name and tree fields are required; everything else is optional.
{
"name": "engineer",
"description": "Implements a task, validates, and commits",
"version": "2.0.0",
"mode": "reactive",
"tree": {
"type": "root",
"child": { "type": "sequence", "children": [] }
},
"subtrees": {
"runValidation": {
"type": "root",
"child": { "type": "action", "call": "RunTests" }
}
},
"blackboardDefaults": {
"maxRetries": 3
}
}| Field | Type | Description |
|---|---|---|
name | string | Identifier for the tree. Required. |
description | string | Human-readable summary. |
version | string | Semantic version string. |
mode | "reactive" | "proactive" | Execution mode. reactive waits for signals (messages, tasks); proactive polls. |
tree | object | Root node. Must be type: "root" with a single child. Required. |
subtrees | object | Named subtrees, each a root node, invoked from a branch node. |
blackboardDefaults | object | Initial values for the blackboard, the shared state all nodes can read and write. |
Common node fields
Every node is an object with a type. These fields are available on any node:
| Field | Applies to | Description |
|---|---|---|
type | all | Node type. Required. |
name | all | Display name, used in debugging and visualization. |
comment | all | Documentation string. |
child | decorators | A single child node (retry, repeat, flip, wait, forEach). |
children | composites | An array of child nodes (sequence, selector, parallel, race, lotto, utility-selector). |
while | all | Guard { call, args, succeedOnAbort } re-checked each tick; aborts the node if it fails. |
until | all | Exit condition { call, args }; succeeds the node when it becomes true. |
entry / exit / step | all | Lifecycle callback names invoked on enter, leave, and each tick. |
Leaf nodes (action, condition, plugin-action, logic-policy, logic-introspect) have no children. Decorators take one child. Composites take a children array. Nodes nest to arbitrary depth.
Leaf nodes
action / condition
{ "type": "action", "call": "RunTests", "args": [] }| Field | Description |
|---|---|
call | Name of the action or condition function to invoke. |
args | Optional arguments array passed to the function. |
An action performs work and reports success or failure. A condition checks state and reports true or false.
plugin-action
{ "type": "plugin-action", "call": "doThing", "args": [] }Like action, but call resolves to a function supplied by a loaded plugin rather than a built-in. Use it to invoke custom tools your project registers.
Composite nodes
sequence / selector / parallel / race
{
"type": "sequence",
"children": [
{ "type": "condition", "call": "CheckPrecondition" },
{ "type": "action", "call": "Execute" }
]
}| Type | Behavior |
|---|---|
sequence | Runs children in order; all must succeed. |
selector | Tries children in order; succeeds on the first that succeeds. |
parallel | Runs all children concurrently. |
race | Runs children concurrently; terminates when the first succeeds. |
lotto
{ "type": "lotto", "weights": [0.3, 0.5, 0.2], "children": [] }| Field | Description |
|---|---|
weights | Numeric weights for random selection; length must equal children.length. |
utility-selector
Picks a child by score. Scores can be static inline weights or come from a learned model in the model store.
| Field | Description |
|---|---|
mode | "max" (greedy), "distribution" (weighted sample), or "threshold-then-random". |
weights | Static inline weights. Mutually exclusive with weightsRef. |
weightsRef | Reference to a learned model, e.g. "my-policy@v1". |
defaultScores | Author-provided preview scores for the UI; length equals children.length. |
featuresFrom | Blackboard keys (dot-notation) feeding the scorer. |
inertiaBonus | Bonus for the currently-running child, to prevent flipping. |
threshold | Cutoff for "threshold-then-random" mode (0–1; default 0.5). |
fallbackChild | Child index used when scoring fails. |
exploreEpsilon | Exploration probability during training (0–1; typically 0 in production). |
Decorator nodes
| Type | Fields | Behavior |
|---|---|---|
retry | attempts, child | Retries the child on failure up to attempts times. |
repeat | iterations, child | Repeats the child iterations times (omit for infinite). |
flip | child | Inverts the child’s success and failure. |
wait | duration, child | Waits duration ms before running the child. |
forEach | collection, itemKey, indexKey, continueOnFailure, child | Iterates an array from the blackboard. |
branch | ref | Invokes a named subtree from the top-level subtrees map. |
For forEach, collection is a dot-notation blackboard path to an array, itemKey and indexKey are where the current item and 0-based index are stored, and continueOnFailure decides whether a failed iteration aborts the loop.
LLM-powered nodes
These nodes call a model at tick time. The neurosymbolic loop relies on them; see The Neurosymbolic Loop.
llm-condition
A boolean decision from a model.
| Field | Description |
|---|---|
prompt | Handlebars template with {{contextKey}} placeholders. |
contextKeys | Blackboard keys included in the prompt. |
outputKey | Blackboard key where the boolean result is stored. |
temperature | Model temperature (0–2). |
confidenceThreshold | Minimum confidence to trust the decision (0–1; default 0.7). |
fallbackValue | Boolean used when confidence is too low. |
model | Model name ("haiku", "sonnet", "opus", or custom). |
maxRetries | Retries on API failure. |
timeout | Milliseconds before timeout. |
llm-action
Structured generation, optionally with an agentic tool loop.
| Field | Description |
|---|---|
prompt | Handlebars template. |
contextKeys | Blackboard keys to inject. |
outputSchema | JSON Schema validating the structured output. |
outputKey | Blackboard key for the result. |
temperature | Model temperature. |
cacheScope | "process" caches results for the daemon lifetime, keyed on context + prompt. |
allowedTools | Tool names the model may invoke, enabling multi-turn loops. |
maxTurns / minTurns | Upper and lower bounds on agentic turns. |
subagent | Load tool, MCP, and system-prompt restrictions from a subagent template, e.g. "engineer". |
llm-selector
The model chooses a named branch.
| Field | Description |
|---|---|
branches | Map of branch name to { description, child }. |
defaultBranch | Branch taken when confidence is too low. |
confidenceThreshold | Minimum confidence to trust the selection. |
contextKeys | Blackboard keys included in the prompt. |
llm-sequence
The model decides which optional steps run.
| Field | Description |
|---|---|
steps | Array of { name, description, child, required? }. |
minSteps | Minimum number of steps that must execute. |
Logic nodes
Deterministic symbolic reasoning over a rule program. See Logic Programming for the rule syntax and a full walkthrough.
logic-policy
Evaluates rules over a set of facts and writes the matching solutions to the blackboard.
| Field | Description |
|---|---|
program | Required. Inline rule program — the relations and rules to evaluate. |
query | Required. Name of the relation to read. Supports {{key}} interpolation and optional column filters, e.g. "may_auto_merge({{changeId}})". |
outputKey | Required. Blackboard key where the result object is written. |
facts | Inline facts (array of predicate(arg, arg) strings). |
factsKey | Blackboard key holding extracted facts; supports dotted paths. Merged with facts. |
ruleSelection | Rule-set names to enable, each asserted as rule_enabled("Name"). |
ruleSelectionKey | Blackboard key holding the rule selection. |
semiring | How confidence is combined: { "kind": "top-k-proofs", "k": 3 } (default) or { "kind": "min-max-prob" }. |
minProbability | Drop solutions below this probability floor. Default 0.0. |
succeedOnSolutions | If true (default), the node succeeds only when the query has at least one solution. |
logic-introspect
Projects a rule program’s schema (its predicates, argument types, and vocabulary) onto the blackboard, so an upstream llm-action can be told exactly which facts it may extract.
| Field | Description |
|---|---|
program | Required. The rule program to introspect. |
outputKey | Required. Blackboard key where the projected schema is written. |
Examples
A simple sequence:
{
"name": "basic-sequence",
"description": "Three actions in order",
"version": "1.0.0",
"tree": {
"type": "root",
"child": {
"type": "sequence",
"children": [
{ "type": "action", "call": "Prepare" },
{ "type": "action", "call": "Execute" },
{ "type": "action", "call": "Cleanup" }
]
}
}
}A retry loop around an llm-action that generates code and validates it, retrying up to three times:
{
"name": "llm-with-retry",
"description": "Generate code; retry up to 3 times on validation failure",
"version": "1.0.0",
"tree": {
"type": "root",
"child": {
"type": "retry",
"attempts": 3,
"child": {
"type": "sequence",
"children": [
{
"type": "llm-action",
"name": "GenerateCode",
"prompt": "Write a function to {{requirement}}",
"contextKeys": ["requirement"],
"outputSchema": {
"type": "object",
"properties": { "code": { "type": "string" } },
"required": ["code"]
},
"outputKey": "generatedCode"
},
{ "type": "action", "call": "ValidateCode" }
]
}
}
}
}A utility-selector that chooses an implementation strategy from features on the blackboard:
{
"name": "strategy-picker",
"description": "Choose implementation strategy based on task complexity",
"version": "1.0.0",
"tree": {
"type": "root",
"child": {
"type": "utility-selector",
"name": "ChooseApproach",
"mode": "max",
"weights": [0.7, 0.2, 0.1],
"featuresFrom": ["taskAnalysis.complexity", "taskAnalysis.riskLevel"],
"defaultScores": [0.8, 0.5, 0.2],
"fallbackChild": 1,
"children": [
{ "type": "action", "call": "DirectImplementation" },
{
"type": "sequence",
"name": "PlanFirst",
"children": [
{ "type": "action", "call": "CreatePlan" },
{ "type": "action", "call": "ImplementByPlan" }
]
},
{ "type": "action", "call": "Escalate" }
]
}
}
}A few fields must stay in sync or the tree will fail to load: defaultScores, weights, and lotto weights must each have the same length as their children array, and a branch node’s ref must name an entry in the top-level subtrees map. The authoring tools keep these aligned for you.