Agents start guessing when they try to do too much. Hide the full plan, ask for a single instruction, and let your agent focus on the present. The agent only ever sees the next step. The plan stays in the tree, not in the prompt.
CLAUDE.md · 487 lines
1. First, check the git status. Confirm a clean tree.
2. Read the existing tests. Note their style.
3. Locate the file containing the function to refactor.
4. Identify single-responsibility violations. Score each.
5. Draft a refactor plan and present to the user.
6. Apply the refactor, splitting concerns into modules.
7. Update the imports across the codebase.
8. Run the test suite. Verify everything stays green.
9. Re-score the codebase against the SRP criteria.
10. Confirm violations resolved. Loop if any remain.
11. Run a multi-agent code review on the diff.
12. Compose a before-vs-after report. Save to disk.
13. ...
→
next_step
instructScore_SRP
Score the codebase for Single
Responsibility violations. Save
the ranked list to $VAR.violations.
Never guess where your agent got stuck. The runtime writes a self-contained trace file as it runs; open it in the browser canvas to see exactly which branches fired, what the agent wrote into $VAR, and where the cursor sat when it finished.
drag to pan · use buttons to zoom
Green nodes ran and succeeded, red ran and failed, the pink ring marks the cursor. Click any node in the live viewer to see its evaluate/instruct prompt, the agent's narration, and its reasoning — $VAR and $CONST references become hoverable badges showing the current value.
Whether you prefer code first or plain text, the TypeScript DSL gives composability and IDE support; YAML and JSON give a no-tooling approach. They all compile to the same runtime tree.
AuthoringSame tree. Different surface.
tree.ts01TypeScript
import {
sequence, selector, action,
evaluate, instruct,
} from"@behaviors-sh/dsl";
// Trees are plain nested function calls.const tree = sequence("Greeting", () => {
// Action: evaluate (precondition) + instruct.action("Detect_Time", () => {
instruct(`Detect the time of day.`);
});
// sequence / selector / parallel — three composites.selector("Choose_Greeting", () => {
action("Morning", () => {
evaluate(`time is morning`);
instruct(`Say "Good morning".`);
});
action("Default", () => {
instruct(`Say "Hello".`);
});
});
});
TREE.yaml02YAML
name:greetingversion:1.0.0tree:type:sequencename:Greetingchildren:# Action: an evaluate (precondition) + an instruct.
- type:actionname:Detect_Timesteps:
- instruct:Detect the time of day.# sequence / selector / parallel — three composites.
- type:selectorname:Choose_Greetingchildren:
- type:actionname:Morningsteps:
- evaluate:time is morning
- instruct:'Say "Good morning".'
- type:actionname:Defaultsteps:
- instruct:'Say "Hello".'
The runtime is exposed as a Model Context Protocol server (STDIO or Streamable HTTP). Any agent that speaks MCP can drive a tree — Claude Code, Claude Desktop, ChatGPT, your own client. Twelve tools, all URI-addressed: the caller picks where the trace lands, the runtime writes there.
text
start_execution(tree_uri, trace_output) → protocol-gate instructsubmit(trace_output, "success") → gate acceptednext_step(trace_output) → instruct | evaluate | done | failurevar_write(trace_output, key, value) → store an agent outputsubmit(trace_output, "success") → advance the action# repeat next_step → … → submit until { status: "done" }
next_step is replay-safe — calling it again mid-step returns the same request unchanged, so a flaky retry doesn't desync the cursor.
Publish to npm, share through GitHub, ship a YAML file in a gist — the runtime reads from any URI. The runtime never sees the distribution; it just reads the tree at the URI you point it at.
In Claude Code, the project-scoped .mcp.json wires the runtime as an MCP server. The agent gets twelve tools (mcp__behaviors-sh__next_step, …__submit, …__var_write, etc.) and a brief that says "drive this tree". That is the entire human-side interaction.
text
Drive the tree at file:///abs/path/to/tree.yaml and write the trace tofile:///abs/path/to/run.json. Acknowledge the protocol gate, then loopnext_step → submit/eval until you see { status: "done" }.
For the long-form walkthrough — clone, configure, drive hello-world from Claude — see Get started.