Skip to main content

Composable Pattern

Note: The @codebolt/agent/composable package and the primitives (Agent, LocalTools, Orchestrator, Workflow) represent planned functionality. They do not exist in the current codebase. For building agents today, use Markdown agents in .codebolt/agents/remix/ or the @codebolt/codeboltjs SDK directly.

Build an agent out of small reusable pieces you can share across agents: Agent, LocalTools, Orchestrator, Workflow. Use when you have several related agents and want to reuse the parts.

When to pick Composable over Unified

Choose Composable when:

  • You're building several related agents that share pieces (tool definitions, sub-workflows, orchestration logic).
  • You want each piece testable in isolation as a plain object.
  • Your team has a library of internal patterns you want to reuse.

If you're building one agent, use Unified Agent — Composable is overkill.

The four primitives

import { Agent, LocalTools, Orchestrator, Workflow } from "@codebolt/agent/composable";
PrimitiveWhat it isReusable?
LocalToolsA bundle of tools + descriptions scoped to one agentYes — share across agents
AgentA single agent with prompt, model, toolsYes — nest inside workflows
OrchestratorLogic that decides which agent runs nextYes — share across workflows
WorkflowA sequence of agents + orchestrationThe top-level runnable

A minimal example

import { Agent, LocalTools, Workflow } from "@codebolt/agent/composable";

// 1. Bundle tools once, reuse across agents
const readTools = new LocalTools([
"codebolt_fs.search_files",
"codebolt_codebase.*",
]);

// 2. Define agents that share the tool bundle
const planner = new Agent({
name: "planner",
systemPrompt: "Produce a structured plan.",
model: "claude-opus-4-6",
tools: readTools,
});

const summariser = new Agent({
name: "summariser",
systemPrompt: "Summarise the plan in 3 sentences.",
model: "claude-sonnet-4-6",
tools: readTools,
});

// 3. Wire them into a workflow
export default new Workflow({
name: "plan-and-summarise",
steps: [planner, summariser],
});

The tool bundle is declared once and used by both agents. Change the bundle in one place and every agent that uses it updates.

Orchestrator — conditional routing

When "just run in sequence" isn't enough, pass an Orchestrator:

import { Orchestrator } from "@codebolt/agent/composable";

const routeByIntent = new Orchestrator({
name: "route-by-intent",
decide: async (ctx, lastOutput) => {
if (lastOutput.intent === "plan") return planner;
if (lastOutput.intent === "execute") return coder;
if (lastOutput.intent === "review") return reviewer;
return null; // stop
},
});

new Workflow({
steps: [intentClassifier, routeByIntent],
});

The orchestrator runs after each step and picks the next agent. Return null to stop the workflow.

Nesting workflows

Workflows can contain other workflows as steps:

const understandPhase = new Workflow({
name: "understand",
steps: [codeReader, structureAnalyser],
});

const implementPhase = new Workflow({
name: "implement",
steps: [planner, coder, tester],
});

export default new Workflow({
name: "full-feature",
steps: [understandPhase, implementPhase, deployer],
});

This is how you build complex agents out of small, testable pieces.

Testing

Each primitive is a plain object. Unit-test them directly:

import { planner } from "./my-agent";

test("planner handles empty task gracefully", async () => {
const result = await planner.run({ task: "" });
expect(result.error).toBe("empty_task");
});

For integration tests, use @codebolt/agent/testing helpers to mock LLM and tool calls.

When to use Composable vs Builder vs Processor

  • Composable — reuse at the agent level. You have many agents sharing configs.
  • Builder — reuse at the phase level. You want to customise one phase (initial prompt, LLM output handling) while keeping others default.
  • Processor — reuse at the slot level. You want to swap individual modifiers in and out.

See also