Orchestrator — a behavior-tree workflow execution engine built on Grain.
ORC provides composable primitives for building, executing, optimizing, and evaluating LLM-powered workflows. It's designed as a library that consumers pull in as a git dependency.
Behavior trees have run game NPCs and robots for decades. The tree ticks top-down, root first; every leaf reads the blackboard (sensor / world state) and writes an action or command — and whole behaviors stack as reusable subbehaviors (here a Swing-Sword tree nests inside Combat, which sits under the brain):
flowchart TB
brain["<b>NPC brain</b><br/>FALLBACK · highest priority that works"]:::fb
brain --> ALERT
brain --> COMBAT
brain --> patrol["<b>patrol</b><br/>ACTION leaf<br/><i>idle behaviour</i><hr/>▸ reads waypoints<br/>◂ writes move_cmd"]:::act
subgraph ALERT["🔍 Alert subbehavior"]
direction TB
a["<b>investigate</b><br/>SEQUENCE"]:::seq
a --> heard(["<b>heard a noise?</b><br/>CONDITION<hr/>▸ reads hearing"]):::cond
a --> goto["<b>move to noise</b><br/>ACTION leaf<hr/>▸ reads noise_pos<br/>◂ writes move_cmd"]:::act
end
subgraph COMBAT["⚔️ Combat subbehavior"]
direction TB
c["<b>engage</b><br/>SEQUENCE"]:::seq
c --> see(["<b>enemy visible?</b><br/>CONDITION<hr/>▸ reads vision"]):::cond
c --> pick["<b>choose attack</b><br/>FALLBACK"]:::fb
pick --> bow["<b>shoot bow</b><br/>ACTION leaf<hr/>▸ reads ammo, enemy_pos<br/>◂ writes fire_cmd"]:::act
pick --> SWORD
subgraph SWORD["🗡️ Swing-Sword subbehavior"]
direction TB
m["<b>melee</b><br/>SEQUENCE"]:::seq
m --> near(["<b>in range & stamina?</b><br/>CONDITION<hr/>▸ reads enemy_dist, stamina"]):::cond
m --> strike["<b>strike</b><br/>ACTION leaf<hr/>▸ reads enemy_pos<br/>◂ writes attack_cmd"]:::act
end
end
classDef fb fill:#7c2d12,stroke:#fb923c,color:#fff,stroke-width:2px;
classDef seq fill:#1e3a8a,stroke:#60a5fa,color:#fff,stroke-width:2px;
classDef cond fill:#713f12,stroke:#facc15,color:#fff;
classDef act fill:#0f766e,stroke:#5eead4,color:#fff;
ORC is the same machine for LLM work — same composites, same top-down tick, same reads/writes contracts, same subbehavior stacking. The leaves just call an LLM or sandboxed code, the blackboard holds your data instead of joint angles, and a stacked subbehavior is a :delegate. Here's a real ORC workflow — note route by type (a fallback, like choose attack), the repl-researcher leaf, and the NDA review subbehavior peeked-inside (the :delegate equivalent of Swing-Sword nesting under Combat):
flowchart TB
root["<b>contract-analysis</b><br/>FALLBACK"]:::fb
root --> main
root --> human["<b>escalate to human</b><br/>LLM · leaf<br/><i>hand off when unsure</i><hr/>▸ reads contract<br/>◂ writes summary"]:::llm
subgraph MAIN["seq: analyze"]
direction TB
main["<b>analyze</b><br/>SEQUENCE"]:::seq
main --> survey["<b>survey</b><br/>LLM · leaf 1<br/><i>extract key clauses</i><hr/>▸ reads contract<br/>◂ writes survey"]:::llm
main --> diff["<b>diff vs prior</b><br/>LLM · leaf 2<br/><i>find changed terms</i><hr/>▸ reads survey<br/>◂ writes diff"]:::llm
main --> route["<b>route by type</b><br/>FALLBACK · leaf 3"]:::fb
main --> risk["<b>quantify risk</b> ▾<br/>REPL-RESEARCHER · leaf 4<br/><i>RLM designs + runs a subtree</i><hr/>▸ reads diff, survey<br/>◂ writes risk_class"]:::rlm
main --> persist["<b>persist findings</b><br/>CODE · leaf 5<br/><i>write record via sci</i><hr/>▸ reads summary, risk_class<br/>◂ writes record"]:::code
route --> isNDA{{"<b>is it an NDA?</b><br/>LLM-CONDITION<br/><i>route on yes / no</i><hr/>▸ reads contract"}}:::llmc
isNDA --> NDA
route --> summarize["<b>summarize clauses</b><br/>LLM · leaf<br/><i>plain-language brief</i><hr/>▸ reads diff<br/>◂ writes summary"]:::llm
subgraph NDA["delegate → NDA review (peek inside) ▾"]
direction TB
ndaRoot["<b>NDA review</b><br/>SEQUENCE"]:::seq
ndaRoot --> parties["<b>extract parties</b><br/>LLM · leaf<br/><i>who is bound</i><hr/>▸ reads contract<br/>◂ writes parties"]:::llm
ndaRoot --> conf["<b>confidentiality check</b><br/>CODE · leaf<br/><i>required clauses present?</i><hr/>▸ reads contract<br/>◂ writes conf_ok"]:::code
end
end
classDef fb fill:#7c2d12,stroke:#fb923c,color:#fff,stroke-width:2px;
classDef seq fill:#1e3a8a,stroke:#60a5fa,color:#fff,stroke-width:2px;
classDef llm fill:#4c1d95,stroke:#c4b5fd,color:#fff;
classDef llmc fill:#5b21b6,stroke:#ddd6fe,color:#fff;
classDef code fill:#0f766e,stroke:#5eead4,color:#fff;
classDef rlm fill:#9d174d,stroke:#f9a8d4,color:#fff,stroke-width:2px;
Game condition → ORC llm-condition; game ACTION → an llm/code leaf; game sensors/commands → blackboard keys you read and write; a stacked game subbehavior → a :delegate. If you can read the game tree, you can read the ORC one. That's the entire mental model — see the full contract-analysis walkthrough.
Early-stage software. ORC is under active development. Expect sharp edges and breaking changes — APIs, event schemas, and conventions may shift between commits. Pin to a specific
:git/shaand review the diff before updating. Expect incomplete docs, use at your own peril!
Start with docs/GETTING-STARTED.md — a progressive contract-analysis walkthrough from bare behavior tree through judges, GEPA, ontology, and self-improvement.
ORC ships as standalone packages — you pull in exactly ONE package and it
bundles every component that capability needs (transitively). You don't
assemble components by hand. Find the row that matches what you want, then add
one dependency: give it the lib name shown and point :deps/root at the project.
| I want… | Pull this package | Heavy deps |
|---|---|---|
| Just run behavior trees (the engine) | obneyai/orc-service → projects/orc-service |
— |
| …plus LLM-as-judge evaluation | obneyai/orc-evaluation → projects/orc-evaluation |
— |
| …plus GEPA prompt optimization | obneyai/orc-gepa → projects/orc-gepa |
— |
| …plus concept graph + DJL embeddings | obneyai/orc-ontology → projects/orc-ontology |
DJL (JVM) |
| …plus ColBERT retrieval (added to ontology) | also obneyai/orc-colbert → projects/orc-colbert |
DJL (JVM) |
| …plus MCP-driven tree generation | obneyai/orc-mcp-sheet-builder → projects/orc-mcp-sheet-builder |
— |
| Everything / the full self-improving loop | obneyai/orc → projects/orc |
DJL (JVM) |
;; deps.edn — pick ONE row above; use its lib name + :deps/root
obneyai/orc-evaluation {:git/url "https://github.com/ObneyAI/orc.git"
:git/sha "..." ;; pin to a reviewed commit
:deps/root "projects/orc-evaluation"}Every non-leaf package bundles the engine (orc-service) transitively, so the
require namespaces are the same whichever you pick. The only time you add a
second package is ColBERT (pull orc-ontology and orc-colbert — distinct
lib names so the keys don't collide). Full per-package detail and the
ontology+colbert combination live in docs/PACKAGES.md; the
layer → internal-component mapping and dependency graph live in
docs/COMPONENT-MAP.md.
Self-improving loop is alpha-stage. The full loop (
:auto-classify?+:recursive?) works end-to-end on workflows that align with the shipped seed corpus, but force-fit classifications appear on out-of-distribution tasks. It needs thecolbertcomponent (pure JVM). See docs/SELF-IMPROVING-LOOP.md for an honest current-state breakdown.
RLM recursive mode is now the default.
:repl-researchernodes default to{:rlm {:recursive? true}}; terminal mode (:rlm true/:rlm {:recursive? false}) is deprecated and will be removed.
Add the package you picked above to your deps.edn. The umbrella (obneyai/orc
→ projects/orc) gives you everything to start experimenting; swap it for a
leaner package (e.g. obneyai/orc-service) once you know which layers you need:
obneyai/orc {:git/url "https://github.com/ObneyAI/orc.git"
:git/sha "..."
:deps/root "projects/orc"}(require '[ai.obney.orc.orc-service.interface :as orc])
;; Define a workflow using the DSL
(def my-workflow
(orc/workflow "summarizer"
(orc/blackboard
{:input :string
:summary :string})
(orc/sequence "main"
(orc/llm "summarize"
:instruction "Summarize the input text in 2 sentences."
:reads [:input]
:writes [:summary]))))
;; Build it (idempotent — no-op if definition hasn't changed)
(orc/build-workflow! ctx my-workflow)
;; Execute it
(orc/execute ctx sheet-id {:input "Long article text..."})
;; => {:status :success, :outputs {:summary "..."}, :duration-ms 1234}The full opt-in layer table, dependency graph, and known issues live in docs/COMPONENT-MAP.md. For judge architecture, rubric design, and custom judge patterns see docs/JUDGE-ARCHITECTURE.md.
| Component | Namespace | Purpose |
|---|---|---|
| orc-service | ai.obney.orc.orc-service |
Core behavior tree execution, DSL, versioning, event sourcing |
| gepa | ai.obney.orc.gepa |
LLM instruction optimization with Pareto frontier selection |
| evaluation | ai.obney.orc.evaluation |
LLM-as-judge evaluation (grounding, reasoning, completeness) |
| colbert | ai.obney.orc.colbert |
Pure-JVM late-interaction retrieval (DJL OnnxRuntime, exact MaxSim) |
| ontology | ai.obney.orc.ontology |
Three-layer concept graph with embeddings and pattern discovery |
| mcp-sheet-builder | ai.obney.orc.mcp-sheet-builder |
Dynamic workflow generation from MCP tool schemas |
| langfuse | ai.obney.orc.langfuse |
Observability and tracing integration |
ORC is built on the Grain event-sourcing framework (CQRS pattern):
Commands -> Events -> Read Models -> Queries
|
v
Todo Processors (side effects)
- Sheets are behavior trees stored as event streams
- Nodes are composable:
sequence,fallback,parallel,map-each,llm,code,condition,repl-researcher - Execution dispatches through the command processor, runs asynchronously via todo processors, and delivers results through a completion registry
- Versioning supports draft/published modes with stash/restore
- The DSL provides a declarative API for building workflows without touching events directly
1. orc/execute dispatches :sheet/tick-tree command
2. Command creates execution snapshot (isolated blackboard)
3. Event triggers todo processor (async)
4. Processor walks the behavior tree:
- sequence: run children in order, fail on first failure
- fallback: run children in order, succeed on first success
- parallel: run children concurrently
- llm: call LLM via DSCloj
- code: evaluate Clojure via SCI
- repl-researcher: iterative code generation + MCP tool calling
5. Result delivered via completion promise
1. Define metric functions (exact-match, contains, judge-based)
2. Start optimization with training examples
3. GEPA proposes instruction variants
4. Evaluates candidates against metrics
5. Pareto frontier selection (multi-objective)
6. Repeat until budget exhausted
| Node | Type | Description |
|---|---|---|
orc/sequence |
Composite | Run children in order. Fails on first failure. |
orc/fallback |
Composite | Run children in order. Succeeds on first success. |
orc/parallel |
Composite | Run all children concurrently. |
orc/map-each |
Composite | Map a subtree over a collection input. |
orc/llm |
Leaf | Call an LLM with instruction + inputs -> outputs. |
orc/code |
Leaf | Execute Clojure code (SCI sandbox). |
orc/condition |
Leaf | Branch based on code predicate. |
orc/llm-condition |
Leaf | Branch based on LLM yes/no judgment. |
orc/repl-researcher |
Leaf | Iterative: generate code, call MCP tools, refine. |
orc/delegate |
Leaf | Execute another workflow with isolated blackboard. |
These compose into real control flow. A fallback that tries a guarded sequence first and falls back to a default sibling is classic if/else:
flowchart TB
route["<b>handle request</b><br/>FALLBACK · if/else"]:::fb
route --> s["<b>premium path</b><br/>SEQUENCE"]:::seq
s --> cond["<b>is premium?</b><br/>CONDITION · code predicate"]:::cond
s --> act["<b>priority reply</b><br/>LLM · leaf"]:::llm
route --> default["<b>default reply</b><br/>LLM · leaf"]:::llm
classDef fb fill:#7c2d12,stroke:#fb923c,color:#fff,stroke-width:2px;
classDef seq fill:#1e3a8a,stroke:#60a5fa,color:#fff,stroke-width:2px;
classDef cond fill:#713f12,stroke:#facc15,color:#fff;
classDef llm fill:#4c1d95,stroke:#c4b5fd,color:#fff;
Swap the code condition for an llm-condition and the same shape becomes LLM-driven routing:
flowchart TB
route["<b>route by urgency</b><br/>FALLBACK"]:::fb
route --> s["<b>urgent path</b><br/>SEQUENCE"]:::seq
s --> q["<b>is it urgent?</b><br/>LLM-CONDITION · yes/no"]:::llmc
s --> esc["<b>escalate</b><br/>LLM · leaf"]:::llm
route --> normal["<b>normal handling</b><br/>LLM · leaf"]:::llm
classDef fb fill:#7c2d12,stroke:#fb923c,color:#fff,stroke-width:2px;
classDef seq fill:#1e3a8a,stroke:#60a5fa,color:#fff,stroke-width:2px;
classDef llmc fill:#5b21b6,stroke:#ddd6fe,color:#fff;
classDef llm fill:#4c1d95,stroke:#c4b5fd,color:#fff;
And the flagship leaf, repl-researcher, is a whole two-phase reasoning loop that drops into a tree like any other node — see the RLM Guide:
flowchart TB
seq["<b>pipeline</b><br/>SEQUENCE"]:::seq
seq --> prep["<b>prep input</b><br/>LLM · leaf<br/><i>normalize the request</i>"]:::llm
seq --> rlm["<b>investigate</b> ▾<br/>REPL-RESEARCHER · leaf<br/><i>two-phase: designs + runs its own subtree</i>"]:::rlm
seq --> fin["<b>finalize</b><br/>CODE · leaf<br/><i>assemble the answer</i>"]:::code
classDef seq fill:#1e3a8a,stroke:#60a5fa,color:#fff,stroke-width:2px;
classDef llm fill:#4c1d95,stroke:#c4b5fd,color:#fff;
classDef code fill:#0f766e,stroke:#5eead4,color:#fff;
classDef rlm fill:#9d174d,stroke:#f9a8d4,color:#fff,stroke-width:2px;
- Java 21+ (with module access for LMDB)
- Clojure CLI (
brew install clojure/tools/clojure)
# Clone
git clone git@github.com:ObneyAI/orc.git && cd orc
# Start nREPL (includes JVM flags for LMDB)
./scripts/nrepl.shclj -M:poly test # changed bricks only
clj -M:poly test :all-bricks # all bricks
clj -M:poly test brick:orc-service # specific brickThere is none. The ColBERT signal is pure JVM (see ADR 0002): the colbert component runs the answerai-colbert-small-v1 encoder checkpoint on DJL OnnxRuntime. On first use it downloads the model (~133 MB) into ~/.cache/orc/colbert/ — after that everything is offline. For air-gapped machines, point -Dcolbert.model.path at a directory containing the model artifacts.
orc/
├── CLAUDE.md # AI assistant instructions
├── README.md
├── deps.edn # Dev alias + Polylith config
├── workspace.edn # Polylith workspace (top-ns: ai.obney.orc)
├── scripts/
│ └── nrepl.sh # nREPL launcher (JVM flags for LMDB)
├── components/
│ ├── orc-service/ # Core execution engine
│ ├── gepa/ # Prompt optimization
│ ├── evaluation/ # LLM-as-judge
│ ├── colbert/ # Semantic retrieval
│ ├── ontology/ # Concept graph
│ ├── mcp-sheet-builder/ # MCP workflow generation
│ ├── langfuse/ # Observability
│ └── grain-test-utils/ # Test infrastructure
├── projects/
│ └── orc/ # Publishable project (git dep target)
├── development/
│ └── src/dev.clj # REPL entry point
└── docs/ # Component guides and architecture
ORC is a library — consumers provide:
- Grain infrastructure: event store (in-memory or Postgres), LMDB cache, control plane
- LLM provider: DSCloj configuration (
:dscloj-providerin context) - Optional: Langfuse client for tracing, MCP servers for tool calling
| Guide | Description |
|---|---|
| Getting Started | Progressive onboarding: core → judges → GEPA → ontology → self-improvement |
| Packages | Standalone packages — pull in only the layer you need |
| Component Map | Opt-in layer table, full dependency graph, known issues |
| Judge Architecture | Rubric design, judge types, custom judges, scale design, composite scoring |
| ORC Principles | Framework-level principles: node palette, :delegate composition, events-first discipline |
| ORC Service Guide | Core execution engine and DSL reference |
| DSL Reference | Complete DSL reference — Core Concepts section is the newcomer entry point |
| RLM Guide | Recursive Language Model — two-phase execution, recursive emit-tree!, drill-down primitives, and the Phase 2 tree DSL |
| Architecture | System architecture and design decisions |
| GEPA Guide | Prompt optimization with GEPA |
| Evaluation | LLM-as-judge evaluation framework |
| ColBERT Integration | The pure-JVM late-interaction retrieval signal |
| Ontology | Concept graph and pattern discovery |
| MCP Sheet Builder | Dynamic workflow generation |
| Self-Improving Loop | Alpha-stage: auto-classify, pattern evolution, behavior minting |
| Event Store Patterns | Grain event sourcing patterns |
| Contributor Grain Patterns | Complete pattern reference (contributors) |
| Document | Description |
|---|---|
| Bench README | How to run the 5-task generalization benchmark suite |
| Bench RESULTS | Headline report — RLM designs 4 distinct tree patterns + 1 "no-tree" decision across structurally different tasks; zero hallucinations across 37+ spot-checks |
MIT. See LICENSE.