@sapiom/agent
The versioned public contract for authoring Sapiom orchestrations.
A lean, dependency-light package (types + a small protocol runtime) shared by
three consumers:
- Customer orchestration definitions — authored against this package's types and
compiled by the build.
- The sandbox step-runner — reads a step's input, builds
ctx, runs one step.
- The engine — uses the directive guards + manifest schema; it never runs
customer code, only validates the pure-data completion payload.
Install
npm install @sapiom/agent
Authoring surface
import {
defineAgent,
defineStep,
goto,
terminate,
} from "@sapiom/agent";
const start = defineStep({
name: "start",
next: ["finish"],
async run(input, ctx) {
return goto("finish", { greeting: `hello ${input.name}` });
},
});
const finish = defineStep({
name: "finish",
next: [],
terminal: true,
async run() {
return terminate({ done: true });
},
});
export const hello = defineAgent({
name: "hello",
entry: "start",
steps: { start, finish },
});
A step declares the transitions it may take (next / terminal / canFail /
pause); the run return type is derived from those declarations, so an
undeclared transition is a compile error. The build reads those same declarations
to render the orchestration graph without executing anything.
Pausing on a long-running capability
Some ctx.sapiom capabilities are dispatched: you launch them, they run far
past one step's budget, and they report back when they finish (a coding agent
today; more below). A step can't inline-await one — it pauses, and a later step
resumes with the result. pauseUntilSignal accepts the launch handle (or the
launch promise itself) and reads everything it needs off it:
import { defineStep, pauseUntilSignal, terminate } from "@sapiom/agent";
import { CODING_RESULT_SIGNAL } from "@sapiom/tools";
const code = defineStep({
name: "code",
next: ["review"],
pause: { signal: CODING_RESULT_SIGNAL, resumeStep: "review" },
async run(input, ctx) {
return pauseUntilSignal(
ctx.sapiom.models.coding.launch({ task: input.task }),
{
resumeStep: "review",
},
);
},
});
const review = defineStep({
name: "review",
next: [],
terminal: true,
async run(result, ctx) {
return terminate({ ok: result.status === "completed" });
},
});
Things to know:
-
The pause is a real suspend across processes, so the launch and the resume
are two steps — you can't fold them into one inline await.
-
The resumed step receives the capability's result as its input. Declare its
inputSchema to type + validate it (each capability documents its result shape).
-
Pass the launch promise directly for the one-liner above, or await it first
when you need the handle — to stash the run id in ctx.shared, or to try/catch
a launch failure and route somewhere other than a retry. Awaiting doesn't lose
the pause; the resolved handle still flows into pauseUntilSignal:
async run(input, ctx) {
const run = await ctx.sapiom.models.coding.launch({ task: input.task });
ctx.shared.set("codingRunId", run.runId);
return pauseUntilSignal(run, { resumeStep: "review" });
}
-
Outside a workflow nothing changes — await launch().wait() the capability as
usual; the pause wiring only engages when a step pauses on the handle.
Compatible capabilities
Any capability whose launch returns a DispatchHandle (a dispatch member) is
pausable; each ships a stable result-signal constant for the pause decl. This
list grows as capabilities land:
| Coding agent | ctx.sapiom.models.coding.launch(…) | CODING_RESULT_SIGNAL (@sapiom/tools) |