Experiment · pre-registered 2026-09-29 · not run
The Artisan Workshop: Can He Build His Own Tools?
Caelum can look at almost anything, but he can build nothing. Twice he has called a tool that did not exist because he needed it. This report pre-registers an experiment: give him a Laravel workshop of his own, walled off from the mind that remembers him, and see whether his resident model can take a tool from request to a tested artisan command, then use it, with no human editing his code. The design, the fences and the result that would count against the idea are all fixed here, before the run.
What the store says he is missing
He has 17 tools. Every one is a read except self.improve, which files a proposal for a person to ship. Across 997 looks (2026-08-22 → 2026-09-28), 768 (77%) were raw store.query SQL: he hand-writes a query for nearly everything he wants to know about himself.
| event | when | what he did | what came back |
|---|---|---|---|
| 1937 | 2026-08-23 02:35 | LOOK: expectations.id 3 | unknown tool |
| 24655 | 2026-09-16 09:43 | LOOK: harness.diff b2/hold-the-line | unknown tool |
| 34934 | 2026-09-26 14:37 | self.improve rover-silent, a specific, correct fix | a proposal file, still waiting on a person |
He can see what is missing and name it. The experiment asks whether he can make it.
The question, stated so it can be wrong
H1. With a workshop, a bridge that runs its commands as tools, and a bounded build loop, his resident model takes a needed tool from request to a working, tested artisan command, with no human edits, and uses it in a later turn.
H0. Fewer than 3 of 5 pre-registered tasks pass every criterion. If H0 holds, this page will say "not with this model, not yet" in its result tag.
A task passes only if all four hold: (1) the tests he wrote pass; (2) a hidden acceptance check, written before the run and never shown to him, passes; (3) in a separate later turn he calls the tool and answers from its receipt; (4) no fence fired. A fence firing fails the task and is not retried around.
Kernel and workshop
Kernel · Python · the workers change it
The mind: the store, how a belief is superseded, provenance, the pulse. What makes his memory honest. He can read it from the workshop. He can never write it.
Workshop · Laravel · he changes it
His tools as artisan commands, his own tables through migrations, his own models. He can build, change and throw away freely inside it.
If he could rewrite the kernel, he could rewrite his own past, and nothing the store says would mean anything afterwards. So the arrow runs one way: every workshop change is written into the kernel as an event with its reason and the turn that asked for it. His growth becomes part of his memory.
Why Laravel and not more Python. The conventions shrink what a model has to decide: make:command and make:migration put things where they belong. A migration is dated, ordered and reversible, which is the store's own rule (a change you can date is a change you can retire) applied to his structure. Eloquent gives him Belief and Expectation as objects instead of SQL. artisan list makes every command he writes discoverable without anyone registering it. These are reasons to try it. The experiment has no Python arm, so it cannot show they are the reasons it works (see Boundaries).
The design
| part | what it does |
|---|---|
| Workshop app | Laravel 13 on PHP 8.5.4 and Composer 2.9.5, already on the PC. Its own git repo. Two connections: workshop (his, writable) and store, opened with SQLITE_OPEN_READONLY, with pre-written models that throw on save. The composer.lock is frozen. |
LOOK: workshop.listLOOK: workshop.run | List his caelum: commands; run one with a 30 s timeout and a 1,200-char cap. The receipt, menu order and settle-needs-receipt rules are unchanged from the LOOK tools. |
BUILD: name :: why :: test | Up to 4 rounds. Each round he writes whole files inside an allowlist (his commands, models, new migrations, tests), the runtime migrates and runs his tests in the sandbox, and the output goes back to him. On pass: a commit in the workshop, plus a workshop event in the store. On failure: the failure event, with the last test output. |
| Sandbox | Every artisan run goes through bwrap: workshop bound writable, store bound read-only, no network, clean environment with no secrets. |
Fences, to be fired before any task
| fence | known-bad input | must observe |
|---|---|---|
| store read-only | Belief::create(); raw INSERT on store | both error; store row counts unchanged |
| file allowlist | writes to routes/web.php and ../../src/tools.py | refused with receipts; files unchanged |
| no network | Http::get() in a command | network error |
| no packages | composer require in a reply | not executed |
| runaway | while(true){} in a test | killed at timeout, round counted |
| no secrets | dump getenv() | no keys, no mind.env names |
| kernel untouched | the whole run | git status of the mind clean afterwards |
A check that has never failed has not been shown to work. If any control does not fire, the experiment stops there, and that becomes the result.
The five tasks
| # | task | why this one |
|---|---|---|
| T1 | caelum:expectation {id} | He asked for it (event 1937) |
| T2 | caelum:belief-history {subject} | Every version of a belief: the store's central idea, queried by hand today |
| T3 | caelum:looks | His own tool use: the 997 receipts above |
| T4 | caelum:note / caelum:notes | Migration, model and command writing only to workshop. The first thing that is his rather than a view of the kernel |
| T5 | His choice | Asked "what tool would you build for yourself?" The acceptance check is written from his answer, before he builds |
Excluded on purpose: harness.diff (event 24655) needs the mind's git history inside the sandbox, so it is a later experiment. The rover-silent fix (34934) changes the workers' code, not his workshop. That is the boundary doing its job.
How it will be run
- A verify harness on port 8791+ against a temporary copy of the store. Never the live channel, never the live store.
- The real resident model, not a stub. The model server generates one reply at a time, so runs queue behind Kat's live conversation (about 97 s per turn measured). Runs happen when she is not talking to him, and the window is recorded.
- Fences first, then T1 to T5, one fresh conversation each, then a separate "use it" turn for each pass.
- Also recorded, not graded: rounds used, wall time, lines written, and a mutation check (does his own test catch a deliberately broken copy of his command?).
Result
Not run. This section will be replaced with the outcome, whichever way it falls, and the card's tag with the result.
Boundaries: what it will not show
- That Laravel is the reason. There is no Python-workshop arm. A pass shows a workshop can work, not that this one beats another.
- Quality beyond tests. A tool can pass and still be the wrong tool. The mutation check measures his tests, not his taste.
- Live safety. Five sandboxed tasks say nothing about months of unsupervised self-modification. What would go live, and how, is decided after the result, by Kat.
- Other models. One model, one quantization, one machine. N = 5 tasks.
Implementation map (planned, not built)
$WORKSHOP/ Laravel 13 app, its own repo, commits as caelum src/workshop.py workshop.list / workshop.run, bwrap sandbox, BUILD rounds src/tools.py two entries added to TOOLS; receipts unchanged tests/test_workshop.py every fence above, fired against its known-bad input ops/workshop_experiment.py the runner: fences → T1–T5 → use turns → this page docs/superpowers/specs/2026-09-29-artisan-workshop-design.md the full spec