Kukum — why the framework generates its own primitives
The problem
Blong's skills describe, in prose, what a handler, a schema, a seed or a realm should look like. That prose is the contract, but it is not executable, and three things went wrong because of it.
Agents transcribed the recipes and drifted. Copying a folder layout out of a skill by hand
produces plausible-looking files with a kebab-case realm name (which cannot work — $subject is
substituted into identifiers), an add handler in the adapter layer, an invented predicate where
get already existed, and a shared file overwritten so the entity added yesterday disappeared. None
of these fail loudly; they fail later, in a test in someone else's realm.
The recipes were duplicated in code. blong-kopi implements the realm recipe imperatively in
createRealm. A second copy was needed for realms, but a third for schemas and a fourth for seeds
would each have to be maintained in lockstep with the skills they mirror, with no shared validation
and no way to ask what exists.
Nothing could describe the running system. There was no way to ask which layers the current intent activated, which handlers a process had mounted, or which artifacts still carry unfinished instructions. An agent had to read the source and guess.
The approach
Model every recipe as declarative data and derive everything else from it.
A descriptor is a record: an identifier, the kinds it supports, the skill that owns its prose,
the roots find should scan, a pure function from context to files, an optional merge-aware
variant, and an optional set of guardrails. One catalogue file lists them. The API surface is then
generated — routes, gateway validation entries and CLI support all come from iterating the catalogue
times the standard predicates, so no list of methods is written by hand and none can fall out of
date.
The engine is pure and host-injected: it plans and applies file changes against an abstract host
that the running realm satisfies with this.platform and the CLI satisfies with node:fs. One
implementation, three entry points, and the planning logic unit-testable without a filesystem.
The shape that follows is one declaration with everything else derived from it:
Two consequences matter more than they first appear. Because a descriptor names its owning skill,
the API becomes a pointer into the documentation rather than a copy of it — primitive.find
returns the skill name so an agent can be sent to the prose. And because the guardrails are
executable, a rule that used to be advice in a document is now a hard error before a single byte is
written.
Trade-offs
Descriptors are not library() handlers. They are data plus pure functions with nothing to bind
to a runtime, so wrapping them would add them to the dispatch graph for no benefit.
auto composes; replace is opt-in. The safer default is the slower one, but silently dropping
a previously declared entity to save a merge helper is not a trade worth making. When a composer
cannot recognise the file it was handed, it warns and overwrites rather than guessing.
Hand-written files are refused, not merged. Merging into a file a human edited is guesswork, so
only files carrying the generated marker are considered machine-owned. The rest are reported, and
--force remains available for the deliberate case.
Tests never update their own baselines. Generation and baseline refresh live in a separate
command (kukum:fixture:update) that a human runs and reviews. A test that regenerated its
expectations would assert nothing; keeping them apart is what makes the committed Playwright
screenshots evidence that the generated UI did not move.
Design principles
- The catalogue is the source of truth. Derive the surface; never enumerate it twice.
- Never silently destroy. Compose when possible, refuse hand-written files, warn when replacing.
- Validate before writing. A guardrail that only exists in prose is not a guardrail.
- Keep the engine pure. No runtime, no globals; the host is an argument.
- Point at the documentation. A descriptor names its skill instead of restating it, so the generated artifact and the prose stay recognisably the same thing.
See the concept for what kukum is and the pattern guide for the endpoint reference and the maintenance recipes.