Psy

A small declarative language for configuration that outgrew YAML but doesn't deserve a programming language.

šŸ”® Psy — a declarative specification language

Your configuration started as five keys in a YAML file. It is now nine hundred lines, has three anchors nobody understands, and there is a Python script that generates it. You know the one.

The Problem: configuration outgrows its format. Every config language starts simple, then grows the features that repetition actually demands — reuse, variants, shared defaults — except it grows them badly, one bolted-on feature at a time, because it was never designed to have them. Anchors and merge keys and !!include and a templating pass. Eventually somebody suggests rewriting the whole thing in a real programming language, and then your config can do anything, which means your tooling can no longer tell you anything.

A Solution: Psy takes the other branch. It stays declarative and adds exactly the features repetition demands — inheritance, composition, references, contracts — and nothing else. No functions. No loops. No conditionals. Nothing that runs.

Everything resolves statically, which is the whole point: your tooling never has to execute your configuration to understand it.

Brief Example

Here is a configuration with some repetition in it.

spec Development:
    region: eu-west-1
    replicas: 2
    logLevel: debug

spec Production:
    region: eu-west-1
    replicas: 8
    logLevel: warn

Two keys are identical and one of them is a lie waiting to happen — the day somebody moves regions, they will update one and not the other.

Here is the same thing in Psy, saying it once:

spec Defaults:
    region: eu-west-1
    replicas: 2
    logLevel: debug

spec Production extends Defaults:
    replicas: 8
    logLevel: warn

Both resolve to the same data as before:

{
  "Defaults":   { "region": "eu-west-1", "replicas": 2, "logLevel": "debug" },
  "Production": { "region": "eu-west-1", "replicas": 8, "logLevel": "warn" }
}

Production is now a delta. It says what is different about production, which is the only interesting thing about production.

(Yes, it is a small example. Inheritance is not a hard sell. The rest of the language is the part that took thinking about.)

Ask the compiler what it made of that:

psy inspect Production
Production (services.psy)
  precedence: services.psy#Production > services.psy#Defaults

  region = "eu-west-1"
  declared in Defaults at services.psy
  provenance:
    literal in Defaults.region (services.psy:2:13)

Every resolved value knows where it came from and can point at the line. That falls out of having no runtime — there is nothing to execute, so there is nothing to guess at.

Getting Started

npm install -D @psy/cli
npx psy init my-project

Or build the standalone binary, which needs no Node at all on the machine that runs it. Either way, Your First Spec walks you from nothing to a compiled, inspected, formatted project.

The one weird thing

Psy describes itself, in itself.

The authoritative description of the language — its syntax, its semantics, its compiler stages, its error codes — lives in a directory of .psy files, and is compiled by the real Psy compiler on every check:

export semantic-rule ParentPrecedence:
    description:
        Defines precedence for multiple inheritance.

    semantics:
        In extends A, B, C, A has higher precedence than B and B has
        higher precedence than C.

That is not a documentation comment. It is a compiled artifact, it is checked in CI, and it is what the project's own maintenance agents read when they change the compiler.

I did not set out to build a language that specifies itself. I set out to build a language that was good at specification, and then could not think of a good excuse not to point it at the hardest specification I had lying around. Psy Describes Psy is the whole story, and it is the last chapter for a reason — it will make more sense once you know what a spec is.

Documentation

These are written to be read in order. Each one assumes the one before it and nothing after it.

Start here

  1. Your First Spec — install it, write one, check it, inspect it.

The language

  1. Building On What You Already Wrote — extends, precedence, super, self.
  2. Composing Instead Of Copying — +, ++, and what they mean per kind.
  3. Values In Depth — strings, lists, objects, references, constants.
  4. More Than One File — modules, imports, exports, resolution.
  5. Your Own Keywords — abstract specs, contracts, and frameworks.

Morphic Agent Framework — building agents, and specifying what they build

  1. What Morphic Is — the vocabulary, and the two problems it solves.
  2. Skills, Agents And Commands — the working half, property by property.
  3. Specifying Software — describe your software so an agent builds it correctly instead of plausibly.
  4. concept — the ideas you have to understand, starting with the program itself.
  5. domain — a region of the problem, and what belongs to it.
  6. feature — behaviour, described from outside.
  7. component — a unit of implementation, and its invariants.
  8. reference — the rules that apply everywhere.
  9. What Gets Generated — sections, validation, and the exact file mapping.
  10. Shipping A Plugin — packaging it as a Claude Code plugin.

Tutorial — spec out a small paint program, one step at a time

  1. A Paint App — concept, domains, features, components, references, an agent, and the files it all generates.

Doing something else with it

  1. Compiling To Data — JSON, YAML and TOML, and how stale output gets pruned.
  2. Day To Day — the CLI, the formatter, the linter, the config file.

The rest

  1. Under The Hood — the pipeline, the IR, provenance.
  2. Psy Describes Psy — the self-hosting loop, and the bootstrap.

Language Reference — every construct, the grammar, diagnostic codes, and what v1 deliberately lacks.

Reference — psy.config.ts, compiler and framework types, installation in full, editor support, and example projects.

On this page