@operatorstack/yield 0.0.0-canary.20260807110004.5bf4016f3efd

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Operator Stack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # Yield
2
+
3
+ Yield is an open-source execution runtime for programmable Agent Skill workflows.
4
+
5
+ **Write one skill workflow. Run it from your coding agents.**
6
+
7
+ Skill workflows are portable, executable processes that combine agent skills
8
+ with deterministic code, state, and verification.
9
+
10
+ Write the workflow in TypeScript, Python, Go, or Rust. Combine agent judgment,
11
+ real commands, human input, checks, and saved state. Yield generates the small
12
+ adapter each coding agent expects.
13
+
14
+ The split is small:
15
+
16
+ | term | meaning |
17
+ |---|---|
18
+ | **skill** | one reusable capability |
19
+ | **workflow** | order, branches, checks, and saved state |
20
+ | **skill workflow** | an executable composition of skills, code, commands, and human input |
21
+ | **adapter** | a generated `SKILL.md` that lets one coding agent discover the workflow |
22
+
23
+ The canonical skill workflow stays beside your code. Generated adapters are
24
+ disposable. The model keeps reasoning, exploration, editing, and judgment;
25
+ normal code owns the repeatable control flow.
26
+
27
+ ## Install
28
+
29
+ Choose one language package. TypeScript and Python include a package-local
30
+ runtime. Go and Rust install the matching runtime under `.yield/bin` in the
31
+ repository. Generated adapters never use a global `yskill` from `PATH`.
32
+
33
+ ```bash
34
+ # TypeScript (public npm)
35
+ npm install --save-exact @operatorstack/yield@0.1.29
36
+ npm exec -- yskill --version
37
+
38
+ # Python, after creating and activating .venv
39
+ python -m pip install yieldskill==0.1.29 --index-url https://get.operatorstack.systems/pip/simple/
40
+ python -m yieldskill --version
41
+
42
+ # Go, from the repository root
43
+ mkdir -p .yield/bin
44
+ GOBIN="$PWD/.yield/bin" GOPROXY=https://get.operatorstack.systems/go,direct \
45
+ go install github.com/operatorstack/yield/cmd/yskill@v0.1.29
46
+ .yield/bin/yskill --version
47
+
48
+ # Rust, from the repository root
49
+ cargo install yieldskill@0.1.29 --root .yield \
50
+ --index sparse+https://get.operatorstack.systems/cargo/index/ --locked
51
+ .yield/bin/yskill --version
52
+ ```
53
+
54
+ Yield creates `.yield/.gitignore` when it registers a Go or Rust workflow, so
55
+ the local runtime and run state stay out of Git.
56
+ On Windows, run the local binary as `.\.yield\bin\yskill.exe`.
57
+
58
+ ## Create and register a skill workflow
59
+
60
+ Keep the canonical workflow beside the language dependencies it uses. Yield writes
61
+ small adapters into each coding agent's project skill directory; it does not
62
+ copy the workflow or install its dependencies again.
63
+
64
+ ```bash
65
+ # TypeScript example
66
+ npm exec -- yskill init skills/review \
67
+ --language typescript \
68
+ --description "Review changed code when the user wants a branch checked before shipping."
69
+
70
+ # Replace the intentionally incomplete starter and fixture, then check it.
71
+ npm exec -- yskill doctor skills/review --test
72
+
73
+ # Detect installed agents, or pass --agent cursor,codex,claude-code.
74
+ npm exec -- yskill register skills/review
75
+ ```
76
+
77
+ `yskill agents` lists the available agent IDs and project paths. Cursor,
78
+ Codex, and Claude Code are verified. Remaining entries support explicit path
79
+ registration from the pinned open registry; they are not presented as
80
+ end-to-end verified.
81
+
82
+ ## How a skill workflow runs
83
+
84
+ Deterministic re-execution: on every run/resume, `yskill` re-executes the
85
+ skill workflow from the top, feeding recorded responses back in order. At
86
+ the first unanswered operation the SDK emits a `yield.v1` request envelope
87
+ and the process exits — no daemon. A replayed step that produces a
88
+ different operation than the journal recorded is a divergence and fails
89
+ the run loudly; it never silently forks.
90
+
91
+ - **`yskill`** owns the append-only run log
92
+ (`.yield/runs/<id>.jsonl`), sequence and digest binding, response
93
+ validation, and every refusal (stale, duplicate, wrong-run,
94
+ schema-invalid, digest-mismatch, completion-unproven).
95
+ - **The skill workflow** is an ordinary program using one Yield SDK; every
96
+ side effect crosses a yielded primitive.
97
+
98
+ Five primitives, two exits:
99
+
100
+ | primitive | who acts |
101
+ |---|---|
102
+ | `AskUser` | the agent asks through its normal interface |
103
+ | `AgentTask` | the model reasons; the result must be schema-valid JSON |
104
+ | `RunCommand` | **yskill executes it itself** — results are observed fact, not transcription |
105
+ | `Require` | a claim bound to evidence; failure makes completion structurally unreachable |
106
+ | `Complete` / `Blocked` / `Refused` | honest terminals, always recorded |
107
+
108
+ ## Four languages, one execution contract
109
+
110
+ Write the skill workflow in Go, TypeScript, Python, or Rust. Every SDK
111
+ implements the same certified execution contract, and the conformance suite
112
+ (`internal/conformance`) runs the same program in all four languages and
113
+ asserts identical observable behavior. The language-neutral schemas are
114
+ documented in the [runtime reference](docs/reference/sdk-parity.md).
115
+
116
+ | language | SDK | example |
117
+ |---|---|---|
118
+ | Go | `sdk/yield` | `examples/investigate` — bounded hypothesis loop |
119
+ | TypeScript | `sdk/typescript` (`@operatorstack/yield`) | `examples/release-checklist` — human-gated deploy |
120
+ | Python | `sdk/python` (`yieldskill`) | `examples/env-doctor` — probe, branch, resume after the human |
121
+ | Rust | `sdk/rust` (`yieldskill`) | `examples/data-migration` — dry-run → approve → apply → verify |
122
+
123
+ Skills declare their language and runner in `skill.json`:
124
+ `{"version": 1, "language": "typescript", "run": ["node", "main.ts"]}`.
125
+
126
+ ## Ten skill workflows, every language
127
+
128
+ The [example library](examples/library/) implements ten common skill workflows
129
+ independently in all four SDKs: branch review, failure
130
+ investigation, web QA, package release, issue triage, CI repair, dependency
131
+ upgrade, database migration, security audit, and iOS publishing.
132
+
133
+ Each language has the same skill workflow, a thin adapter, and a scripted
134
+ fixture. Start from the work you already do instead of starting from a
135
+ framework tutorial.
136
+
137
+ ## Documentation
138
+
139
+ Start with [what a skill workflow is](docs/skill-workflows.md), then build one
140
+ with the [ten-minute TypeScript quickstart](docs/quickstart.md). Continue with
141
+ the documentation for your job:
142
+
143
+ - [primitive guides](docs/primitives/README.md) — commands, model work,
144
+ human input, evidence gates, and outcomes;
145
+ - [tutorials](docs/tutorials/README.md) — review, approval, environment
146
+ repair, bounded debugging, and migration;
147
+ - [examples](docs/examples.md) — working programs in all four languages;
148
+ - [coding-agent setup](docs/agent-setup.md) — register one skill workflow with the
149
+ agents used by the project;
150
+ - [Agent Plugins and Yield](docs/agent-plugins.md) — where portable packaging ends
151
+ and workflow execution begins;
152
+ - [test workflow effects](docs/testing-fixtures.md) — deterministic fixture
153
+ setup, response effects, standard-input JSON, and cleanup;
154
+ - [evaluations](evals/README.md) — first-party workflow conformance and runtime
155
+ invariant results, including the exact claim boundary;
156
+ - [convert an existing skill](docs/convert-existing-skill.md) — move
157
+ control flow into code without claiming that fixture execution proves
158
+ every reading of the original prose;
159
+ - [CLI and runtime reference](docs/reference/cli.md).
160
+
161
+ ## Try it
162
+
163
+ ```
164
+ go build -o yskill ./cmd/yskill
165
+ ./yskill test examples/library/typescript/review-branch
166
+ ./yskill test examples/library/python/review-branch
167
+ ./yskill test examples/library/go/review-branch
168
+ ./yskill test examples/library/rust/review-branch
169
+ YSKILL="$PWD/yskill" bash ./examples/library/test-all.sh
170
+ ./yskill test examples/investigate # Go: scripted fixture run to completion
171
+ ./yskill test examples/release-checklist # TypeScript (Node >= 23.6)
172
+ ./yskill test examples/env-doctor # Python 3.10+
173
+ ./yskill test examples/data-migration # Rust (cargo)
174
+ ./yskill run examples/investigate # prints the first operation envelope
175
+ ./yskill init my-skill --description "Run this skill workflow when ..."
176
+ ./yskill register my-skill --agent codex # write a thin project adapter
177
+ ./yskill doctor my-skill --agent codex # verify package + adapter wiring
178
+ ```
179
+
180
+ The reference skill, `examples/investigate`, encodes an investigation
181
+ discipline in code: at least three hypotheses, cheapest-to-disprove
182
+ first, at most three failed attempts, completion requires a causal chain
183
+ — or an honest `Blocked` at the frontier.
184
+
185
+ ## What it guarantees — and what it doesn't
186
+
187
+ Guaranteed: deterministic control flow, typed requests/responses,
188
+ persistent state, replay (divergence fails loudly), stale/duplicate
189
+ rejection, evidence-bound completion.
190
+
191
+ Not guaranteed: that the agent performed *only* the requested operation,
192
+ or that a schema-valid `agent_task` result is true — schema validity is
193
+ not truth. `RunCommand` is the exception by construction: commands are
194
+ executed by the Yield CLI, so exit codes and output enter the log as
195
+ observed fact. Runtime and conformance tests enforce these guarantees.
196
+
197
+ ## What it is not
198
+
199
+ Not a daemon, not a hosted runtime, not a workflow DSL, not a
200
+ marketplace, not a new agent loop, not a multi-agent orchestrator, not a
201
+ security sandbox.
202
+
203
+ ---
204
+
205
+ This is Yield's canonical source repository. Changes, verification, release
206
+ intent, and publishing control all live here. MIT licensed.
@@ -0,0 +1,34 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ const packages = new Map([
4
+ ["darwin:x64", "@operatorstack/yield-darwin-amd64"],
5
+ ["darwin:arm64", "@operatorstack/yield-darwin-arm64"],
6
+ ["linux:x64", "@operatorstack/yield-linux-amd64"],
7
+ ["linux:arm64", "@operatorstack/yield-linux-arm64"],
8
+ ["win32:x64", "@operatorstack/yield-windows-amd64"],
9
+ ["win32:arm64", "@operatorstack/yield-windows-arm64"],
10
+ ]);
11
+
12
+ export function runtimePackage(platform = process.platform, arch = process.arch) {
13
+ const name = packages.get(`${platform}:${arch}`);
14
+ if (!name) {
15
+ throw new Error(`Yield does not provide a runtime for ${platform}/${arch}`);
16
+ }
17
+ return name;
18
+ }
19
+
20
+ export function resolveRuntime({
21
+ platform = process.platform,
22
+ arch = process.arch,
23
+ resolve = createRequire(import.meta.url).resolve,
24
+ } = {}) {
25
+ const name = runtimePackage(platform, arch);
26
+ try {
27
+ return resolve(name);
28
+ } catch (error) {
29
+ throw new Error(
30
+ `The runtime package ${name} is missing. Reinstall @operatorstack/yield for ${platform}/${arch}.`,
31
+ { cause: error },
32
+ );
33
+ }
34
+ }
@@ -0,0 +1,30 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { resolveRuntime, runtimePackage } from "./runtime.mjs";
4
+
5
+ const cases = [
6
+ ["darwin", "x64", "@operatorstack/yield-darwin-amd64"],
7
+ ["darwin", "arm64", "@operatorstack/yield-darwin-arm64"],
8
+ ["linux", "x64", "@operatorstack/yield-linux-amd64"],
9
+ ["linux", "arm64", "@operatorstack/yield-linux-arm64"],
10
+ ["win32", "x64", "@operatorstack/yield-windows-amd64"],
11
+ ["win32", "arm64", "@operatorstack/yield-windows-arm64"],
12
+ ];
13
+
14
+ test("selects the exact runtime package for every supported target", () => {
15
+ for (const [platform, arch, expected] of cases) {
16
+ assert.equal(runtimePackage(platform, arch), expected);
17
+ assert.equal(resolveRuntime({ platform, arch, resolve: (name) => `/packages/${name}` }), `/packages/${expected}`);
18
+ }
19
+ });
20
+
21
+ test("rejects unsupported targets", () => {
22
+ assert.throws(() => runtimePackage("freebsd", "x64"), /does not provide a runtime/);
23
+ });
24
+
25
+ test("does not fall back when the selected package is missing", () => {
26
+ assert.throws(
27
+ () => resolveRuntime({ platform: "linux", arch: "x64", resolve: () => { throw new Error("missing"); } }),
28
+ /Reinstall @operatorstack\/yield/,
29
+ );
30
+ });
package/bin/yskill.mjs ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import process from "node:process";
4
+ import { resolveRuntime } from "./runtime.mjs";
5
+
6
+ let binary;
7
+ try {
8
+ binary = resolveRuntime();
9
+ } catch (error) {
10
+ console.error(`yskill: ${error.message}`);
11
+ process.exit(1);
12
+ }
13
+
14
+ const child = spawn(binary, process.argv.slice(2), {
15
+ stdio: "inherit",
16
+ env: { ...process.env, YIELD_LANGUAGE: process.env.YIELD_LANGUAGE ?? "typescript" },
17
+ });
18
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
19
+ process.on(signal, () => {
20
+ if (!child.killed) child.kill(signal);
21
+ });
22
+ }
23
+ child.on("error", (error) => {
24
+ console.error(`yskill: could not start the packaged runtime: ${error.message}`);
25
+ process.exit(1);
26
+ });
27
+ child.on("exit", (code, signal) => {
28
+ if (signal && process.platform !== "win32") {
29
+ process.kill(process.pid, signal);
30
+ return;
31
+ }
32
+ process.exit(code ?? 1);
33
+ });
package/dist/index.js ADDED
@@ -0,0 +1,219 @@
1
+ // Generated from src/index.ts by scripts/build.mjs. Do not edit.
2
+ import { createHash } from "node:crypto";
3
+ import { readFileSync } from "node:fs";
4
+ import { exit, env, stdout, stderr } from "node:process";
5
+ export class Blocked extends Error {
6
+ reason;
7
+ constructor(reason){
8
+ super(`blocked: ${reason}`);
9
+ this.reason = reason;
10
+ }
11
+ }
12
+ export class Refused extends Error {
13
+ reason;
14
+ constructor(reason){
15
+ super(`refused: ${reason}`);
16
+ this.reason = reason;
17
+ }
18
+ }
19
+ class EmitSignal {
20
+ output;
21
+ constructor(output){
22
+ this.output = output;
23
+ }
24
+ }
25
+ const digest = (b)=>"sha256:" + createHash("sha256").update(b).digest("hex");
26
+ const compact = (v)=>v === undefined ? "" : JSON.stringify(v);
27
+ const requestDigest = (r)=>digest(Buffer.concat([
28
+ Buffer.from(String(r.kind)),
29
+ Buffer.from([
30
+ 0
31
+ ]),
32
+ Buffer.from(r.id),
33
+ Buffer.from([
34
+ 0
35
+ ]),
36
+ Buffer.from(compact(r.payload)),
37
+ Buffer.from([
38
+ 0
39
+ ]),
40
+ Buffer.from(compact(r.output_schema))
41
+ ]));
42
+ export class Context {
43
+ idx = 0;
44
+ requirements = [];
45
+ journal;
46
+ constructor(journal){
47
+ this.journal = journal;
48
+ }
49
+ askUser(id, question, options) {
50
+ const valueSchema = {
51
+ type: "string"
52
+ };
53
+ if (options?.length) valueSchema.enum = options.map((option)=>option.value);
54
+ const resp = this.step({
55
+ id,
56
+ kind: "ask_user",
57
+ payload: options ? {
58
+ question,
59
+ options
60
+ } : {
61
+ question
62
+ },
63
+ output_schema: {
64
+ type: "object",
65
+ required: [
66
+ "value"
67
+ ],
68
+ additionalProperties: false,
69
+ properties: {
70
+ value: valueSchema
71
+ }
72
+ }
73
+ });
74
+ return resp.result.value;
75
+ }
76
+ agentTask(id, instruction, context, schema) {
77
+ const resp = this.step({
78
+ id,
79
+ kind: "agent_task",
80
+ payload: context === undefined ? {
81
+ instruction
82
+ } : {
83
+ instruction,
84
+ context
85
+ },
86
+ output_schema: schema
87
+ });
88
+ return resp.result;
89
+ }
90
+ runCommand(id, command, timeoutSeconds = 0) {
91
+ const payload = timeoutSeconds > 0 ? {
92
+ command,
93
+ timeout_seconds: timeoutSeconds
94
+ } : {
95
+ command
96
+ };
97
+ const resp = this.step({
98
+ id,
99
+ kind: "run_command",
100
+ payload
101
+ });
102
+ return resp.result;
103
+ }
104
+ require(ok, claim, evidence) {
105
+ const req = {
106
+ claim,
107
+ passed: ok
108
+ };
109
+ if (evidence !== undefined) req.evidence_digest = digest(compact(evidence));
110
+ this.requirements.push(req);
111
+ if (!ok) {
112
+ throw new EmitSignal({
113
+ type: "terminal",
114
+ terminal: {
115
+ status: "requirement_failed",
116
+ reason: claim
117
+ },
118
+ requirements: this.requirements
119
+ });
120
+ }
121
+ }
122
+ blocked(reason) {
123
+ throw new Blocked(reason);
124
+ }
125
+ refused(reason) {
126
+ throw new Refused(reason);
127
+ }
128
+ step(req) {
129
+ const entries = this.journal.entries ?? [];
130
+ const seq = this.idx + 1;
131
+ if (this.idx < entries.length) {
132
+ const entry = entries[this.idx];
133
+ const want = requestDigest(entry.request);
134
+ const got = requestDigest(req);
135
+ if (want !== got) {
136
+ throw new EmitSignal({
137
+ type: "diverged",
138
+ divergence: {
139
+ sequence: seq,
140
+ expected_digest: want,
141
+ got_digest: got,
142
+ detail: `replay produced operation "${req.id}" (${req.kind}) where the journal recorded "${entry.request.id}" (${entry.request.kind})`
143
+ }
144
+ });
145
+ }
146
+ this.idx++;
147
+ return entry.response;
148
+ }
149
+ this.idx++;
150
+ throw new EmitSignal({
151
+ type: "request",
152
+ envelope: {
153
+ protocol: "yield.v1",
154
+ run_id: this.journal.run_id,
155
+ skill: this.journal.skill,
156
+ sequence: seq,
157
+ request: req
158
+ },
159
+ requirements: this.requirements
160
+ });
161
+ }
162
+ terminalFor(err) {
163
+ if (err instanceof EmitSignal) return err.output;
164
+ if (err instanceof Blocked) return {
165
+ type: "terminal",
166
+ terminal: {
167
+ status: "blocked",
168
+ reason: err.reason
169
+ },
170
+ requirements: this.requirements
171
+ };
172
+ if (err instanceof Refused) return {
173
+ type: "terminal",
174
+ terminal: {
175
+ status: "refused",
176
+ reason: err.reason
177
+ },
178
+ requirements: this.requirements
179
+ };
180
+ return null;
181
+ }
182
+ completed(result) {
183
+ return {
184
+ type: "terminal",
185
+ terminal: {
186
+ status: "completed",
187
+ result
188
+ },
189
+ requirements: this.requirements
190
+ };
191
+ }
192
+ }
193
+ function emit(output) {
194
+ stdout.write(JSON.stringify(output) + "\n");
195
+ exit(0);
196
+ }
197
+ export function defineSkill(program) {
198
+ const path = env.YIELD_JOURNAL;
199
+ if (!path) {
200
+ stderr.write("yield: YIELD_JOURNAL is not set; this program is run by yskill, not directly\n");
201
+ exit(2);
202
+ }
203
+ let journal;
204
+ try {
205
+ journal = JSON.parse(readFileSync(path, "utf8"));
206
+ } catch (err) {
207
+ stderr.write(`yield: cannot read journal: ${String(err)}\n`);
208
+ exit(2);
209
+ }
210
+ const ctx = new Context(journal);
211
+ try {
212
+ emit(ctx.completed(program(ctx)));
213
+ } catch (err) {
214
+ const out = ctx.terminalFor(err);
215
+ if (out) emit(out);
216
+ stderr.write(`yield: program error: ${String(err)}\n`);
217
+ exit(1);
218
+ }
219
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@operatorstack/yield",
3
+ "version": "0.0.0-canary.20260807110004.5bf4016f3efd",
4
+ "description": "Yield skill-program SDK for TypeScript: turn SKILL.md workflows into resumable programs.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "bin",
9
+ "dist",
10
+ "src"
11
+ ],
12
+ "bin": {
13
+ "yskill": "bin/yskill.mjs"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./src/index.ts",
18
+ "import": "./dist/index.js",
19
+ "default": "./dist/index.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "node scripts/build.mjs",
24
+ "test": "node --test",
25
+ "prepack": "npm run build"
26
+ },
27
+ "engines": {
28
+ "node": ">=23.6"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/operatorstack/yield.git",
33
+ "directory": "sdk/typescript"
34
+ },
35
+ "homepage": "https://github.com/operatorstack/yield#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/operatorstack/yield/issues"
38
+ },
39
+ "keywords": [
40
+ "agent",
41
+ "skills",
42
+ "workflow",
43
+ "resumable",
44
+ "cli"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true,
49
+ "registry": "https://registry.npmjs.org/"
50
+ },
51
+ "optionalDependencies": {
52
+ "@operatorstack/yield-darwin-amd64": "0.0.0-canary.20260807110004.5bf4016f3efd",
53
+ "@operatorstack/yield-darwin-arm64": "0.0.0-canary.20260807110004.5bf4016f3efd",
54
+ "@operatorstack/yield-linux-amd64": "0.0.0-canary.20260807110004.5bf4016f3efd",
55
+ "@operatorstack/yield-linux-arm64": "0.0.0-canary.20260807110004.5bf4016f3efd",
56
+ "@operatorstack/yield-windows-amd64": "0.0.0-canary.20260807110004.5bf4016f3efd",
57
+ "@operatorstack/yield-windows-arm64": "0.0.0-canary.20260807110004.5bf4016f3efd"
58
+ }
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,328 @@
1
+ // Yield skill-program SDK for TypeScript (yield.v1).
2
+ //
3
+ // Implements the tested SDK execution contract (see ir/README.md):
4
+ // load the journal, replay recorded operations with a digest comparison at
5
+ // EVERY replayed step before consuming its response, emit exactly one
6
+ // program output (request | terminal | diverged) on stdout, then exit.
7
+ //
8
+ // Programs are synchronous and must be deterministic between yields: same
9
+ // journal, same operations, every execution. Wall clocks, RNGs, and
10
+ // filesystem reads are side effects — cross them through a yielded
11
+ // operation or leave them out.
12
+ //
13
+ // Runs under Node >= 23.6 (native type stripping): `node main.ts`.
14
+
15
+ import { createHash } from "node:crypto";
16
+ import { readFileSync } from "node:fs";
17
+ import { exit, env, stdout, stderr } from "node:process";
18
+
19
+ export type OpKind = "ask_user" | "agent_task" | "run_command";
20
+
21
+ export interface SkillRef {
22
+ name: string;
23
+ version?: string;
24
+ digest: string;
25
+ }
26
+
27
+ export interface Request {
28
+ id: string;
29
+ kind: OpKind;
30
+ payload: unknown;
31
+ output_schema?: unknown;
32
+ }
33
+
34
+ export interface RequestEnvelope {
35
+ protocol: "yield.v1";
36
+ run_id: string;
37
+ skill: SkillRef;
38
+ sequence: number;
39
+ request: Request;
40
+ }
41
+
42
+ export interface ResponseEnvelope {
43
+ run_id: string;
44
+ sequence: number;
45
+ request_id: string;
46
+ status: "completed" | "failed";
47
+ result: unknown;
48
+ }
49
+
50
+ export interface Option {
51
+ value: string;
52
+ label?: string;
53
+ }
54
+
55
+ export interface CommandResult {
56
+ exit_code: number;
57
+ stdout: string;
58
+ stderr: string;
59
+ timed_out?: boolean;
60
+ }
61
+
62
+ export interface Requirement {
63
+ claim: string;
64
+ passed: boolean;
65
+ evidence_digest?: string;
66
+ }
67
+
68
+ interface Journal {
69
+ run_id: string;
70
+ skill: SkillRef;
71
+ entries?: { request: Request; response: ResponseEnvelope }[];
72
+ }
73
+
74
+ type ProgramOutput =
75
+ | { type: "request"; envelope: RequestEnvelope; requirements?: Requirement[] }
76
+ | {
77
+ type: "terminal";
78
+ terminal: {
79
+ status: "completed" | "blocked" | "refused" | "requirement_failed";
80
+ result?: unknown;
81
+ reason?: string;
82
+ };
83
+ requirements?: Requirement[];
84
+ }
85
+ | {
86
+ type: "diverged";
87
+ divergence: {
88
+ sequence: number;
89
+ expected_digest: string;
90
+ got_digest: string;
91
+ detail?: string;
92
+ };
93
+ requirements?: Requirement[];
94
+ };
95
+
96
+ /** Terminal exit: a true frontier was reached — say so explicitly. */
97
+ export class Blocked extends Error {
98
+ reason: string;
99
+ constructor(reason: string) {
100
+ super(`blocked: ${reason}`);
101
+ this.reason = reason;
102
+ }
103
+ }
104
+
105
+ /** Terminal exit: the skill declines to proceed, with a stated reason. */
106
+ export class Refused extends Error {
107
+ reason: string;
108
+ constructor(reason: string) {
109
+ super(`refused: ${reason}`);
110
+ this.reason = reason;
111
+ }
112
+ }
113
+
114
+ // Thrown to unwind the program when an output has been decided; defineSkill
115
+ // catches it. Never observable by program code that doesn't catch blindly.
116
+ class EmitSignal {
117
+ output: ProgramOutput;
118
+ constructor(output: ProgramOutput) {
119
+ this.output = output;
120
+ }
121
+ }
122
+
123
+ const digest = (b: string | Buffer): string =>
124
+ "sha256:" + createHash("sha256").update(b).digest("hex");
125
+
126
+ const compact = (v: unknown): string =>
127
+ v === undefined ? "" : JSON.stringify(v);
128
+
129
+ /** sha256 over kind\0id\0compact(payload)\0compact(schema) — the IR digest. */
130
+ const requestDigest = (r: Request): string =>
131
+ digest(
132
+ Buffer.concat([
133
+ Buffer.from(String(r.kind)),
134
+ Buffer.from([0]),
135
+ Buffer.from(r.id),
136
+ Buffer.from([0]),
137
+ Buffer.from(compact(r.payload)),
138
+ Buffer.from([0]),
139
+ Buffer.from(compact(r.output_schema)),
140
+ ]),
141
+ );
142
+
143
+ export class Context {
144
+ private idx = 0;
145
+ private requirements: Requirement[] = [];
146
+ private journal: Journal;
147
+
148
+ constructor(journal: Journal) {
149
+ this.journal = journal;
150
+ }
151
+
152
+ /** Yield a question asked through the host's normal interface. */
153
+ askUser(id: string, question: string, options?: Option[]): string {
154
+ const valueSchema: Record<string, unknown> = { type: "string" };
155
+ if (options?.length) valueSchema.enum = options.map((option) => option.value);
156
+ const resp = this.step({
157
+ id,
158
+ kind: "ask_user",
159
+ payload: options ? { question, options } : { question },
160
+ output_schema: {
161
+ type: "object",
162
+ required: ["value"],
163
+ additionalProperties: false,
164
+ properties: { value: valueSchema },
165
+ },
166
+ });
167
+ return (resp.result as { value: string }).value;
168
+ }
169
+
170
+ /**
171
+ * Delegate reasoning to the model. `schema` (JSON Schema) is enforced by
172
+ * the supervisor on resume; the returned value is schema-valid by
173
+ * construction.
174
+ */
175
+ agentTask<T = unknown>(
176
+ id: string,
177
+ instruction: string,
178
+ context?: unknown,
179
+ schema?: unknown,
180
+ ): T {
181
+ const resp = this.step({
182
+ id,
183
+ kind: "agent_task",
184
+ payload: context === undefined ? { instruction } : { instruction, context },
185
+ output_schema: schema,
186
+ });
187
+ return resp.result as T;
188
+ }
189
+
190
+ /**
191
+ * Yield a command that yskill executes itself — the result is observed
192
+ * fact, not the agent's account of it.
193
+ */
194
+ runCommand(id: string, command: string, timeoutSeconds = 0): CommandResult {
195
+ const payload =
196
+ timeoutSeconds > 0
197
+ ? { command, timeout_seconds: timeoutSeconds }
198
+ : { command };
199
+ const resp = this.step({ id, kind: "run_command", payload });
200
+ return resp.result as CommandResult;
201
+ }
202
+
203
+ /**
204
+ * Bind a claim to evidence. A failed requirement terminates the program
205
+ * immediately; completion is structurally unreachable past it.
206
+ */
207
+ require(ok: boolean, claim: string, evidence?: unknown): void {
208
+ const req: Requirement = { claim, passed: ok };
209
+ if (evidence !== undefined) req.evidence_digest = digest(compact(evidence));
210
+ this.requirements.push(req);
211
+ if (!ok) {
212
+ throw new EmitSignal({
213
+ type: "terminal",
214
+ terminal: { status: "requirement_failed", reason: claim },
215
+ requirements: this.requirements,
216
+ });
217
+ }
218
+ }
219
+
220
+ blocked(reason: string): never {
221
+ throw new Blocked(reason);
222
+ }
223
+
224
+ refused(reason: string): never {
225
+ throw new Refused(reason);
226
+ }
227
+
228
+ /** @internal replay-or-emit; the certified contract's step. */
229
+ private step(req: Request): ResponseEnvelope {
230
+ const entries = this.journal.entries ?? [];
231
+ const seq = this.idx + 1;
232
+ if (this.idx < entries.length) {
233
+ const entry = entries[this.idx];
234
+ const want = requestDigest(entry.request);
235
+ const got = requestDigest(req);
236
+ if (want !== got) {
237
+ // Mandatory per-step check: consuming a recorded response for a
238
+ // drifted operation is the forbidden state the rival design fails.
239
+ throw new EmitSignal({
240
+ type: "diverged",
241
+ divergence: {
242
+ sequence: seq,
243
+ expected_digest: want,
244
+ got_digest: got,
245
+ detail: `replay produced operation "${req.id}" (${req.kind}) where the journal recorded "${entry.request.id}" (${entry.request.kind})`,
246
+ },
247
+ });
248
+ }
249
+ this.idx++;
250
+ return entry.response;
251
+ }
252
+ this.idx++;
253
+ throw new EmitSignal({
254
+ type: "request",
255
+ envelope: {
256
+ protocol: "yield.v1",
257
+ run_id: this.journal.run_id,
258
+ skill: this.journal.skill,
259
+ sequence: seq,
260
+ request: req,
261
+ },
262
+ requirements: this.requirements,
263
+ });
264
+ }
265
+
266
+ /** @internal */
267
+ terminalFor(err: unknown): ProgramOutput | null {
268
+ if (err instanceof EmitSignal) return err.output;
269
+ if (err instanceof Blocked)
270
+ return {
271
+ type: "terminal",
272
+ terminal: { status: "blocked", reason: err.reason },
273
+ requirements: this.requirements,
274
+ };
275
+ if (err instanceof Refused)
276
+ return {
277
+ type: "terminal",
278
+ terminal: { status: "refused", reason: err.reason },
279
+ requirements: this.requirements,
280
+ };
281
+ return null;
282
+ }
283
+
284
+ /** @internal */
285
+ completed(result: unknown): ProgramOutput {
286
+ return {
287
+ type: "terminal",
288
+ terminal: { status: "completed", result },
289
+ requirements: this.requirements,
290
+ };
291
+ }
292
+ }
293
+
294
+ function emit(output: ProgramOutput): never {
295
+ stdout.write(JSON.stringify(output) + "\n");
296
+ exit(0);
297
+ }
298
+
299
+ /**
300
+ * Run a skill program under the supervisor protocol. The program's return
301
+ * value is the run result; throw `ctx.blocked(...)`/`ctx.refused(...)` for
302
+ * the honest terminals.
303
+ */
304
+ export function defineSkill(program: (ctx: Context) => unknown): void {
305
+ const path = env.YIELD_JOURNAL;
306
+ if (!path) {
307
+ stderr.write(
308
+ "yield: YIELD_JOURNAL is not set; this program is run by yskill, not directly\n",
309
+ );
310
+ exit(2);
311
+ }
312
+ let journal: Journal;
313
+ try {
314
+ journal = JSON.parse(readFileSync(path, "utf8")) as Journal;
315
+ } catch (err) {
316
+ stderr.write(`yield: cannot read journal: ${String(err)}\n`);
317
+ exit(2);
318
+ }
319
+ const ctx = new Context(journal!);
320
+ try {
321
+ emit(ctx.completed(program(ctx)));
322
+ } catch (err) {
323
+ const out = ctx.terminalFor(err);
324
+ if (out) emit(out);
325
+ stderr.write(`yield: program error: ${String(err)}\n`);
326
+ exit(1);
327
+ }
328
+ }