Library / SDK
splintered-reality/py_trees avatar
splintered-reality/py_trees

py_trees: behaviour trees as decision engines in Python

Python implementation of behaviour trees.

648 stars189 forksPythonNOASSERTION

At a glance

What is it?
py_trees is a Python implementation of behaviour trees, aimed at medium sized decision making engines in fields such as robotics. It gives you the usual node types, a blackboard for data sharing, a ports API with an XML parser for declarative definitions, and a large demo suite, and the project is honest in its own packaging metadata that it targets engines that do not need to be real time reactive.
Who is it for?
py_trees fits a robotics or agent team that has genuinely branching decision logic with reusable subtrees and needs a blackboard to carry data between behaviours, and it does not fit a hard real time control loop or a fixed sequential flow that a state machine expresses more directly.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A decision engine, not a robot framework

py_trees is described as a Python implementation of behaviour trees designed to facilitate the rapid development of medium sized decision making engines for use in fields like robotics. The phrase medium sized is doing real work in that sentence, and the packaging metadata in setup.py is even blunter about it, calling the project a behaviour tree implementation for rapid development of small scale decision making engines that do not need to be real time reactive.

That scoping is the most useful thing in the documentation, because behaviour trees get sold as a general solution and then used where they do not fit. A behaviour tree gives you priority based selection, interruptible subtrees and a clean way to express reactive fallback logic, and all of that is worth having when the decision structure is genuinely branching and the environment can change under you. It is overhead when your robot follows a fixed sequence with a couple of conditionals, which is what a state machine expresses more directly.

The feature list is the standard set. Behaviours, decorators, sequences, selectors, parallels and the BehaviourTree itself, with blackboards for data sharing, a library of behaviours, decorators and idioms, an abstract ports API with an XML parser for declarative behaviour definitions, and output that can be serialised to a dot graph or rendered as ascii or unicode in a terminal. The README also states the platform coverage plainly: tested on Linux and Mac, with Windows in the yes, maybe, maybe not category.

The library is a single PyPI package, py_trees, with a BSD 3-Clause licence, and the documentation lives at py-trees.readthedocs.io.

The blackboard carries the data between behaviours

Behaviour trees on their own are just control flow. What makes them useful in a robot is the blackboard, the shared key value store that behaviours read from and write to, and py_trees treats that as a first class part of the design rather than an application concern.

The 2.0.x release line is where blackboards V2 arrived, and the release notes since then show the blackboard continuing to grow. The 2.4.x line added a new CompareBlackboardVariables behaviour and allowed callables in comparison expressions, which is the kind of change that only makes sense once the blackboard is treated as the data interface of the tree rather than as a dictionary.

The feature set around it is broader than a plain dictionary, and the demo names are the clearest evidence of how much surface there is. There is a plain blackboard demo, a blackboard namespaces demo and a blackboard remappings demo, three separate programs, which tells you that namespacing and remapping are first class ideas rather than afterthoughts. Namespacing lets a subtree address keys under a prefix without knowing the prefix, and remapping lets a subtree rename or redirect the keys it sees, which is what you need when you reuse a subtree in two places that want different data.

That machinery is the difference between a tree you can compose and a tree where every behaviour has to know the global key names. If you are building a tree that is more than one level deep, the blackboard work is where the time goes.

Ports and XML are the declarative half of the library

The newer releases have pushed in a direction that is worth understanding, because it changes what a behaviour tree in py_trees can be.

The 2.5.x line introduced a new Ports API with XML file parsing, and 2.6.x improved the Ports API and the XML file parsing further, with default values and performance improvements called out in the release notes. The 2.2.x line is the other visible change, adding selectors and sequences with and without memory, and improving the testing and the style and type checking.

A port is a typed input or output on a behaviour, and the XML parser lets a behaviour definition be written in a file rather than in Python. The declared behaviour definitions in the feature list point the same way. The library is moving from a Python only construction of trees towards trees whose interface is described in a separate document, which is a different proposition for a team that wants to change a parameter without touching code.

The provenance is worth noting too. The README credits Sunrise Robotics with the port declarations, the type validation and the XML parser, so the declarative half arrived as a contribution rather than growing organically from the core.

The caveat is that this is the newest and least settled part of the library, and the release notes are where you have to read for the details. The 2.6.x improvements to defaults and performance are recent, and the 2.6.0 release is dated 2026-09-10.

The demo suite is the fastest way to learn the model

The Getting Started section does not tell you to write a tree. It tells you to explore the demos, and there are a lot of them.

The command is py-trees-demo with a tab completion over roughly fifteen programs, and the names read like a table of contents for the library's concepts: action behaviour, behaviour lifecycle, blackboard, blackboard namespaces, blackboard remappings, context switching, display modes, dot graphs, either-or, eternal guard, logging, pick-up-where-you-left-off, selector, sequence, and tree stewardship.

Several of those names are answers to questions every tree author eventually asks. Behaviour lifecycle is about the setup, update and shutdown calls a behaviour goes through, which is the part people get wrong. Tree stewardship is about transferring control between trees, which is the part nobody discovers until they need it. Pick up where you left off is about resuming a subtree that was interrupted. Either-or is about the choice between two branches, and eternal guard is about a behaviour that never gives up its slot.

The output side has the same treatment. A separate entry point, py-trees-render, renders a tree, and the library can serialise to a dot graph or render as ascii or unicode in a terminal, so you can inspect a tree in a console or in a pipeline that wants a graph file.

For something larger than the demos, the README points at the PyTrees ROS tutorials on ReadTheDocs, and describes them as significantly more edifying than the demos, walking incrementally through building a decision making layer for a robot. They use ROS 2 sparsely, so the README says browsing them is worthwhile even if you do not use ROS.

The development environment is uv, ruff, ty and pytest

The contributor path is unusually well specified for a library of this size, and it starts by installing uv and cloning the repository.

bash
# Install dependencies into a .venv
$ uv sync

The demo entry points are then reachable as console scripts, and the quality gates are four commands.

bash
# Run the Formatter, Linter, Type-Checker and Tests
$ uv run ruff format
$ uv run ruff check
$ uv run ty check
$ uv run pytest -s tests/

Two details there are worth a note. The type checker is ty rather than mypy, which is a recent addition to the toolchain, and the 2.2.x release notes mention improved style and type checking, so the project is treating static analysis as part of the definition of done rather than an optional extra.

Documentation is built with Sphinx through a Makefile target that sets PY_TREES_DISABLE_COLORS=1 for the build, which is a small thing that tells you the docs are built in CI as well as on a laptop, since the colour escape codes would otherwise end up in the generated output. There is a .readthedocs.yaml at the top of the tree, a CHANGELOG.rst rather than a Markdown changelog, a DEVELOPING.md that the Getting Started section points to for more detail, and a uv.lock for reproducible environments.

The maintainers are Daniel Stonier and Sebastian Castro, and the pyproject lists the authors as Daniel Stonier, Naveed Usmani and Michal Staniaszek.

Version support is a matrix you should read before upgrading

The README carries a compatibility table across five branches, and it is one of the more useful pieces of documentation in the project because it tells you which Python versions a given release supports.

The devel branch and the 2.6.x and 2.5.x branches support Python 3.14, 3.12 and 3.10. The 2.4.x and 2.3.x branches support Python 3.12 and 3.10. Each branch has its own CI status badge and its own documentation build, so a bug fixed in devel is not necessarily fixed in the branch you are pinned to.

The release notes explain how the matrix moves. The 2.3.x line added support for Python 3.12 and dropped Python 3.8. The 2.5.x line added support for Python 3.14 along with the new Ports API, the XML file parsing and the uv based dev environment. The 2.4.x line added the ForEach decorator, the CompareBlackboardVariables behaviour and callables in comparison expressions. The 2.6.x line is where the Ports API and XML parsing improvements landed.

The pyproject makes the floor explicit with requires-python of 3.10 or newer and below 4.0, and the package has exactly one runtime dependency, pydot at 1.4 or newer, which is what the dot graph output needs. A single dependency is a good sign for a library that ends up inside somebody else's robot.

The releases are 2.6.0 on 2026-09-10, 2.5.0 on 2026-07-14 and 2.4.0 on 2025-11-13, the default branch is devel, the last push was on 2026-09-17 and the repository is not archived.

Where behaviour trees are the wrong abstraction

Three limits are visible in the documentation, and the first one is stated by the project itself.

The setup.py description says the library is for decision making engines that do not need to be real time reactive. So for a control loop with a hard deadline, or a task where a missed tick means a dropped payload, the tick based tree is not the mechanism, and the project does not pretend otherwise.

The second limit is a packaging one that will bite a robotics team specifically. The pyproject carries a comment explaining that requires-python was deliberately omitted, because colcon build, the ament_python build, introspects setup.py and repr plus ast.literal_eval on the setuptools metadata, and setuptools merges the PEP 621 field as a packaging.SpecifierSet whose repr is not a valid literal, which breaks the colcon build. The fix was to keep a minimal setup.py for ROS builds and put everything else in pyproject.toml. If your package is built with colcon, you need to know that, because a field you would naturally add to pyproject will break your ROS build.

The third is the comparison with a plain state machine, which is the honest alternative. A state machine is smaller, easier to trace, and completely predictable for a fixed flow, and it is what most software should use. A behaviour tree earns its extra machinery when the branching is genuine, when subtrees need to be reusable with different remapped data, and when priority based selection with interruptible fallbacks is the behaviour you actually want.

The sibling packages define the boundary. py_trees_ros is a tree manager and behaviours for ROS 2, py_trees_ros_viewer is a Qt and ROS 2 implementation of the visualiser, and py_trees_js is a JavaScript library for building your own runtime visualisation tool. So the core stays framework free, and the ROS and visualisation parts are separate projects, which is the right split if you want the library in a non-ROS stack.

Editorial conclusion

py_trees fits a robotics or agent team that has genuinely branching decision logic with reusable subtrees and needs a blackboard to carry data between behaviours, and it does not fit a hard real time control loop or a fixed sequential flow that a state machine expresses more directly. Before adopting it, read the compatibility table for the branch you would pin, confirm your Python version is covered, and note that the ROS build path needs the setup.py file kept separate from pyproject.toml.

Frequently asked questions

How do I install py_trees?

Released versions install from PyPI with pip install py_trees, or from conda-forge with conda install conda-forge::py-trees. For development, clone the repository, install uv, and run uv sync to populate a .venv, then use the demo entry points with uv run.

Which Python versions does py_trees support?

The devel, 2.6.x and 2.5.x branches support Python 3.14, 3.12 and 3.10, while 2.4.x and 2.3.x support 3.12 and 3.10. The pyproject requires Python 3.10 or newer and below 4.0, and 2.3.x was where Python 3.8 support was dropped.

What is a blackboard in py_trees?

It is the shared key value store that behaviours use to exchange data, and the library treats it as a first class part of the design. Blackboards V2 arrived in the 2.0.x line, and later releases added a CompareBlackboardVariables behaviour, callables in comparison expressions, and separate demos for blackboard namespaces and remappings.

What is the Ports API in py_trees?

It is the more abstract interface introduced in the 2.5.x line, together with an XML parser so that behaviour definitions can be written declaratively rather than only in Python. The 2.6.x line improved the Ports API and the XML file parsing, including default values and performance improvements, and the port declarations, type validation and XML parser were contributed by Sunrise Robotics.

Is py_trees suitable for real time control loops?

The project describes itself as a behaviour tree implementation for rapid development of small scale decision making engines that do not need to be real time reactive, so it is aimed at decision logic rather than hard deadline control loops. The core is also kept framework free, with ROS 2 specific behaviour in the separate py_trees_ros package.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. splintered-reality/py_trees on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/splintered-reality-py-trees.svg)](https://hysenlabs.com/projects/splintered-reality-py-trees)

Was this useful?