bt

package module
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 6 Imported by: 0

README

bt-go

Go Reference

A small, composable behavior tree library for Go.

Install

go get github.com/ratlabs-io/bt-go@v1.6.1
import "github.com/ratlabs-io/bt-go"
// package name is bt → bt.NewEnv, bt.NewSequence, …

Core model

Concept Role
Behavior Any node: Tick(env) → Success | Failure | Running
Env Per-tick environment (not a context.Context)
Blackboard Hierarchical KV store for agent/world state
context.Context Cancel/deadline only — env.Context()
Halt Abort cleanup when a parent abandons Running work
Env vs context.Context
Concern API
Cancel / deadline env.Context()
Agent state env.Blackboard() or Set/Get/GetAs[T] / Key[T]

Do not put health/targets in context.WithValue. Use the blackboard.

env := bt.NewEnv(parentCtx)
env.Set("health", 100)
h, ok := bt.GetAs[int](env, "health")

const Health bt.Key[int] = "health"
bt.SetKey(env, Health, 100)
Reactive vs memory
Constructor Style
NewSequence / NewSelector Reactive — restart at child 0 each tick
NewMemorySequence / NewMemorySelector Resume last Running child

Selector is priority order (earlier = higher). Preempted Running children are Halted.

Parallel
Constructor Execution
NewParallel Sequential ticks (default, deterministic)
NewConcurrentParallel One goroutine per child

Policies: RequireOne, RequireAll, SuccessOnOne, SuccessOnAll.
When a policy returns Success/Failure while some children are still Running, those residual children are Halted.

Halt and abort hooks
branch := bt.NewAbortHook(longRunningSubtree, func(env bt.Env) {
    // stop pathing, clear target, …
})
// When a higher-priority Selector branch wins, Halt runs OnAbort.
// Same for Sequence earlier-sibling failure, BinarySelector/Switch flips,
// Conditional condition fail, Parallel residual Running, runner cancel.

TreeRunner.Run Halts the tree when env.Context() is cancelled. In-flight Tick bodies are not interrupted — long actions should watch env.Context().Done().

Observation
API Scope
NewObserving One node
Instrument / InstrumentRecorder Whole tree (rebuilds wrappers; original unchanged; visualizer collapses Observing)

Quick start

tree := bt.NewSequence(
    bt.NewNamed("Greet", bt.NewAction(func(env bt.Env) bt.RunStatus {
        fmt.Println(bt.MustGet[string](env, "greeting"))
        return bt.Success
    })),
)
env := bt.NewEnv(context.Background())
env.Set("greeting", "Hello")
tree.Tick(env)

Examples:

  • examples/hello — minimal Sequence
  • examples/agent — flee / memory combat / patrol
  • Godoc Example* tests in the package (Sequence, Memory, AbortHook, Keys, Instrument, …)

Node catalog

Leaves: NewAction, NewCondition

Composites: NewSequence, NewMemorySequence, NewSelector, NewMemorySelector, NewParallel, NewConcurrentParallel, NewBinarySelector, NewSwitch, NewConditional

Decorators: NewInverter, NewRepeater, NewUntilSuccess, NewUntilFailure, NewNamed, NewObserving, NewAbortHook

Runner: NewTreeRunner + WithTickRate / WithCallbacks

Typed data: GetAs[T], MustGet[T], Key[T], SetKey / GetKey / MustGetKey

Debug: NewTreeVisualizer, NewStatusRecorder, NewObserving, Instrument, InstrumentRecorder

Development

go test ./...
go test ./... -race
go run ./examples/hello
go run ./examples/agent

See CHANGELOG.md for release history.

License

MIT — see LICENSE.

Documentation

Overview

Package bt is a composable behavior tree library for Go.

Import: "github.com/ratlabs-io/bt-go" (package name bt).

A behavior tree is a hierarchy of Behavior nodes. Each tick, a node returns one of Success, Failure, or Running. Leaf nodes (Action, Condition) do work; composites (Sequence, Selector, Parallel, …) combine children; decorators (Inverter, Repeater, Named, Observing, AbortHook, …) wrap a single child.

Environment (Env)

Nodes receive an Env on every Tick. Env is not a context.Context:

  • env.Context() — stdlib context for cancellation/deadlines only
  • blackboard via Set/Get/GetAs — mutable agent/world state

Put agent data on the blackboard, never in context.WithValue.

Abort (Halt)

When a parent abandons a Running child (preemption, branch switch, cancel), Halt is called on Haltable nodes so they can clean up (see AbortHook). Control-flow nodes that can abandon work (Sequence, Selector, memory composites, Parallel, BinarySelector, Switch, Conditional, decorators) implement Haltable and forward Halt to the abandoned child.

Observation

NewObserving wraps a single node. Instrument / InstrumentRecorder wrap an entire tree so every tick reports to a callback or StatusRecorder.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetAs

func GetAs[T any](env Env, key string) (T, bool)

GetAs reads key from env's blackboard and type-asserts to T. ok is false if the key is missing or the value is not of type T.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	env.Set("health", 42)
	h, ok := bt.GetAs[int](env, "health")
	fmt.Println(h, ok)
}
Output:
42 true

func GetKey added in v1.6.0

func GetKey[T any](env Env, key Key[T]) (T, bool)

GetKey reads key from env's blackboard and type-asserts to T. ok is false if the key is missing or the value is not of type T.

func Halt

func Halt(env Env, node Behavior)

Halt calls Halt on node if it implements Haltable; otherwise it is a no-op.

func HaltAll

func HaltAll(env Env, nodes ...Behavior)

HaltAll calls Halt on each node.

func InstrumentRecorder added in v1.6.0

func InstrumentRecorder(root Behavior, rec *StatusRecorder) (Behavior, *StatusRecorder)

InstrumentRecorder is like Instrument but records each node status into rec. If rec is nil, a new StatusRecorder is created. The recorder and instrumented root are both returned so callers can Visualize after ticking.

func MustGet

func MustGet[T any](env Env, key string) T

MustGet is like GetAs but panics if the key is missing or has the wrong type. Prefer GetAs in production paths; MustGet is for tests and trusted setup.

func MustGetKey added in v1.6.0

func MustGetKey[T any](env Env, key Key[T]) T

MustGetKey is like GetKey but panics if the key is missing or has the wrong type.

func SetKey added in v1.6.0

func SetKey[T any](env Env, key Key[T], value T)

SetKey stores value under key on env's blackboard.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	const Health bt.Key[int] = "health"
	env := bt.NewEnv(context.Background())
	bt.SetKey(env, Health, 100)
	fmt.Println(bt.MustGetKey(env, Health))
}
Output:
100

Types

type AbortHook

type AbortHook struct {
	BaseDecorator
	OnAbort func(env Env)
	// contains filtered or unexported fields
}

AbortHook runs a callback when the node is Halted while still considered active (last tick returned Running). Use it for cleanup when a reactive parent preempts a long-running branch (stop pathing, clear target, etc.).

func NewAbortHook

func NewAbortHook(child Behavior, onAbort func(env Env)) *AbortHook

NewAbortHook wraps child with an abort callback.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	low := bt.NewAbortHook(
		bt.NewAction(func(env bt.Env) bt.RunStatus { return bt.Running }),
		func(env bt.Env) { fmt.Println("aborted") },
	)
	highReady := false
	sel := bt.NewSelector(
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			if highReady {
				return bt.Success
			}
			return bt.Failure
		}),
		low,
	)
	fmt.Println(sel.Tick(env))
	highReady = true
	fmt.Println(sel.Tick(env))
}
Output:
Running
aborted
Success

func (*AbortHook) Halt

func (a *AbortHook) Halt(env Env)

Halt invokes OnAbort if the child was Running, then Halt the child.

func (*AbortHook) Tick

func (a *AbortHook) Tick(env Env) RunStatus

Tick runs the child and tracks whether it is mid-run.

type Action

type Action struct {
	// contains filtered or unexported fields
}

Action is a leaf node that runs a user-supplied function.

func NewAction

func NewAction(runFunc func(env Env) RunStatus) *Action

NewAction creates an Action from runFunc. runFunc should return Success, Failure, or Running as appropriate. If runFunc is nil, Tick returns Failure.

func (*Action) Tick

func (a *Action) Tick(env Env) RunStatus

Tick executes the action function.

type BaseDecorator

type BaseDecorator struct {
	Child Behavior
}

BaseDecorator holds the child pointer shared by concrete decorators.

func (*BaseDecorator) GetChild

func (d *BaseDecorator) GetChild() Behavior

GetChild returns the decorated child.

func (*BaseDecorator) HaltChild

func (d *BaseDecorator) HaltChild(env Env)

HaltChild aborts the child if it is Haltable. Embedded types may call this from Halt.

func (*BaseDecorator) SetChild

func (d *BaseDecorator) SetChild(child Behavior)

SetChild sets the decorated child.

type Behavior

type Behavior interface {
	// Tick executes one step of the node and returns its status.
	Tick(env Env) RunStatus
}

Behavior is implemented by every node in a behavior tree.

func Instrument added in v1.6.0

func Instrument(root Behavior, after TickObserver) Behavior

Instrument returns a new tree that reports every node tick to after.

The original tree is not modified. Structure is preserved: composites, decorators, BinarySelector, Switch, and Conditional are rebuilt with instrumented children, then each rebuilt node is wrapped in Observing.

after may be nil (structure is still cloned/wrapped). Pointer identity of nodes differs from root — use the returned tree for Tick and visualization.

This is the deep-observation alternative to wrapping individual leaves with NewObserving. Prefer per-node Observing when only a few nodes matter.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	root := bt.NewSequence(
		bt.NewNamed("A", bt.NewAction(func(env bt.Env) bt.RunStatus { return bt.Success })),
	)
	var ticks int
	tree := bt.Instrument(root, func(node bt.Behavior, status bt.RunStatus) {
		ticks++
	})
	_ = tree.Tick(env)
	fmt.Println(ticks >= 1)
}
Output:
true

type BinarySelector

type BinarySelector struct {
	Condition Behavior
	IfTrue    Behavior
	IfFalse   Behavior
	// contains filtered or unexported fields
}

BinarySelector chooses between two branches based on a condition behavior.

If Condition returns Success, IfTrue is ticked; otherwise IfFalse is ticked. Condition statuses other than Success (including Running) select IfFalse. For a boolean leaf, pass a *Condition.

When the selected branch changes while the previous branch was Running, the abandoned branch is Halted. Halt propagates to the last Running branch.

func NewBinarySelector

func NewBinarySelector(condition, ifTrue, ifFalse Behavior) *BinarySelector

NewBinarySelector creates a BinarySelector.

func (*BinarySelector) Halt added in v1.6.0

func (node *BinarySelector) Halt(env Env)

Halt aborts the last running branch.

func (*BinarySelector) Tick

func (node *BinarySelector) Tick(env Env) RunStatus

Tick evaluates Condition and runs the matching branch.

type Blackboard

type Blackboard struct {
	// contains filtered or unexported fields
}

Blackboard is a hierarchical, thread-safe key-value store for tree state.

Child blackboards can see parent entries (Get/Has walk upward). Writes and deletes apply only to the local blackboard, so a child can shadow a parent key without mutating it.

func NewBlackboard

func NewBlackboard() *Blackboard

NewBlackboard creates an empty root blackboard.

func NewBlackboardWithParent

func NewBlackboardWithParent(parent *Blackboard) *Blackboard

NewBlackboardWithParent creates a blackboard that falls back to parent on miss.

func (*Blackboard) AllEntries

func (bb *Blackboard) AllEntries() []string

AllEntries returns keys visible from this blackboard, including parents. Local keys shadow parent keys of the same name (counted once).

func (*Blackboard) Clear

func (bb *Blackboard) Clear()

Clear removes all local entries. Parents are unaffected.

func (*Blackboard) Delete

func (bb *Blackboard) Delete(key string)

Delete removes a key from this blackboard only.

func (*Blackboard) Entries

func (bb *Blackboard) Entries() []string

Entries returns keys set on this blackboard (not parents).

func (*Blackboard) Get

func (bb *Blackboard) Get(key string) (interface{}, bool)

Get retrieves a value, checking this blackboard then parents.

func (*Blackboard) Has

func (bb *Blackboard) Has(key string) bool

Has reports whether key exists on this blackboard or any parent.

func (*Blackboard) HasLocal

func (bb *Blackboard) HasLocal(key string) bool

HasLocal reports whether key is set on this blackboard (ignoring parents).

func (*Blackboard) Set

func (bb *Blackboard) Set(key string, value interface{})

Set stores a value on this blackboard only.

type ChildrenProvider

type ChildrenProvider interface {
	GetChildren() []Behavior
}

ChildrenProvider is implemented by nodes that expose multiple children. Used by tooling (visualization) without type-switching every composite.

type Composite

type Composite struct {
	Children []Behavior
}

Composite is embedded by multi-child control nodes. Children are public so callers and tools (e.g. visualizers) can inspect the tree.

func (*Composite) GetChildren

func (c *Composite) GetChildren() []Behavior

GetChildren returns the composite's child nodes.

type Condition

type Condition struct {
	// contains filtered or unexported fields
}

Condition is a leaf that maps a boolean check to Success or Failure. It never returns Running.

func NewCondition

func NewCondition(checkFunc func(env Env) bool) *Condition

NewCondition creates a Condition from checkFunc. If checkFunc is nil, Tick returns Failure.

func (*Condition) Tick

func (c *Condition) Tick(env Env) RunStatus

Tick returns Success when the check is true, otherwise Failure.

type Conditional

type Conditional struct {
	Condition *Condition
	Action    Behavior
	// contains filtered or unexported fields
}

Conditional runs Action only when Condition succeeds. If the condition fails, Conditional returns Failure without ticking Action. Condition must be non-nil; a nil Action is treated as Failure when selected.

When Condition fails after Action was Running, Action is Halted. Halt propagates to Action when it was left Running.

func NewConditional

func NewConditional(condition *Condition, action Behavior) *Conditional

NewConditional creates a Conditional with the given condition and action.

func (*Conditional) Halt added in v1.6.0

func (c *Conditional) Halt(env Env)

Halt aborts Action if it was left Running.

func (*Conditional) Tick

func (c *Conditional) Tick(env Env) RunStatus

Tick checks the condition, then optionally runs the action.

type Decorator

type Decorator interface {
	Behavior
	SetChild(child Behavior)
	GetChild() Behavior
}

Decorator wraps a single child and alters when it runs or what status it reports.

type Env

type Env interface {
	// Context returns the stdlib context used for cancellation and deadlines.
	// It is never used as a value bag for agent state.
	Context() context.Context

	// Blackboard returns the hierarchical store for agent/world state.
	Blackboard() *Blackboard

	// Set stores a value on the blackboard (convenience for Blackboard().Set).
	Set(key string, value interface{})
	// Get reads a value from the blackboard hierarchy.
	Get(key string) (value interface{}, ok bool)
	// Delete removes a key from this blackboard only (not parents).
	Delete(key string)
	// Has reports whether key exists on this blackboard or any parent.
	Has(key string) bool
}

Env is the per-tick environment passed to every node.

It is intentionally not a context.Context. The two concerns are separate:

  • Context() — stdlib context.Context for cancellation and deadlines only
  • Blackboard — mutable agent/world state (hierarchical key-value store)

Do not put agent state in context.WithValue. Use the blackboard (or the convenience Set/Get/Delete/Has methods, which write through to it).

Env does not implement context.Context (has-a, not is-a).

func NewEnv

func NewEnv(parent context.Context, options ...EnvOption) Env

NewEnv creates an Env that uses parent for cancellation/deadlines. A new blackboard is allocated unless WithBlackboard is supplied. If parent is nil, context.Background() is used.

type EnvOption

type EnvOption func(*env)

EnvOption configures an Env at construction time.

func WithBlackboard

func WithBlackboard(bb *Blackboard) EnvOption

WithBlackboard sets the blackboard used by the env. If bb is nil, a fresh blackboard is used instead.

type Haltable

type Haltable interface {
	Halt(env Env)
}

Haltable is implemented by nodes that need cleanup when a parent aborts them without delivering a terminal Success/Failure tick (preemption, cancel).

Halt must be safe to call when the node is not running (no-op). Composites that track a running child should Halt that child and clear memory. Reset does not Halt — call Halt explicitly when aborting mid-run.

type Inverter

type Inverter struct {
	BaseDecorator
}

Inverter swaps Success ↔ Failure. Running is unchanged.

func NewInverter

func NewInverter(child Behavior) *Inverter

NewInverter creates an Inverter around child.

func (*Inverter) Halt

func (i *Inverter) Halt(env Env)

Halt aborts the child.

func (*Inverter) Tick

func (i *Inverter) Tick(env Env) RunStatus

Tick runs the child and inverts terminal statuses.

type Key added in v1.6.0

type Key[T any] string

Key is a typed blackboard key. The type parameter documents the value type expected at that key; it is not enforced by the store itself.

const Health bt.Key[int] = "health"
bt.SetKey(env, Health, 100)
h, ok := bt.GetKey(env, Health)

String keys remain fully supported via Env.Set/Get and GetAs. Key helpers are an additive, compile-time documentation layer on the same blackboard.

func (Key[T]) String added in v1.6.0

func (k Key[T]) String() string

String returns the underlying blackboard key string.

type KeyFunc

type KeyFunc func(env Env) string

KeyFunc derives a case key from the context for Switch selection.

type MemorySelector

type MemorySelector struct {
	Composite
	// contains filtered or unexported fields
}

MemorySelector is a Selector that remembers which child was Running.

Unlike the reactive Selector (NewSelector), once a child returns Running the next Tick resumes at that child and does not re-evaluate higher-priority (earlier) siblings until the remembered child finishes.

If the remembered child fails, evaluation continues with later siblings only (earlier ones are not re-tried until the whole selector returns Failure and memory is cleared). Call Reset to clear memory without ticking; Halt aborts.

func NewMemorySelector

func NewMemorySelector(children ...Behavior) *MemorySelector

NewMemorySelector creates a memory Selector with the given children.

func (*MemorySelector) Halt

func (s *MemorySelector) Halt(env Env)

Halt aborts the remembered child (if any) and clears memory.

func (*MemorySelector) Reset

func (s *MemorySelector) Reset()

Reset clears resume state so the next Tick starts at the first child. It does not Halt the current child; call Halt when aborting mid-run.

func (*MemorySelector) RunningIndex

func (s *MemorySelector) RunningIndex() int

RunningIndex returns the remembered child index, or -1 when idle.

func (*MemorySelector) Tick

func (s *MemorySelector) Tick(env Env) RunStatus

Tick tries children from the remembered index. See type docs.

type MemorySequence

type MemorySequence struct {
	Composite
	// contains filtered or unexported fields
}

MemorySequence is a Sequence that remembers which child was Running.

Unlike the reactive Sequence (NewSequence), once a child returns Running the next Tick resumes at that child and does not re-tick earlier siblings that already succeeded in this run.

Memory is cleared when the sequence returns Success or Failure (including nil-child failure). Call Reset to clear memory without ticking; Halt aborts the current child and resets. Idle runningIndex is -1 (Halt is a no-op).

func NewMemorySequence

func NewMemorySequence(children ...Behavior) *MemorySequence

NewMemorySequence creates a memory Sequence with the given children.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	n := 0
	tree := bt.NewMemorySequence(
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			fmt.Println("prep")
			return bt.Success
		}),
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			n++
			fmt.Println("work", n)
			if n < 2 {
				return bt.Running
			}
			return bt.Success
		}),
	)
	fmt.Println(tree.Tick(env))
	fmt.Println(tree.Tick(env))
}
Output:
prep
work 1
Running
work 2
Success

func (*MemorySequence) Halt

func (s *MemorySequence) Halt(env Env)

Halt aborts the child at the resume index (if any) and resets memory.

func (*MemorySequence) Reset

func (s *MemorySequence) Reset()

Reset clears resume state so the next Tick starts at the first child. It does not Halt the current child; call Halt when aborting mid-run.

func (*MemorySequence) RunningIndex

func (s *MemorySequence) RunningIndex() int

RunningIndex returns the child index that will be resumed on the next Tick, or -1 when idle / after a terminal status.

func (*MemorySequence) Tick

func (s *MemorySequence) Tick(env Env) RunStatus

Tick executes children from the remembered index. See type docs.

type Named

type Named struct {
	BaseDecorator
	Name string
}

Named wraps a child with a human-readable label for visualization and logs. Tick is a pure pass-through.

func NewNamed

func NewNamed(name string, child Behavior) *Named

NewNamed creates a Named decorator. Empty name falls back to "Named" in dumps.

func (*Named) Halt

func (n *Named) Halt(env Env)

Halt propagates abort to the child.

func (*Named) Tick

func (n *Named) Tick(env Env) RunStatus

Tick runs the child unchanged.

func (*Named) VisualizeNode

func (n *Named) VisualizeNode() string

VisualizeNode returns the custom name for TreeVisualizer.

type NodeVisualizer

type NodeVisualizer interface {
	VisualizeNode() string
}

NodeVisualizer lets custom nodes supply their own label for tree dumps.

type Observing

type Observing struct {
	BaseDecorator
	// AfterTick is invoked after the child is ticked (including when child is nil → Failure).
	AfterTick TickObserver
}

Observing wraps a child and reports each tick result to AfterTick. Only this node is observed — wrap additional nodes to observe deeper, or use Instrument / InstrumentRecorder for the whole tree.

func NewObserving

func NewObserving(child Behavior, after TickObserver) *Observing

NewObserving creates an Observing decorator. after may be nil (no-op).

func (*Observing) Halt

func (o *Observing) Halt(env Env)

Halt propagates abort to the child.

func (*Observing) Tick

func (o *Observing) Tick(env Env) RunStatus

Tick runs the child and notifies AfterTick.

type Parallel

type Parallel struct {
	Composite
	// contains filtered or unexported fields
}

Parallel ticks all children each call and aggregates results by policy.

By default ticks are sequential (deterministic order, same stack) — the usual behavior-tree meaning of “parallel”: all children get a chance this frame. Use NewConcurrentParallel for one goroutine per child.

Children share the same Env. Under concurrent mode the blackboard is thread-safe; other shared mutable state in actions must be synchronized.

Every child is ticked on every Parallel.Tick (no sticky completion memory). When the policy returns Success or Failure while some children are still Running, those residual Running children are Halted for this tick.

func NewConcurrentParallel

func NewConcurrentParallel(policy ParallelPolicy, children ...Behavior) *Parallel

NewConcurrentParallel creates a Parallel that ticks each child in its own goroutine.

func NewParallel

func NewParallel(policy ParallelPolicy, children ...Behavior) *Parallel

NewParallel creates a sequential Parallel node (classic BT parallel).

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	p := bt.NewParallel(bt.RequireAll,
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			fmt.Println("a")
			return bt.Success
		}),
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			fmt.Println("b")
			return bt.Success
		}),
	)
	fmt.Println(p.Tick(env))
}
Output:
a
b
Success

func (*Parallel) Concurrent

func (p *Parallel) Concurrent() bool

Concurrent reports whether children are ticked in goroutines.

func (*Parallel) Halt

func (p *Parallel) Halt(env Env)

Halt aborts every child.

func (*Parallel) Policy

func (p *Parallel) Policy() ParallelPolicy

Policy returns the aggregation policy.

func (*Parallel) Tick

func (p *Parallel) Tick(env Env) RunStatus

Tick runs all children (sequentially or concurrently) and returns the policy result.

type ParallelPolicy

type ParallelPolicy int

ParallelPolicy defines how a Parallel node aggregates child results.

const (
	// RequireOne succeeds if at least one child succeeds; fails only if all fail.
	RequireOne ParallelPolicy = iota
	// RequireAll succeeds only if all children succeed; fails if any fail.
	RequireAll
	// SuccessOnAll succeeds only if all children succeed; otherwise Running
	// (never Failure — failures are treated as “not yet success”).
	SuccessOnAll
	// SuccessOnOne succeeds if at least one child succeeds; otherwise Running
	// (never Failure).
	SuccessOnOne
)

func (ParallelPolicy) String

func (p ParallelPolicy) String() string

String returns the policy name.

type Repeater

type Repeater struct {
	BaseDecorator
	Count int
	// contains filtered or unexported fields
}

Repeater runs its child a fixed number of successful times. Each Tick advances at most one successful child completion. Failure from the child aborts and resets the counter. count <= 0 means infinite (always returns Running after a successful child tick).

func NewRepeater

func NewRepeater(child Behavior, count int) *Repeater

NewRepeater creates a Repeater. count <= 0 means repeat forever.

func (*Repeater) Halt

func (r *Repeater) Halt(env Env)

Halt resets the counter and aborts the child.

func (*Repeater) Reset

func (r *Repeater) Reset()

Reset clears the success counter so the next Tick starts a fresh series.

func (*Repeater) Tick

func (r *Repeater) Tick(env Env) RunStatus

Tick executes the child and tracks successful completions toward Count.

type RunStatus

type RunStatus int

RunStatus is the result of ticking a behavior node.

const (
	// Success means the node completed its task successfully.
	Success RunStatus = iota
	// Failure means the node failed to complete its task.
	Failure
	// Running means the node is still in progress and should be ticked again.
	Running
)

func (RunStatus) String

func (rs RunStatus) String() string

String returns a human-readable name for the status.

type RunnerOption

type RunnerOption func(*TreeRunner)

RunnerOption configures a TreeRunner.

func WithCallbacks

func WithCallbacks(onSuccess, onFailure, onRunning func()) RunnerOption

WithCallbacks registers hooks invoked after each tick for the returned status. Nil callbacks are treated as no-ops.

func WithTickRate

func WithTickRate(rate time.Duration) RunnerOption

WithTickRate sets how often the tree is ticked. Default is 100ms (10 Hz).

type Selector

type Selector struct {
	Composite
	// contains filtered or unexported fields
}

Selector ticks children left-to-right until one succeeds or is still running.

Semantics (reactive / restart-from-start each tick — classic priority order):

  • Success from any child → Success
  • Running from any child → Running
  • All Failure → Failure

Earlier children have higher priority: every Tick re-evaluates from the first child, so a higher-priority branch can preempt a lower one that was Running. When preemption occurs, the abandoned child is Halted if it implements Haltable. For stick-to-running-child semantics (no preemption), use NewMemorySelector.

func NewSelector

func NewSelector(children ...Behavior) *Selector

NewSelector creates a Selector with the given children, in priority order.

func (*Selector) Halt

func (s *Selector) Halt(env Env)

Halt aborts the last running child and clears tracking.

func (*Selector) Tick

func (s *Selector) Tick(env Env) RunStatus

Tick tries each child until one does not fail. See type docs for status rules.

type Sequence

type Sequence struct {
	Composite
	// contains filtered or unexported fields
}

Sequence ticks children left-to-right until one fails or is still running.

Semantics (reactive / restart-from-start each tick):

  • Failure from any child → Failure
  • Running from any child → Running
  • All Success → Success

Sequence does not remember progress for control flow; each Tick starts at the first child. It does track the last Running child so that if an earlier sibling later fails (or the sequence is Halted), the abandoned child receives Halt. Children that already returned a terminal status this tick are not Halted. For resume-from-running control flow, use NewMemorySequence.

func NewSequence

func NewSequence(children ...Behavior) *Sequence

NewSequence creates a Sequence with the given children, in order.

Example
package main

import (
	"context"
	"fmt"

	"github.com/ratlabs-io/bt-go"
)

func main() {
	env := bt.NewEnv(context.Background())
	tree := bt.NewSequence(
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			fmt.Println("step1")
			return bt.Success
		}),
		bt.NewAction(func(env bt.Env) bt.RunStatus {
			fmt.Println("step2")
			return bt.Success
		}),
	)
	fmt.Println(tree.Tick(env))
}
Output:
step1
step2
Success

func (*Sequence) Halt

func (s *Sequence) Halt(env Env)

Halt aborts the last running child.

func (*Sequence) Tick

func (s *Sequence) Tick(env Env) RunStatus

Tick executes children in order. See type docs for status rules.

type StatusRecorder

type StatusRecorder struct {
	// contains filtered or unexported fields
}

StatusRecorder records the status of nodes as they are ticked. It is a debugging aid, not a tree node itself.

Prefer wrapping ticks you care about:

rec := bt.NewStatusRecorder()
status := rec.Tick(env, root) // records root only

For full-tree status maps, use InstrumentRecorder (or wrap leaves with Observing).

func NewSaveTreeSnapshot deprecated

func NewSaveTreeSnapshot() *StatusRecorder

NewSaveTreeSnapshot is a deprecated alias for NewStatusRecorder.

Deprecated: use NewStatusRecorder.

func NewStatusRecorder

func NewStatusRecorder() *StatusRecorder

NewStatusRecorder creates an empty status recorder.

func (*StatusRecorder) GetStatusMap

func (s *StatusRecorder) GetStatusMap() map[Behavior]RunStatus

GetStatusMap returns the map of recorded node statuses.

func (*StatusRecorder) Tick

func (s *StatusRecorder) Tick(env Env, node Behavior) RunStatus

Tick runs node, records its status, and returns that status. Only the node itself is recorded — not its descendants.

func (*StatusRecorder) Visualize

func (s *StatusRecorder) Visualize(root Behavior) string

Visualize renders root with recorded statuses annotated.

type Switch

type Switch struct {
	KeyFunc KeyFunc
	Cases   map[string]Behavior
	Default Behavior
	// contains filtered or unexported fields
}

Switch selects a child by a dynamic string key.

KeyFunc is evaluated every Tick. If Cases[key] exists it is ticked; otherwise Default is ticked. If neither matches, Switch returns Failure.

When the selected case changes while the previous case was Running, the abandoned case is Halted. Halt propagates to the last Running case.

func NewSwitch

func NewSwitch(keyFunc KeyFunc, cases map[string]Behavior, defaultBehavior Behavior) *Switch

NewSwitch creates a Switch. cases may be nil (only Default will run).

func (*Switch) Halt added in v1.6.0

func (s *Switch) Halt(env Env)

Halt aborts the last running case.

func (*Switch) Tick

func (s *Switch) Tick(env Env) RunStatus

Tick selects and runs the matching case or default.

type TickObserver

type TickObserver func(node Behavior, status RunStatus)

TickObserver is called after a watched node finishes a Tick.

type TreeRunner

type TreeRunner struct {
	// contains filtered or unexported fields
}

TreeRunner ticks a behavior tree on a fixed interval until cancelled.

func NewTreeRunner

func NewTreeRunner(tree Behavior, options ...RunnerOption) *TreeRunner

NewTreeRunner returns a runner for tree with optional configuration.

func (*TreeRunner) Run

func (tr *TreeRunner) Run(env Env)

Run ticks the tree at the configured rate until env.Context() is cancelled.

Cancellation stops scheduling further ticks and Halts the tree so Haltable nodes can clean up. A Tick that is already in progress is not interrupted — long-running actions should watch env.Context().Done() themselves.

func (*TreeRunner) RunOnce

func (tr *TreeRunner) RunOnce(env Env) RunStatus

RunOnce ticks the tree once, fires the matching callback, and returns the status. It does not Halt afterward (the tree may still be Running).

type TreeVisualizer

type TreeVisualizer struct {
	// contains filtered or unexported fields
}

TreeVisualizer renders a behavior tree as indented text.

func NewTreeVisualizer

func NewTreeVisualizer(root Behavior) *TreeVisualizer

NewTreeVisualizer creates a visualizer for root.

func (*TreeVisualizer) Visualize

func (tv *TreeVisualizer) Visualize() string

Visualize returns a multi-line text representation of the tree.

func (*TreeVisualizer) WithNodeStatuses

func (tv *TreeVisualizer) WithNodeStatuses(statuses map[Behavior]RunStatus) *TreeVisualizer

WithNodeStatuses enables status annotations from the given map.

type UntilFailure

type UntilFailure struct {
	BaseDecorator
}

UntilFailure ticks the child until it returns Failure. When the child fails, the decorator returns Success (the wait succeeded).

func NewUntilFailure

func NewUntilFailure(child Behavior) *UntilFailure

NewUntilFailure creates an UntilFailure decorator.

func (*UntilFailure) Halt

func (u *UntilFailure) Halt(env Env)

Halt aborts the child.

func (*UntilFailure) Tick

func (u *UntilFailure) Tick(env Env) RunStatus

Tick returns Success when the child fails; otherwise Running.

type UntilSuccess

type UntilSuccess struct {
	BaseDecorator
}

UntilSuccess ticks the child until it returns Success. Failure and Running both yield Running from the decorator.

func NewUntilSuccess

func NewUntilSuccess(child Behavior) *UntilSuccess

NewUntilSuccess creates an UntilSuccess decorator.

func (*UntilSuccess) Halt

func (u *UntilSuccess) Halt(env Env)

Halt aborts the child.

func (*UntilSuccess) Tick

func (u *UntilSuccess) Tick(env Env) RunStatus

Tick returns Success only when the child succeeds; otherwise Running.

Directories

Path Synopsis
examples
agent command
Agent demonstrates reactive vs memory control flow for a simple NPC.
Agent demonstrates reactive vs memory control flow for a simple NPC.
hello command
Hello is a minimal bt-go example: a two-step Sequence that prints a greeting.
Hello is a minimal bt-go example: a two-step Sequence that prints a greeting.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL