Open Source Register
The Harmos mark: two dressed blocks joined by a butterfly clamp

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 Page

Before 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.

examples/minischematic/app/src/transactions/symbol/place.rsRust
#[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.

docs/src/content/docs/guide/07-undo-and-redo.mdRust
#[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.

Two Pins, One Netnet.connect
Two symbols joined by a net, and the entry that recorded the connection.R1U1net VCC3 · net.connect v1irreversible

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.

The Canvas Is a Readprojection
Five journal entries folded into the canvas the editor draws.the journal1 symbol.place2 symbol.move3 net.connect4 net.rename5 symbol.movefoldcanvasa projection, not a copy

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.

Across the Storage Portyour format
The committed order folded across the storage port into an adapter and a file format the application owns.committed orderstorage port · a contractyour adapteryou write ityour file formatyou own itthe runtime never learns which engine it got

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.

Work, a File, and a Factexport.completed
An export running as supervised work, writing a file, and announcing itself with one journal record.exportsupervised worknetlistbytes, outside history4 · export.completed v1record

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.

Snapshot Plus Tailreplay
A snapshot plus the entries stored after it, replayed into the document exactly as it was.state = origin + stored entries since itsnapshot@ position 240+the tailthe document, exactly as it was

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.

Simulated, Then Livethe same entries
One scripted run under a seed and the same run against the real equipment, producing the same ordered entries.simulated · seeded, no clockplacemoveundoconnectexportthe same orderlive · the bench, the instrumentplacemoveundoconnectexport

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.

One Plain Valueyour struct
One plain Rust struct holding the whole working state of the editor.struct Schematicsymbols: BTreeMap<SymbolId, Symbol>nets: BTreeMap<NetId, Net>sheet: SheetIdone value, in memory, that replay can rebuild

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.

Two Kinds Enter Historytransaction · record
Transactions and records entering the same ordered history from two different contracts.transactionchanges statereplayed, alwayscheck · apply · invertrecordchanges nothingskippable by a readera witnessed factone ordered history

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.

The Writer Pipelineone writer
One commit through the writer pipeline: check, capture the inverse, apply, seal, publish, and reply with a receipt.one writer · one ordercheckinvertapplysealpublishReceipt { position }a refusal changes nothing

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.

Two Questions, Two Verbspositioned answers
Reading the state returns one positioned value; reading history returns the entries that produced it.readwhat is true nowhistorywhat has happenedAt { value, position }both answers carry the position they observed

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.

Applied and Storedthe risk window
Ten entries with the stored frontier behind the applied frontier, and the risk window between them.the journalstoredappliedthe risk windowstate = origin + stored entries since it

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 Guide

Fig. 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.

The origin, five committed entries, and the consumers that fold themState is the origin plus the committed history applied in order. Five entries follow the origin: place a symbol, move a symbol, connect a net, a record that an export completed, and a fifth entry that reverses the second one and is linked to it. A snapshot lies beside the first three entries, marked as an optimization rather than a second truth. Below them, four consumers fold the same order: the state itself, persistence, projections, and audit.state = origin + committed historylink: UndoOf(2)ORIGINbefore thefirst entry+1symbol.placetransaction2symbol.movetransaction3net.connecttransaction4export.completedrecord5symbol.movethe inverse of 2snapshot @ 3a cache of the fold so far, never a second truththe one committed orderSTATEthe fold of every entryread @ 5PERSISTENCEthe order, in your formata consumerPROJECTIONSviews built from the ordera consumerAUDITwho changed what, and whena consumernone of them becomes a second authority, so none of them can disagree

Scroll the figure sideways

Five entries of one journal. Position 5 reverses position 2 by committing its inverse, and position 2 still says today exactly what it said when it was sealed.
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.

The application over the journal, with the facilities that surround itThe application sits at the top, naming its state and its catalogs. Beneath it the journal holds one order under a single writer. Streams and supervised work stand to the left, evidence and artifacts to the right, and the storage port sits below the journal, leading to an adapter and a file format the application owns. Simulation and the signal kernels close the drawing. Every box carries the Cargo feature that opens it: default for the journal, streams, work, and evidence; artifact, sidecar, guest, componentize, and plugin for the artifact carriers; sim for simulation; and signal for the measurement kernels. The storage port carries no feature, because Harmos ships contracts and no engine.APPLICATIONstate · transactionsrecords · streamsyour crateSTREAMSa bounded windowoutside historydefaultJOURNALone order, one writerundo · replay · historydefaultEVIDENCEfile-shaped output,named by a journal factdefaultWORKfinite jobs on lanes,resident servicesdefaultSTORAGE PORThistory · snapshotsstreams · evidencea contract, no featureARTIFACTSsupervised processes,bounded wasm componentsartifact · sidecar · guestSIMULATIONa seed, virtual time,scripted faultssimYOUR ADAPTERyour engine,your file formatyoursPACKAGINGone installable bundle,and measurement kernelsplugin · signalan application depends on harmos and names nothing beneath it

Scroll the figure sideways

Live observation does not pretend to be durable history, and background work does not pass itself off as a transaction. Each facility owns one boundary whose correctness depends on the order beside it.
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.

One Entry, Witnessedposition 302
One journal entry at position 302, carrying its id, its kind, its principal, and the entry it reverses.ENTRY @ 302idnet.rename v2kindtransactionprincipalalicelinkUndoOf(298)

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.

A Bounded Windowgap accounting
A bounded window of stream rows with the oldest falling out and an exact count of the rows that were missed.window · the last 8 rowsgap · 12 rows missedexact, never roundedevery row carries the position it witnessed

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.

Lanes and Residentsjobs · services
A declared lane admitting two running jobs and holding a third, beside a resident service supervised until stop.lane: export · budget 2jobjobwaitingservice: instrumentresident until stopif the caller awaits it, it is a job

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.

Draft, Verified, Namedvault
File-shaped output written as a draft, verified, then named by one journal fact, with the bytes kept in a vault.draftverifiednamedone fact in the journalbytes in your vault

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.

Two Carriers, One Boundarysidecar · guest
A native sidecar and a WebAssembly guest either side of one boundary, each carrying a declaration read before its code runs.declaration read before executionSIDECARnative executablesupervised processfeature: sidecarGUESTwasm componentbounded storefeature: guestonly what the host offers and the operator grants

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
One Seed, Two Lawssim
A seeded scripted run, and the two standing laws every simulated run is checked against.seed 0x5EEDvirtual time, scriptedcommits · jobs · faultstwo standing lawshistory rebuilds the state the run reachedevery inverse restored what it replaced
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.

What the Runtime Carriesharmos
The seven facilities the runtime carries, sitting under the line an application is built on.the line your application stands onjournalreplayundostreamsworkartifactssimulationbuilt once, in one crate, with one order beneath it

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.
What Stays Yoursyour crate
The four parts an application keeps, sitting above the line the runtime draws.yours, and rightly soyour transactionsyour storage engineyour interfaceyour driversharmos stops exactly here, on purpose

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.

Cargo.tomlTOML
[dependencies]
harmos = {
  git = "https://gitlab.com/stokker-technologies/open-source/harmos",
  rev = "<tested-commit>",
}