@sapiom/agent-core 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +246 -0
- package/LICENSE +21 -0
- package/dist/cjs/__tests__/execution-detail.wire.fixture.json +101 -0
- package/dist/cjs/__tests__/execution-projection.preseam.fixture.json +37 -0
- package/dist/cjs/__tests__/execution-projection.with-cost.fixture.json +60 -0
- package/dist/cjs/bundle.d.ts +5 -0
- package/dist/cjs/bundle.js +134 -0
- package/dist/cjs/check.d.ts +10 -0
- package/dist/cjs/check.js +154 -0
- package/dist/cjs/client.d.ts +21 -0
- package/dist/cjs/client.js +108 -0
- package/dist/cjs/clone.d.ts +17 -0
- package/dist/cjs/clone.js +71 -0
- package/dist/cjs/config.d.ts +16 -0
- package/dist/cjs/config.js +40 -0
- package/dist/cjs/decode.d.ts +4 -0
- package/dist/cjs/decode.js +172 -0
- package/dist/cjs/deploy.d.ts +12 -0
- package/dist/cjs/deploy.js +67 -0
- package/dist/cjs/errors.d.ts +15 -0
- package/dist/cjs/errors.js +23 -0
- package/dist/cjs/git.d.ts +12 -0
- package/dist/cjs/git.js +76 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.js +72 -0
- package/dist/cjs/inspect.d.ts +40 -0
- package/dist/cjs/inspect.js +99 -0
- package/dist/cjs/link.d.ts +15 -0
- package/dist/cjs/link.js +19 -0
- package/dist/cjs/local/dispatcher.d.ts +37 -0
- package/dist/cjs/local/dispatcher.js +149 -0
- package/dist/cjs/local/load.d.ts +6 -0
- package/dist/cjs/local/load.js +102 -0
- package/dist/cjs/local/run-local.d.ts +32 -0
- package/dist/cjs/local/run-local.js +95 -0
- package/dist/cjs/local/stubs.d.ts +8 -0
- package/dist/cjs/local/stubs.js +35 -0
- package/dist/cjs/run.d.ts +11 -0
- package/dist/cjs/run.js +29 -0
- package/dist/cjs/scaffold.d.ts +24 -0
- package/dist/cjs/scaffold.js +190 -0
- package/dist/cjs/schedule.d.ts +67 -0
- package/dist/cjs/schedule.js +33 -0
- package/dist/cjs/signal.d.ts +12 -0
- package/dist/cjs/signal.js +24 -0
- package/dist/cjs/types.d.ts +99 -0
- package/dist/cjs/types.js +11 -0
- package/dist/cjs/watch.d.ts +10 -0
- package/dist/cjs/watch.js +101 -0
- package/dist/esm/__tests__/execution-detail.wire.fixture.json +101 -0
- package/dist/esm/__tests__/execution-projection.preseam.fixture.json +37 -0
- package/dist/esm/__tests__/execution-projection.with-cost.fixture.json +60 -0
- package/dist/esm/bundle.d.ts +5 -0
- package/dist/esm/bundle.js +95 -0
- package/dist/esm/check.d.ts +10 -0
- package/dist/esm/check.js +115 -0
- package/dist/esm/client.d.ts +21 -0
- package/dist/esm/client.js +103 -0
- package/dist/esm/clone.d.ts +17 -0
- package/dist/esm/clone.js +65 -0
- package/dist/esm/config.d.ts +16 -0
- package/dist/esm/config.js +31 -0
- package/dist/esm/decode.d.ts +4 -0
- package/dist/esm/decode.js +167 -0
- package/dist/esm/deploy.d.ts +12 -0
- package/dist/esm/deploy.js +61 -0
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +19 -0
- package/dist/esm/git.d.ts +12 -0
- package/dist/esm/git.js +69 -0
- package/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/inspect.d.ts +40 -0
- package/dist/esm/inspect.js +92 -0
- package/dist/esm/link.d.ts +15 -0
- package/dist/esm/link.js +16 -0
- package/dist/esm/local/dispatcher.d.ts +37 -0
- package/dist/esm/local/dispatcher.js +145 -0
- package/dist/esm/local/load.d.ts +6 -0
- package/dist/esm/local/load.js +63 -0
- package/dist/esm/local/run-local.d.ts +32 -0
- package/dist/esm/local/run-local.js +87 -0
- package/dist/esm/local/stubs.d.ts +8 -0
- package/dist/esm/local/stubs.js +31 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/run.d.ts +11 -0
- package/dist/esm/run.js +25 -0
- package/dist/esm/scaffold.d.ts +24 -0
- package/dist/esm/scaffold.js +179 -0
- package/dist/esm/schedule.d.ts +67 -0
- package/dist/esm/schedule.js +26 -0
- package/dist/esm/signal.d.ts +12 -0
- package/dist/esm/signal.js +20 -0
- package/dist/esm/types.d.ts +99 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/watch.d.ts +10 -0
- package/dist/esm/watch.js +96 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +73 -0
- package/templates/coding-pause/AGENTS.md +48 -0
- package/templates/coding-pause/CLAUDE.md +7 -0
- package/templates/coding-pause/README.md +53 -0
- package/templates/coding-pause/_gitignore +3 -0
- package/templates/coding-pause/index.ts +109 -0
- package/templates/coding-pause/package.json +21 -0
- package/templates/coding-pause/tsconfig.json +12 -0
- package/templates/default/AGENTS.md +48 -0
- package/templates/default/CLAUDE.md +7 -0
- package/templates/default/README.md +30 -0
- package/templates/default/_gitignore +3 -0
- package/templates/default/index.ts +26 -0
- package/templates/default/package.json +21 -0
- package/templates/default/tsconfig.json +12 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Working in this orchestration
|
|
2
|
+
|
|
3
|
+
This project defines exactly one Sapiom orchestration in `index.ts`, authored against `@sapiom/agent`. Inside a step's `run`, Sapiom capabilities are on `ctx.sapiom` (e.g. `ctx.sapiom.repositories.list()`, `repo.pushFromSandbox(...)`).
|
|
4
|
+
|
|
5
|
+
## Authoring
|
|
6
|
+
|
|
7
|
+
- An orchestration is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export.
|
|
8
|
+
- **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.
|
|
9
|
+
|
|
10
|
+
## Validating
|
|
11
|
+
|
|
12
|
+
When you've made a coherent change and want to validate it — the same point you'd run tests in any project — this project ships a near-complete local suite. Reach for it then; you don't need to run it after every small edit.
|
|
13
|
+
|
|
14
|
+
- **`npm run typecheck`** — types, and confirms every `ctx.sapiom.*` capability/method you used exists.
|
|
15
|
+
- **check** — typecheck + bundle + manifest + step-graph validation. The full local pre-flight before deploy.
|
|
16
|
+
- **run_local** — runs your **real** step code locally against **stub capabilities**: every `ctx.sapiom.*` call (namespace calls *and* handle methods like `repo.pushFromSandbox`) returns a built-in default, so a workflow runs end-to-end with zero setup. Returns a per-step trace.
|
|
17
|
+
- **deploy** — ship it.
|
|
18
|
+
|
|
19
|
+
> Write each step the way it should run in production. `run_local` adapts to your code (stub capabilities), not the other way around — never weaken or drop real logic to shape a local run.
|
|
20
|
+
|
|
21
|
+
## Stubs (overrides)
|
|
22
|
+
|
|
23
|
+
`run_local` works with **no stubs** — capabilities return sensible defaults. Add an override only when a step's logic branches on a specific result (e.g. "if `repositories.list()` already contains my repo, skip create"). Put overrides in `.sapiom-dev/stubs.json` (committed and human-reviewable); `run_local` reads it automatically. Shape:
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{ "version": 1, "steps": { "<stepName>": { "<capability.path>": <response> } } }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Capability paths are namespace methods (`repositories.list`, `repositories.create`, `agent.coding.run`) or handle methods, which use the **singular** handle type (`repository.pushFromSandbox`, `sandbox.exec`) — not the plural namespace.
|
|
30
|
+
- `<response>` is returned **verbatim** — it is the value that call would return, so match its real shape. `repositories.list` takes the array `list()` returns: `[{ "slug": "...", "cloneUrl": "..." }]` (each element a repository — *not* `[[ … ]]`). `repositories.create`/`get`/`attach` take a single `{ "slug", "cloneUrl" }`.
|
|
31
|
+
- `run_local` reports **`unusedStubs`** (a key that matched no call — usually a typo or the plural/singular mistake) and **`stubWarnings`** (a key matched but the value was the wrong shape). A green run with either non-empty means a stub silently didn't take effect — check them.
|
|
32
|
+
|
|
33
|
+
## Dispatched runs: pause & resume
|
|
34
|
+
|
|
35
|
+
A long-running capability (today the coding agent) is launched fire-and-forget and the workflow suspends until it finishes:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const run = await ctx.sapiom.models.coding.launch({ task, gitRepository: repo }); // returns a handle, not a result
|
|
39
|
+
return pauseUntilSignal(run, { resumeStep: "finalize" }); // suspend on the run's result signal
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **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.
|
|
43
|
+
- 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). Anything else the resumed step needs, stash in `ctx.shared` before pausing.
|
|
44
|
+
- **To stub the resume payload** (e.g. to exercise the failure branch), override `agent.coding.run` *in the launching step* — that one value is both the `run()` result and the payload the paused step resumes with. `agent.coding.launch` is accepted there too.
|
|
45
|
+
|
|
46
|
+
## Determinism
|
|
47
|
+
|
|
48
|
+
A step body runs **once** on the happy path; it re-runs only on retry (after a throw). Don't rely on a value being recomputed identically across a pause/resume — capture non-deterministic values (timestamps, ids) once and pass them forward via the `goto(...)` input or `ctx.shared`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Working in this orchestration
|
|
2
|
+
|
|
3
|
+
Follow [AGENTS.md](./AGENTS.md) for authoring and validation.
|
|
4
|
+
|
|
5
|
+
This project ships a near-complete local suite — `npm run typecheck`, the **check** tool (typecheck + bundle + manifest + graph), and **run_local** (runs your real step code against stub capabilities, including handle methods like `repo.pushFromSandbox`). Reach for it when you'd normally validate a change (not after every small edit), and rely on `check` / `typecheck` to confirm the `ctx.sapiom.*` capabilities you used exist.
|
|
6
|
+
|
|
7
|
+
`run_local` works with no stubs (capabilities return defaults); add overrides in `.sapiom-dev/stubs.json` only for results a step branches on. Write each step the way it should run in production — never weaken or drop real logic to shape a local run.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
A Sapiom orchestration that runs a **coding agent without blocking the workflow**:
|
|
4
|
+
it launches the agent fire-and-forget, suspends on the run's result signal, and
|
|
5
|
+
resumes once the run finishes — authored as code against
|
|
6
|
+
[`@sapiom/agent`](https://www.npmjs.com/package/@sapiom/agent).
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
prepare → kickoff ──pause(models.coding.result)──▶ finalize
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- **kickoff** calls `agent.coding.launch(...)` (which returns a handle, *not* a
|
|
13
|
+
result) and returns `pauseUntilSignal(handle, { resumeStep: "finalize" })`. The
|
|
14
|
+
workflow suspends — a long run holds no worker.
|
|
15
|
+
- **finalize** is resumed by the engine when the run reaches a terminal state.
|
|
16
|
+
Its `input` **is** the run's result signal payload (typed `CodingResultPayload`).
|
|
17
|
+
Because that payload crossed the pause/resume boundary, `sandbox` is plain data
|
|
18
|
+
— re-attach a live handle with `ctx.sapiom.sandboxes.attach(name)` to push.
|
|
19
|
+
|
|
20
|
+
State that must survive the pause goes in `ctx.shared`. To test the **failure**
|
|
21
|
+
branch locally, stub a failed result under the *launching* step in
|
|
22
|
+
`.sapiom-dev/stubs.json` — that value is also the resume payload:
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
{ "version": 1, "steps": { "kickoff": { "agent.coding.launch": { "status": "failed", "result": { "success": false }, "error": { "stage": "run", "message": "…" } } } } }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Getting started
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npm install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then open `index.ts`. The orchestration is defined with `defineAgent({ steps })`; each step is a `defineStep({ name, next, run })`. The `run` body is ordinary code — and inside it, the full Sapiom tool catalog is available, pre-auth'd and tenant-scoped, on `ctx.sapiom`:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const box = await ctx.sapiom.sandboxes.create({ name: 'demo' });
|
|
38
|
+
const repo = await ctx.sapiom.repositories.create('my-repo');
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No credentials to wire — a per-execution tenant credential is injected when your orchestration runs.
|
|
42
|
+
|
|
43
|
+
## The loop
|
|
44
|
+
|
|
45
|
+
Author and run this orchestration with the Sapiom dev tools:
|
|
46
|
+
|
|
47
|
+
- **check** — validate locally (bundle, manifest, step graph). Offline.
|
|
48
|
+
- **run_local** — execute the steps locally against stubs (no real capability calls), iterating until it completes.
|
|
49
|
+
- **deploy** — build and ship.
|
|
50
|
+
|
|
51
|
+
`npm run typecheck` and `npm run format` are also available for editor-level checks.
|
|
52
|
+
|
|
53
|
+
See `AGENTS.md` for the full authoring loop.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineAgent,
|
|
3
|
+
defineStep,
|
|
4
|
+
goto,
|
|
5
|
+
terminate,
|
|
6
|
+
fail,
|
|
7
|
+
pauseUntilSignal,
|
|
8
|
+
} from "@sapiom/agent";
|
|
9
|
+
import { CODING_RESULT_SIGNAL, type CodingResultPayload } from "@sapiom/tools";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* __PROJECT_NAME__ — a non-blocking coding-agent workflow.
|
|
13
|
+
*
|
|
14
|
+
* prepare → kickoff ──pause(models.coding.result)──▶ finalize
|
|
15
|
+
*
|
|
16
|
+
* `kickoff` *launches* the coding agent (fire-and-forget) and returns
|
|
17
|
+
* `pauseUntilSignal(handle, …)`, suspending the workflow on the run's result
|
|
18
|
+
* signal — so a long run holds no worker. When the run reaches a terminal state
|
|
19
|
+
* the engine resumes `finalize` with the run result as its input.
|
|
20
|
+
*
|
|
21
|
+
* Key idea: the resumed step's `input` IS the signal payload (typed here as
|
|
22
|
+
* `CodingResultPayload`). It crossed a wire boundary, so there are no live
|
|
23
|
+
* handles — re-attach the run's sandbox from `executionEnvironment` with
|
|
24
|
+
* `ctx.sapiom.sandboxes.attach(executionEnvironment.id)`. Anything else the
|
|
25
|
+
* resumed step needs is stashed in `ctx.shared` before pausing.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const REPO_SLUG = "__PROJECT_NAME__-notes";
|
|
29
|
+
|
|
30
|
+
interface Shared extends Record<string, unknown> {
|
|
31
|
+
slug: string;
|
|
32
|
+
cloneUrl: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Find the repo, or create it on the first run. */
|
|
36
|
+
const prepare = defineStep({
|
|
37
|
+
name: "prepare",
|
|
38
|
+
next: ["kickoff"],
|
|
39
|
+
async run(_input, ctx) {
|
|
40
|
+
const existing = await ctx.sapiom.repositories.list();
|
|
41
|
+
const repo =
|
|
42
|
+
existing.find((r) => r.slug === REPO_SLUG) ??
|
|
43
|
+
(await ctx.sapiom.repositories.create(REPO_SLUG));
|
|
44
|
+
ctx.shared.set("slug", repo.slug);
|
|
45
|
+
ctx.shared.set("cloneUrl", repo.cloneUrl);
|
|
46
|
+
return goto("kickoff", {});
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** Launch the agent and suspend until it finishes — do NOT await the run. */
|
|
51
|
+
const kickoff = defineStep({
|
|
52
|
+
name: "kickoff",
|
|
53
|
+
next: [],
|
|
54
|
+
pause: { signal: CODING_RESULT_SIGNAL, resumeStep: "finalize" },
|
|
55
|
+
async run(_input, ctx) {
|
|
56
|
+
const repo = ctx.sapiom.repositories.attach(
|
|
57
|
+
ctx.shared.get("slug") as string,
|
|
58
|
+
ctx.shared.get("cloneUrl") as string,
|
|
59
|
+
);
|
|
60
|
+
const run = await ctx.sapiom.models.coding.launch({
|
|
61
|
+
task: "Make a small, self-contained change to this repository and commit it.",
|
|
62
|
+
gitRepository: repo, // auto-cloned into the sandbox at /workspace/<slug>
|
|
63
|
+
});
|
|
64
|
+
ctx.logger.info("agent launched; suspending until it finishes", {
|
|
65
|
+
runId: run.runId,
|
|
66
|
+
});
|
|
67
|
+
return pauseUntilSignal(run, { resumeStep: "finalize" });
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** Resumed once the run is done. The `input` is the coding result payload. */
|
|
72
|
+
const finalize = defineStep({
|
|
73
|
+
name: "finalize",
|
|
74
|
+
next: [],
|
|
75
|
+
terminal: true,
|
|
76
|
+
canFail: true,
|
|
77
|
+
async run(run: CodingResultPayload, ctx) {
|
|
78
|
+
if (run.status !== "completed" || !run.result?.success) {
|
|
79
|
+
return fail(
|
|
80
|
+
`coding agent did not succeed: ${run.error?.message ?? run.status}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// Re-attach live handles from plain values, then publish the agent's work.
|
|
84
|
+
const repo = ctx.sapiom.repositories.attach(
|
|
85
|
+
ctx.shared.get("slug") as string,
|
|
86
|
+
ctx.shared.get("cloneUrl") as string,
|
|
87
|
+
);
|
|
88
|
+
const env = run.executionEnvironment;
|
|
89
|
+
if (!env) {
|
|
90
|
+
return fail("coding run did not provision an execution environment");
|
|
91
|
+
}
|
|
92
|
+
const sandbox = ctx.sapiom.sandboxes.attach(env.id);
|
|
93
|
+
const push = await repo.pushFromSandbox(sandbox, {
|
|
94
|
+
message: "chore: automated change",
|
|
95
|
+
});
|
|
96
|
+
return terminate({
|
|
97
|
+
runId: run.runId,
|
|
98
|
+
pushed: push.pushed,
|
|
99
|
+
sha: push.sha,
|
|
100
|
+
summary: run.summary,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const agent = defineAgent<unknown, Shared>({
|
|
106
|
+
name: "__PROJECT_NAME__",
|
|
107
|
+
entry: "prepare",
|
|
108
|
+
steps: { prepare, kickoff, finalize },
|
|
109
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"typecheck": "tsc --noEmit",
|
|
8
|
+
"format": "prettier --write \"**/*.ts\"",
|
|
9
|
+
"format:check": "prettier --check \"**/*.ts\""
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@sapiom/agent": "__AGENT_VERSION__",
|
|
13
|
+
"@sapiom/tools": "__TOOLS_VERSION__",
|
|
14
|
+
"zod": "__ZOD_VERSION__"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^20.11.30",
|
|
18
|
+
"prettier": "^3.2.5",
|
|
19
|
+
"typescript": "^5.4.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Working in this orchestration
|
|
2
|
+
|
|
3
|
+
This project defines exactly one Sapiom orchestration in `index.ts`, authored against `@sapiom/agent`. Inside a step's `run`, Sapiom capabilities are on `ctx.sapiom` (e.g. `ctx.sapiom.repositories.list()`, `repo.pushFromSandbox(...)`).
|
|
4
|
+
|
|
5
|
+
## Authoring
|
|
6
|
+
|
|
7
|
+
- An orchestration is `defineAgent({ entry, steps })`; each step is `defineStep({ name, next, run })`. Keep exactly one `defineAgent(...)` export.
|
|
8
|
+
- **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.
|
|
9
|
+
|
|
10
|
+
## Validating
|
|
11
|
+
|
|
12
|
+
When you've made a coherent change and want to validate it — the same point you'd run tests in any project — this project ships a near-complete local suite. Reach for it then; you don't need to run it after every small edit.
|
|
13
|
+
|
|
14
|
+
- **`npm run typecheck`** — types, and confirms every `ctx.sapiom.*` capability/method you used exists.
|
|
15
|
+
- **check** — typecheck + bundle + manifest + step-graph validation. The full local pre-flight before deploy.
|
|
16
|
+
- **run_local** — runs your **real** step code locally against **stub capabilities**: every `ctx.sapiom.*` call (namespace calls *and* handle methods like `repo.pushFromSandbox`) returns a built-in default, so a workflow runs end-to-end with zero setup. Returns a per-step trace.
|
|
17
|
+
- **deploy** — ship it.
|
|
18
|
+
|
|
19
|
+
> Write each step the way it should run in production. `run_local` adapts to your code (stub capabilities), not the other way around — never weaken or drop real logic to shape a local run.
|
|
20
|
+
|
|
21
|
+
## Stubs (overrides)
|
|
22
|
+
|
|
23
|
+
`run_local` works with **no stubs** — capabilities return sensible defaults. Add an override only when a step's logic branches on a specific result (e.g. "if `repositories.list()` already contains my repo, skip create"). Put overrides in `.sapiom-dev/stubs.json` (committed and human-reviewable); `run_local` reads it automatically. Shape:
|
|
24
|
+
|
|
25
|
+
```jsonc
|
|
26
|
+
{ "version": 1, "steps": { "<stepName>": { "<capability.path>": <response> } } }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Capability paths are namespace methods (`repositories.list`, `repositories.create`, `agent.coding.run`) or handle methods, which use the **singular** handle type (`repository.pushFromSandbox`, `sandbox.exec`) — not the plural namespace.
|
|
30
|
+
- `<response>` is returned **verbatim** — it is the value that call would return, so match its real shape. `repositories.list` takes the array `list()` returns: `[{ "slug": "...", "cloneUrl": "..." }]` (each element a repository — *not* `[[ … ]]`). `repositories.create`/`get`/`attach` take a single `{ "slug", "cloneUrl" }`.
|
|
31
|
+
- `run_local` reports **`unusedStubs`** (a key that matched no call — usually a typo or the plural/singular mistake) and **`stubWarnings`** (a key matched but the value was the wrong shape). A green run with either non-empty means a stub silently didn't take effect — check them.
|
|
32
|
+
|
|
33
|
+
## Dispatched runs: pause & resume
|
|
34
|
+
|
|
35
|
+
A long-running capability (today the coding agent) is launched fire-and-forget and the workflow suspends until it finishes:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const run = await ctx.sapiom.models.coding.launch({ task, gitRepository: repo }); // returns a handle, not a result
|
|
39
|
+
return pauseUntilSignal(run, { resumeStep: "finalize" }); // suspend on the run's result signal
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **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.
|
|
43
|
+
- 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). Anything else the resumed step needs, stash in `ctx.shared` before pausing.
|
|
44
|
+
- **To stub the resume payload** (e.g. to exercise the failure branch), override `agent.coding.run` *in the launching step* — that one value is both the `run()` result and the payload the paused step resumes with. `agent.coding.launch` is accepted there too.
|
|
45
|
+
|
|
46
|
+
## Determinism
|
|
47
|
+
|
|
48
|
+
A step body runs **once** on the happy path; it re-runs only on retry (after a throw). Don't rely on a value being recomputed identically across a pause/resume — capture non-deterministic values (timestamps, ids) once and pass them forward via the `goto(...)` input or `ctx.shared`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Working in this orchestration
|
|
2
|
+
|
|
3
|
+
Follow [AGENTS.md](./AGENTS.md) for authoring and validation.
|
|
4
|
+
|
|
5
|
+
This project ships a near-complete local suite — `npm run typecheck`, the **check** tool (typecheck + bundle + manifest + graph), and **run_local** (runs your real step code against stub capabilities, including handle methods like `repo.pushFromSandbox`). Reach for it when you'd normally validate a change (not after every small edit), and rely on `check` / `typecheck` to confirm the `ctx.sapiom.*` capabilities you used exist.
|
|
6
|
+
|
|
7
|
+
`run_local` works with no stubs (capabilities return defaults); add overrides in `.sapiom-dev/stubs.json` only for results a step branches on. Write each step the way it should run in production — never weaken or drop real logic to shape a local run.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
A Sapiom orchestration, authored as code against [`@sapiom/agent`](https://www.npmjs.com/package/@sapiom/agent).
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then open `index.ts`. The orchestration is defined with `defineAgent({ steps })`; each step is a `defineStep({ name, next, run })`. The `run` body is ordinary code — and inside it, the full Sapiom tool catalog is available, pre-auth'd and tenant-scoped, on `ctx.sapiom`:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const box = await ctx.sapiom.sandboxes.create({ name: 'demo' });
|
|
15
|
+
const repo = await ctx.sapiom.repositories.create('my-repo');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No credentials to wire — a per-execution tenant credential is injected when your orchestration runs.
|
|
19
|
+
|
|
20
|
+
## The loop
|
|
21
|
+
|
|
22
|
+
Author and run this orchestration with the Sapiom dev tools:
|
|
23
|
+
|
|
24
|
+
- **check** — validate locally (bundle, manifest, step graph). Offline.
|
|
25
|
+
- **run_local** — execute the steps locally against stubs (no real capability calls), iterating until it completes.
|
|
26
|
+
- **deploy** — build and ship.
|
|
27
|
+
|
|
28
|
+
`npm run typecheck` and `npm run format` are also available for editor-level checks.
|
|
29
|
+
|
|
30
|
+
See `AGENTS.md` for the full authoring loop.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineAgent, defineStep, goto, terminate } from '@sapiom/agent';
|
|
2
|
+
|
|
3
|
+
const start = defineStep({
|
|
4
|
+
name: 'start',
|
|
5
|
+
next: ['finish'],
|
|
6
|
+
async run(input, ctx) {
|
|
7
|
+
ctx.logger.info('starting', { input });
|
|
8
|
+
// Sapiom capabilities: ctx.sapiom.sandboxes.create(), ctx.sapiom.repositories.create()
|
|
9
|
+
return goto('finish', { greeting: 'hello from Sapiom' });
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const finish = defineStep({
|
|
14
|
+
name: 'finish',
|
|
15
|
+
next: [],
|
|
16
|
+
terminal: true,
|
|
17
|
+
async run() {
|
|
18
|
+
return terminate({ done: true });
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const agent = defineAgent({
|
|
23
|
+
name: '__PROJECT_NAME__',
|
|
24
|
+
entry: 'start',
|
|
25
|
+
steps: { start, finish },
|
|
26
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"typecheck": "tsc --noEmit",
|
|
8
|
+
"format": "prettier --write \"**/*.ts\"",
|
|
9
|
+
"format:check": "prettier --check \"**/*.ts\""
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@sapiom/agent": "__AGENT_VERSION__",
|
|
13
|
+
"@sapiom/tools": "__TOOLS_VERSION__",
|
|
14
|
+
"zod": "__ZOD_VERSION__"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^20.11.30",
|
|
18
|
+
"prettier": "^3.2.5",
|
|
19
|
+
"typescript": "^5.4.2"
|
|
20
|
+
}
|
|
21
|
+
}
|