Kyro
An embeddable JavaScript engine. I designed it and Claude built it.
What it is, and what I actually did
01Kyro is a JavaScript engine in Rust. Parser, garbage collector, object model, interpreter, and an ahead of time compiler. It existed so Peko could run a JavaScript UI framework inside a native app binary.
I want to be exact about my role, because the repository is around 65,000 lines and I did not type most of them. I designed the system. Claude wrote the implementation against my spec. The architecture, the build order, the representation choices, and the rulings that resolved every ambiguity are mine. The Rust largely is not.
What I can defend is the design. Ask me about the rulings and the build order and I can account for all of it. Ask me why a particular function is written the way it is and I probably cannot.
The spec
02The work I did lives in docs/, about 1,300 lines across four files. architecture.md is subtitled Worker Handoff and decisions.md is subtitled answers to worker questions, which gives away how the project ran.
The plan sets the build order and the final form of each component. The decisions file answers whatever the plan left open and explicitly overrides it wherever they disagree, including a section of amendments recording what had to change once the collector and object model were real. Nobody can make progress on an engine like this without rulings on pointer representation, string layout, exception convention, bytecode encoding, atom lifetime, and heap sizing. Producing those was the job.
The decisions I would defend
03Every phase builds the final form of whatever it touches. Nothing gets built as a placeholder to swap out later, so value encoding, object layout, the collector, frame layout, and the ABI are settled up front because those are the expensive things to change. Everything after grows by adding cases. The cost is written into the document rather than discovered later, since building final forms first means the first test262 signal shows up after the real collector exists. Longer runway, nothing thrown away.
One semantic source of truth. Every JavaScript operation is implemented once as a runtime helper, and both the interpreter and compiled code call the same exported symbol so they cannot drift apart about what a program means. That is why the engine can have two back ends without doubling the correctness surface.
No machine code at run time, because some platforms forbid executable memory outright. A JIT was off the table, so compiled code keeps its dispatch and calls helpers instead of specializing. LLVM stays on the developer machine and a CI script fails the build if it ever appears in the dependency graph of anything shipped.
What came out of it
04Further than I expected. NaN boxed values with heap references compressed to 32 bit offsets. A generational collector where both spaces move, with a card table write barrier and ephemerons. A shape based object model. A parser covering ECMAScript 2025, JSX, and the full TypeScript grammar, checked against 7,000 real source files. A bytecode interpreter, an LLVM 18 back end, an in-process lld linker, and enough of a Node shim that real npm packages ran.
It is not a complete or conformant engine and never got close. Test262 was a source of panics to chase, not a suite it passed.
The thing I actually took from it is that writing a spec precise enough for someone else to build from is a different skill from building, and I was worse at it than I expected. The failure mode is not writing too little. It is writing something that reads as finished and contains an ambiguity you cannot see, because you already resolved it in your head without noticing. Every entry in decisions.md is one of those.
Why it is postponed
05Kyro existed to serve Peko. When Peko went on hold the thing Kyro was for went with it, and an embedded JavaScript engine with nothing to embed into is a research project.