rayspec
Declarative, YAML-defined workflows for coding agents — running on the Claude Agent SDK and the OpenAI Codex SDK. A pure CLI: no server, no UI, no database.
mkdir myproj && cd myproj
pip install rayspec # the engine AND both agent CLIs — no Node, no npm, nothing else
rayspec quickstart # check this machine, scaffold a project, prove it with a free dry run
YAML coordinates. Code computes. Agents judge.
A workflow says what runs, in what order, under which gates, with which agent. Deterministic
shell:/python: steps do the computing (and their verdicts are authoritative); prompt: steps
hand judgement to an agent; the engine runs the DAG, fans out, loops, pauses for humans, keeps
every step's output as a file and resumes from it.
# .rayspec/workflows/fix_issue.yaml
rayspec: 1
name: fix_issue
inputs:
issue: { type: integer, required: true }
agents:
triage: { provider: claude, model: small, access: read-only }
implementer: { provider: codex, model: medium, effort: high }
steps:
- id: fetch
shell: gh issue view "$RAYSPEC_INPUT_ISSUE" --json title,body
- id: assess
needs: [fetch]
agent: triage
prompt: "{{ steps.fetch.output }}\nIs this real and worth fixing now?"
output_schema: { type: object, properties: { verdict: { enum: [fix, skip] } }, required: [verdict] }
- id: bail
needs: [assess]
when: steps.assess.output.verdict == 'skip'
stop: { status: cancelled, reason: not worth fixing }
- id: build
needs: [assess]
when: steps.assess.output.verdict == 'fix'
loop:
max_iterations: 3
until: steps.check.ok
steps:
- id: implement
agent: implementer
session: implement
prompt: "Fix the issue with the smallest change that works. {{ steps.fetch.output }}"
- id: check
needs: [implement]
shell: pytest -q
allow_failure: true
- id: confirm
needs: [build]
approve: "Open a PR for the fix?"
- id: pr
needs: [confirm]
shell: git push -u origin HEAD && gh pr create --fill
outputs:
verdict: "{{ steps.assess.output.verdict }}"
What you get: an implicit DAG with parallel siblings, each: fan-out, loop: with an until
verdict, when: branches and stop:, approve: gates (TTY prompt, else pause + resume),
include: for reusable blocks, named agents, strict Jinja templating with loud failures, Claude
and Codex behind one capability-checked adapter, file-based runs with resume, and a git
worktree per run by default.
Quickstart
mkdir myproj && cd myproj # quickstart scaffolds where you stand — not in $HOME
pip install rayspec # the engine AND both agent CLIs — no Node, no npm, nothing else to install
rayspec quickstart # check this machine, scaffold a project, prove it with a free dry run
That is the whole first five minutes. rayspec quickstart prints what the machine has (Python,
git, both bundled agent CLIs, whether you are logged in), offers the two things it cannot
decide for you — a provider login, and git init when you are not in a repository — scaffolds
.rayspec/, and then runs a workflow end to end with scripted agents: no credentials, no
network, no cost. It finishes by naming the commands that matter next and saying which one
spends money.
Nothing above needs an account. The whole authoring loop — write, validate, plan, dry-run — is
free, and only a real agent run needs a login. rayspec quickstart --no-interactive asks
nothing and is safe in a Dockerfile; rayspec doctor answers "what is missing?" on its own at
any time.
Other ways in
uv tool install rayspec # or: pipx install rayspec
uvx rayspec version # one-off, nothing installed
uv tool install git+https://github.com/rayspec-labs/rayspec-py # from source, over HTTPS
uv tool install git+ssh://git@github.com/rayspec-labs/rayspec-py # from source, over SSH
uvx --from git+https://github.com/rayspec-labs/rayspec-py rayspec version # one-off, from source
uv tool install <path-to-checkout> # from a local clone (or `uv tool install .` inside it)
What quickstart set up, and what to do with it
rayspec version # prints `rayspec <version>`
rayspec doctor # Python, RAYSPEC_HOME, git/uv, SDKs, bundled CLIs, auth hints; --probe runs one real turn per provider
# the project quickstart scaffolded (rayspec init does this on its own)
# .rayspec/{workflows/example.yaml, agents/reviewer.yaml, prompts/, config.yaml, stubs/example.yaml, tests/example/approves.yaml}
# + .claude/skills/{rayspec-workflows,rayspec-cli}/ (the coding-agent skills; --no-skill to skip)
# --kind content for a non-code project; rayspec init --force to overwrite
# check it
rayspec workflows # discovered workflows
rayspec validate # schema, graph, references, provider capabilities
rayspec test # the scaffolded case — a scripted dry run, no login needed
rayspec plan example # inputs, resolved agents/models, step order, capability report
# dry-run it — providers become a scripted stub, shell steps are skipped, no login needed
rayspec run example --dry-run --stubs .rayspec/stubs/example.yaml # ✓ files · ✓ review · outputs table
# run it for real (the example reviews in place; delete its `isolation: none` line to get a git worktree per run)
rayspec run example --input target=src
For real agent runs you need a logged-in claude (claude.ai) or codex (codex login) — both
CLIs ship with rayspec, and rayspec quickstart offers to run the login for you, by absolute
path. Dry runs need neither.
Prefer to start from a blank file? A workflow is one YAML document; --stubs-init writes the
stub answers for a dry run from it:
mkdir -p .rayspec/workflows
cat > .rayspec/workflows/review.yaml <<'EOF'
rayspec: 1
name: review
description: Review the working tree and summarise findings.
inputs:
target: { type: string, default: "." }
agents:
reviewer: { provider: claude, model: small, access: read-only,
instructions: You are a meticulous code reviewer. Be concrete. }
steps:
- id: files
shell: git ls-files "{{ inputs.target }}" | head -50
- id: review
needs: [files]
agent: reviewer
prompt: |
Review these files and list findings:
{{ steps.files.output }}
output_schema:
type: object
properties: { verdict: { enum: [approve, request_changes] }, summary: { type: string } }
required: [verdict, summary]
outputs:
verdict: "{{ steps.review.output.verdict }}"
summary: "{{ steps.review.output.summary }}"
EOF
# 3. check it
rayspec workflows # discovered workflows
rayspec validate # schema, graph, references, provider capabilities
rayspec plan review # inputs, resolved agents/models, step order, capability report
# 4. dry-run it — providers become a scripted stub, shell steps are skipped, no login needed
rayspec run review --dry-run # ✓ files · ✓ review · outputs table
rayspec run review --dry-run --stubs-init stubs.yaml # optional: scaffold stub answers to edit …
rayspec run review --dry-run --stubs stubs.yaml # … and replay them
# 5. run it for real (a git worktree on branch rayspec/review-<id> is created for the run)
rayspec run review --input target=src
A run prints one line per step, the outputs: table, the worktree path and branch, tokens/cost
and the run directory (~/.rayspec/projects/<slug>/runs/<run-id>/). Exit codes: 0 succeeded ·
1 failed · 2 usage/validation error · 3 paused at an approval gate · 4 cancelled ·
130 interrupted. Inspect runs with rayspec runs, rayspec show <run> and rayspec logs <run>;
continue an interrupted or paused run with rayspec resume <run> (or rayspec run review --resume <run>), decide a gate without a terminal with rayspec approve|reject <run>.
Use rayspec from a coding agent
rayspec ships two Claude Code skills — each a skill file plus compressed
references to these docs — that teach an agent to author, validate, dry-run, run and debug
workflows without reading this repository: rayspec-workflows for the YAML DSL and rayspec-cli
for the command line. Each points at the other. rayspec init writes both into the project; you
can also install them by hand:
rayspec skill install # <project>/.claude/skills/{rayspec-workflows,rayspec-cli}/
rayspec skill install --global # ~/.claude/skills/… (every project)
rayspec skill install rayspec-cli # just one of them
Open a fresh Claude Code session afterwards — the skills load automatically. rayspec skill show
tells you whether the installed copies are up to date; rayspec skill install --force refreshes
them after upgrading rayspec.
Documentation
- docs/concepts.md — the mental model (workflow/steps/run, DAG + bodies, scopes, outputs, runs, isolation)
- docs/schema.md — every field and default, the join truth table, statuses, exit codes, Jinja traps
- docs/templating.md — context, filters, the shell env-ref rule, python bodies, inputs
- docs/providers.md — the neutral adapter, the generated capability matrix, Claude/Codex mapping, auth, pricing
- docs/cli.md — every command, flag and
--jsonshape - docs/runs-and-resume.md — the run directory,
run.json, events, resume, approval gates - docs/policy.md —
policy.yaml, the worktree change guard, trusted workflows, and what is only advisory - docs/isolation.md — worktrees,
--repo, registered projects, locks - docs/extending.md — adding a provider via entry points, sinks, stores, embedding
- docs/examples.md — the example projects and what each one shows
- docs/testing.md —
rayspec test: declarative cases,--junitin CI, the golden corpus - docs/ci.md — rayspec in CI: the dry-run check as a reusable workflow, and how rayspec is released
- docs/constitution.md — why the schema is narrow (admissibility test, case law)
- docs/agent-skill.md — the Claude Code skill: what it contains,
rayspec skill install|show|path, how it is generated - docs/README.md — index of the above
Module boundaries are documented in CONTRACTS.md.
Status
Released — rayspec version prints the build you run, and CHANGELOG.md has
the history.
Shipped: the schema, loader and validator, templating, the Claude and Codex adapters, the engine
(DAG, loop/each/include, approval, resume, dry run, the per-workdir path lock), run-level caps
(budget_usd, max_tokens, timeout_total), step-level artifacts:, the file store, worktree
isolation and --repo, the Rich live console (rayspec run on a TTY; one line per step
otherwise), secret: true inputs, extension entry points for commands, stores, sinks and approval
prompts, the two packaged Claude Code skills (rayspec-workflows for authoring the YAML and
rayspec-cli for operating the engine), and the examples/ gallery.
Released on PyPI: rayspec — pip install rayspec (or
uv tool install rayspec) gets you this build, and it brings the Claude Code and Codex CLIs with
it, so no separate install of either is needed.
Commands: quickstart, init, new, doctor, run, resume, approve, reject, cancel,
validate, plan, test, explain, eval, show, logs, audit, runs, costs, lock,
workflows, agents, providers, plugins, projects, worktrees, trust, schema,
skill, completion, version.
Development
uv sync --all-groups
uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest -q -m 'not live'
uv run python scripts/gen_capability_matrix.py # regenerate the matrix in docs/providers.md
uv run python scripts/gen_skill.py # regenerate both skills' references/ from docs/ + mirror .claude/skills/ (--check in the gate)
Python ≥ 3.11, anyio-only concurrency.
Contributing
Bug reports, patches and questions are all welcome. CONTRIBUTING.md has the setup, the one-line quality gate, what a test is expected to look like, and the (deliberately high) bar for new schema fields. Everyone taking part is expected to follow the Code of Conduct.
Found something exploitable? Please do not open an issue — SECURITY.md has the
private reporting channel, the 90-day disclosure window and the threat model, which is unusual
enough to be worth reading first: executing what a workflow declares is the product, so a hostile
workflow file is a hostile script, while a leaked secret: true input is a real vulnerability.
License
Apache License 2.0 — see LICENSE and NOTICE.
Contributions are accepted under the Developer Certificate of Origin:
sign off each commit with git commit -s, which adds a Signed-off-by: trailer certifying you have the
right to submit the work under this license.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rayspec-1.0.2.tar.gz.
File metadata
- Download URL: rayspec-1.0.2.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09cb6fded1da1b1ab780aae5f29d2f22e89c7e3a38e5f2085ee9e09b8bf00638
|
|
| MD5 |
477ade955e5667f5b10c05e66b4af5c3
|
|
| BLAKE2b-256 |
c5954bcdd1d41f0e8015927ef5797c2a24298e42c8112a00c70c9e5d70b19d49
|
Provenance
The following attestation bundles were made for rayspec-1.0.2.tar.gz:
Publisher:
release.yml on rayspec-labs/rayspec-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rayspec-1.0.2.tar.gz -
Subject digest:
09cb6fded1da1b1ab780aae5f29d2f22e89c7e3a38e5f2085ee9e09b8bf00638 - Sigstore transparency entry: 2575434894
- Sigstore integration time:
-
Permalink:
rayspec-labs/rayspec-py@d5312ffe29c578709b3c46cd892872a5aa4e40c1 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/rayspec-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d5312ffe29c578709b3c46cd892872a5aa4e40c1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rayspec-1.0.2-py3-none-any.whl.
File metadata
- Download URL: rayspec-1.0.2-py3-none-any.whl
- Upload date:
- Size: 950.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b06cc9b6c17cbee2a0cbfafbdbe9e53091c5b140756128d1e22ca723e9db5db
|
|
| MD5 |
13784efcd38b3e65da73b8943b1da41a
|
|
| BLAKE2b-256 |
fbc9657fc20c6da88c8cc38c22587093e46b5df8218e3552cad54537dd7411b5
|
Provenance
The following attestation bundles were made for rayspec-1.0.2-py3-none-any.whl:
Publisher:
release.yml on rayspec-labs/rayspec-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rayspec-1.0.2-py3-none-any.whl -
Subject digest:
3b06cc9b6c17cbee2a0cbfafbdbe9e53091c5b140756128d1e22ca723e9db5db - Sigstore transparency entry: 2575434978
- Sigstore integration time:
-
Permalink:
rayspec-labs/rayspec-py@d5312ffe29c578709b3c46cd892872a5aa4e40c1 -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/rayspec-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d5312ffe29c578709b3c46cd892872a5aa4e40c1 -
Trigger Event:
push
-
Statement type: