Step 7: Build It
Turn eighteen files of Psy into artifacts an agent can actually load — and see what changes.
psy buildbuild: 18 artifacts across 1 target (18 updated, 0 unchanged)
claude: 18 artifacts in .claude.claude/
.psy-manifest
agents/
build-paint.md
skills/
brush/SKILL.md
canvas/SKILL.md
canvas-rules/SKILL.md
canvas-surface/SKILL.md
color-picker/SKILL.md
drawing-tool/SKILL.md
eraser/SKILL.md
file-store/SKILL.md
files/SKILL.md
implement-tool/SKILL.md
naming/SKILL.md
new-drawing/SKILL.md
paint/SKILL.md
pencil/SKILL.md
save-drawing/SKILL.md
stroke-model/SKILL.md
tool-palette/SKILL.md
tools/SKILL.md
voice/SKILL.md
write-interface-copy/SKILL.mdEvery definition became a skill, so each one loads when it becomes relevant rather than all at once. Three of them are actual skills; the other fourteen are the description of the product.
Run it again:
psy buildbuild: 18 artifacts across 1 target (0 updated, 18 unchanged)
claude: 18 artifacts in .claudeNothing updated. Generation is deterministic — same source, byte-identical output,
no timestamps anywhere. That is what makes .claude/ safe to commit.
Check it in CI
psy build --checkbuild: 18 artifacts across 1 target (0 stale)That fails if anyone edits a .psy file and forgets to rebuild, or edits a
generated file by hand. Put it next to your tests.
Inspect before you trust
The composed prompt on ImplementTool came from two files. You do not have to
read the generated Markdown and work out which + super produced what:
psy inspect ImplementTool.promptImplementTool.prompt (src/skills/ImplementTool.psy)
= "Only CanvasSurface writes pixels. If a change seems to need a putPixel
somewhere else, that is a design problem — say so rather than working
around it.\n\nA stroke is one undo step. Snapshot before it starts, never
during.\n\nA tool is a stamp, not a drawing loop. If you are writing
interpolation code, StrokeModel already does it — use it.\n\nEvery tool
keeps its own options. Adding a size to one tool does not touch another."
declared in ImplementTool at src/skills/ImplementTool.psy
provenance:
block string in ImplementTool.prompt (src/skills/ImplementTool.psy:20:9)
super CanvasRules.prompt in CanvasRules.prompt (src/skills/CanvasRules.psy:16:5)
text in CanvasRules.prompt (src/skills/CanvasRules.psy:17:9)
text in ImplementTool.prompt (src/skills/ImplementTool.psy:23:9)Two contributors, in precedence order, each pointing at a line number.
And the whole picture for one declaration:
psy inspect BuildPaintBuildPaint (src/agents/BuildPaint.psy)
abstract: false
keyword: agent
precedence: src/agents/BuildPaint.psy#BuildPaint > @psy/morphic#Agent
description = "Implement changes in the paint program."
model = "opus"
tools = ["Read", "Edit", "Bash"]
use = [CanvasRules, ImplementTool, WriteInterfaceCopy]
context = [Paint]Note use and context printed as spec references, not strings. That is why a
typo in a skill name is a compile error rather than a silently missing skill.
Now change something
This is the part that justifies the whole exercise. Suppose the Brush should accumulate opacity within a stroke after all.
Edit one line in src/features/Brush.psy:
edgeCases:
- A click without a drag stamps the brush once.
- Overlapping passes within a single stroke accumulate, so a slow
scribble is darker where it crosses itself.psy buildbuild: 18 artifacts across 1 target (1 updated, 17 unchanged)
claude: 18 artifacts in .claudeOne file changed. git diff shows exactly the sentence you edited, in
.claude/skills/brush/SKILL.md. The decision and the artifact an agent reads
cannot drift apart, because one is generated from the other.
And rename something
Suppose ColorPicker becomes Palette:
psy buildbuild: 18 artifacts across 1 target (1 updated, 17 unchanged)
claude: 18 artifacts in .claude
note: removed 1 stale artifact
.claude/skills/color-picker/SKILL.mdThe old artifact was pruned. That works because we set
artifacts: { manifest: true } back in
the setup — without a manifest or a header, a
build cannot prove it wrote a file, so it refuses to delete it and
says so.
Except we also have to fix the reference to it, which we get told about first:
src/skills/ImplementTool.psy:5:5 error PSY3002: cannot find name `ColorPicker`.A renamed component in a Markdown file would have left a dangling mention nobody noticed for a year.
What you have
Eighteen .psy files. A compiled, checked description of a small program, and an
agent that receives the relevant parts of it automatically.
More usefully: a place to put the next decision. When somebody asks for a
fill-bucket tool, the work is a feature file and a line in
ToolPalette.responsibilities — and ImplementTool will hand whoever builds it
the stroke model, the palette rules and the reminder that a tool is a stamp.
And when somebody asks for layers, the answer is already written down, with the
reason, in Paint.nonGoals.
Where to go next
- Specifying Software — the practice, in general, including how to start on a codebase that already exists.
- Shipping A Plugin — if this should be installable rather than project-local.
- Compiling To Data — the same definitions as JSON or YAML, for something other than Claude Code.
- Psy Describes Psy — the same mechanism, pointed at a compiler instead of a paint program.