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: warnTwo 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: warnBoth 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 ProductionProduction (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-projectOr 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
- Your First Spec ā install it, write one, check it, inspect it.
The language
- Building On What You Already Wrote ā
extends, precedence,super,self. - Composing Instead Of Copying ā
+,++, and what they mean per kind. - Values In Depth ā strings, lists, objects, references, constants.
- More Than One File ā modules, imports, exports, resolution.
- Your Own Keywords ā abstract specs, contracts, and frameworks.
Morphic Agent Framework ā building agents, and specifying what they build
- What Morphic Is ā the vocabulary, and the two problems it solves.
- Skills, Agents And Commands ā the working half, property by property.
- Specifying Software ā describe your software so an agent builds it correctly instead of plausibly.
- concept ā the ideas you have to understand, starting with the program itself.
- domain ā a region of the problem, and what belongs to it.
- feature ā behaviour, described from outside.
- component ā a unit of implementation, and its invariants.
- reference ā the rules that apply everywhere.
- What Gets Generated ā sections, validation, and the exact file mapping.
- Shipping A Plugin ā packaging it as a Claude Code plugin.
Tutorial ā spec out a small paint program, one step at a time
- A Paint App ā concept, domains, features, components, references, an agent, and the files it all generates.
Doing something else with it
- Compiling To Data ā JSON, YAML and TOML, and how stale output gets pruned.
- Day To Day ā the CLI, the formatter, the linter, the config file.
The rest
- Under The Hood ā the pipeline, the IR, provenance.
- 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.