New:Socket for Asana Is Now Available.Learn more
Get Started

@sapiom/agent-core

Package Overview
Dependencies
Maintainers
4
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sapiom/agent-core - npm Package Compare versions

Comparing version
0.11.4
to
0.12.0
+43
dist/cjs/__tests__/step-io-detail.wire.fixture.json
{
"executionId": "exec_0001",
"id": "step_0002",
"stepName": "render",
"stepOrder": 1,
"attempt": 2,
"status": "failed",
"input": {
"items": 12
},
"output": null,
"error": {
"message": "template not found",
"trace": {
"frames": [
{
"function": "render",
"file": "src/steps/render.ts",
"line": 12,
"column": 11
},
{
"function": "run",
"file": "src/engine/runner.ts",
"line": 88,
"column": 5
}
],
"sourceMapped": true,
"raw": "Error: template not found\n at render (dist/steps/render.mjs:8:9)"
},
"traceUnavailableReason": null
},
"logs": [
{
"ts": "2026-01-01T00:00:46.000Z",
"level": "error",
"msg": "template not found"
}
],
"startedAt": "2026-01-01T00:00:45.000Z",
"finishedAt": "2026-01-01T00:02:30.000Z"
}
{
"executionId": "exec_0001",
"id": "step_0002",
"stepName": "render",
"stepOrder": 1,
"attempt": 2,
"status": "failed",
"input": {
"items": 12
},
"output": null,
"error": {
"message": "template not found",
"trace": {
"frames": [
{
"function": "render",
"file": "src/steps/render.ts",
"line": 12,
"column": 11
},
{
"function": "run",
"file": "src/engine/runner.ts",
"line": 88,
"column": 5
}
],
"sourceMapped": true,
"raw": "Error: template not found\n at render (dist/steps/render.mjs:8:9)"
},
"traceUnavailableReason": null
},
"logs": [
{
"ts": "2026-01-01T00:00:46.000Z",
"level": "error",
"msg": "template not found"
}
],
"startedAt": "2026-01-01T00:00:45.000Z",
"finishedAt": "2026-01-01T00:02:30.000Z"
}
+26
-0
# @sapiom/orchestration-core
## 0.12.0
### Minor Changes
- 00b8814: Adds `inspectStep(opts, client)` — fetches one step attempt's full-fidelity `input`/`output`/`error`/`logs` (`GET /executions/:id/steps/:stepId/io`), at a higher size cap than `inspect()`'s own `steps[]` bounds its aggregate read to. Reach for it when a step's fields on the execution projection look truncated.
- New exported `inspectStep`, `InspectStepOptions`, `StepIoDetail`, and `decodeStepIoDetail` (the tolerant decoder, mirroring `decodeExecutionProjection`'s degradation posture).
- `StepProjection` gains an optional `id` field (the step-attempt row id) — previously decoded from the wire but silently dropped; needed to call `inspectStep` from an `inspect()` read. `null` on a read from a server that doesn't report it — existing consumers are unaffected.
### Patch Changes
- 5a8eeea: `sapiom-agent-authoring` skill: teaches the LLM call-surface rule from step
code (`llm.run` one-shot vs `models.run` platform-driven loop vs `agents.run`
deployed-agent dispatch, with a worked example against the "reply with only
JSON" + string-parsing mistake) and settles the platform's naming
conventions (the overloaded "agent"/"run"/"task"/"session"/"dispatch" terms,
and "label" as the author-facing term for a `model:` value). Synced across
the canonical source, both scaffold templates, and the Claude Code plugin
copy. `@sapiom/tools`: corrected stale `agent.run`/`agent.coding` naming in
`models/index.ts`'s doc comments — the actual exported namespace is
`models`.
- Updated dependencies [5a8eeea]
- Updated dependencies [5a8eeea]
- @sapiom/tools@0.30.0
- @sapiom/agent@0.10.1
## 0.11.4

@@ -4,0 +30,0 @@

+2
-1

@@ -1,4 +0,5 @@

import type { CostNode, ExecutionProjection, ExecutionRef } from "./types.js";
import type { CostNode, ExecutionProjection, ExecutionRef, StepIoDetail } from "./types.js";
export declare function decodeCostNode(raw: unknown): CostNode | null;
export declare function decodeExecutionRef(raw: unknown): ExecutionRef;
export declare function decodeStepIoDetail(raw: unknown): StepIoDetail;
export declare function decodeExecutionProjection(raw: unknown): ExecutionProjection;

@@ -5,2 +5,3 @@ "use strict";

exports.decodeExecutionRef = decodeExecutionRef;
exports.decodeStepIoDetail = decodeStepIoDetail;
exports.decodeExecutionProjection = decodeExecutionProjection;

@@ -118,2 +119,3 @@ function isRecord(v) {

return {
id: strOrNull(r.id),
stepName: str("", r.stepName),

@@ -137,2 +139,19 @@ stepOrder: numOr(0, r.stepOrder),

}
function decodeStepIoDetail(raw) {
const r = rec(raw);
return {
executionId: str("", r.executionId),
id: str("", r.id),
stepName: str("", r.stepName),
stepOrder: numOr(0, r.stepOrder),
attempt: numOr(0, r.attempt),
status: str("", r.status),
input: r.input ?? null,
output: r.output ?? null,
error: decodeStepError(r.error),
logs: r.logs ?? null,
startedAt: strOrNull(r.startedAt),
finishedAt: strOrNull(r.finishedAt),
};
}
function decodeExecutionProjection(raw) {

@@ -139,0 +158,0 @@ const r = rec(raw);

@@ -23,7 +23,7 @@ export { AgentOperationError } from "./errors.js";

export type { RunOptions, RunResult } from "./run.js";
export type { ExecutionProjection, StepProjection, CostNode, SettleState, ExecutionRef, DispatchRef, StepError, StepErrorTrace, StepErrorFrame, StepEvent, SseEvent, SseEventType, } from "./types.js";
export type { ExecutionProjection, StepProjection, StepIoDetail, CostNode, SettleState, ExecutionRef, DispatchRef, StepError, StepErrorTrace, StepErrorFrame, StepEvent, SseEvent, SseEventType, } from "./types.js";
export { SSE_EVENT_TYPES } from "./types.js";
export { decodeExecutionProjection } from "./decode.js";
export { inspect, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export type { InspectOptions, InspectBuildOptions, InspectBuildResult, BuildDetail, WaitForExecutionOptions, WaitForExecutionResult, WaitStopReason, } from "./inspect.js";
export { decodeExecutionProjection, decodeStepIoDetail } from "./decode.js";
export { inspect, inspectStep, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export type { InspectOptions, InspectStepOptions, InspectBuildOptions, InspectBuildResult, BuildDetail, WaitForExecutionOptions, WaitForExecutionResult, WaitStopReason, } from "./inspect.js";
export { watchExecution, parseSseFrame, parseSseEvent } from "./watch.js";

@@ -30,0 +30,0 @@ export type { WatchExecutionOptions } from "./watch.js";

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadDefinition = exports.STUBS_FILE = exports.runLocalFromDir = exports.runLocal = exports.STUB_FILE_VERSION = exports.parseStubFile = exports.redactCredentials = exports.cloneRepo = exports.pushHead = exports.assertDeployable = exports.previewCron = exports.cancelSchedule = exports.getSchedule = exports.listSchedules = exports.createSchedule = exports.sendFeedback = exports.parseSignalPayload = exports.signal = exports.parseSseEvent = exports.parseSseFrame = exports.watchExecution = exports.isExecutionTerminal = exports.waitForExecution = exports.inspectBuild = exports.listExecutions = exports.inspect = exports.decodeExecutionProjection = exports.SSE_EVENT_TYPES = exports.parseJsonInput = exports.run = exports.deploy = exports.clone = exports.link = exports.bundleForDeploy = exports.check = exports.describeBundleFailure = exports.installProjectDependencies = exports.DEFAULT_TEMPLATE = exports.listTemplates = exports.resolveTemplate = exports.resolveVersions = exports.scaffold = exports.CONFIG_FILE = exports.writeConfig = exports.requireConfig = exports.readConfig = exports.DEFAULT_WORKFLOWS_HOST = exports.createClient = exports.GatewayClient = exports.AgentOperationError = void 0;
exports.LocalStubDispatcher = void 0;
exports.runLocalFromDir = exports.runLocal = exports.STUB_FILE_VERSION = exports.parseStubFile = exports.redactCredentials = exports.cloneRepo = exports.pushHead = exports.assertDeployable = exports.previewCron = exports.cancelSchedule = exports.getSchedule = exports.listSchedules = exports.createSchedule = exports.sendFeedback = exports.parseSignalPayload = exports.signal = exports.parseSseEvent = exports.parseSseFrame = exports.watchExecution = exports.isExecutionTerminal = exports.waitForExecution = exports.inspectBuild = exports.listExecutions = exports.inspectStep = exports.inspect = exports.decodeStepIoDetail = exports.decodeExecutionProjection = exports.SSE_EVENT_TYPES = exports.parseJsonInput = exports.run = exports.deploy = exports.clone = exports.link = exports.bundleForDeploy = exports.check = exports.describeBundleFailure = exports.installProjectDependencies = exports.DEFAULT_TEMPLATE = exports.listTemplates = exports.resolveTemplate = exports.resolveVersions = exports.scaffold = exports.CONFIG_FILE = exports.writeConfig = exports.requireConfig = exports.readConfig = exports.DEFAULT_WORKFLOWS_HOST = exports.createClient = exports.GatewayClient = exports.AgentOperationError = void 0;
exports.LocalStubDispatcher = exports.loadDefinition = exports.STUBS_FILE = void 0;
var errors_js_1 = require("./errors.js");

@@ -43,4 +43,6 @@ Object.defineProperty(exports, "AgentOperationError", { enumerable: true, get: function () { return errors_js_1.AgentOperationError; } });

Object.defineProperty(exports, "decodeExecutionProjection", { enumerable: true, get: function () { return decode_js_1.decodeExecutionProjection; } });
Object.defineProperty(exports, "decodeStepIoDetail", { enumerable: true, get: function () { return decode_js_1.decodeStepIoDetail; } });
var inspect_js_1 = require("./inspect.js");
Object.defineProperty(exports, "inspect", { enumerable: true, get: function () { return inspect_js_1.inspect; } });
Object.defineProperty(exports, "inspectStep", { enumerable: true, get: function () { return inspect_js_1.inspectStep; } });
Object.defineProperty(exports, "listExecutions", { enumerable: true, get: function () { return inspect_js_1.listExecutions; } });

@@ -47,0 +49,0 @@ Object.defineProperty(exports, "inspectBuild", { enumerable: true, get: function () { return inspect_js_1.inspectBuild; } });

import { GatewayClient } from "./client.js";
import type { ExecutionProjection, ExecutionRef, SseEvent } from "./types.js";
import type { ExecutionProjection, ExecutionRef, SseEvent, StepIoDetail } from "./types.js";
export interface BuildDetail {

@@ -12,2 +12,7 @@ id?: string;

export declare function inspect(opts: InspectOptions, client: GatewayClient): Promise<ExecutionProjection>;
export interface InspectStepOptions {
executionId: string;
stepExecutionId: string;
}
export declare function inspectStep(opts: InspectStepOptions, client: GatewayClient): Promise<StepIoDetail>;
export declare function isExecutionTerminal(status: string): boolean;

@@ -14,0 +19,0 @@ export type WaitStopReason = "terminal" | "needs-signal" | "timeout";

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspect = inspect;
exports.inspectStep = inspectStep;
exports.isExecutionTerminal = isExecutionTerminal;

@@ -14,2 +15,6 @@ exports.waitForExecution = waitForExecution;

}
async function inspectStep(opts, client) {
const raw = await client.get(`/executions/${opts.executionId}/steps/${opts.stepExecutionId}/io`);
return (0, decode_js_1.decodeStepIoDetail)(raw);
}
const TERMINAL_STATUSES = new Set([

@@ -16,0 +21,0 @@ "completed",

@@ -51,2 +51,3 @@ export type SettleState = "pending" | "settling" | "final";

export interface StepProjection {
id?: string | null;
stepName: string;

@@ -101,1 +102,15 @@ stepOrder: number;

}
export interface StepIoDetail {
executionId: string;
id: string;
stepName: string;
stepOrder: number;
attempt: number;
status: string;
input: unknown;
output: unknown;
error: StepError | null;
logs: unknown;
startedAt: string | null;
finishedAt: string | null;
}
export declare const VERSION_FALLBACK: {
readonly agent: "0.10.0";
readonly tools: "0.29.0";
readonly agent: "0.10.1";
readonly tools: "0.30.0";
};

@@ -5,4 +5,4 @@ "use strict";

exports.VERSION_FALLBACK = {
agent: "0.10.0",
tools: "0.29.0",
agent: "0.10.1",
tools: "0.30.0",
};

@@ -1,4 +0,5 @@

import type { CostNode, ExecutionProjection, ExecutionRef } from "./types.js";
import type { CostNode, ExecutionProjection, ExecutionRef, StepIoDetail } from "./types.js";
export declare function decodeCostNode(raw: unknown): CostNode | null;
export declare function decodeExecutionRef(raw: unknown): ExecutionRef;
export declare function decodeStepIoDetail(raw: unknown): StepIoDetail;
export declare function decodeExecutionProjection(raw: unknown): ExecutionProjection;

@@ -112,2 +112,3 @@ function isRecord(v) {

return {
id: strOrNull(r.id),
stepName: str("", r.stepName),

@@ -131,2 +132,19 @@ stepOrder: numOr(0, r.stepOrder),

}
export function decodeStepIoDetail(raw) {
const r = rec(raw);
return {
executionId: str("", r.executionId),
id: str("", r.id),
stepName: str("", r.stepName),
stepOrder: numOr(0, r.stepOrder),
attempt: numOr(0, r.attempt),
status: str("", r.status),
input: r.input ?? null,
output: r.output ?? null,
error: decodeStepError(r.error),
logs: r.logs ?? null,
startedAt: strOrNull(r.startedAt),
finishedAt: strOrNull(r.finishedAt),
};
}
export function decodeExecutionProjection(raw) {

@@ -133,0 +151,0 @@ const r = rec(raw);

@@ -23,7 +23,7 @@ export { AgentOperationError } from "./errors.js";

export type { RunOptions, RunResult } from "./run.js";
export type { ExecutionProjection, StepProjection, CostNode, SettleState, ExecutionRef, DispatchRef, StepError, StepErrorTrace, StepErrorFrame, StepEvent, SseEvent, SseEventType, } from "./types.js";
export type { ExecutionProjection, StepProjection, StepIoDetail, CostNode, SettleState, ExecutionRef, DispatchRef, StepError, StepErrorTrace, StepErrorFrame, StepEvent, SseEvent, SseEventType, } from "./types.js";
export { SSE_EVENT_TYPES } from "./types.js";
export { decodeExecutionProjection } from "./decode.js";
export { inspect, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export type { InspectOptions, InspectBuildOptions, InspectBuildResult, BuildDetail, WaitForExecutionOptions, WaitForExecutionResult, WaitStopReason, } from "./inspect.js";
export { decodeExecutionProjection, decodeStepIoDetail } from "./decode.js";
export { inspect, inspectStep, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export type { InspectOptions, InspectStepOptions, InspectBuildOptions, InspectBuildResult, BuildDetail, WaitForExecutionOptions, WaitForExecutionResult, WaitStopReason, } from "./inspect.js";
export { watchExecution, parseSseFrame, parseSseEvent } from "./watch.js";

@@ -30,0 +30,0 @@ export type { WatchExecutionOptions } from "./watch.js";

@@ -14,4 +14,4 @@ export { AgentOperationError } from "./errors.js";

export { SSE_EVENT_TYPES } from "./types.js";
export { decodeExecutionProjection } from "./decode.js";
export { inspect, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export { decodeExecutionProjection, decodeStepIoDetail } from "./decode.js";
export { inspect, inspectStep, listExecutions, inspectBuild, waitForExecution, isExecutionTerminal, } from "./inspect.js";
export { watchExecution, parseSseFrame, parseSseEvent } from "./watch.js";

@@ -18,0 +18,0 @@ export { signal, parseSignalPayload } from "./signal.js";

import { GatewayClient } from "./client.js";
import type { ExecutionProjection, ExecutionRef, SseEvent } from "./types.js";
import type { ExecutionProjection, ExecutionRef, SseEvent, StepIoDetail } from "./types.js";
export interface BuildDetail {

@@ -12,2 +12,7 @@ id?: string;

export declare function inspect(opts: InspectOptions, client: GatewayClient): Promise<ExecutionProjection>;
export interface InspectStepOptions {
executionId: string;
stepExecutionId: string;
}
export declare function inspectStep(opts: InspectStepOptions, client: GatewayClient): Promise<StepIoDetail>;
export declare function isExecutionTerminal(status: string): boolean;

@@ -14,0 +19,0 @@ export type WaitStopReason = "terminal" | "needs-signal" | "timeout";

@@ -1,2 +0,2 @@

import { decodeExecutionProjection, decodeExecutionRef } from "./decode.js";
import { decodeExecutionProjection, decodeExecutionRef, decodeStepIoDetail, } from "./decode.js";
import { watchExecution } from "./watch.js";

@@ -7,2 +7,6 @@ export async function inspect(opts, client) {

}
export async function inspectStep(opts, client) {
const raw = await client.get(`/executions/${opts.executionId}/steps/${opts.stepExecutionId}/io`);
return decodeStepIoDetail(raw);
}
const TERMINAL_STATUSES = new Set([

@@ -9,0 +13,0 @@ "completed",

@@ -51,2 +51,3 @@ export type SettleState = "pending" | "settling" | "final";

export interface StepProjection {
id?: string | null;
stepName: string;

@@ -101,1 +102,15 @@ stepOrder: number;

}
export interface StepIoDetail {
executionId: string;
id: string;
stepName: string;
stepOrder: number;
attempt: number;
status: string;
input: unknown;
output: unknown;
error: StepError | null;
logs: unknown;
startedAt: string | null;
finishedAt: string | null;
}
export declare const VERSION_FALLBACK: {
readonly agent: "0.10.0";
readonly tools: "0.29.0";
readonly agent: "0.10.1";
readonly tools: "0.30.0";
};
export const VERSION_FALLBACK = {
agent: "0.10.0",
tools: "0.29.0",
agent: "0.10.1",
tools: "0.30.0",
};
{
"name": "@sapiom/agent-core",
"version": "0.11.4",
"version": "0.12.0",
"description": "Pure, stateless core functions for scaffolding, validating, and operating Sapiom agents — shared by the CLI and MCP packages.",

@@ -40,6 +40,6 @@ "license": "MIT",

"esbuild": "^0.28.1",
"@sapiom/agent": "^0.10.0",
"@sapiom/agent": "^0.10.1",
"@sapiom/agent-runtime": "^0.5.0",
"@sapiom/analytics-core": "^0.2.1",
"@sapiom/tools": "^0.29.0"
"@sapiom/tools": "^0.30.0"
},

@@ -46,0 +46,0 @@ "devDependencies": {

@@ -308,2 +308,104 @@ ---

## Calling LLMs from Steps
Three DIFFERENT capabilities call an LLM from step code — picking the wrong one for the
job is the most common mistake in authored agents:
| Capability | Use for | Never for |
| ------------------------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `ctx.sapiom.llm.run` | ONE LLM call — summarize, extract, classify, one-shot generate | A multi-turn task, or anything needing its own tool-calling loop |
| `ctx.sapiom.models.run` | A platform-driven multi-turn reasoning + tool-calling loop (minutes, not seconds). `models.coding.run` for sandboxed coding tasks. | A one-shot completion — it will loop and overthink |
| `ctx.sapiom.agents.run` | Dispatching a DEPLOYED agent by slug — composing systems from small deployed agents | Anything that isn't itself a deployed agent |
**⚠️ The mistake to never repeat:** sending single-shot, fixed-shape intent through
`models.run`'s multi-turn loop instead of one `llm.run` call. The symptom: the run takes
far longer than the task needs, "overthinks" a trivial extraction, and — if the caller just
grabs the first content block hoping it's the answer — returns a `thinking` block instead.
### Worked example: a trivial fixed-shape-JSON task
**Wrong** — one-shot intent sent through the multi-turn loop, then the answer string-parsed
out of free text:
```typescript
// DON'T: models.run for a one-shot extraction — it loops and overthinks, and
// "reply with only JSON" is brittle (prose, invalid JSON, or a leading
// `thinking` block all break a naive `JSON.parse(content[0])`).
const run = await ctx.sapiom.models.run({
prompt: `Reply with ONLY JSON: {"priority": "...", "category": "..."} for: ${input.text}`,
});
const parsed = JSON.parse(run.output ?? "{}"); // brittle, and pays for a reasoning loop
```
**Right** — `llm.run` with `output` for the fixed shape, read back with `structuredOf`
(forced tool-use output has no `text` block — the result lives in `tool_use`, never
`content[0]`):
```typescript
const response = await ctx.sapiom.llm.run({
request: {
messages: [{ role: "user", content: `Classify this support ticket: ${input.text}` }],
max_tokens: 256,
},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
output: {
name: "classify_ticket",
schema: {
type: "object",
properties: {
priority: { type: "string", enum: ["low", "medium", "high"] },
category: { type: "string" },
},
required: ["priority", "category"],
},
},
});
const { priority, category } = ctx.sapiom.llm.structuredOf<{
priority: string;
category: string;
}>(response)!;
```
`output` automates the forced tool call and its `tool_choice` wiring; `structuredOf` reads
the result back out. For a **plain-text** reply instead, use
`ctx.sapiom.llm.textOf(response)` — it reads only the `type === 'text'` block, skipping a
`thinking` block that may precede it.
### The label rule
**You never pick a model.** Every `model`/`label` field across `llm.*`, `models.run`, and
`models.coding.run` takes a **routing label** (e.g. `"smart"`) that the platform resolves
against its configured label set — never a raw provider model id (never honored, on any
surface). Omit it entirely to let the platform choose (the recommended default); pass
`"smart"` if you must pin. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing
lane it executed in) — never a model or provider id.
### Debugging a run
Find the run in the dashboard's run detail view, find the suspicious step's row id, then
open the **Run Inspector** for that step's full-fidelity input/output/error/logs.
Full guide: [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface).
## Naming Conventions
Several words are overloaded across this platform. Know which meaning a given context
uses — conflating two costs you a wrong capability choice, not just a wrong word:
| Term | Meaning(s) on this platform |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **agent** | (1) `@sapiom/agent` — this authoring framework (`defineAgent`, this skill). (2) A **deployed agent** — one project's compiled definition, dispatched by `ctx.sapiom.agents.run`/`launch` (addressed by its slug via `AgentRunSpec.definition`) — the customer-facing name is always "agent." (3) `ctx.sapiom.models.run`'s managed multi-turn loop — a managed loop the platform runs for you; you call it, you never author it. (4) A **Claude Code subagent** — an unrelated feature of the coding tool itself, not part of the Sapiom SDK. |
| **run** | (1) `ctx.sapiom.llm.run` — one synchronous LLM call. (2) `ctx.sapiom.models.run` / `agents.run` — `launch()` + `wait()`, blocking until terminal (vs. `launch()` alone, which returns a pausable handle). (3) An execution instance/row of any of the above — the thing you inspect/debug. (4) The dashboard's Run button / Local Run / Prod Run (Studio UI actions, not an API call). |
| **task** | `CodingRunSpec.task` — the coding agent's prompt-equivalent field. Deliberately not called `prompt`: it's handed to a sandboxed coding agent, not a bare LLM call. |
| **session** | (1) `ctx.sapiom.llm.createSession`/`callSession` — reserved LLM capacity accepting repeated drop-in calls until its TTL/budget ends it (replacing the deferred `submit`/`redeem` lane). (2) A Studio harness terminal session — unrelated, no LLM-capacity semantics. |
| **dispatch** | The structural contract (`DispatchHandle`) a long-running capability's `launch()` handle satisfies so a step can `pauseUntilSignal(handle, …)` and resume on completion. Every dispatched capability (coding, `models.run`, `agents.run`, more later) shares this ONE contract — "dispatch" always means this pattern, never anything else. |
| **label** | The author-facing term for a `model:`/`label:` *input* value (e.g. `"smart"`) — never a raw provider model id (never honored, on any surface). Not a contradiction that a result's `servedClass` field says "class": that field *reports* the billing class the platform resolved your label to — it's a disclosure field, not author-facing input vocabulary. You still write `label`; the platform still reports back `servedClass`. |
**The rule new capabilities must follow:** don't re-overload "agent" or "run" further. If a
new capability needs its own verb, name it something else (`dispatch`, `launch`, `submit`,
`create*`) rather than adding a sixth meaning to a word that already has five.
## Failure Handling & Retries

@@ -526,2 +628,3 @@

| [Capabilities](https://docs.sapiom.ai/capabilities) | The full `ctx.sapiom.*` catalog with pricing |
| [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface) | `llm.run` vs `models.run` vs `agents.run` — which to call and why |
| `AGENTS.md` in your scaffold | The quick in-project reference |

@@ -308,2 +308,104 @@ ---

## Calling LLMs from Steps
Three DIFFERENT capabilities call an LLM from step code — picking the wrong one for the
job is the most common mistake in authored agents:
| Capability | Use for | Never for |
| ------------------------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `ctx.sapiom.llm.run` | ONE LLM call — summarize, extract, classify, one-shot generate | A multi-turn task, or anything needing its own tool-calling loop |
| `ctx.sapiom.models.run` | A platform-driven multi-turn reasoning + tool-calling loop (minutes, not seconds). `models.coding.run` for sandboxed coding tasks. | A one-shot completion — it will loop and overthink |
| `ctx.sapiom.agents.run` | Dispatching a DEPLOYED agent by slug — composing systems from small deployed agents | Anything that isn't itself a deployed agent |
**⚠️ The mistake to never repeat:** sending single-shot, fixed-shape intent through
`models.run`'s multi-turn loop instead of one `llm.run` call. The symptom: the run takes
far longer than the task needs, "overthinks" a trivial extraction, and — if the caller just
grabs the first content block hoping it's the answer — returns a `thinking` block instead.
### Worked example: a trivial fixed-shape-JSON task
**Wrong** — one-shot intent sent through the multi-turn loop, then the answer string-parsed
out of free text:
```typescript
// DON'T: models.run for a one-shot extraction — it loops and overthinks, and
// "reply with only JSON" is brittle (prose, invalid JSON, or a leading
// `thinking` block all break a naive `JSON.parse(content[0])`).
const run = await ctx.sapiom.models.run({
prompt: `Reply with ONLY JSON: {"priority": "...", "category": "..."} for: ${input.text}`,
});
const parsed = JSON.parse(run.output ?? "{}"); // brittle, and pays for a reasoning loop
```
**Right** — `llm.run` with `output` for the fixed shape, read back with `structuredOf`
(forced tool-use output has no `text` block — the result lives in `tool_use`, never
`content[0]`):
```typescript
const response = await ctx.sapiom.llm.run({
request: {
messages: [{ role: "user", content: `Classify this support ticket: ${input.text}` }],
max_tokens: 256,
},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
output: {
name: "classify_ticket",
schema: {
type: "object",
properties: {
priority: { type: "string", enum: ["low", "medium", "high"] },
category: { type: "string" },
},
required: ["priority", "category"],
},
},
});
const { priority, category } = ctx.sapiom.llm.structuredOf<{
priority: string;
category: string;
}>(response)!;
```
`output` automates the forced tool call and its `tool_choice` wiring; `structuredOf` reads
the result back out. For a **plain-text** reply instead, use
`ctx.sapiom.llm.textOf(response)` — it reads only the `type === 'text'` block, skipping a
`thinking` block that may precede it.
### The label rule
**You never pick a model.** Every `model`/`label` field across `llm.*`, `models.run`, and
`models.coding.run` takes a **routing label** (e.g. `"smart"`) that the platform resolves
against its configured label set — never a raw provider model id (never honored, on any
surface). Omit it entirely to let the platform choose (the recommended default); pass
`"smart"` if you must pin. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing
lane it executed in) — never a model or provider id.
### Debugging a run
Find the run in the dashboard's run detail view, find the suspicious step's row id, then
open the **Run Inspector** for that step's full-fidelity input/output/error/logs.
Full guide: [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface).
## Naming Conventions
Several words are overloaded across this platform. Know which meaning a given context
uses — conflating two costs you a wrong capability choice, not just a wrong word:
| Term | Meaning(s) on this platform |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **agent** | (1) `@sapiom/agent` — this authoring framework (`defineAgent`, this skill). (2) A **deployed agent** — one project's compiled definition, dispatched by `ctx.sapiom.agents.run`/`launch` (addressed by its slug via `AgentRunSpec.definition`) — the customer-facing name is always "agent." (3) `ctx.sapiom.models.run`'s managed multi-turn loop — a managed loop the platform runs for you; you call it, you never author it. (4) A **Claude Code subagent** — an unrelated feature of the coding tool itself, not part of the Sapiom SDK. |
| **run** | (1) `ctx.sapiom.llm.run` — one synchronous LLM call. (2) `ctx.sapiom.models.run` / `agents.run` — `launch()` + `wait()`, blocking until terminal (vs. `launch()` alone, which returns a pausable handle). (3) An execution instance/row of any of the above — the thing you inspect/debug. (4) The dashboard's Run button / Local Run / Prod Run (Studio UI actions, not an API call). |
| **task** | `CodingRunSpec.task` — the coding agent's prompt-equivalent field. Deliberately not called `prompt`: it's handed to a sandboxed coding agent, not a bare LLM call. |
| **session** | (1) `ctx.sapiom.llm.createSession`/`callSession` — reserved LLM capacity accepting repeated drop-in calls until its TTL/budget ends it (replacing the deferred `submit`/`redeem` lane). (2) A Studio harness terminal session — unrelated, no LLM-capacity semantics. |
| **dispatch** | The structural contract (`DispatchHandle`) a long-running capability's `launch()` handle satisfies so a step can `pauseUntilSignal(handle, …)` and resume on completion. Every dispatched capability (coding, `models.run`, `agents.run`, more later) shares this ONE contract — "dispatch" always means this pattern, never anything else. |
| **label** | The author-facing term for a `model:`/`label:` *input* value (e.g. `"smart"`) — never a raw provider model id (never honored, on any surface). Not a contradiction that a result's `servedClass` field says "class": that field *reports* the billing class the platform resolved your label to — it's a disclosure field, not author-facing input vocabulary. You still write `label`; the platform still reports back `servedClass`. |
**The rule new capabilities must follow:** don't re-overload "agent" or "run" further. If a
new capability needs its own verb, name it something else (`dispatch`, `launch`, `submit`,
`create*`) rather than adding a sixth meaning to a word that already has five.
## Failure Handling & Retries

@@ -526,2 +628,3 @@

| [Capabilities](https://docs.sapiom.ai/capabilities) | The full `ctx.sapiom.*` catalog with pricing |
| [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface) | `llm.run` vs `models.run` vs `agents.run` — which to call and why |
| `AGENTS.md` in your scaffold | The quick in-project reference |

@@ -308,2 +308,104 @@ ---

## Calling LLMs from Steps
Three DIFFERENT capabilities call an LLM from step code — picking the wrong one for the
job is the most common mistake in authored agents:
| Capability | Use for | Never for |
| ------------------------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `ctx.sapiom.llm.run` | ONE LLM call — summarize, extract, classify, one-shot generate | A multi-turn task, or anything needing its own tool-calling loop |
| `ctx.sapiom.models.run` | A platform-driven multi-turn reasoning + tool-calling loop (minutes, not seconds). `models.coding.run` for sandboxed coding tasks. | A one-shot completion — it will loop and overthink |
| `ctx.sapiom.agents.run` | Dispatching a DEPLOYED agent by slug — composing systems from small deployed agents | Anything that isn't itself a deployed agent |
**⚠️ The mistake to never repeat:** sending single-shot, fixed-shape intent through
`models.run`'s multi-turn loop instead of one `llm.run` call. The symptom: the run takes
far longer than the task needs, "overthinks" a trivial extraction, and — if the caller just
grabs the first content block hoping it's the answer — returns a `thinking` block instead.
### Worked example: a trivial fixed-shape-JSON task
**Wrong** — one-shot intent sent through the multi-turn loop, then the answer string-parsed
out of free text:
```typescript
// DON'T: models.run for a one-shot extraction — it loops and overthinks, and
// "reply with only JSON" is brittle (prose, invalid JSON, or a leading
// `thinking` block all break a naive `JSON.parse(content[0])`).
const run = await ctx.sapiom.models.run({
prompt: `Reply with ONLY JSON: {"priority": "...", "category": "..."} for: ${input.text}`,
});
const parsed = JSON.parse(run.output ?? "{}"); // brittle, and pays for a reasoning loop
```
**Right** — `llm.run` with `output` for the fixed shape, read back with `structuredOf`
(forced tool-use output has no `text` block — the result lives in `tool_use`, never
`content[0]`):
```typescript
const response = await ctx.sapiom.llm.run({
request: {
messages: [{ role: "user", content: `Classify this support ticket: ${input.text}` }],
max_tokens: 256,
},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
output: {
name: "classify_ticket",
schema: {
type: "object",
properties: {
priority: { type: "string", enum: ["low", "medium", "high"] },
category: { type: "string" },
},
required: ["priority", "category"],
},
},
});
const { priority, category } = ctx.sapiom.llm.structuredOf<{
priority: string;
category: string;
}>(response)!;
```
`output` automates the forced tool call and its `tool_choice` wiring; `structuredOf` reads
the result back out. For a **plain-text** reply instead, use
`ctx.sapiom.llm.textOf(response)` — it reads only the `type === 'text'` block, skipping a
`thinking` block that may precede it.
### The label rule
**You never pick a model.** Every `model`/`label` field across `llm.*`, `models.run`, and
`models.coding.run` takes a **routing label** (e.g. `"smart"`) that the platform resolves
against its configured label set — never a raw provider model id (never honored, on any
surface). Omit it entirely to let the platform choose (the recommended default); pass
`"smart"` if you must pin. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing
lane it executed in) — never a model or provider id.
### Debugging a run
Find the run in the dashboard's run detail view, find the suspicious step's row id, then
open the **Run Inspector** for that step's full-fidelity input/output/error/logs.
Full guide: [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface).
## Naming Conventions
Several words are overloaded across this platform. Know which meaning a given context
uses — conflating two costs you a wrong capability choice, not just a wrong word:
| Term | Meaning(s) on this platform |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **agent** | (1) `@sapiom/agent` — this authoring framework (`defineAgent`, this skill). (2) A **deployed agent** — one project's compiled definition, dispatched by `ctx.sapiom.agents.run`/`launch` (addressed by its slug via `AgentRunSpec.definition`) — the customer-facing name is always "agent." (3) `ctx.sapiom.models.run`'s managed multi-turn loop — a managed loop the platform runs for you; you call it, you never author it. (4) A **Claude Code subagent** — an unrelated feature of the coding tool itself, not part of the Sapiom SDK. |
| **run** | (1) `ctx.sapiom.llm.run` — one synchronous LLM call. (2) `ctx.sapiom.models.run` / `agents.run` — `launch()` + `wait()`, blocking until terminal (vs. `launch()` alone, which returns a pausable handle). (3) An execution instance/row of any of the above — the thing you inspect/debug. (4) The dashboard's Run button / Local Run / Prod Run (Studio UI actions, not an API call). |
| **task** | `CodingRunSpec.task` — the coding agent's prompt-equivalent field. Deliberately not called `prompt`: it's handed to a sandboxed coding agent, not a bare LLM call. |
| **session** | (1) `ctx.sapiom.llm.createSession`/`callSession` — reserved LLM capacity accepting repeated drop-in calls until its TTL/budget ends it (replacing the deferred `submit`/`redeem` lane). (2) A Studio harness terminal session — unrelated, no LLM-capacity semantics. |
| **dispatch** | The structural contract (`DispatchHandle`) a long-running capability's `launch()` handle satisfies so a step can `pauseUntilSignal(handle, …)` and resume on completion. Every dispatched capability (coding, `models.run`, `agents.run`, more later) shares this ONE contract — "dispatch" always means this pattern, never anything else. |
| **label** | The author-facing term for a `model:`/`label:` *input* value (e.g. `"smart"`) — never a raw provider model id (never honored, on any surface). Not a contradiction that a result's `servedClass` field says "class": that field *reports* the billing class the platform resolved your label to — it's a disclosure field, not author-facing input vocabulary. You still write `label`; the platform still reports back `servedClass`. |
**The rule new capabilities must follow:** don't re-overload "agent" or "run" further. If a
new capability needs its own verb, name it something else (`dispatch`, `launch`, `submit`,
`create*`) rather than adding a sixth meaning to a word that already has five.
## Failure Handling & Retries

@@ -526,2 +628,3 @@

| [Capabilities](https://docs.sapiom.ai/capabilities) | The full `ctx.sapiom.*` catalog with pricing |
| [Choose a call surface](https://docs.sapiom.ai/guides/choose-a-call-surface) | `llm.run` vs `models.run` vs `agents.run` — which to call and why |
| `AGENTS.md` in your scaffold | The quick in-project reference |

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet