Harmos · Rust Runtime
Tired of Rebuilding the Same Abstractions for Every App You Make?
Harmos is the runtime we built so we would stop. Undo, autosave, crash recovery, audit, background work, and plugins come from one ordered history instead of being rebuilt per project.
Rust · Pre-1.0 · Linux and Windows
Shapes It Was Built Against
- Editors
- documents people change, and expect to take back
- Engineering Tools
- state that has to be explained months later
- Instrument Hosts
- measurements, equipment, and long runs
- Configuration Systems
- changes that owe an account of themselves
- Maturity
- Pre-1.0
- License
- MIT OR Apache-2.0
- Platforms
- Linux and Windows
Origin
The Internal Substrate
Harmos started as the shared runtime substrate under every internal Stokker Technologies project, because we kept writing the same undo stack, the same autosave timer, and the same recovery path in each one.
Dokime is a Harmos application from its desktop app down to its storage and its plugin SDK: those crates depend on Harmos and pin its main line. Publishing the runtime means anyone building a stateful tool starts from the same foundation instead of rebuilding it.
Read the Dokime PageBefore and After
Per Project Versus Harmos
Today each hard feature is built against its own view of state. With Harmos there is one journal, and every feature is a read over the same order.
Undo
Built Per ProjectA stack beside the state, holding copies of whatever somebody hoped would be enough to put things back.
With HarmosA new entry that commits the inverse, checked against the current state like any other change.
Autosave and Recovery
Built Per ProjectA timer writing snapshots, and a recovery path that gets tested by hand the week before a release.
With HarmosRecovery replays the stored entries. It is the same path every ordinary boot already runs.
The Audit Trail
Built Per ProjectA second stream of messages, written by hand at each call site, drifting from what the code did.
With HarmosThe committed order every other feature already reads. There is no second account to keep honest.
Background Work and Plugins
Built Per ProjectEach one with its own status table, its own retries, and its own idea of what is in progress.
With HarmosSupervised jobs, resident services, and loaded artifacts, all positioned against the same history.
Fewer Classes of Bugs
Two features cannot disagree about what happened, because there is only one account of it. The whole family of bugs where undo and autosave hold different pasts stops existing.
A Faster First Release
The infrastructure is there on the first day. The first thing anyone writes is a change that means something to the product, not a mutation path to carry it.
A Product That Explains Itself
History, audit, and replay are reads over an order the application already keeps. Answering what happened on Tuesday costs a query rather than a project.
A Worked Application
Building a Schematic Editor
Symbols on a canvas, pins joined by nets, undo, autosave, export, recovery, and a plugin. Eight moves, and every one of them is an entry in the journal or a read over the entries.
This is not a sketch. It is examples/minischematic, the complete application the guide embeds its code from, and the ids below are the ids it declares.
#[harmos::transaction(id = "symbol.place", version = 1, irreversible)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct PlaceSymbol {
/// The name the placement takes, for as long as it exists.
pub symbol: SymbolId,
/// What the drawing calls it.
pub reference: String,
/// Where it goes.
pub at: Point,
/// How far it is turned.
pub rotation: Rotation,
}
/* === Enums === */
/* === Traits === */
/* === Implementations === */
impl Transaction<Schematic> for PlaceSymbol {
type Error = SchematicError;
fn check(&self, schematic: &Schematic) -> Result<(), Self::Error> {
schematic.require_symbol_free(self.symbol)
}
fn apply(&self, schematic: &mut Schematic) {
schematic.symbols.insert(
self.symbol,
Symbol {
reference: self.reference.clone(),
position: self.at,
rotation: self.rotation,
},
);
}
}
/* === Public Functions === */
/* === Private Functions === */
/* === Tests === */
01 · symbol.place v1
Place a Symbol
A transaction owns a permanent wire id, a precondition, and an application that cannot fail once the precondition holds. Placing asks whether the name is free, then writes the symbol into the canvas. The runtime records the entry only after the change has applied, so nothing in history is a change that did not happen.
This one is declared irreversible, and the declaration says why: taking a placement back would mean deciding what happens to every net that has since connected to its pins.
#[harmos::transaction(id = "symbol.move", version = 1, inverse = Self)]
pub struct MoveSymbol { /* … */ } // its own inverse
#[harmos::transaction(id = "ledger.deposit", version = 1, inverse = Withdraw)]
pub struct Deposit { /* … */ } // another change takes it back
#[harmos::transaction(id = "symbol.place", version = 1, irreversible)]
pub struct PlaceSymbol { /* … */ } // it cannot be taken back
02 · symbol.move v1
Move a Symbol
A move is its own inverse, so the editor writes no undo code at all. Every change answers the undo question in its own attribute, and silence does not compile: a change is its own inverse, another change takes it back, or it is irreversible. There is no fourth answer and no default.
Pressing the undo key commits a new entry that puts the state back, checked against the current state like any other change. History gets longer, never shorter, and two people editing at once each keep their own undo.
03 · net.connect v1
Connect a Net
Joining two pins is another entry against the same state, checked by the same writer, landing at the next position. Nothing about it is a special case: the editor gains a second kind of change and gains no second mechanism to keep in agreement with the first.
04 · A Read
Draw the Canvas
What the user looks at is a projection: a read over the committed order, handed back with the journal position it observed. A view that has fallen behind can say so exactly, because its answer carries a number it can compare with the last receipt.
The canvas is never a second copy of the drawing that could disagree with the journal. It is the journal, folded.
05 · The Storage Port
Save the File
Autosave is the committed order folded into a file, through a port the runtime declares and an adapter the application writes. Harmos publishes the contracts for history, snapshots, streams, and evidence, and ships no engine behind them, so the on-disk layout is yours to change.
The editor does not gain a save routine that has to guess what changed since last time. It gains a consumer that folds entries it has not folded yet.
06 · export.completed v1
Export the Drawing
The export itself is ordinary work the runtime supervises: it runs, it produces a file, and the file never enters the journal. What enters the journal is the fact that it finished, as a record, which changes no state and can be skipped by a reader that does not know it.
The editor waits until that record is durable before it announces the export, which is how an effect outside the process is gated on something inside it.
07 · Replay
Open It Again
After a power cut the document opens by replaying the same entries that produced it the first time, from the most recent snapshot forward. A snapshot is an optimization and never a second authority, so there is no repair procedure and no separate post-crash path to keep working.
The boot after a crash is the same operation as the boot after a clean exit. That is the only reason it can be trusted.
08 · Simulation
Run It Without a Window
The same declaration boots under a seed, with virtual time instead of a clock and a script instead of a user. The run commits the same transactions, publishes the same streams, and reboots the same way, so a workflow can be exercised in a test before any of it is driven by a person or an instrument.
Then the same entries run against the real thing, because they are the same entries.
Developer Experience
Authoring an Application
Five steps, in the order the guide takes them. Every one of them is a decision about the product, and not one of them is infrastructure somebody has to carry.
Step 01
Model the State
Before any runtime API, one decision is yours alone: what the application’s state is. It is one plain Rust value, the kind you would have written without this library at all, holding what a reader of the product would call the document.
Four rules keep replay honest, and all four are about what does not belong in that value: no clock, no randomness, no handles, and no data whose only job is to be looked at.
Step 02
Author Your Entries
Two kinds of thing enter history, and both are plain structs. A transaction is a change: it mutates state, and replaying it is how the state comes back. A record is a fact: something that happened and belongs in the story, which changes nothing.
A transaction is two methods, a check and an application, plus one attribute that stamps a permanent wire id on it. That much is testable with an ordinary unit test and nothing running.
Step 03
Commit
One writer validates and commits typed changes atomically. When the call resolves, the change was checked against the current state, applied to it, and sealed into history at a position the receipt names.
When the check refuses, the caller gets its own typed error back and nothing happened at all. The inverse is captured in the one instant that has both properties it needs: after the check succeeded, before the change applies.
Step 04
Read and Observe
Two questions pull things back out, so there are two verbs. What is true now is one value, handed back with the journal position it observed. What has happened is a sequence, and it is read as entries rather than reconstructed from the value.
Because both answers carry a position, a view can tell a stale answer from a fresh one instead of guessing.
Step 05
Persist and Recover
Everything up to here runs in memory, which is the correct default rather than a gap. Durability is one declaration handed to the builder, and not a line already written changes.
Two frontiers then become visible: what has been applied, and what has been stored. The distance between them is exactly what a hard crash would cost, stated as a number rather than hoped about.
Nothing above is a framework asking for its own shape back. The state is your struct, the changes are your structs, the errors are your errors, and the storage format is whatever you decide to write.
Walk the GuideFig. 1 · The Fold
The Journal
Undo, recovery, persistence, projections, and audit are not five subsystems that have to be kept in agreement. They are five readings of one sequence.
Scroll the figure sideways
- 1The Source of Truth
- State is the origin with every accepted entry applied in order. It is not a log copied out of some other state, which is what gives replay a precise job: rebuilding is running the same entries again.
- 2Undo Entries
- Position 5 puts position 2 back by committing its inverse, checked against the current state like any other change. Nothing is deleted and no position renumbers, so pressing the undo key makes history longer rather than shorter.
- 3Snapshots
- A snapshot is a cache of the fold so far, sitting beside the journal rather than inside it. Losing every snapshot costs time on the next boot and costs no information at all.
- 4Downstream Consumers
- Persistence, projections, and audit are consumers of one sequence. None of them becomes a second authority, so there is no pair of accounts that can drift apart and no reconciliation to write.
Fig. 2 · The Runtime Map
Modules and Cargo Features
The journal is the middle of the drawing because everything else is positioned against it. Each facility carries the Cargo feature that turns it on, and a default build carries none of the optional ones.
Scroll the figure sideways
- 1One Dependency
- An application depends on harmos and names nothing under it. Every optional facility is a Cargo feature that opens a module of the same name, and a default build compiles none of them.
- 2The Storage Port
- There is no storage row in the feature table and no engine in the box. Harmos publishes the contracts for committed history, snapshots, streams, and evidence; the adapter behind them depends on Harmos, and never the other way round.
You do not start from nothing. examples/jsonl-adapter is a complete worked implementation of those ports, with a JSONL journal, snapshots, stream capture, crash recovery, and an evidence directory. Copy the parts you need and own the resulting format.
Facilities
What the Runtime Owns
Harmos owns the boundaries whose correctness depends on one order, and nothing else. Live observation does not pretend to be durable history, and background work does not pass itself off as a transaction.
Journal · default
The Journal
Checked transitions through a single writer, typed records beside them in the same position space, receipts, undo, historical reads, and replay. An entry carries a position, a kind, the principal the runtime witnessed, and, when it reverses something, the position it answers.
The undo stack is not a structure anywhere in the runtime. It is that link, folded over the order, which is why a crash costs it nothing.
Streams · default
Streams
High-rate typed rows that stay outside history, in a bounded live window, and never make their source wait. A row that fell out of the window is reported as an exact count of what was missed rather than quietly treated as a complete range.
Every row carries the journal position it witnessed, which is the one coordinate the two sides share.
Work · default
Supervised Work
Finite jobs with attempts, deadlines, and a retry policy, admitted on named lanes that own concurrency. Resident services supervised until stop, reporting why an attempt exited.
The split is mechanical rather than stylistic: if callers await the result it is a job, and if shutdown has to stop it it is a service.
Evidence · default
Evidence
A screen recording, a multi-gigabyte log, a rendered report: bulk bytes never enter the journal, because an order that is replayed on every boot should not carry them.
They are still evidence, so the split is made once and made honestly. The bytes live in a vault the application owns, written as a draft and verified, and one journal fact names what they are.
Artifacts · artifact, sidecar, guest
Artifacts
A native executable is a sidecar and a WebAssembly component is a guest. They are parallel kinds rather than children of a common abstraction, and both carry their own identity, version, and declaration, which the host reads before it runs any of their code.
There is no extra manifest between the file and its declaration. A guest receives only the capabilities the host offers and the operator grants.
The Complete Feature Table
- default
- Journal, reads, streams, work, and evidence
- artifact
- The declaration contract both carriers name
- sidecar
- Native-process authoring and supervision
- guest
- The WebAssembly host beside its authoring kit
- componentize
- The component encoder, without the host
- signal
- Measurement kernels over plain slices of f64
- plugin
- Packaging and installation for both carriers
- sim
- Seeded deterministic execution
An application depends on harmos and names nothing beneath it. Every optional facility is a Cargo feature that opens a module of the same name, and a default build compiles none of them.
Proof
The Simulation Harness
A deterministic application is what makes recovery possible. It also makes replay a test oracle: after any scripted run, folding the history again has to produce the exact state the live run reached.
The harness stands the same declaration up on nothing but a seed, with no storage, no clock, and no environment. It drives commits, records, streams, jobs, services, stop, and reboot, so a workflow is exercised before anyone connects an instrument to it. Then the same entries run against the real thing, because they are the same entries.
A failing seed is a reproducible test case rather than a hint. What the harness does not do is invent infrastructure failures: a corrupted file or a partitioned network belongs to the adapter’s own tests.
Read the Simulation Chapter- Scheduling
- The order work is admitted in, decided by the seed.
- Time
- Virtual, so a deadline passes without anything sleeping.
- Service Exits
- Scripted faults, so a resident can be made to fail on cue.
- Entropy
- Seeded, so the same run produces the same run again.
Design Intent
The Division of Labour
The line is drawn where it is on purpose. Nothing below it knows what your application is about, and nothing above it has to be written twice.
Below the Line
What Harmos Gives You
- The Journal
- Checked, atomic transitions through one writer, in one order.
- Replay
- Recovery that rebuilds state from the entries that produced it.
- Undo and Redo
- Per principal, per focus, derived rather than stored.
- Streams
- High-rate rows in a bounded window, with exact loss accounting.
- Supervised Work
- Finite jobs on declared lanes, and resident services.
- Plugin Contracts
- Declarations read before a sidecar or a guest runs.
- Simulation
- Seeded execution, with replay standing in as the oracle.
Above the Line
What You Bring
- Your Domain Transactions
- What a change means, what refuses it, and what it does to the value you defined.
- Your Storage Engine and File Format
- Which engine, which layout, which on-disk decisions. The runtime never learns any of them.
- Your User Interface
- Every pixel. Harmos hands back positioned values and never asks for a shape in return.
- Your Equipment Drivers
- The instruments, the vendor libraries, and the years of knowing what breaks on them.
Scope and Limits
Not a Database
If an external database already owns the authoritative state, keep it authoritative. Copying that authority into an in-memory journal needs a deliberate boundary, not a convenience.
Not a Message Broker
The journal is one application's ordered history. It is not a transport between services, and consumers fold it rather than subscribe across a network.
Not a High-Rate Sample Store
Data with no effect on replay belongs in a derived view, an ordinary file, or a stream. Putting it in the journal makes every future replay pay for it.
Harmos fits when the sequence of accepted changes matters as much as the latest value. Editors, engineering tools, instrument hosts, and configuration systems benefit when recovery, audit, undo, and observation must agree.
Open Source
Contributing
Focused fixes, careful API discussions, documentation improvements, and examples that clarify how the journal model works in a real application are welcome. Open an issue before a substantial API, wire-format, storage-contract, or crate-boundary change. Harmos is pre-1.0, and clean breaks still need a clear reason and updated callers, docs, and examples.
- License
- MIT OR Apache-2.0
- Platforms
- Linux and Windows
Built and maintained by Stokker Technologies.
Pinning a Revision
Harmos is not published to crates.io yet, and its public surface still moves. Depend on a Git revision you have tested rather than tracking a branch.
[dependencies]
harmos = {
git = "https://gitlab.com/stokker-technologies/open-source/harmos",
rev = "<tested-commit>",
}