Skip to content
Like what we’re building? Star on GitHub

DSL and runtime — the compiled graph and how it executes ​

Node types, edge and reference syntax, the workflow-level fields (compress:, auto_memory:, permission:, budget), and what the engine does with them at run time: error handling, persistence, resume, concurrency, worktree finalization.

Full language reference: ../dsl.md. This page is the working summary plus the gotchas that cost a session each.

Architecture ​

.bot files are parsed into an AST, compiled into an IR (directed graph of nodes and edges), validated, then executed by the runtime engine. Nodes include Agent (LLM), Judge, Router, Human (pause/resume), Tool, Compute, Subbot (nested child .bot run), and terminal nodes (Done/Fail). Parallel branches converge on downstream nodes via await: wait_all or await: best_effort; there is no top-level Join node. The runtime supports parallel branch scheduling, loop detection, budget enforcement, and resumable execution.

Compilation Pipeline ​

.bot source → Lexer (indent-sensitive tokens) → Parser (recursive-descent) → AST
  → ir.Compile() → IR Workflow (nodes + edges + schemas + prompts + budget)
  → Diagnostics from ir.Compile(), whose validation is a pass inside it and not a separate call (sparse codes C001–C313, the C2xx band shared with pkg/bundlelint: compile errors, reachability, routing, cycles, attachments, presets, capability checks (C080–C082), cursor declarations (C083–C086), etc.)
  → runtime.Engine.Run() → execution with events, budget, and persistence

ir.Compile takes an unknown execution target and never probes host credentials or executables. CompileWithCapabilities accepts an explicit target snapshot for C018; transport callers retain unknown, and the executor resolves/builds on its actual target. Node and workflow auto both fall through through ir.BackendFallsThrough; the forfait/wire preflight keeps its pessimistic stop. See backend compilation and resolution.

Node Types ​

TypeDescription
AgentLLM node with tools, structured I/O, and any selected backend (claw, claude_code, codex, pi, kimi, grok, or opencode)
JudgeLLM node producing verdicts (typically no tools)
RouterRouting node with 5 modes: fan_out_all, fan_out_each, condition, round_robin, llm (see docs/routers.md)
HumanPause/resume via interaction: human (default for human nodes); optional interaction: llm or llm_or_human can auto-answer or escalate. Agent/judge nodes can instead declare interaction: async (ADR-081): the agent posts questions via ask_user_async and KEEPS WORKING — answers arrive in its message queue (node-scoped inbox) whenever the operator replies; the await_answers tool is the LLM-discretion sync point (pauses only if something is still pending). See docs/async-interaction.md.
ToolDirect shell command execution (no LLM). ACTION tool nodes may opt into the Verified Action quad (goal+postcondition+policy+recovery) so a brittle recipe self-heals (idempotent-skip → recipe → self-repair → agent → policy) instead of hard-blocking; the postcondition is the deterministic truth oracle at every rung. Gates stay deterministic — never attach recovery to a recipe == postcondition gate (enforced by C103–C106). See docs/adr/044-adaptive-recovery-for-deterministic-action-nodes.md.
ComputeDeterministic expression node for derived structured output (no LLM, no shell)
SubbotRuns another .bot as a nested child run (subbot <name>: with source:, var mapping, resource leases, and an isolated: workspace-safety assertion); child outputs read back as outputs.<subbot>.<field>. Diagnostic C119.
Emit / WaitIn-bot event-driven primitives (ADR-051): emit publishes a named run-scoped event with an immutable payload; wait blocks a branch until that event fires (mandatory timeout: — the bornage). A reactive coordination pair between parallel branches (actor/CSP model, not the JS event loop — payloads are immutable, no shared mutable heap), backed by a run-local reliable registry, distinct from the lossy cross-run pkg/eventbus. Diagnostics C196–C198. See docs/adr/051-in-bot-event-driven-primitives.md + examples/events/pingpong.bot.
Await answersawait_answers (ADR-081): the deterministic sync point for async human questions — parks its branch (only its branch) until every pending ask_user_async question of the from: node (or the whole run) is answered; mandatory timeout:; output {answers: [...]}. Level-triggered against the interaction store (doorbell + 5s poll), so cross-process answers and resume both work. Diagnostics C240–C242. See docs/async-interaction.md + examples/async-questions/main.bot.
DoneTerminal: workflow success
FailTerminal: workflow failure

Where the rest went ​

DSL quick reference · errors, persistence and resume · worktree finalization.