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.12.2
to
0.13.0
+33
-0
CHANGELOG.md
# @sapiom/orchestration-core
## 0.13.0
### Minor Changes
- 555475d: Make `ctx.shared.set()` on the SDK's `InMemoryContextStore` an atomic
whole-snapshot quota gate. `runLocal` now constructs this store with step
context; hosts that have not adopted this store version may enforce the contract
only at execution boundaries during rollout. The store measures the complete
candidate as compact JSON UTF-8 before committing it, so oversized writes throw
`CTX_SHARED_SIZE_LIMIT_EXCEEDED` and retain the previous state.
Publish the bounded `CTX_SHARED_SERIALIZATION_FAILED` terminal error contract
for circular references, BigInt values, throwing `toJSON` methods, and other
`JSON.stringify` failures. Completion serializers and runners recognize the
new code through the closed platform-error registry and fail the step on its
current attempt without retrying. Ordinary JSON omission and coercion semantics
remain unchanged.
**Breaking:** `InMemoryContextStore.set()` can now throw synchronously for an
oversized or unserializable candidate. Existing local agents that wrote such
state now fail non-retryably on attempt 0. Migrate bulk state to durable storage
and keep only compact, JSON-compatible values or references in `ctx.shared`. If
a store is seeded with legacy invalid state, replace an offending key with a
value small enough to bring the complete candidate within the quota;
`TypedContextStore` has no `delete()` operation.
### Patch Changes
- 52efab3: `sapiom-agent-authoring` skill + scaffold `AGENTS.md`: system-design teaching for multi-stage builds. New "Composing Deployed Agents" section — one agent per PROJECT; a multi-stage system is several small projects composed via `ctx.sapiom.agents.run`, with a worked coordinator example — and the scaffold's "keep exactly one `defineAgent` export" rule now says so inline, so it reads as a per-project rule rather than a design instruction to inline every stage. Also drops the "pass `smart` if you must pin" no-op from the label rule (omitting `model` is the recommendation; `smart` already is the default).
- Updated dependencies [555475d]
- @sapiom/agent@0.12.0
- @sapiom/agent-runtime@0.7.0
## 0.12.2

@@ -4,0 +37,0 @@

+3
-1

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

});
const sharedStore = new agent_1.InMemoryContextStore(request.shared);
const sharedStore = new agent_1.InMemoryContextStore(request.shared, {
stepName: request.stepName,
});
const overrides = (this.stubs.steps[request.stepName] ?? {});

@@ -56,0 +58,0 @@ let usedKeys = this.usedKeysByStep.get(request.stepName);

export declare const VERSION_FALLBACK: {
readonly agent: "0.11.0";
readonly agent: "0.12.0";
readonly tools: "0.31.0";
};

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

exports.VERSION_FALLBACK = {
agent: "0.11.0",
agent: "0.12.0",
tools: "0.31.0",
};

@@ -50,3 +50,5 @@ import { InMemoryContextStore, StepInputValidationError, } from "@sapiom/agent";

});
const sharedStore = new InMemoryContextStore(request.shared);
const sharedStore = new InMemoryContextStore(request.shared, {
stepName: request.stepName,
});
const overrides = (this.stubs.steps[request.stepName] ?? {});

@@ -53,0 +55,0 @@ let usedKeys = this.usedKeysByStep.get(request.stepName);

export declare const VERSION_FALLBACK: {
readonly agent: "0.11.0";
readonly agent: "0.12.0";
readonly tools: "0.31.0";
};
export const VERSION_FALLBACK = {
agent: "0.11.0",
agent: "0.12.0",
tools: "0.31.0",
};
{
"name": "@sapiom/agent-core",
"version": "0.12.2",
"version": "0.13.0",
"description": "Pure, stateless core functions for scaffolding, validating, and operating Sapiom agents — shared by the CLI and MCP packages.",

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

"esbuild": "^0.28.1",
"@sapiom/agent": "^0.11.0",
"@sapiom/agent-runtime": "^0.6.0",
"@sapiom/agent": "^0.12.0",
"@sapiom/agent-runtime": "^0.7.0",
"@sapiom/analytics-core": "^0.2.1",

@@ -44,0 +44,0 @@ "@sapiom/tools": "^0.31.0"

@@ -243,5 +243,14 @@ ---

documents, or research data in durable storage and carry only the ID/reference.
The SDK contract does not by itself make `ctx.shared.set()` a synchronous size
gate. Hosts enforce it at execution boundaries, and older hosts may temporarily
enforce a smaller legacy limit during rollout.
Hosts that construct this SDK version's `InMemoryContextStore` get setter-time
validation: `ctx.shared.set()` synchronously measures the complete candidate
snapshot before committing it. An oversized or unserializable write throws and
leaves the previous snapshot unchanged. Measurement follows `JSON.stringify`:
circular references, BigInt values, and throwing `toJSON` methods fail, while
values JSON normally omits or coerces retain those semantics. Hosts that have
not adopted this store version may temporarily enforce the contract only at
execution boundaries during rollout. If you catch a failure, use the exported
structural payload guards rather than `instanceof`, because host and definition
bundles can contain separate SDK copies. There is no `delete()` operation; to
recover from legacy invalid state, replace an offending key with a compact,
JSON-compatible value that brings the complete candidate within the quota.

@@ -350,4 +359,5 @@ **A step's `run(input, ctx)` first argument is its inbound input** — the entry input at the

},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
// No `model` — omit it and let the platform choose (recommended; passing
// "smart" would be a no-op — it already is the default — and a raw provider
// model id is never honored).
output: {

@@ -382,4 +392,4 @@ name: "classify_ticket",

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
surface). Omit it entirely to let the platform choose (the recommended default) — passing
`"smart"` is a no-op: it already is the default. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing

@@ -395,2 +405,49 @@ lane it executed in) — never a model or provider id.

## Composing Deployed Agents (System Design)
**One agent per project — but a system is several projects.** The "keep exactly one
`defineAgent(...)` export" rule is a statement about a PROJECT, not about your system. A
multi-stage system ("research → write script → voiceover → assemble → post") is not one
big agent with five steps: it is several small agents, each its own project, deployed
separately, composed by a thin coordinator that dispatches them by slug. A small deployed
agent is independently testable, versioned, and reusable from more than one caller; a
monolith couples every stage into a single deploy unit and step graph, so any stage change
redeploys — and risks — all of them.
**Wrong** — one project inlining every stage as a step:
```typescript
// DON'T: video-pipeline/index.ts with research, script, voiceover, assemble,
// post as five steps of ONE defineAgent. No stage is reusable or
// independently testable, and every stage change redeploys all five.
```
**Right** — each stage its own deployed project; a coordinator composes them:
```typescript
// research-topic/, write-script/, generate-voiceover/, assemble-video/,
// post-video/: five small projects, each deployed on its own slug.
// video-pipeline/ is then just the coordinator:
const research = await ctx.sapiom.agents.run({
definition: "research-topic", // the deployed child's slug
input: { topic: input.topic },
});
// agents.run resolves on ANY terminal status (completed | failed | cancelled)
// and does NOT throw — a non-completed child is data the coordinator must
// branch on, or a failed stage silently feeds `null` downstream.
if (research.status !== "completed") {
// (fail() requires this step to declare canFail: true)
return fail(`research-topic ${research.status}: ${String(research.error)}`);
}
const script = await ctx.sapiom.agents.run({
definition: "write-script",
input: { research: research.output },
});
// …and so on. Use agents.launch + pauseUntilSignal for a long-running child
// so the coordinator's step doesn't time out.
```
Building a system in one session? Scaffold the stages as separate projects and deploy
bottom-up — children first, the coordinator last (it dispatches them by their slugs).
## Naming Conventions

@@ -397,0 +454,0 @@

@@ -243,5 +243,14 @@ ---

documents, or research data in durable storage and carry only the ID/reference.
The SDK contract does not by itself make `ctx.shared.set()` a synchronous size
gate. Hosts enforce it at execution boundaries, and older hosts may temporarily
enforce a smaller legacy limit during rollout.
Hosts that construct this SDK version's `InMemoryContextStore` get setter-time
validation: `ctx.shared.set()` synchronously measures the complete candidate
snapshot before committing it. An oversized or unserializable write throws and
leaves the previous snapshot unchanged. Measurement follows `JSON.stringify`:
circular references, BigInt values, and throwing `toJSON` methods fail, while
values JSON normally omits or coerces retain those semantics. Hosts that have
not adopted this store version may temporarily enforce the contract only at
execution boundaries during rollout. If you catch a failure, use the exported
structural payload guards rather than `instanceof`, because host and definition
bundles can contain separate SDK copies. There is no `delete()` operation; to
recover from legacy invalid state, replace an offending key with a compact,
JSON-compatible value that brings the complete candidate within the quota.

@@ -350,4 +359,5 @@ **A step's `run(input, ctx)` first argument is its inbound input** — the entry input at the

},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
// No `model` — omit it and let the platform choose (recommended; passing
// "smart" would be a no-op — it already is the default — and a raw provider
// model id is never honored).
output: {

@@ -382,4 +392,4 @@ name: "classify_ticket",

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
surface). Omit it entirely to let the platform choose (the recommended default) — passing
`"smart"` is a no-op: it already is the default. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing

@@ -395,2 +405,49 @@ lane it executed in) — never a model or provider id.

## Composing Deployed Agents (System Design)
**One agent per project — but a system is several projects.** The "keep exactly one
`defineAgent(...)` export" rule is a statement about a PROJECT, not about your system. A
multi-stage system ("research → write script → voiceover → assemble → post") is not one
big agent with five steps: it is several small agents, each its own project, deployed
separately, composed by a thin coordinator that dispatches them by slug. A small deployed
agent is independently testable, versioned, and reusable from more than one caller; a
monolith couples every stage into a single deploy unit and step graph, so any stage change
redeploys — and risks — all of them.
**Wrong** — one project inlining every stage as a step:
```typescript
// DON'T: video-pipeline/index.ts with research, script, voiceover, assemble,
// post as five steps of ONE defineAgent. No stage is reusable or
// independently testable, and every stage change redeploys all five.
```
**Right** — each stage its own deployed project; a coordinator composes them:
```typescript
// research-topic/, write-script/, generate-voiceover/, assemble-video/,
// post-video/: five small projects, each deployed on its own slug.
// video-pipeline/ is then just the coordinator:
const research = await ctx.sapiom.agents.run({
definition: "research-topic", // the deployed child's slug
input: { topic: input.topic },
});
// agents.run resolves on ANY terminal status (completed | failed | cancelled)
// and does NOT throw — a non-completed child is data the coordinator must
// branch on, or a failed stage silently feeds `null` downstream.
if (research.status !== "completed") {
// (fail() requires this step to declare canFail: true)
return fail(`research-topic ${research.status}: ${String(research.error)}`);
}
const script = await ctx.sapiom.agents.run({
definition: "write-script",
input: { research: research.output },
});
// …and so on. Use agents.launch + pauseUntilSignal for a long-running child
// so the coordinator's step doesn't time out.
```
Building a system in one session? Scaffold the stages as separate projects and deploy
bottom-up — children first, the coordinator last (it dispatches them by their slugs).
## Naming Conventions

@@ -397,0 +454,0 @@

@@ -9,3 +9,3 @@ # Working in this agent project

- An agent is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export.
- An agent is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export — one agent per project. A multi-stage system is several small projects composed with `ctx.sapiom.agents.run` (see the sapiom-agent-authoring skill’s "Composing Deployed Agents").
- **Capabilities come from the types.** What's available on `ctx.sapiom` is defined by `@sapiom/tools` — read the types / use autocomplete rather than guessing. A wrong capability or method name fails typecheck.

@@ -76,3 +76,3 @@

- **The resumed step's `input` IS the run's result signal payload.** Annotate it with `CodingResultPayload` (from `@sapiom/tools`) — you don't have to hand-roll the shape.
- That payload crossed a wire boundary, so it carries **no live handles** — to act on the run's sandbox, re-attach one from **`executionEnvironment`** with `ctx.sapiom.sandboxes.attach(result.executionEnvironment.id)` (`executionEnvironment` is `null` when the run provisioned none, e.g. a launch failure). Before pausing, stash only compact state, IDs, or durable-storage references in `ctx.shared`; its whole compact-JSON snapshot has an inclusive 256 KiB UTF-8 quota. The SDK contract does not make `ctx.shared.set()` a synchronous size gate by itself; hosts enforce it at execution boundaries, and older hosts may temporarily enforce a smaller legacy limit during rollout.
- That payload crossed a wire boundary, so it carries **no live handles** — to act on the run's sandbox, re-attach one from **`executionEnvironment`** with `ctx.sapiom.sandboxes.attach(result.executionEnvironment.id)` (`executionEnvironment` is `null` when the run provisioned none, e.g. a launch failure). Before pausing, stash only compact state, IDs, or durable-storage references in `ctx.shared`; its whole compact-JSON snapshot has an inclusive 256 KiB UTF-8 quota. Hosts that construct this SDK version's `InMemoryContextStore` get synchronous complete-candidate validation and retain the previous snapshot after an oversized or unserializable write; hosts that have not adopted it may enforce only at execution boundaries during rollout.
- **To stub the resume payload** (e.g. to exercise the failure branch), override `models.coding.run` _in the launching step_ — that one value is both the `run()` result and the payload the paused step resumes with. `models.coding.launch` is accepted there too.

@@ -79,0 +79,0 @@ - `gitRepository` accepts a Sapiom repository returned by `repositories.create`, `get`, or `list`; `repositories.attach` only rehydrates such a handle.

@@ -243,5 +243,14 @@ ---

documents, or research data in durable storage and carry only the ID/reference.
The SDK contract does not by itself make `ctx.shared.set()` a synchronous size
gate. Hosts enforce it at execution boundaries, and older hosts may temporarily
enforce a smaller legacy limit during rollout.
Hosts that construct this SDK version's `InMemoryContextStore` get setter-time
validation: `ctx.shared.set()` synchronously measures the complete candidate
snapshot before committing it. An oversized or unserializable write throws and
leaves the previous snapshot unchanged. Measurement follows `JSON.stringify`:
circular references, BigInt values, and throwing `toJSON` methods fail, while
values JSON normally omits or coerces retain those semantics. Hosts that have
not adopted this store version may temporarily enforce the contract only at
execution boundaries during rollout. If you catch a failure, use the exported
structural payload guards rather than `instanceof`, because host and definition
bundles can contain separate SDK copies. There is no `delete()` operation; to
recover from legacy invalid state, replace an offending key with a compact,
JSON-compatible value that brings the complete candidate within the quota.

@@ -350,4 +359,5 @@ **A step's `run(input, ctx)` first argument is its inbound input** — the entry input at the

},
// No `model` — omit it and let the platform choose (recommended). To pin
// instead: `model: "smart"` (a label, never a raw provider model id).
// No `model` — omit it and let the platform choose (recommended; passing
// "smart" would be a no-op — it already is the default — and a raw provider
// model id is never honored).
output: {

@@ -382,4 +392,4 @@ name: "classify_ticket",

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
surface). Omit it entirely to let the platform choose (the recommended default) — passing
`"smart"` is a no-op: it already is the default. The result discloses what actually served, in the platform's own
vocabulary — `servedClass` (the billing size the label resolved to) and `lane` (the billing

@@ -395,2 +405,49 @@ lane it executed in) — never a model or provider id.

## Composing Deployed Agents (System Design)
**One agent per project — but a system is several projects.** The "keep exactly one
`defineAgent(...)` export" rule is a statement about a PROJECT, not about your system. A
multi-stage system ("research → write script → voiceover → assemble → post") is not one
big agent with five steps: it is several small agents, each its own project, deployed
separately, composed by a thin coordinator that dispatches them by slug. A small deployed
agent is independently testable, versioned, and reusable from more than one caller; a
monolith couples every stage into a single deploy unit and step graph, so any stage change
redeploys — and risks — all of them.
**Wrong** — one project inlining every stage as a step:
```typescript
// DON'T: video-pipeline/index.ts with research, script, voiceover, assemble,
// post as five steps of ONE defineAgent. No stage is reusable or
// independently testable, and every stage change redeploys all five.
```
**Right** — each stage its own deployed project; a coordinator composes them:
```typescript
// research-topic/, write-script/, generate-voiceover/, assemble-video/,
// post-video/: five small projects, each deployed on its own slug.
// video-pipeline/ is then just the coordinator:
const research = await ctx.sapiom.agents.run({
definition: "research-topic", // the deployed child's slug
input: { topic: input.topic },
});
// agents.run resolves on ANY terminal status (completed | failed | cancelled)
// and does NOT throw — a non-completed child is data the coordinator must
// branch on, or a failed stage silently feeds `null` downstream.
if (research.status !== "completed") {
// (fail() requires this step to declare canFail: true)
return fail(`research-topic ${research.status}: ${String(research.error)}`);
}
const script = await ctx.sapiom.agents.run({
definition: "write-script",
input: { research: research.output },
});
// …and so on. Use agents.launch + pauseUntilSignal for a long-running child
// so the coordinator's step doesn't time out.
```
Building a system in one session? Scaffold the stages as separate projects and deploy
bottom-up — children first, the coordinator last (it dispatches them by their slugs).
## Naming Conventions

@@ -397,0 +454,0 @@

@@ -9,3 +9,3 @@ # Working in this agent project

- An agent is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export.
- An agent is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export — one agent per project. A multi-stage system is several small projects composed with `ctx.sapiom.agents.run` (see the sapiom-agent-authoring skill’s "Composing Deployed Agents").
- **Capabilities come from the types.** What's available on `ctx.sapiom` is defined by `@sapiom/tools` — read the types / use autocomplete rather than guessing. A wrong capability or method name fails typecheck.

@@ -76,3 +76,3 @@

- **The resumed step's `input` IS the run's result signal payload.** Annotate it with `CodingResultPayload` (from `@sapiom/tools`) — you don't have to hand-roll the shape.
- That payload crossed a wire boundary, so it carries **no live handles** — to act on the run's sandbox, re-attach one from **`executionEnvironment`** with `ctx.sapiom.sandboxes.attach(result.executionEnvironment.id)` (`executionEnvironment` is `null` when the run provisioned none, e.g. a launch failure). Before pausing, stash only compact state, IDs, or durable-storage references in `ctx.shared`; its whole compact-JSON snapshot has an inclusive 256 KiB UTF-8 quota. The SDK contract does not make `ctx.shared.set()` a synchronous size gate by itself; hosts enforce it at execution boundaries, and older hosts may temporarily enforce a smaller legacy limit during rollout.
- That payload crossed a wire boundary, so it carries **no live handles** — to act on the run's sandbox, re-attach one from **`executionEnvironment`** with `ctx.sapiom.sandboxes.attach(result.executionEnvironment.id)` (`executionEnvironment` is `null` when the run provisioned none, e.g. a launch failure). Before pausing, stash only compact state, IDs, or durable-storage references in `ctx.shared`; its whole compact-JSON snapshot has an inclusive 256 KiB UTF-8 quota. Hosts that construct this SDK version's `InMemoryContextStore` get synchronous complete-candidate validation and retain the previous snapshot after an oversized or unserializable write; hosts that have not adopted it may enforce only at execution boundaries during rollout.
- **To stub the resume payload** (e.g. to exercise the failure branch), override `models.coding.run` _in the launching step_ — that one value is both the `run()` result and the payload the paused step resumes with. `models.coding.launch` is accepted there too.

@@ -79,0 +79,0 @@ - `gitRepository` accepts a Sapiom repository returned by `repositories.create`, `get`, or `list`; `repositories.attach` only rehydrates such a handle.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet