Rust core, adapters as needed
State: open/published
Summary
Section titled “Summary”- Build the core in Rust; add a language adapter when a consumer 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 as they narrow toward a domain: the scheduling core, the general work runner over it, and the project-specific engine 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.
The core this principle governs is the layer stack D-VSLI-distributed-work-runner-architecture defines: scheduling core, work-runner engine, and the SDLC engine over it. The op and entity substrate stays TypeScript per D-0006-typescript-substrate; the engine invokes it as a step executor over the step protocol, so it is a consumer at the edge, not a second implementation of the engine. Contract types that cross that seam are Rust-first and generated outward.
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 with a process boundary, a serialization step, and two type definitions of everything crossing it.
Deferring an adapter is close to free because the pattern is established. Building one early is not: it is maintenance charged every release 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.
A consumer exists when something already written or already committed to will call the adapter: a shipped surface, a merged task, or an external user who has asked. A roadmap entry, a possible future integration, or symmetry with another language does not count.
P-0004-leverage-best-tool decides first whether a thing is built or adopted. An adopted component keeps its own language and is reached through an edge adapter like any other; this principle governs only the layers this repo writes. When the answer is build, the layer is Rust unless a Decision records why not. It serves P-0008-harness-agnostic-substrate rather than straining it: adapters at the edge are how the substrate stays reachable from any harness.
Implications
Section titled “Implications”- A field an adapter needs is a change to the core: the Rust definition changes and the adapter regenerates. Every adapter build carries that generation step.
- Language coverage stays uneven, and that is intended. “No TypeScript client exists yet” is not a gap on the backlog. A named consumer opens that work, not the absence itself.
- Twinning has a fixed price. A second implementation of the same semantics ships with a
language-neutral conformance corpus, as
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 an adapter’s language is the consumer’s question.
- D-VSLI-distributed-work-runner-architecture defines the layer stack this governs, so changes to that stack are where the principle is applied next.