This is an update on solid-ml, an experiment to determine if the philosophy of SolidJS (fine-grained reactivity, a "no-Virtual-DOM" approach, and high-performance hydration) can be faithfully ported by AI agents to the OCaml ecosystem.
The project has transitioned from a proof-of-concept into an attractive-looking (in terms of shared component authoring experience), but not battle-tested framework. With over 200 passing tests across its core packages, solid-ml now claims to support Server-Side Rendering (SSR), client-side hydration, and SSR-aware routing. Here is the story of how it's being built and why I believe ideas like these are worth exploring, even if the outcome may be that, no, LLMs aren't suitable to fill ecosystem gaps in this way. I still have hope that upcoming new models will be able to do that, though.
The Workflow: Human as "Governor"
The most unusual aspect of this project is the development process: I haven't written the OCaml code myself. Instead, I have acted as a "Governor" for two high-reasoning AI agents: Claude 4.5 Opus and GPT-5.2.
My role is to manage the architecture and keep the agents on track. When the models suggest over-engineered or "crazy sounding" ideas, I stop them and ask them to step back to explain their reasoning. I sometimes point them back to the core blueprint: "What would SolidJS do?", or help them choose the simplest or the most correct implementation, depending on what the tradeoffs appear to be. This keeps the implementation grounded in the simple, effective primitives that made SolidJS successful, rather than letting the AI drift into unnecessary abstractions.
I've noticed a distinct difference in the "personalities" of the models:
- Opus 4.5 was the engine of the early phase. It felt snappier and was excellent at generating the initial momentum.
- GPT-5.2 is a slower, more thorough model. It doesn't enthusiastically claim everything is "production-ready" when half the implementation is missing. Instead, it is pessimistic in a helpful way, pointing out shortcomings and reasoning through complex logic like hydration markers and reactive dependency tracking.
Why OCaml? The Trust Factor and SSR
You might ask why we need a SolidJS port in OCaml when frameworks like Bonsai, Eliom, or ReasonReact already exist. And then there's also Elm (not OCaml, but spiritually close). I don't think we necessarily need a "SolidJS but in OCaml". However, I wanted to see what's possible to build with AI agents today. Architecting a framework involves thousands of subtle tradeoffs. By following a "faithful port" strategy from SolidJS, we rely on the decisions encoded in an architecture that is already proven to work in the trenches of the JavaScript world.
Beyond the API, we gain some specific "unfair advantages" from the OCaml language:
- The Binary Advantage: On the server side, we get native OCaml performance, roughly in the same ballpark as Go. We can compile the entire application into a single, lightweight, statically linked binary and deploy it to any Linux VPS without worrying about Node.js runtimes or heavy container images.
- Multicore Readiness: Since OCaml 5, we have native multicore support.
solid-mlis designed with this in mind, using Domain-local storage to ensure the reactive graph remains thread-safe during parallel execution on the server.
The Trade-off: Client Performance vs. Engineering Safety
It is important to be matter-of-fact about the performance. While the SSR side is bound to be fast, we will take a performance hit on the client side. OCaml compiled to JavaScript (via Melange) is generally not as lean as hand-optimized JS.
However, this is offset by a massive engineering advantage: Shared Logic.
By using OCaml for both the backend and the frontend, we can share the entire data model and all validation logic. If you refactor a core type on the backend, the compiler will immediately show you every single breakage in your UI components. Client-side validation and server-side validation cannot get out of sync. This end-to-end safety makes for applications that are much easier to maintain and refactor over time. This is a non-negligible advantage when building complex systems.
The Secret Sauce: MLX and Melange
The developer experience (DX) of solid-ml is shaped by two specific tools that bridge the gap between OCaml and the modern web:
- MLX: This is a JSX-like syntax for OCaml that, in my opinion, feels better than TSX. It is safe and readable, and it ensures your HTML open/close tags match. It hits a sweet spot: it is more ergonomic than fully-typed solutions like TyXML, but much safer than basic string templates. It's exactly what you want when writing HTML templates in OCaml.
- Melange: This handles the client-side compilation. The JS interop is smooth, and the possibility to have separate-file compilation is a major practical benefit. It allows us to keep the frontend bundle organized and integrated with the wider JS ecosystem when necessary.
The "Uncanny Valley" of AI-Driven Code
We are currently in a kind of "uncanny valley." The tests pass, the demos work. There's a multi-page app that loads and displays posts, with multiple routes to explore. But this remains an LLM-driven project. There is always a risk that the code could turn into a "jungle" of subtle bugs that are difficult to fix once the complexity reaches a certain threshold.
This is a reality for any project of this scale, whether led by humans or AI. Projects are allowed to fail. If this turns into an unfixable mess, I'll end up with a good story for having tried and there's no shame in going back to "old-school" SolidJS, or making some experiments with bonsai or other pre-AI-era OCaml frontend frameworks.
The next step: I'm about to merge a branch that introduces proper MLX templates and sharing of components between SSR and the client. I'll let the agents rewrite the entire frontend of an existing work-in-progress app to use this new API. That will be a realistic test. If it succeed, I might end up using it everywhere until something better comes along. But who knows... certainly not me, at this point.
How solid-ml Was Built: A Timeline
The project started on January 5th with a basic reactive core. Within 24 hours, it became clear that the initial implementation didn't match SolidJS's architecture closely enough: the bidirectional signal<->computation links and state machine that make SolidJS work were missing. Day 2 saw a complete rewrite by the agent, followed immediately by extracting a shared solid-ml-core package to eliminate the 450+ lines of duplicated code between the server and browser runtimes.
By Day 3, the focus shifted to hydration and routing. The agent implemented marker-based text node adoption (critical for SSR hydration) and built a browser router with History API support. I had the agent implement the popular js-framework-benchmark in order to understand how bad the performance hit would be. Performance issues were uncovered and there were some improvements achieved: the agent implemented the udomdiff reconciliation algorithm to match SolidJS, and by Day 6 the bundle size had been reduced by 66% (235KB → 80KB).
Day 7 brought a major architectural shift: it became apparent that the SSR and Browser HTML modules had incompatible APIs, making true isomorphism impossible. A shared Html_intf.S interface was designed, and both runtimes were refactored to satisfy it. The same day, inline onclick handlers were replaced with global event delegation - that's both a security fix and a scalability improvement.
Async data handling was the focus of Day 9, with Async/Action/Store modules added. A critical bug was found: reactive dependency tracking for Promise-based computations was broken and had to be fixed. Days 11-16 saw the template compiler emerge: MLX integration, a PPX system for compile-time template processing, and careful handling of whitespace normalization (formatting whitespace was interfering with template compilation).
Days (16-17): The full SSR app was migrated to MLX templates, scoped helpers were added, and strict SSR rendering was enforced.
Day 18: Debugging the examples.
| Days | Focus | Key Changes |
|---|---|---|
| 1-2 | Reactive core | Complete rewrite (SolidJS patterns), extracted shared functor (450 lines removed) |
| 3-6 | SSR & performance | Hydration markers, browser router, udomdiff, 66% bundle reduction |
| 7-8 | Unification | Unified SSR/Browser APIs under shared interface, event delegation (security) |
| 9-10 | Async data | Async/Action/Store modules, fixed reactive dependency tracking |
| 11-16 | Template compiler | MLX integration, PPX system, whitespace normalization |
| 17 | MLX migration | Full app to MLX, scoped helpers, strict SSR |
| 18 | Debugging examples | Dockerizing complex examples |
The pattern that emerged: early work was frequently replaced with more faithful implementations of SolidJS architecture. Each major feature (routing, SSR, templates) required multiple iterations as hidden complexity surfaced.
Result: 18 days, 125 commits, 200+ passing tests, and a framework where we don't really know whether it really properly supports SSR, client-side hydration, and SSR-aware routing in practice.
solid-ml is a SolidJS-inspired framework for OCaml. Built with Melange and MLX.
