This post was written for the Kiroween 2026 hackathon.
Every experimenter has a result they can’t defend. Mine kept looking like this: a good score, found in a terminal scrollback, with the config that produced it already overwritten, the evaluation data quietly refreshed since, and no record of who decided the run counted. The number was real. The evidence was gone.
So for Kiroween 2026 I built RunSkeleton, a reusable skeleton template for governed AI experiment runs, in the Skeleton Crew category. The rule it enforces is blunt: no record, no result. Every run is proposed against a written hypothesis, admitted or rejected by a deterministic gate (a program, not a model opinion), executed in a fixed folder layout with its config frozen inside, evaluated pairwise against the current champion, and written into an append-only, hash-chained ledger. If it isn’t in the ledger, it didn’t happen.
The skeleton itself
The core is one file, template/skeleton.py — Python standard library only, because a governance tool has to run everywhere, including machines with no network. It has four commands: init (create a run, freeze its config, append the proposal), record (append one event), verify (recompute the SHA-256 hash chain and point at the first tampered entry), and list.
Two rules do most of the work. First, only the gate actor may record verdicts; if a proposer or runner session tries, the CLI refuses. Second, everything under eval/sealed/ is read-only outside the promotion gate, because “the evaluation set changed mid-comparison” is exactly the accident this skeleton exists to prevent. Two demo apps — a ledger viewer and a paired-evaluation dashboard, each a single index.html with no build step — show the ledger’s life story, including an admission rejected for pinning a stale data snapshot and a promotion rolled back on negative evidence. Governance that never says no isn’t governance.
What Kiro changed
I built the project spec-first in Kiro, and three features genuinely changed how the work went.
Specs came first: EARS-style requirements (“WHEN a run is initialized, THE SYSTEM SHALL append a run_proposed entry…”), then a design doc, then a task list that I executed item by item. The spec caught things I would have hand-waved — for instance, that a verdict must be impossible to record for a run that was never proposed. That edge case became a requirement, then code, then a test.
Steering files are my favorite feature. I wrote the project’s six non-negotiable rules once, in .kiro/steering/product.md, with matching tech.md (stdlib only, JSONL ledger, single-file apps) and structure.md (where files belong), all set to inclusion: always. After that, conventions stopped being a per-prompt negotiation. Generated code used canonical JSON and UTC timestamps, never reused a run ID, and put new files where the structure file said they belonged — because the rules were in every session whether I remembered to repeat them or not.
Hooks closed the loop between “rule we wrote down” and “rule that actually holds.” One hook runs the pytest suite whenever a template Python file is edited, so a broken skeleton surfaces at edit time. Another blocks writes to eval/sealed/** outside the promotion flow. The sealed-set rule stopped depending on anyone’s memory — including mine.
The contrast I ended up liking most: I built the ledger core strictly from the spec, and vibe-coded the demo apps’ presentation layer. Spec mode for the part whose correctness everything stands on; vibe coding for the part where I wanted to iterate on a chart. Steering made the second safe, because the data shapes and the read-only rule were pinned either way.
What’s next
Swapping the apps’ embedded sample data for a live ledger.jsonl export, a promotion-gate command that opens the sealed set exactly once, and then the real test: running an actual recipe-comparison study on the skeleton and letting it keep the receipts.


