Lloyaldocs
Build

Agents and orchestration

Decide which agents exist and how their work relates. The pool runs them together over one model.

On this page

An agent in Lloyal is a branch of the model's live state with a task, a budget and a way to finish — not a request to an endpoint. Every agent forks from state the model has already read, and the pool advances all of them together, one batched step at a time, over the one resident model.

When to use what

You want Use
One agent: plan, classify, write the answer agent({...}) — or useAgent({...}) when later work must fork from it
Several agents at once, over shared context agentPool({ orchestrate: parallel(...) })
Each step to build on the last orchestrate: chain(...)
One survey, then independent follow-ups orchestrate: fanout(...)
Steps with dependencies orchestrate: dag(...)
Several pools that share one header — tools, instructions, evidence withSpine(...) around them
An agent that recruits specialists mid-task A tool that starts a pool from the caller's branch — see Tools

Quickstart

src/harness/wiki.ts
import { agentPool, parallel, withSpine } from "@lloyal-labs/lloyal-agents";
import { citedReport, renderSpine, taskKey } from "@lloyal-labs/rig";

return yield* withSpine(
  { parent: trunk ?? undefined, systemPrompt: renderSpine({ abilities }), tools },
  function* (spine) {
    const pool = yield* agentPool({
      tools,
      parent: spine,
      terminal: citedReport.tool,
      budget: { maxTurns: 8 },
      orchestrate: parallel(
        ANGLES.map((angle, i) => ({
          key: taskKey(i),
          content: `${query}\n\nFocus: ${angle}`,
          systemPrompt: agentPreamble(abilities[0], i),
          seed: 1000 + i,
        })),
      ),
    });
    return ANGLES.map((_, i) => citedReport.read(pool.byKey(taskKey(i)) ?? { result: null }) ?? "");
  },
);

withSpine decodes the shared header — the tools, the abilities' instructions — once; every agent forks from it instead of reading it again. The pool starts one agent per angle, and returns when they have all finished.

Choose a shape

Orchestrator Takes Runs
parallel(specs, { afterDone? }) One SpawnSpec per agent All at once, siblings off the spine. A wide list runs in waves as seats free. afterDone(i, outcome) fires as each settles.
chain(items, toStep) toStep(item, i) → { task, userContent?, beforeSpawn?, afterExtend? } One after another. A step's finding extends the spine under userContent, so the next forks from what it found.
fanout(landscape, domains) One ChainStep, then SpawnSpec[] The landscape first (extending the spine), then every domain in parallel from there. Domains do not see each other.
dag(nodes) { id, task, dependsOn?, userContent? }[] Each node when its dependencies have finished and extended the spine; independent nodes in parallel.

A SpawnSpec is { content, systemPrompt, seed?, parent?, key? }: the task, the agent's system prompt, a sampler seed for diversity, a branch to fork from other than the spine, and a label to read the result back by.

A dag
orchestrate: dag([
  { id: "facts", task: { content: `${q}\n\nEstablish the facts.`, systemPrompt: WORKER }, userContent: "Facts" },
  { id: "dispute", task: { content: `${q}\n\nWhere do sources disagree?`, systemPrompt: WORKER } },
  { id: "verdict", dependsOn: ["facts", "dispute"], task: { content: `${q}\n\nWeigh it.`, systemPrompt: WORKER } },
]),

Write your own orchestrator

An orchestrator is just a generator over the pool's context. JavaScript control flow is the orchestration language:

TypeScript
import type { Orchestrator } from "@lloyal-labs/lloyal-agents";

const untilSettled = (questions: string[]): Orchestrator => function* (ctx) {
  for (const q of questions) {
    if (!ctx.canFit(600)) break;                                   // stop opening work when the room is short
    const agent = yield* ctx.waitFor(yield* ctx.spawn({ content: q, systemPrompt: WORKER }));
    if (agent.result) yield* ctx.extendSpine(`Question: ${q}`, agent.result);
  }
};
ctx. Does
spawn(spec) Requests an agent. It is seated when there is room — context, a free sequence, capacity — which may be several ticks later. Throws SpawnRefused when it can never be seated.
waitFor(agent) Waits until it has finished, and returns the final agent
extendSpine(user, assistant) Writes a turn onto the spine, so agents started after it inherit it
canFit(tokens) Whether another agent of that size would fit under the current pressure

Read the results

agentPool returns when every agent has finished:

Field Holds
outcomes One per spawn, in spawn order: { key?, agentId, result, exitReason?, failed }
byKey(key) The outcome of the spawn that carried key — use it rather than position, since agents finish in any order
agents Per-agent detail, including any replacement for an agent that was restarted
failure What ended the pool early, or null
totalTokens, totalToolCalls Totals, for display

An outcome's failed says why an agent ended without a result — refused a seat, or its own failure — so one agent's failure never ends its siblings.

One agent

TypeScript
import { agent, useAgent } from "@lloyal-labs/lloyal-agents";

const planner = yield* agent({ systemPrompt: PLAN, content: query, terminal: plan.tool });   // runs, finishes, released
const settle = yield* useAgent({ parent: spine, ...prompt, acceptFreeText: true });         // held open until the scope ends

agent runs a single agent in its own scope and returns it finished. useAgent keeps its branch alive for the rest of the enclosing scope, so later work can fork from what it read.

Trunk and spine

  • The trunk (session.trunk) is the conversation's memory: what the next question starts from. It changes only where your harness commits a turn.
  • A spine is a run's shared workspace. It may fork from the trunk (parent: session.trunk), so a follow-up's agents start from the conversation so far; it is released when the run ends.

Findings leave a run as data; the trunk takes only what the harness accepts. Build your first harness shows the one place the basic template commits.

Seats and waves

A pool seats as many agents as the context and its sequences hold. capacity: n caps it lower; spawns beyond it wait in order and are seated as others finish, so any shape runs in waves. The pool also frees an agent's branch as soon as it returns (the scaffold's default), so later agents get the room.

↑
Search