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

Behavior is code, facts are data

Game engines face the choice a build tool faces: write a thing as code, or declare it as data an engine interprets. Two questions settle it. First, does the person who changes it have the code? A game's players usually do not, so a recipe ships as data. A build's author always does: the magusfile sits in the repository it builds, beside the code it compiles, so the usual case for data never arises.

Second, would the data form narrow what the thing can do? Data-driving part of a system restricts it to one behavior with different parameters. A game that data-drives its armor too early ends with five hundred pieces that all feel the same. A build tool that data-drives its targets ends with every target an executor plus an options block, and every build the executor did not anticipate needs a new executor. The patch for the lost expressivity is a type field that picks an algorithm ("executor": "@nx/vite:test", Skaffold's custom builder handing off to a script), and options then layer from schema defaults, workspace defaults, the target, a named configuration and the command line until finding the value that won is its own investigation. The data has grown into a language with no stack trace, no debugger, and no way to read it top to bottom.

So magus splits along that line. A target is a Buzz function in the magusfile; its dependencies are calls you can follow, and an unusual build is an ordinary function rather than a plugin with a build step of its own. magus.yaml holds facts with no algorithm in them: a remote spell's tag, a remote cache's trusted keys, the charms every run applies. The stores stay JSON Lines, which is data in the narrow sense: records magus writes and reads back, never behavior a person authors.

This prevents the homogenized build, where every project looks alike because the format left no room for one to differ, and the one that must differ gets a fork of the plugin.

Enforced by

  • magus.yaml's decoder rejects an unknown key (decodeFile in internal/config/load.go), so the file cannot grow a dialect by accident.
  • The knobs rule answers each key added on purpose.
  • A target exists only as a function a magusfile exports; there is no declarative target format to fall back to.

Good

A target from docs/magusfile.buzz: what it reads, what it writes and what it runs, in order, in one place.

export fun index_generate(ctx: magus\Context, args: [str]) > void !> any {
    ctx.writesFiles("MAGUS.md");
    ctx.readsFiles("magusfile.buzz", "../magus.yaml", "../internal/render/target_graph.go",
                   "../internal/render/md/md.go", "../internal/hint/cli_command.go");
    index\render("docs");
}

Bad

{
  "targets": {
    "test": {
      "executor": "@nx/vite:test",
      "options": { "passWithNoTests": true },
      "configurations": { "ci": { "coverage": true } }
    }
  }
}

The executor string is a type field that picks an algorithm somewhere else. What runs, and which of the schema default, targetDefaults, the target's options, the named configuration or the command line set each value, cannot be read from this file.

See also

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

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. Project in full.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. Magusfile in full.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. Target 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.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. Spell in full.

Charm

An execution modifier attached with : (lint:rw) that changes how a target runs, not which one; the built-in rw flips a check-only target to mutate in place, and ci always strips it. Charm in full.

Buzz

The language magusfiles are written in (the .buzz engine). Buzz in full.

Engine

The interpreter a magusfile runs on; magus embeds the Buzz engine. Engine in full.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. Cache 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.

Trace

OpenTelemetry's name for one whole magus invocation; every target it runs is a span beneath it. Trace in full.

Remote cache

A CI-only backend that shares content-addressed artifacts across runners: a cold machine replays a build another runner already did instead of rebuilding. Every remote artifact must be signed by a trusted key. Remote cache in full.

MAGUS.md

The committed routing index at a workspace root, regenerated from the knowledge graph: it lists every node and points at the exact query for a given question, so it is the entry point an agent reads first. MAGUS.md 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.

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.

Conventions

This page uses none of the site's convention markers. The full set is on the conventions page.