Choose a call surface
ctx.sapiom gives you three different-shaped ways to get an LLM to do something from a step. Picking the wrong one is a common authoring mistake — usually a trivial task landing on the heaviest surface and running for minutes when it should have returned in a couple of seconds.
The one-sentence rule
Section titled “The one-sentence rule”One LLM call → ctx.sapiom.llm.run. A platform-driven agentic loop (multi-turn, tool use, no filesystem) → ctx.sapiom.models.run. Dispatching an already-deployed agent by its slug → ctx.sapiom.agents.run. You never pick a raw provider model — you pick a routing label (or omit it for the platform default), and the platform discloses back the resolved class and lane it actually served you on. On the surfaces that support scheduling, you set deadlineMinutes instead of naming a model and let the platform place the call.
Pick one
Section titled “Pick one”| Your task | Surface | What to expect |
|---|---|---|
| One prompt, one response — extract a field, classify text, translate, generate short JSON | llm.run | A single synchronous call, always immediate (run_now) — never queues. |
| Multi-turn reasoning with tools, but no filesystem or sandbox | models.run | Runs Sapiom’s in-server agent loop; returns final text once it’s done. |
| Editing a real code checkout — needs a filesystem and shell | models.coding.run | Runs in an isolated sandbox; returns a live Sandbox handle. See Agent authoring. |
| Running an already-deployed agent (a separate automation) from a step | agents.run | Whatever that agent’s own steps take. Addressed by its slug, not inlined. |
| A routed call you’re willing to queue for capacity, or a burst of repeated calls | llm.submit / llm.redeem, or llm.createSession / callSession | Deferred: deadlineMinutes controls how long you’re willing to wait. Sessions add repeatable calls against one reservation. |
llm.run — one routed call
Section titled “llm.run — one routed call”const reply = await ctx.sapiom.llm.run({ request: { messages: [{ role: "user", content: "Classify the sentiment of: 'This is fantastic!'" }], max_tokens: 64, }, model: "smart", // a routing label — omit for the platform default});
// `reply` is the verbatim Anthropic Messages response. A `thinking` block can// be present alongside the text block, so filter rather than assume index 0:const text = (reply.content as Array<{ type: string; text?: string }>).find( (block) => block.type === "text",)?.text;There is no output: schema parameter and no .text accessor today — request is the verbatim Anthropic Messages body, and the return value is that API’s verbatim response (typed Record<string, unknown> by default; pass a generic if you want to assert your own type). For structured output, use tool calling: define a tool whose input schema is the shape you want, force it with tool_choice, and read the tool call’s input.
const reply = await ctx.sapiom.llm.run({ request: { messages: [{ role: "user", content: "Extract the name and age from: 'Priya is 34.'" }], max_tokens: 256, tools: [ { name: "record_person", input_schema: { type: "object", properties: { name: { type: "string" }, age: { type: "number" } }, required: ["name", "age"], }, }, ], tool_choice: { type: "tool", name: "record_person" }, }, model: "smart",});
const call = (reply.content as Array<{ type: string; input?: unknown }>).find( (block) => block.type === "tool_use",);const person = call?.input as { name: string; age: number } | undefined;Deferred and session calls
Section titled “Deferred and session calls”Reach for these when you’d rather queue for available capacity than pay for immediate execution, or when you’re making several calls against the same reservation.
// One deferred call — pause the step until the platform grants it:const handle = await ctx.sapiom.llm.submit({ request: { messages: [{ role: "user", content: "Summarize this transcript…" }], max_tokens: 1024 }, model: "smart", deadlineMinutes: 30,});return pauseUntilSignal(handle, { resumeStep: "useReply" });
// Repeated calls against one reservation — a session:const session = await ctx.sapiom.llm.createSession({ label: "smart", deadlineMinutes: 60, budget: { maxTokens: 2_000_000, ttlMinutes: 120 },});const ready = await session.wait();const reply = await ctx.sapiom.llm.callSession(session, { messages: [{ role: "user", content: "…" }], max_tokens: 512,});A deferred call that can’t be granted by its deadline fails visibly — the resumed step’s status is "failed" with an error such as deadline_exhausted, never a silent hang.
models.run — the instant agentic loop
Section titled “models.run — the instant agentic loop”For a task that needs a few turns of reasoning or a remote MCP tool call, but no filesystem:
const result = await ctx.sapiom.models.run({ prompt: "Look up the current weather in Lisbon using the provided tool, then summarize it in one sentence.", model: "smart", // omit for the platform default mcps: [{ url: "https://example.com/weather-mcp" }],});
if (result.status !== "completed" || result.output === null) { throw new Error(result.error?.message ?? `Model run ${result.status}`);}console.log(result.output);models.run awaits completion; models.launch returns a handle for pauseUntilSignal on longer work. See Agent authoring for the sandboxed coding-agent sibling (models.coding.run), which adds a filesystem for tasks that actually need to edit files.
agents.run — dispatch a deployed agent
Section titled “agents.run — dispatch a deployed agent”Composition, not inlining: call an already-deployed agent by its slug from inside another agent’s step.
const result = await ctx.sapiom.agents.run({ definition: "enrich-lead", // the child agent's deployed slug input: { domain: "acme.com" },});Prefer several small, independently deployed agents wired together with agents.run over one large multi-step agent. A small agent is independently testable, versioned, and reusable from more than one caller; a large one couples unrelated concerns into a single deploy unit and step graph. Use agents.launch + pauseUntilSignal when the child is long-running, so the parent step doesn’t time out. See Sub-agents for the full pattern.
Model selection and disclosure
Section titled “Model selection and disclosure”Every surface above resolves your (optional) routing label to an actual served deployment — you never name a provider or a specific model id. What comes back instead:
served_class— the size the label resolved to (small/medium/large), the vocabulary you’re billed in.lane— which of the lanes below it ran in.usage— metered token counts.
llm.run’s response carries served_class and lane as top-level fields alongside the usual Anthropic response fields — read reply.served_class / reply.lane off the returned object. The typed models.run result does not yet surface servedClass/lane on its outcome — that disclosure is on the wire but not yet mapped into the SDK’s typed return value, so don’t rely on it from models.run today.
Deadline → lane mapping
Section titled “Deadline → lane mapping”On the surfaces that take deadlineMinutes (llm.submit, llm.createSession) — llm.run and the other synchronous surfaces are always run_now and take no deadline:
deadlineMinutes | Lane |
|---|---|
Omitted, or <= 0 | run_now — immediate, top priority |
<= 15 | priority |
<= 60 | standard |
> 60 | flex |
A longer deadline gives the platform more room to place your call cheaply; there’s no way to name a lane directly — you express your patience as a deadline, and the platform derives the lane from it.
Debugging a call
Section titled “Debugging a call”A step’s evidence (the exact request it sent and the exact response it got back) is part of the ordinary run audit — see Inspect a run for reading it from Run Inspector or sapiom_dev_agents_inspect, the same way you’d debug any other step.
For a programmatic escape hatch — full-fidelity input, output, error, and logs for one specific step attempt, at a raised size cap — call:
GET /v1/workflows/executions/:id/steps/:stepId/ioThe step-attempt row id is steps[].id on the execution detail (GET /v1/workflows/executions/:id), not the step’s name — a retried step writes a new row per attempt, each with its own id.
© 2026 Sapiom, Inc.