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 (
/workflowsunder 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 |
The cross-link, as it appears
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