Harness blueprint

Multi-agent workforce

A workforce is many harnesses working on one outcome: a lead that splits the work, specialists that each own a part, and a record of who is doing what. The hard parts are not the agents. They are starting the right agents when work arrives, handing results between runs that do not overlap in time, and seeing what happened afterwards. Foundry has a primitive for each, so the workforce is mostly prompts and definitions.

This harness uses 29 primitives across 15 parts. Everything listed is already built; the dashed box is what you write.

Pick the smallest boundary for each hand-off

Most multi-agent designs fail by using one mechanism for everything. Each hand-off in a workforce has a shape, and Foundry has a primitive for each shape:

The hand-offUseThe result comes back
A bounded sub-task the lead waits for, with fewer toolsdefineSubagentAs the lead's tool result, in the same run
Separate durable work that should run on its ownglove_foundry_spawnThrough the shared inbox, a task or a workspace entry
Peers that must talk while both are workingmeshIn the receiver's inbox, on its next turn
A result that may outlive any one runWorkspace entries, tasks, the shared inboxWhenever someone reads it
Work arriving from outside should start a teamA playbook subscriptionProvisioned instances, each with a run
Fan-out and fan-in that your code controlsThe typed clienthandle.wait()

How it fits together

A brief arrives through a transmission. A subscription matches it and provisions a lead instance for that brief (provisioning: { mode: "per-thread" }), so two briefs never share a lead. The lead's first run writes a plan as tasks with glove_foundry_tasks, then calls glove_foundry_spawn once per specialist: a researcher and a writer, each a new instance with its own conversation, queued at once. The lead's run ends; it does not wait.

The researcher saves its findings to a file and posts a shared inbox item addressed to the lead's conversation with the path, not the body. When the lead runs next (on the writer's report, or on a schedule that checks progress), its inboxes resolver loads the items posted to it and Glove injects them into that turn. Tasks move from open to completed as each part lands. Anything that publishes is permission-gated, so the final step waits on an approval. The inspector shows the whole tree: the arrival, the provisioned lead, every spawned run, each model and tool call, and the approval.

Outside worldSlack · email · webhooksTransmissionverify · normalisePlaybookmatch · provisionSchedulescron · interval · sleepInstancedefinition + dataClienttyped · REST · OpenAIConversationdurable threadRuna fresh Glove per runReplyoutbound toolApprovalpause · approve · denyInspectorevery run and calleventtypedroutes · createsactivationmessagehistoryassemblesrepliesasksemits
A workforce is the system layer used many times over: one arrival provisions a lead, the lead's runs spawn specialists, and every run's events land in one inspector.

Skeleton

agents/text
lead/agent.ts                       plans, spawns specialists, assembles, publishes (gated)
lead/subscriptions/brief.subscription.ts   one lead per incoming brief
researcher/agent.ts                 sources and numbers; writes files, reports by shared inbox
writer/agent.ts                     drafts from the researcher's files
agents/lead/agent.tstypescript
import type { InboxItem } from "glove-core";
import { createAdapter } from "glove-core/models/providers";
import { defineAgent, defineSubagent } from "glove-foundry";
import { Effect } from "effect";
import { z } from "zod";
import { FileStore } from "../../lib/file-store.js";
import { publish } from "../../lib/publish.js"; // your code: where finished work goes

export default defineAgent({
  description: "Leads one brief: plans it, hands out the parts, assembles the result",
  model: () => createAdapter({ provider: "anthropic", model: "claude-sonnet-5-5" }),
  store: ({ conversationId }) => FileStore.open(conversationId),
  systemPrompt: (_agent, ctx) => `You lead the team for one brief (conversation ${ctx.conversationId}).
1. Write the plan as tasks with glove_foundry_tasks.
2. Start specialists with glove_foundry_spawn: definitionId "researcher" for sources and numbers,
   "writer" for drafts. Give each the conversation id above so it can report back.
3. Specialists post results to the shared inbox as file paths. Never paste documents.
4. When every task is completed, assemble and call publish.`,
  // Hand each shared-inbox item posted to this conversation to the model, once.
  // glove_foundry_shared_inbox "post" creates items as "pending".
  inboxes: async (_agent, ctx) => {
    const items = await Effect.runPromise(ctx.data.listInboxItems(ctx.workspaceId));
    const ready = items.filter((item) => item.conversationId === ctx.conversationId && item.status === "pending");
    for (const item of ready) {
      await Effect.runPromise(ctx.data.putInboxItem({ ...item, status: "dismissed", updatedAt: new Date().toISOString() }));
    }
    return ready.map((item): InboxItem => ({
      id: item.id,
      tag: item.topic,
      request: `Result: ${item.topic}`,
      response: JSON.stringify(item.payload).slice(0, 4_000),
      status: "resolved",
      blocking: false,
      created_at: item.createdAt,
      resolved_at: item.updatedAt,
    }));
  },
  tools: [
    {
      name: "publish",
      description: "Publish the finished deliverable.",
      inputSchema: z.object({ path: z.string() }),
      requiresPermission: true, // a person signs off in the inspector or your UI
      async do({ path }) {
        return { status: "success" as const, data: await publish(path) };
      },
    },
  ],
  subagents: [
    defineSubagent({
      name: "editor",
      description: "Review a draft for gaps and contradictions. Read-only; returns notes.",
      systemPrompt: "Review the draft you are given. Return a short list of concrete problems.",
    }),
  ],
});

A specialist reports back with the same built-in tools every Foundry agent has. Its prompt tells it to finish with glove_foundry_shared_inbox({ action: "post", topic, payload, conversationId }) addressed to the lead's conversation, and to mark its task completed with glove_foundry_tasks. The subscription that starts a lead per brief follows the urgent-ticket example with provisioning: { mode: "per-thread" }.

◆

Each Foundry run is its own process, so an in-memory mesh cannot connect two runs. When peers must talk while both are running, give the mesh field a transport-backed adapter, or build the whole team inside one run as braind-storm does. Files several instances share need a shared backend too: give each working environment the same cachedRemote VFS over a bucket.

What not to build

  • An orchestrator service that creates agents for incoming work. Subscriptions provision zero, one or many instances per event, atomically and idempotently.
  • A job queue between agents. glove_foundry_spawn queues a durable run on another instance and returns at once.
  • Coordination tables. Workspace entries, tasks and the shared inbox are already persisted, and compareAndSetWorkspaceEntry gives you an atomic claim so only one agent takes a job.
  • A message bus for live peers. Mesh delivers into inboxes, with blocking sends and acknowledgements, over a transport you supply.
  • A dashboard to see what the team did. The inspector correlates every run by definition, instance, conversation and run, and steering redirects work in place.

Deploy on Kuiper

  1. Deploy the Foundry app as one unit. Every definition and instance runs inside it; Kuiper keeps one replica of a unit with a volume, which is what one runtime per data store needs (two runtimes on one store would both fire its schedules).
  2. Keep the data adapter, transcripts and memory on /data. When the team's files outgrow the volume or must be shared, put them in a Kuiper bucket behind a cachedRemote VFS; its credentials arrive as variables.
  3. Route every role's model through the gateway. kuiper models usage --by unit shows what the workforce costs, and a budget can notify or block before it surprises you.
  4. Watch it from the project's Agents page: instances, runs and failures for every definition, read from the app's own API.

Full walkthrough: Deploy on Kuiper.

Go further