Workflows — io.best.agents.workflows

For choosing between recipes and event-driven choreography, see Composing Processes.

The canonical use: an agent connects to a service, reads the command catalogue, and needs to run a process that takes five operations in a specific order with facts threaded between them. Without a recipe it reconstructs that choreography from raw schemas — and gets it subtly wrong. With one, it reads the steps and drives them.

Workflows are published recipes: read-only, named sequences of catalogue operations with per-step guidance, for multi-step processes whose order is a fixed, well-known happy path. The capability is strictly descriptive — the service never executes, retries, tracks, or branches the steps; the caller (typically an LLM agent) sends each operation itself and waits for its outcome before proceeding. Optional capability; declared in the manifest like any other. Schema: workflows.json

Method Path Description
GET /workflows Workflow index — id, name, description per recipe, never the steps
GET /workflows/{id} One full recipe with its ordered steps; 404 for an unknown id

The index is deliberately shallow so a consumer can hold the entire list in one read and choose; full recipes are fetched one at a time. A service declares the capability active only while it publishes at least one recipe; one that publishes none, yet or any more, declares it planned and serves no index, so a consumer that reads active finds something to follow.

Workflow ids are stable, service-defined, URL-path-safe strings — reverse-domain (io.example.workflows.onboard-a-worker) or kebab-case. Renaming an id is a breaking change for anything linking to it.

Steps. Each step carries:

Field Required Description
kind yes "command" or "query" — whether the step is a POST /commands or a GET /queries/{schema}
dataschema yes Resolvable URI of the operation's schema document in this service's live catalogue — recipes reference the catalogue, never duplicate it, so they cannot drift from the real contracts
optional no true when the step applies only in some runs; absent means required
guidance no How this step combines with the others — what to carry forward, what to wait for, when to skip. Anything about the single operation in isolation belongs in that operation's schema description instead.

guidance is bound by Manifest Discipline rule 5: it speaks about this service's steps only, and never tells the consumer what to do with its own client or configuration.

GET /workflows/io.example.workflows.onboard-a-worker
{
  "id": "io.example.workflows.onboard-a-worker",
  "name": "Onboard a worker",
  "description": "Create the engagement, assign a contact, and invite the worker. Drive each step yourself and wait for its outcome before the next.",
  "steps": [
    { "kind": "command", "dataschema": "https://api.example.com/commands/submit-employee/1.0",
      "guidance": "Creates the engagement. Keep the correlationId — every later step references it." },
    { "kind": "query",   "dataschema": "https://api.example.com/queries/list-employees/1.0",
      "guidance": "Poll until the new engagement appears — commands are asynchronous." },
    { "kind": "command", "dataschema": "https://api.example.com/commands/invite-worker/1.0",
      "optional": true, "guidance": "Only when the user wants the invitation sent immediately." }
  ]
}

Cross-linking — the single discoverability mechanism. The protocol defines exactly one way an operation advertises the recipes it belongs to: the workflows array — the ids of the published workflows the operation participates in — carried wherever the operation is described:

  • on its catalogue entry (GET /commands, GET /queries), and
  • on its schema document (GET /commands/{schema}/{version}, GET /queries/{schema}/{version}) as a top-level member. JSON Schema tolerates unknown keywords, and BEST names this one; validators ignore it.

Both surfaces carry the same array, so the pointer is present at whichever read a consumer performs before acting. Servers publishing workflows should stamp it in both places for every operation that appears in a recipe; consumers should fetch the referenced recipe (GET /workflows/{id}) before composing a multi-step sequence themselves. Human-readable descriptions are free to mention recipes, but the protocol attaches no discoverability role to prose — the cross-link array is the mechanism, and there is no other.

Boundary. The moment a service executes, retries, persists, or branches steps on the caller's behalf, it has built an execution runtime — out of BEST scope, and not something to put behind this capability (put Temporal, Durable Functions, or similar behind the service instead).

Before 0.9.4 this surface existed only as a vendor-extension convention (/workflows under an implementer-owned namespace). Existing publishers migrate by declaring the capability, splitting the old full-list response into index + per-id detail, and adopting the step shape above.

Workflows beside the other capabilities

Capability HTTP Returns Changes state?
agents.commands POST /commands 201 (async) Yes
agents.queries GET /queries/{schema} Current state (sync) No
agents.workflows GET /workflows Published recipes No

On the catalogue entry (GET /commands, GET /queries):

{
  "schema": "submit-employee",
  "version": "1.0",
  "dataschema": "https://api.example.com/commands/submit-employee/1.0",
  "description": "Create a worker engagement record.",
  "workflows": ["io.example.workflows.onboard-a-worker"]
}

On the schema document (GET /commands/{schema}/{version}, GET /queries/{schema}/{version}):

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "description": "Create a worker engagement record.",
  "type": "object",
  "properties": { "...": "..." },
  "workflows": ["io.example.workflows.onboard-a-worker"]
}

Deriving both from the recipe definitions themselves (rather than maintaining them by hand) keeps drift impossible.

Usage pattern

GET /commands                                     → catalogue entry links a workflow id
GET /workflows                                    → index: pick the recipe by description
GET /workflows/io.example.workflows.onboard-a-worker  → the ordered steps
POST /commands · GET /queries/... per step        → the CALLER drives each one, in order
spec/v0.9.21