diff --git a/.changeset/readme-prose-to-code.md b/.changeset/readme-prose-to-code.md new file mode 100644 index 0000000..3eed5c3 --- /dev/null +++ b/.changeset/readme-prose-to-code.md @@ -0,0 +1,5 @@ +--- +"@operatorstack/yield": patch +--- + +Show a tested prose-to-code workflow in the README and point npm packages to the Yield website. diff --git a/README.md b/README.md index 4df8e5d..62dc8c3 100644 --- a/README.md +++ b/README.md @@ -1,215 +1,196 @@ -# Yield +

+ + Yield + +

+ +

Yield

+ +

Move repeatable coding-agent instructions from words into code.

+ +

+ In-repository workflows for TypeScript, Python, Go, and Rust. +

-

- npm +

+ npm version Build status + MIT license

-Yield runs portable, resumable workflows for coding agents. +

+ Website · + Documentation · + npm · + GitHub +

-**Write one skill workflow. Run it from your coding agents.** +Yield turns repeated instructions for coding agents into typed, resumable +programs. The canonical workflow stays inside your repository beside the code +and dependencies it uses. Generated `SKILL.md` files only help coding agents +discover it. -Skill workflows are portable, executable processes that combine agent skills -with deterministic code, state, and verification. +Verified with Cursor, Codex, and Claude Code. Registry-backed project paths are +available for 73 more coding agents. -Write the workflow in TypeScript, Python, Go, or Rust. Combine agent judgment, -real commands, human input, checks, and saved state. Yield generates the small -adapter each coding agent expects. +## Move repeated instructions into code -The split is small: +A release skill often starts as prose: -| term | meaning | -|---|---| -| **skill** | one reusable capability | -| **workflow** | order, branches, checks, and saved state | -| **skill workflow** | an executable composition of skills, code, commands, and human input | -| **adapter** | a generated `SKILL.md` that lets one coding agent discover the workflow | +> Run the tests. Review the release. Stop if the review finds a critical issue. +> Ask me before publishing. Publish the package, then verify the registry. -The canonical skill workflow stays beside your code. Generated adapters are -disposable. The model keeps reasoning, exploration, editing, and judgment; -normal code owns the repeatable control flow. +Yield makes the order and stopping rules executable: -## Install + +```typescript +import { defineSkill } from "@operatorstack/yield"; -Choose one language package. TypeScript and Python include a package-local -runtime. Go and Rust install the matching runtime under `.yield/bin` in the -repository. Generated adapters never use a global `yskill` from `PATH`. +type Review = { critical: number; summary: string }; -```bash -# TypeScript (public npm) -npm install --save-exact @operatorstack/yield@0.1.30 -npm exec -- yskill --version +defineSkill((ctx) => { + const tests = ctx.runCommand("test", "echo tests-ok", 300); + ctx.require(tests.exit_code === 0, "the test command succeeds", tests); + + const review = ctx.agentTask( + "review-release", + "Review this release. Report critical findings and a short summary.", + { stdout: tests.stdout, stderr: tests.stderr }, + { + type: "object", + required: ["critical", "summary"], + properties: { + critical: { type: "integer", minimum: 0 }, + summary: { type: "string", minLength: 1 }, + }, + }, + ); + ctx.require(review.critical === 0, "the review has no critical findings", review); + + const approval = ctx.askUser("approve-publish", "Publish this package?", [ + { value: "yes", label: "Publish" }, + { value: "no", label: "Stop" }, + ]); + if (approval !== "yes") ctx.refused("the operator declined publication"); + + const publish = ctx.runCommand("publish", "echo publish-ok", 600); + ctx.require(publish.exit_code === 0, "the publish command succeeds", publish); + + const registry = ctx.runCommand("verify-registry", "echo registry-ok", 300); + ctx.require(registry.exit_code === 0, "the registry contains the release", registry); + + return { published: true, summary: review.summary }; +}); +``` + -# Python, after creating and activating .venv -python -m pip install yieldskill==0.1.29 --index-url https://get.operatorstack.systems/pip/simple/ -python -m yieldskill --version +The example uses harmless commands so its fixture can run in any checkout. +Replace them with the test, publish, and registry commands for your project. +The complete tested source is in +[`examples/release-checklist`](examples/release-checklist/). -# Go, from the repository root -mkdir -p .yield/bin -GOBIN="$PWD/.yield/bin" GOPROXY=https://get.operatorstack.systems/go,direct \ - go install github.com/operatorstack/yield/cmd/yskill@v0.1.29 -.yield/bin/yskill --version +## Install and create a workflow -# Rust, from the repository root -cargo install yieldskill@0.1.29 --root .yield \ - --index sparse+https://get.operatorstack.systems/cargo/index/ --locked -.yield/bin/yskill --version +Install the TypeScript SDK and its repository-local CLI: + +```bash +npm install --save-exact @operatorstack/yield +npm exec -- yskill --version ``` [Public npm releases](https://www.npmjs.com/package/@operatorstack/yield) use trusted publishing. The SDK package and all six runtime packages include SLSA v1 provenance. -Yield creates `.yield/.gitignore` when it registers a Go or Rust workflow, so -the local runtime and run state stay out of Git. -On Windows, run the local binary as `.\.yield\bin\yskill.exe`. - -## Create and register a skill workflow - -Keep the canonical workflow beside the language dependencies it uses. Yield writes -small adapters into each coding agent's project skill directory; it does not -copy the workflow or install its dependencies again. +Create, test, and register a workflow: ```bash -# TypeScript example -npm exec -- yskill init skills/review \ +npm exec -- yskill init skills/release \ --language typescript \ - --description "Review changed code when the user wants a branch checked before shipping." + --description "Test, review, approve, publish, and verify a package." -# Replace the intentionally incomplete starter and fixture, then check it. -npm exec -- yskill doctor skills/review --test +# Replace the starter with your workflow and fixture. +npm exec -- yskill doctor skills/release --test # Detect installed agents, or pass --agent cursor,codex,claude-code. -npm exec -- yskill register skills/review +npm exec -- yskill register skills/release ``` -`yskill agents` lists the available agent IDs and project paths. Cursor, -Codex, and Claude Code are verified. Remaining entries support explicit path -registration from the pinned open registry; they are not presented as -end-to-end verified. - -## How a skill workflow runs +Yield writes small adapters into each coding agent's project skill directory. +It does not copy the workflow or install its dependencies again. -Deterministic re-execution: on every run/resume, `yskill` re-executes the -skill workflow from the top, feeding recorded responses back in order. At -the first unanswered operation the SDK emits a `yield.v1` request envelope -and the process exits — no daemon. A replayed step that produces a -different operation than the journal recorded is a divergence and fails -the run loudly; it never silently forks. +## How Yield runs and resumes -- **`yskill`** owns the append-only run log - (`.yield/runs/.jsonl`), sequence and digest binding, response - validation, and every refusal (stale, duplicate, wrong-run, - schema-invalid, digest-mismatch, completion-unproven). -- **The skill workflow** is an ordinary program using one Yield SDK; every - side effect crosses a yielded primitive. +1. Your workflow emits one typed operation. +2. Yield records the request and exits. It does not run a daemon. +3. The coding agent, user, or CLI supplies the result. +4. Yield resumes from the journal and replays the program to the next operation. -Five primitives, two exits: +If replay produces a different operation, the run fails instead of silently +forking. Every side effect crosses one of these primitives: -| primitive | who acts | +| Primitive | Purpose | |---|---| -| `AskUser` | the agent asks through its normal interface | -| `AgentTask` | the model reasons; the result must be schema-valid JSON | -| `RunCommand` | **yskill executes it itself** — results are observed fact, not transcription | -| `Require` | a claim bound to evidence; failure makes completion structurally unreachable | -| `Complete` / `Blocked` / `Refused` | honest terminals, always recorded | +| `runCommand` | Execute a command and record its exit code and output. | +| `agentTask` | Ask the coding agent for schema-valid JSON. | +| `askUser` | Request an explicit human decision. | +| `require` | Bind a required claim to recorded evidence. | +| `blocked` / `refused` | Stop honestly when work cannot or must not continue. | -## Four languages, one execution contract +See the [primitive guides](docs/primitives/README.md) and +[runtime reference](docs/reference/cli.md) for the full contract. -Write the skill workflow in Go, TypeScript, Python, or Rust. Every SDK -implements the same certified execution contract, and the conformance suite -(`internal/conformance`) runs the same program in all four languages and -asserts identical observable behavior. The language-neutral schemas are -documented in the [runtime reference](docs/reference/sdk-parity.md). +## Languages and coding agents -| language | SDK | example | +All four SDKs implement the same execution contract. The conformance suite runs +the same program in every language and compares observable behavior. + +| Language | SDK | Example | |---|---|---| -| Go | `sdk/yield` | `examples/investigate` — bounded hypothesis loop | -| TypeScript | `sdk/typescript` (`@operatorstack/yield`) | `examples/release-checklist` — human-gated deploy | -| Python | `sdk/python` (`yieldskill`) | `examples/env-doctor` — probe, branch, resume after the human | -| Rust | `sdk/rust` (`yieldskill`) | `examples/data-migration` — dry-run → approve → apply → verify | - -Skills declare their language and runner in `skill.json`: -`{"version": 1, "language": "typescript", "run": ["node", "main.ts"]}`. - -## Ten skill workflows, every language - -The [example library](examples/library/) implements ten common skill workflows -independently in all four SDKs: branch review, failure -investigation, web QA, package release, issue triage, CI repair, dependency -upgrade, database migration, security audit, and iOS publishing. - -Each language has the same skill workflow, a thin adapter, and a scripted -fixture. Start from the work you already do instead of starting from a -framework tutorial. - -## Documentation - -Start with [what a skill workflow is](docs/skill-workflows.md), then build one -with the [ten-minute TypeScript quickstart](docs/quickstart.md). Continue with -the documentation for your job: - -- [primitive guides](docs/primitives/README.md) — commands, model work, - human input, evidence gates, and outcomes; -- [tutorials](docs/tutorials/README.md) — review, approval, environment - repair, bounded debugging, and migration; -- [examples](docs/examples.md) — working programs in all four languages; -- [coding-agent setup](docs/agent-setup.md) — register one skill workflow with the - agents used by the project; -- [Agent Plugins and Yield](docs/agent-plugins.md) — where portable packaging ends - and workflow execution begins; -- [test workflow effects](docs/testing-fixtures.md) — deterministic fixture - setup, response effects, standard-input JSON, and cleanup; -- [evaluations](evals/README.md) — first-party workflow conformance and runtime - invariant results, including the exact claim boundary; -- [convert an existing skill](docs/convert-existing-skill.md) — move - control flow into code without claiming that fixture execution proves - every reading of the original prose; -- [CLI and runtime reference](docs/reference/cli.md). - -## Try it +| TypeScript | [`@operatorstack/yield`](sdk/typescript/) | [`release-checklist`](examples/release-checklist/) | +| Python | [`yieldskill`](sdk/python/) | [`env-doctor`](examples/env-doctor/) | +| Go | [`sdk/yield`](sdk/yield/) | [`investigate`](examples/investigate/) | +| Rust | [`yieldskill`](sdk/rust/) | [`data-migration`](examples/data-migration/) | -``` -go build -o yskill ./cmd/yskill -./yskill test examples/library/typescript/review-branch -./yskill test examples/library/python/review-branch -./yskill test examples/library/go/review-branch -./yskill test examples/library/rust/review-branch -YSKILL="$PWD/yskill" bash ./examples/library/test-all.sh -./yskill test examples/investigate # Go: scripted fixture run to completion -./yskill test examples/release-checklist # TypeScript (Node >= 23.6) -./yskill test examples/env-doctor # Python 3.10+ -./yskill test examples/data-migration # Rust (cargo) -./yskill run examples/investigate # prints the first operation envelope -./yskill init my-skill --description "Run this skill workflow when ..." -./yskill register my-skill --agent codex # write a thin project adapter -./yskill doctor my-skill --agent codex # verify package + adapter wiring -``` +Cursor, Codex, and Claude Code are verified integrations. Yield also includes +registry-backed project paths for 73 more coding agents. Those paths support +explicit registration; they are not presented as end-to-end verified. + +Run `yskill agents` to inspect the pinned registry and available project paths. + +## Guarantees and limits + +Yield provides deterministic control flow, typed requests and responses, +persistent run state, replay with divergence detection, stale and duplicate +response rejection, and evidence-bound completion. -The reference skill, `examples/investigate`, encodes an investigation -discipline in code: at least three hypotheses, cheapest-to-disprove -first, at most three failed attempts, completion requires a causal chain -— or an honest `Blocked` at the frontier. +Schema validity is not truth. Yield cannot prove that an agent performed only +the requested work. `runCommand` is different: the Yield CLI executes the +command, so the recorded exit code and output are observed facts. -## What it guarantees — and what it doesn't +Yield is not a daemon, hosted runtime, workflow DSL, marketplace, new agent +loop, multi-agent orchestrator, or security sandbox. -Guaranteed: deterministic control flow, typed requests/responses, -persistent state, replay (divergence fails loudly), stale/duplicate -rejection, evidence-bound completion. +## Documentation and development -Not guaranteed: that the agent performed *only* the requested operation, -or that a schema-valid `agent_task` result is true — schema validity is -not truth. `RunCommand` is the exception by construction: commands are -executed by the Yield CLI, so exit codes and output enter the log as -observed fact. Runtime and conformance tests enforce these guarantees. +- [What a skill workflow is](docs/skill-workflows.md) +- [Ten-minute TypeScript quickstart](docs/quickstart.md) +- [Working examples in all four languages](docs/examples.md) +- [Coding-agent setup](docs/agent-setup.md) +- [Testing workflow effects](docs/testing-fixtures.md) +- [Guarantees and evaluation results](evals/README.md) -## What it is not +Run the main checks from the repository root: + +```bash +go test ./... +npm run test:release +``` -Not a daemon, not a hosted runtime, not a workflow DSL, not a -marketplace, not a new agent loop, not a multi-agent orchestrator, not a -security sandbox. +The [example library](examples/library/) contains ten common workflows in all +four SDKs, including code review, failure investigation, CI repair, dependency +updates, database migration, security audit, and package release. --- -This is Yield's canonical source repository. Changes, verification, release -intent, and publishing control all live here. MIT licensed. +Yield is MIT licensed. This repository is its canonical source. diff --git a/evals/results/latest.json b/evals/results/latest.json index 5968aa3..bbeeafa 100644 --- a/evals/results/latest.json +++ b/evals/results/latest.json @@ -1,8 +1,8 @@ { "schema_version": 2, "methodology_version": "1.1", - "generated_at": "2026-08-07T09:37:20.395Z", - "source_digest": "88346db6f97443ded683c99283a07f2695805bf2628e113acab5fbc8a041e268", + "generated_at": "2026-08-07T11:37:55.058Z", + "source_digest": "d75c9a27b4782ff37a16c76b472327ecb6c8ab1b014dcf8e01626870340a82ac", "status": "passed", "workflow_conformance": { "passed": 40, diff --git a/examples/release-checklist/SKILL.md b/examples/release-checklist/SKILL.md index de36e85..a78b077 100644 --- a/examples/release-checklist/SKILL.md +++ b/examples/release-checklist/SKILL.md @@ -1,6 +1,6 @@ --- name: release-checklist -description: Deploy with an explicit human gate, build verification, and evidence-bound completion. +description: Test, review, approve, publish, and verify a package in a fixed order. --- Run: @@ -9,14 +9,14 @@ Run: Follow each returned operation exactly. -- `ask_user`: ask the user using the host's normal interface. - `agent_task`: perform the task and return schema-valid JSON. +- `ask_user`: ask the user through the host's normal interface. - `run_command`: yskill executes it itself; you will not see this kind. Resume the run after each operation: yskill resume --response response.json --skill . -Do not skip an operation or invent its response. The program enforces the -order: approval before build, build before notes, notes before deploy — -and completion requires both commands' observed exit codes. +Do not skip an operation or invent its response. The program enforces this +order: test, review, approval, publish, then registry verification. Critical +review findings and failed commands stop completion. diff --git a/examples/release-checklist/fixtures/responses.json b/examples/release-checklist/fixtures/responses.json index 5e2303a..ba1324e 100644 --- a/examples/release-checklist/fixtures/responses.json +++ b/examples/release-checklist/fixtures/responses.json @@ -1,6 +1,7 @@ { - "approve-deploy": { "value": "yes" }, - "release-notes": { - "notes": "Ships the resumable-run supervisor with evidence-bound completion; no schema changes; rollback is redeploy of the previous tag." - } + "review-release": { + "critical": 0, + "summary": "Tests passed and the release has no critical findings." + }, + "approve-publish": { "value": "yes" } } diff --git a/examples/release-checklist/main.ts b/examples/release-checklist/main.ts index 5924641..b3feeb4 100644 --- a/examples/release-checklist/main.ts +++ b/examples/release-checklist/main.ts @@ -1,31 +1,41 @@ -// Example skill (TypeScript): a release runbook where the model cannot -// skip step four. Order, the human gate, and the verification requirement -// are code; judgment (the release notes) stays with the model. +// Example skill (TypeScript): a release checklist where tests, review, +// approval, publishing, and registry verification cannot be skipped. import { defineSkill } from "../../sdk/typescript/src/index.ts"; -defineSkill((ctx) => { - const approval = ctx.askUser("approve-deploy", "Deploy to production?", [ - { value: "yes", label: "Deploy" }, - { value: "no", label: "Abort" }, - ]); - if (approval !== "yes") ctx.refused("the operator declined the deploy"); +// README_EXAMPLE_START +type Review = { critical: number; summary: string }; - const build = ctx.runCommand("build", "echo build-ok", 300); - ctx.require(build.exit_code === 0, "the build succeeds", build); +defineSkill((ctx) => { + const tests = ctx.runCommand("test", "echo tests-ok", 300); + ctx.require(tests.exit_code === 0, "the test command succeeds", tests); - const notes = ctx.agentTask<{ notes: string }>( - "release-notes", - "Draft one-paragraph release notes for this deploy.", - { approved: approval }, + const review = ctx.agentTask( + "review-release", + "Review this release. Report critical findings and a short summary.", + { stdout: tests.stdout, stderr: tests.stderr }, { type: "object", - required: ["notes"], - properties: { notes: { type: "string", minLength: 1 } }, + required: ["critical", "summary"], + properties: { + critical: { type: "integer", minimum: 0 }, + summary: { type: "string", minLength: 1 }, + }, }, ); + ctx.require(review.critical === 0, "the review has no critical findings", review); + + const approval = ctx.askUser("approve-publish", "Publish this package?", [ + { value: "yes", label: "Publish" }, + { value: "no", label: "Stop" }, + ]); + if (approval !== "yes") ctx.refused("the operator declined publication"); + + const publish = ctx.runCommand("publish", "echo publish-ok", 600); + ctx.require(publish.exit_code === 0, "the publish command succeeds", publish); - const deploy = ctx.runCommand("deploy", "echo deploy-ok", 600); - ctx.require(deploy.exit_code === 0, "the deploy command succeeds", deploy); + const registry = ctx.runCommand("verify-registry", "echo registry-ok", 300); + ctx.require(registry.exit_code === 0, "the registry contains the release", registry); - return { deployed: true, notes: notes.notes }; + return { published: true, summary: review.summary }; }); +// README_EXAMPLE_END diff --git a/packaging/assemble.mjs b/packaging/assemble.mjs index 7f3b544..447de63 100644 --- a/packaging/assemble.mjs +++ b/packaging/assemble.mjs @@ -74,7 +74,7 @@ async function assembleNpm({ version, binaries, output }) { name: npmPackage(target), version, description: `Yield runtime for ${target.id}`, license: "MIT", os: [target.nodeOs], cpu: [target.nodeCpu], main: `./${runtime}`, files: [runtime, "LICENSE"], repository: { type: "git", url: "git+https://github.com/operatorstack/yield.git" }, - homepage: "https://github.com/operatorstack/yield#readme", + homepage: "https://yield.operatorstack.systems/", bugs: { url: "https://github.com/operatorstack/yield/issues" }, publishConfig: { access: "public", provenance: true, registry: "https://registry.npmjs.org/" }, }, null, 2)}\n`); diff --git a/packaging/assemble.test.mjs b/packaging/assemble.test.mjs index 5ad26cb..bd7d870 100644 --- a/packaging/assemble.test.mjs +++ b/packaging/assemble.test.mjs @@ -6,6 +6,8 @@ import { tmpdir } from "node:os"; import { assemble, isPackageVersion } from "./assemble.mjs"; import { binaryName, npmPackage, targets } from "./targets.mjs"; +const homepage = "https://yield.operatorstack.systems/"; + test("accepts stable and exact Yield canary versions", () => { assert.equal(isPackageVersion("1.2.3"), true); assert.equal(isPackageVersion("0.0.0-canary.20260807104031.b081bae38282"), true); @@ -31,6 +33,7 @@ test("assembles one public npm package and six matching runtimes", async (t) => assert.equal(main.name, "@operatorstack/yield"); assert.equal(main.version, "1.2.3"); + assert.equal(main.homepage, homepage); assert.deepEqual(main.publishConfig, { access: "public", provenance: true, @@ -40,13 +43,16 @@ test("assembles one public npm package and six matching runtimes", async (t) => main.optionalDependencies, Object.fromEntries(targets.map((target) => [npmPackage(target), "1.2.3"])), ); - assert.match(await readFile(join(output, "npm/yield/README.md"), "utf8"), /^# Yield/m); + const assembledReadme = await readFile(join(output, "npm/yield/README.md"), "utf8"); + assert.equal(assembledReadme, await readFile(join(import.meta.dirname, "../README.md"), "utf8")); + assert.match(assembledReadme, /

Yield<\/h1>/); assert.match(await readFile(join(output, "npm/yield/LICENSE"), "utf8"), /MIT License/); for (const target of targets) { const runtime = await readJson(join(output, `npm/${target.id}/package.json`)); assert.equal(runtime.name, npmPackage(target)); assert.equal(runtime.version, "1.2.3"); + assert.equal(runtime.homepage, homepage); assert.deepEqual(runtime.os, [target.nodeOs]); assert.deepEqual(runtime.cpu, [target.nodeCpu]); assert.equal(runtime.publishConfig.provenance, true); diff --git a/scripts/readme.test.mjs b/scripts/readme.test.mjs new file mode 100644 index 0000000..cada309 --- /dev/null +++ b/scripts/readme.test.mjs @@ -0,0 +1,54 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { resolve } from "node:path"; + +const root = resolve(import.meta.dirname, ".."); + +async function text(path) { + return readFile(resolve(root, path), "utf8"); +} + +test("README release example matches the tested TypeScript source", async () => { + const [readme, source] = await Promise.all([ + text("README.md"), + text("examples/release-checklist/main.ts"), + ]); + + const readmeMatch = readme.match( + /\s*```typescript\n([\s\S]*?)\n```\s*/, + ); + assert.ok(readmeMatch, "README release example markers are missing"); + + const sourceMatch = source.match( + /\/\/ README_EXAMPLE_START\n([\s\S]*?)\n\/\/ README_EXAMPLE_END/, + ); + assert.ok(sourceMatch, "TypeScript release example markers are missing"); + + const readmeProgram = readmeMatch[1] + .replace(/^import \{ defineSkill \} from "@operatorstack\/yield";\n+/, "") + .trim(); + assert.equal(readmeProgram, sourceMatch[1].trim()); +}); + +test("README agent claims match the pinned registry", async () => { + const [readme, registryText] = await Promise.all([ + text("README.md"), + text("cmd/yskill/registry/agents.json"), + ]); + const registry = JSON.parse(registryText); + const verified = registry.agents.filter((agent) => agent.tier === "verified"); + const registryBacked = registry.agents.filter((agent) => agent.tier === "registry"); + const normalized = readme.replace(/\s+/g, " "); + + assert.deepEqual( + verified.map((agent) => agent.id).sort(), + ["claude-code", "codex", "cursor"], + ); + assert.match(normalized, /Verified with Cursor, Codex, and Claude Code\./); + assert.match( + normalized, + new RegExp(`Registry-backed project paths are available for ${registryBacked.length} more coding agents\\.`), + ); + assert.doesNotMatch(readme, /Agent Plugins and Yield/); +}); diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index e6878c4..7a1eadf 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -32,7 +32,7 @@ "url": "git+https://github.com/operatorstack/yield.git", "directory": "sdk/typescript" }, - "homepage": "https://github.com/operatorstack/yield#readme", + "homepage": "https://yield.operatorstack.systems/", "bugs": { "url": "https://github.com/operatorstack/yield/issues" },