Bahini
Open app

Core

Teams

A team runs many employees together — a flat parallel fan-out or a coordinator-planned DAG with dependencies, conditional edges, bounded concurrency, and self-heal replan. Define one inline, or save a preset and run it repeatedly.

Run a team inline#

A coordinator plans and delegates to workers; an optional synthesizer folds the results. Guardrails set here are inherited by every member.

 
ts
const { runId } = await bahini.runSwarm({
  coordinatorAgentId: "agent_coord",
  workerAgentIds: ["agent_scout", "agent_analyst", "agent_writer"],
  synthesizerAgentId: "agent_editor",
  prompt: "Produce the weekly BI digest.",
  guardrails: { guards: ["pii", "secrets", "injection"], mode: "enforce" },
  concurrency: 2,       // parallelism cap
  swarmMaxTokens: 500_000, // hard cost cap for the whole run
});

Wire a DAG#

Pass a plan to shape the workers into a graph. Each node names a worker by key with dependsOn (hard — a failed upstream skips the node) and optionalDependsOn (soft — the node still runs on partial inputs). A condition makes a conditional edge for dynamic branching.

 
ts
const { runId } = await bahini.runSwarm({
  coordinatorAgentId: "agent_coord",
  workerAgentIds: ["scout", "analyst", "writer"],
  prompt: "Draft the competitor brief.",
  plan: [
    { id: "a", employee: "scout" },
    { id: "b", employee: "analyst", dependsOn: ["a"] },
    { id: "c", employee: "writer",  dependsOn: ["b"],
      condition: { node: "b", contains: "PRICE_CHANGE" } },
  ],
});
A malformed plan is ignored server-side (it falls back to a flat fan-out) so it never wedges a run. Validate ahead of time in CI with validateSwarmPlan — it returns { ok: false, errors } naming unknown employees, duplicate ids, unknown deps, or cycles.

Or say it in one line#

For a staged fan-out and fan-in — the common shape — flowis the same graph written the way you'd say it. A comma runs employees together; -> makes the next stage wait for the whole stage before it.

 
ts
const { runId } = await bahini.runSwarm({
  coordinatorAgentId: "agent_coord",
  workerAgentIds: ["scout", "pricer", "analyst"],
  prompt: "Draft the competitor brief.",
  flow: "scout, pricer -> analyst",
});

Reach for plan when you need what flow cannot say — soft edges, conditional branches, or a node that repeats. validateSwarmPlan takes either, and gives a flow and the plan it expands to the identical answer. One difference: a malformed plan falls back to a fan-out, but a malformed flow is rejected — you typed it, so you can fix it.

Build a team you don't have yet#

A coordinator can only plan over employees that already exist, so an empty workspace has nothing to coordinate. buildSwarm designs the roster: a coordinator, two to six specialists, a synthesizer, and the DAG wiring them — created as real employees and a real preset you can edit like any other.

 
ts
const built = await bahini.buildSwarm({
  description:
    "Every Monday, check our three competitors' pricing pages, flag " +
    "anything that moved more than 5%, and write it up for sales.",
});

if (!built.ok) {
  console.log(built.clarification); // too vague — ask again, don't retry
} else {
  console.log(built.draft.flow);            // "price-scout, page-watcher -> change-analyst"
  console.log(built.team?.reusedAgentKeys); // employees you already had
  await bahini.runPreset(built.team!.presetId, { prompt: "This week's check." });
}
Pass dryRun: true to get the draft back without creating anything. An employee you already have under one of the drafted keys is wired in as-is — its instructions are never overwritten.

Watch the graph#

getRunDagjoins the persisted plan with each node's live status — poll it (or pair with waitForRun) to render the graph as nodes go running → completed / failed / skipped. Returns null for a non-coordinator run.

 
ts
const dag = await bahini.getRunDag(runId);
for (const node of dag?.nodes ?? []) {
  console.log(node.id, node.agentName, node.status);
}

Presets (reusable teams)#

createSwarmPreset(input)

Save a team's shape once, run it repeatedly with runPreset. kind: "basic" runs workers in parallel; "coordinator" adds an orchestrator.

 
ts
const { id } = await bahini.createSwarmPreset({
  key: "weekly-bi",
  name: "Weekly BI",
  kind: "coordinator",
  coordinatorAgentId: "agent_coord",
  workerAgentIds: ["scout", "analyst", "writer"],
});

// Persist a guardrail policy so every run of the preset is governed:
await bahini.setPresetGuardrails(id, { guards: ["pii"], mode: "enforce" });

await bahini.runPreset(id, { prompt: "Run this week's digest." });