
Research
/Security News
OpenAPI React Query Codegen Compromised in Mini Shai-Hulud npm Supply Chain Attack
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.
@sapiom/agent
Advanced tools
Versioned public contract for authoring Sapiom agents: types, directive constructors/guards, defineAgent, defineStep, InMemoryContextStore. Shared by customer agent definitions, the sandbox step-runner, and the engine.
The versioned public contract for authoring Sapiom orchestrations.
A lean, dependency-light package (types + a small protocol runtime) shared by three consumers:
ctx, runs one step.npm install @sapiom/agent
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.
A step's inputSchema (a zod schema, imported from zod/v4) types and validates that
step's input. The entry step's inputSchema is special — it is the agent's public API:
the dashboard Run form, the trigger snippet, and the engine's pre-dispatch validation are
all derived from it. Declare it on the entry step, with a .default() on each field so a
zero-input run still validates:
import { defineStep, terminate } from "@sapiom/agent";
import { z } from "zod/v4";
const start = defineStep({
name: "start",
next: [],
terminal: true,
inputSchema: z.object({
repo: z.string().default("sapiom/sapiom"),
}),
// `input` is inferred + validated from inputSchema: { repo: string }
async run(input) {
return terminate({ scanned: input.repo });
},
});
inputSchema on a non-entry step types that step's inbound payload the same way — including
a resumed step's signal payload, shown next.
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"],
// capability's exported signal constant so the decl can't drift from the handle.
pause: { signal: CODING_RESULT_SIGNAL, resumeStep: "review" },
async run(input, ctx) {
// launch returns immediately; hand it straight to pauseUntilSignal. The run
// parks at status='paused' and the dispatch loop exits.
return pauseUntilSignal(
ctx.sapiom.models.coding.launch({ task: input.task }),
{
resumeStep: "review",
},
);
},
});
const review = defineStep({
name: "review",
next: [],
terminal: true,
// Give this step an `inputSchema` (a zod schema for the capability's result
// shape) to type + validate what it receives.
async run(result, ctx) {
// Fires on success OR failure — branch on the terminal result.
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); // readable from the resumed step
return pauseUntilSignal(run, { resumeStep: "review" });
}
Outside an agent run nothing changes — await launch().wait() the capability as
usual; the pause wiring only engages when a step pauses on the handle.
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:
| Capability | Launch | Pause signal |
|---|---|---|
| Coding agent | ctx.sapiom.models.coding.launch(…) | CODING_RESULT_SIGNAL (@sapiom/tools) |
FAQs
Versioned public contract for authoring Sapiom agents: types, directive constructors/guards, defineAgent, defineStep, InMemoryContextStore. Shared by customer agent definitions, the sandbox step-runner, and the engine.
The npm package @sapiom/agent receives a total of 4,146 weekly downloads. As such, @sapiom/agent popularity was classified as popular.
We found that @sapiom/agent demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.

Research
/Security News
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.

Security News
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.

Product
Enterprise security teams can now detect malware, credential theft, suspicious network activity, and risky updates across Microsoft Edge extensions.