Rust core, adapters as needed
Status: open/published
Summary
Section titled “Summary”- Build the core in Rust; add a language adapter when a consumer actually needs one, never in anticipation.
- The stack stays Rust as it wraps: a scheduling core, the domain-blind engine over it, and the domain-specific engine over that. Wrapping does not change language.
- Other languages enter at the edges as adapters — an authoring surface, a harness plugin, a client — and consume generated types rather than defining them.
- The test is naming the consumer. No consumer, no adapter.
Statement
Section titled “Statement”Semantics live once, in Rust, at the center. Layers that wrap that center stay in Rust even when they narrow toward a domain: the scheduling core, the general work runner built over it, and the project-specific engine built over that are one language, because each is the next layer of the same machine rather than a translation of it. Other languages appear only where something outside the machine has to talk to it — an authoring kit, a harness plugin, a dashboard client — and those are adapters. An adapter is written when a consumer for it exists.
A second implementation of the same semantics is a second thing to keep true. This repo already
carries that cost deliberately in markdown-util and yaml-splice, where both tiers have real
consumers and a shared language-neutral conformance corpus keeps them honest. That price is worth
paying for two live consumers and pure waste for one — the twin still needs the corpus, the tests,
and the review attention, and it drifts the moment nobody is watching.
Wrapping is where the temptation to switch is strongest and the cost is highest. A domain layer feels like a different kind of thing from the core it wraps, but it sits on the same hot paths and passes the same values; changing language there buys expressiveness and pays for it in a process boundary, a serialization step, and two type definitions of everything crossing it.
Deferring adapters is close to free because the pattern is already established. Building one early is not: it is maintenance charged every release, forever, for a consumer that may never arrive.
How it applies
Section titled “How it applies”| Layer | Language | Why |
|---|---|---|
| Scheduling core (graph-scheduler) | Rust | The engine calls the frontier in-process on its dispatcher tick |
| Work-runner engine | Rust | Domain-blind core; one self-contained binary |
| Domain engine wrapping it (SDLC’s) | Rust | A wrapper is the next layer, not a translation |
| Authoring surfaces (flowline), harness plugins, clients | Whatever fits the consumer | Adapters, at the edge, consuming generated types |
Contract types are defined in Rust and generated outward, so an adapter binds to emitted types instead of declaring its own. When a twin genuinely earns its place, it twins against one language-neutral conformance corpus — the established pattern — rather than being maintained by inspection.
This does not compete with P-0004-leverage-best-tool: that principle governs whether to build a thing at all, this one governs what language the things we do build are written in. It serves P-0008-harness-agnostic-substrate rather than straining it — adapters at the edge are precisely how the substrate stays reachable from any harness.
Implications
Section titled “Implications”- A field an adapter needs is a change to the core. Contract types are authored in Rust and generated outward, so an adapter cannot extend the contract from its own side: the Rust definition changes and the adapter regenerates. Every adapter build carries that generation step.
- A new layer over the core is Rust by default. Only leaving Rust needs an argument recorded as a Decision; staying does not.
- Language coverage stays uneven, and that is the intended state. “No TypeScript client exists yet” is not a gap on the backlog. What opens that work is a named consumer, not the absence itself.
- Twinning has a fixed price. A second implementation of the same semantics ships with a
language-neutral conformance corpus, the way
markdown-utilandyaml-splicedo. A proposal that cannot fund the corpus is a proposal to let two implementations drift apart. - This does not say “Rust everywhere”. The edges are open on purpose, and what language an adapter is written in is the consumer’s question rather than this principle’s.
- D-VSLI-distributed-work-runner-architecture defines the layer stack this governs — the scheduling core, the work-runner engine over it, and the domain engine over that — so changes to that stack are where the principle is applied next.