Retro is a retrograde programming language — software is built backwards from guarantees to code. Declare your future state, then refine toward reality. A validator ensures every layer satisfies the layers above it, so agents can’t drift from the spec.
Retro is a programming language for AI agents — not humans. Agents write the levels, the validator enforces consistency, and you read the guarantees. No new syntax to memorize. No IDE plugins to install. Just tell your agent what must be true, and Retro structures the path from intent to code.
system CollabWhiteboard { @future { guarantee convergence { description: "All clients converge to same canvas state" constraint: latency < 200ms } behavior undo { scope: per_user } } @shape { module Canvas { serves: [convergence, persistence] state: CRDT accepts: [StrokeEvent, UndoEvent] } module Sync { serves: [convergence] @version("websocket", active) { protocol: websocket } @version("sse", dormant, reason: "violates latency guarantee") { protocol: sse } } } @flow { on StrokeEvent from Client { merge(Canvas.state, event) -> validate(Canvas.state) -> broadcast(Sync, Canvas.state) } } }
A Retro program co-locates intent, architecture, logic, and implementation. Add levels to add detail — the program is coherent at every stage. An optional @header sits above the four core levels and gives agents a one-paragraph entrypoint.
| Level | What it captures | Example |
|---|---|---|
| @header | Agent intropoint (optional) | summary: "real-time whiteboard", tags: [crdt] |
| @future | Guarantees & constraints | latency < 200ms, xss_vectors = blocked |
| @shape | Modules & interfaces | module Canvas { accepts: [StrokeEvent] } |
| @flow | Logic & transitions | on StrokeEvent -> merge -> broadcast |
| @impl | Real code in any language | TypeScript, Python, C++, HTML |
Five core blocks handle every system, every domain. Domain-specific vocabularies — @fleet for logistics, @timing for hardware, @parties for legal — live as extensions without touching core. Three-tier model: open passthrough, declared schemas, and (planned) inline macros.
| Tier | What it does | Status |
|---|---|---|
| 1 — Passthrough | Any @identifier { ... } parses. Validator emits INFO, never errors. |
Shipped |
2 — @extension declaration |
Name + version + schema. Listed by retro status, validated structurally. |
Shipped (schema enforcement deferred) |
3 — @macro |
Parameterized expansions. Designed only. | Future |
// Extensions layer on top of core — no core changes needed. system StellarSupplyChain { @extension "logistics.stellar" version "0.1" { schema: { fleet: required, jumproute: required } } @future { guarantee delivery { constraint: on_time_rate > 0.95 } } @fleet { ship_class: heavy_freighter, base_port: "Sol-Gateway-3" } @jumproute { origin: "Sol-Gateway-3", destination: "Proxima-Terminal-1" } }
@import.When subsystems have independent guarantees, split across .retro files. The loader walks the import graph, the validator catches cross-file reference errors, the CLI works the same.
// One program composing many. system AppMain { @import "./auth.retro" as Auth @import "./billing.retro" as Billing @flow { on HttpRequest from Client { verify(Auth.TokenStore, session) -> meter(Billing.UsageMeter, usage) -> respond(RequestHandler, result) } } }
Full reference: /docs
ui_main.retro I had to decide: does the generation flow authenticate → license-check → model-route, or does auth come after routing? I answered it in the .retro in 30 seconds. Without the spec, I'd have made that decision mid-api/v1.py, hit a dead end 20 minutes later, and refactored. Multiply that by the ~12 similar “where does this belong?” questions across 8 subsystems and you save 2–3 hours of thrash.
@impl file blocks are a checklist. When I went to populate the backend I literally walked through each .retro's @impl list and created the files in order: ui/backend/auth/supabase_client.py, signup.py, session.py, oauth.py, done → next module. No “wait, what else did I need?” moments. It's the opposite of the usual “I forgot to implement the refresh_token endpoint and now discover it three files downstream.”
retro validate ui/ui_main.retro probably 5 times in the first 30 minutes. Each run < 1 second. When S003 fired because cross-subsystem events weren't accepted in ui_main's @shape, it taught me to add the orchestrator module. That would have been a “wait why isn't this connected” bug hours later, found instead in the second I wrote the broken flow.
The biggest compounding effect though is what happened today when you said “build the scan UI”: I already knew (a) it belonged in backend.retro's API layer, (b) the frontend piece was a new component matching frontend.retro's module list, (c) the contract between them was already implicit in the 8-file spec. I didn't re-architect — I just extended. Second feature is faster than first, and it keeps compounding.
A calculator written as a .retro file. The source on the left produced the working widget on the right.
system Calculator { @future { guarantee accuracy { description: "All arithmetic operations return correct results" constraint: precision = IEEE754_double } guarantee responsiveness { constraint: input_latency < 16ms } guarantee safety { constraint: eval_usage = none } behavior operations { supported: [add, subtract, multiply, divide] } } @shape { module Display { accepts: [UpdateEvent] color: green_on_dark } module InputHandler { accepts: [ClickEvent, KeyboardEvent] @version("event-delegation", active) { strategy: single_listener } @version("per-button", dormant, reason: "50+ listeners, no benefit") {} } module Engine { @version("state-machine", active) { no_eval: true } @version("string-eval", dormant, reason: "violates safety guarantee") {} } } @flow { on ClickEvent from User { identify(InputHandler, target) -> dispatch(Engine, input) -> compute(Engine.state) -> update(Display, Engine.state) } } @impl(target: html) { // 180 lines of HTML + CSS + JavaScript // retro emit calculator.retro --out ./ } }
Each pass adds a level of detail. The validator checks every layer against the layers above it.
Start with @future — guarantees, constraints, and observable behaviors. No architecture, no implementation. Just intent. retro init "your idea" scaffolds the file.
Add @shape — your building blocks, what specific technologies you're using for each, and how they connect. Use serves: to map to guarantees. Use @version to capture technology alternatives and why they were chosen or rejected.
@flow captures inter-module choreography — what events trigger what transitions, and what typed data crosses module boundaries. Typed arrows verify integration contracts at the structural level.
Add @impl with real code in any target language. Multiple @impl blocks for different languages, all validated against the same abstract program. Then retro emit to extract working files.
Retro's upper levels naturally eliminate vague identity claims. The syntax pushes you toward measurable behaviors and constraints — not essences.
Inspired by E-Prime (Bourland, 1965) and General Semantics (Korzybski, 1933) — the idea that removing “is” from language forces clearer thinking. Retro’s syntax doesn’t ban identity claims by rule, but makes behavioral descriptions the path of least resistance. Vague specs produce vague code. Precise constraints produce testable implementations. Garbage language in, garbage software out.
Properties that don't exist in forward-progressive development.
Modules declare serves: [guarantee] to map explicitly to @future constraints. The validator enforces completeness — every guarantee has a server, every mapping is real.
Structural rules catch type mismatches and orphan guarantees. Typed arrows in @flow verify integration contracts. Adversarial semantic prompts find weaknesses, not confirm correctness.
@version blocks keep rejected designs with reasons. When requirements change, dormant versions resurface automatically.
The program is always valid. A file with only @future is complete. Each level adds detail without breaking coherence.
Multiple @impl blocks for TypeScript, Python, C++ — all validated against the same guarantees, shape, and flow.
Designed for LLMs. The validator creates a self-correcting loop: write a layer, validate against layers above, fix, repeat.
Retro is a Node.js CLI. Works with any LLM agent.
Then use /retro to start building backwards.
# Scaffold a new .retro file retro init "real-time chat with encryption" # Fill @future, @shape, @flow, @impl... # Validate at each step retro validate chat.retro # Check program state retro status chat.retro # Extract real code retro emit chat.retro --out ./src # See all design alternatives retro versions chat.retro # View change history retro delta chat.retro
Retro is open source under Apache 2.0. Built by Tensorpunk Labs.