magus v0.4.3 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
5 min read

Everything is a file

magus keeps its local state in files a person can read with the tools already on the machine. A store is one file of JSON Lines: one entry per line, one JSON object per entry. cat reads a store, grep finds an entry, diff compares two, tail follows one and wc -l counts it. None of that needs magus running or a parser for its format.

The idea is old, and magus borrows from it selectively. Unix made the line the record, so grep, sort, cut and wc work on any file that keeps to it. git stores an object under a two-character fan-out of its hash and a ref as one line naming a commit, updated through a lock file and a rename. Maildir keeps one message per file and delivers by renaming from tmp/ into new/, so no reader sees half a message and no writer takes a lock. /proc exposes one value per file. Plan 9 went furthest, with every service a file tree. magus takes what fits a store on disk: the layout, the line, the rename. It does not take synthetic file servers; the daemon still answers over RPC.

The failure this prevents is state nobody can inspect when it matters. A stuck run, a lease that will not release, or a line that reads wrong is debugged at a terminal with whatever is installed, often after the process that wrote it is gone. A format only magus can read puts the broken tool between you and the evidence of what broke it, and one document holding every entry turns one corrupt write into a lost store; a corrupt line costs the line.

The conventions

  • Layout and naming. A store lives under the workspace's .magus/ or a user directory resolved by libs/xdg (StateDir, CacheDir), never a literal path. A directory names the kind and the file in it is the store (jobs.jsonl), so the listing is the index. A content-addressed store of blobs fans out on the first two hex digits of the hash, as git's does (cas/ab/ab12...).
  • JSON Lines. A store is one append-only .jsonl file: one JSON object per entry, one entry per line, each line ending in a newline. A single JSON document, whose entries only a parser can find, is not a store format, and neither is a name<TAB>value pair or a file per entry holding a document.
  • The last line for a key wins. A change appends a line for the entry's key and a reader keeps the last one it finds. Compaction rewrites the file to one line per key through a rename and never edits a line in place, once the file holds more than twice as many lines as keys, plus 64. A store a person edits by hand, which nothing compacts, refuses a repeated key instead (the conformance ratchet, hack/policy/conformance.jsonl).
  • Atomic write via rename. A rewrite goes to a temporary file in the same directory and is renamed over the old one (file.WriteFileAtomic, which also syncs). A reader sees the old file or the new one, never half of either. An append is one write of one complete line.
  • Encoding lives on the type. An entry type owns its encoding in MarshalJSON and UnmarshalJSON. A reader hands each line to the type and never splits, scans or pattern-matches a line itself. A store a person edits decodes strictly (internal/json's StrictDecoder): an unknown field, a duplicate key, a wrong kind or a bad value is an error naming path:line. A store only magus writes may skip a field a newer magus added.
  • An old format is named, never read. When a store changes shape, the old file is not read as a second format and not skipped in silence: the error names it and says how to move it aside.
  • Readable with cat, grep and diff. Text is UTF-8. The one binary exception is a blob holding the bytes a tool produced, stored verbatim.
  • One owner. Only the package that owns a store knows where it lives. Another package calls the owner's reader or writer and never builds, stats or opens the path itself.

Debt and exceptions

The stores that do not follow the format yet, as found on 2026-10-14:

  • Runtime records are a capped log kept as one rewritten JSON array in the knowledge graph store. The state cannot be rebuilt, so this is debt owed a conversion to appended lines.
  • Owed regenerations are one rewritten JSON document in vcs/owed.go. The state cannot be rebuilt, so this is debt owed a conversion to appended lines.
  • Name and value pairs. The lock, pipe and watchdog records (internal/file/record) write one name<TAB>value pair per line and want one JSON object per line.
  • Derived, rebuildable caches are accelerators, exempt from the rule. The rule covers state that cannot be rebuilt, and a cache that is deleted and recomputed loses nothing.
  • One file per entry, a JSON document inside. Cache manifests, run descriptors, knowledge-graph shards, merge-queue verdicts, the service journal and connector records break the one-file and the line rules, and stay an exception.

Enforced by

  • file.JSONL gives a keyed store its append, its last-line read, its compaction and its lock.
  • file.WriteFileAtomic gives every rewrite its rename.
  • internal/json gives every line its strict decoder.
  • The store-path-export conformance metric fails an exported path of a state-writing package that code outside it references; the shared root resolvers (config.CacheDir, config.StoreDir, vcs.StateDir) are listed in the ratchet with the reason.
  • Nothing refuses a new JSON document, so the format itself holds in review alone.

Good

One entry per line; a change appends, and the last line for a key wins.

{"id":"docs-index","state":"open","model":"economy"}
{"id":"docs-index","state":"pass","model":"economy"}

Bad

{
  "jobs": [
    { "id": "docs-index", "state": "pass", "model": "economy" }
  ]
}

Every change rewrites the whole document, grep cannot find an entry without a parser, and one torn write loses every job in it.

See also

designprinciplestateunix
Last updated (2df714e8)
Glossary

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. Workspace in full.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). Op in full.

Manifest

The file that declares what a package is: its name, dependencies, entry points and tool config (go.mod, package.json, pyproject.toml, Cargo.toml). How the workspace builds, tests and lints the package belongs in the magusfile, not in the manifest's scripts. Manifest in full.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. Cache in full.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. Service in full.

Daemon

The background process a person starts with magus server start. It serves MCP, the console, background jobs and the warm knowledge graph, and adopts nested magus calls into one pool. Daemon in full.

CI

An ordinary target you compose yourself with ctx.needs; magus does not hardcode its stages. It is the anchor magus affected ci keys off, and ci strips the rw charm. CI in full.

Knowledge graph

The queryable graph of a workspace's spells, targets, docs, and code relationships; query it with magus query/explain/path. Knowledge graph in full.

Tool

An action a model can call through its host: a name, a description, a typed input and a result. magus serves its tools over MCP, each a read of the workspace model or a run of the CLI it already has. Tool in full.

Job

The unit of delegated work, and one row of the job store: what an orchestrating agent handed out, with its goal, the checkpoint it was cut against, the paths it may write or must not touch, and the one check it runs. A job's holder is either a session, for work an orchestrator handed out, or the daemon, for its own maintenance. The store records; the agent guard is what reads those facts back when grading a write. Job in full.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate. Run in full.

Lease

The grant a holder takes on a job: the write and read paths that job declared, enforced in the checkout that took it with magus job exec. A job is the piece of work; a lease is permission over it. Lease in full.

Conventions

Placeholders

Angle brackets mark a value you replace with your own; never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.