magus v0.3.0 is out. See what's new

Tour

A guided walk through magusfiles and Buzz, step by step. Open any example in the Playground to run and edit it.

  1. 1

    Magusfiles: write your first target

    A magusfile is a build definition; each exported function is a runnable target. Targets

    import "magus";
    
    export fun greet(ctx: magus\Context, args: [str]) > void {
        magus\info("hello from magus");
    }
    
  2. 2

    Targets: wire the dependencies

    Wire a pipeline with magus.needs; ci is the conventional anchor it all keys off. Targets

    import "magus";
    
    export fun build(ctx: magus\Context, args: [str]) > void {
        magus\info("compiling");
    }
    
    export fun test(ctx: magus\Context, args: [str]) > void {
  3. 3

    Spells: bind a real toolchain

    Bind a real toolchain (Go, TypeScript, Docker) as cached, kebab-case ops. Spells

    import "magus";
    import "magus/spell/go";     // the Go toolchain: go-build, go-test, go-vet, ...
    
    magus\project({ "spells": [go] });
    
    export fun build(ctx: magus\Context, args: [str]) > void {
        go["go-build"](ctx);
  4. 4

    Buzz: branch, match, recurse

    Functions, recursion, closures and match - evaluated live in your browser. Engines

    import "magus";
    
    // Recursion, the classic. `eval fibo(30)` computes 832040 in a real bytecode VM.
    fun fibo(n: int) > int {
        if (n < 2) { return n; }
        return fibo(n - 1) + fibo(n - 2);
    }
  5. 5

    Slots: taming a greedy target

    A target starts with one slot; reserve more so a parallel tool cannot starve the rest. Operations

    import "magus";
    import "magus/spell/go";
    import "magus/spell/typescript";
    
    // Size the heavy job once, then feed the SAME number to both levers: the slots
    // magus reserves for the target, and the --concurrency eslint runs with. Reserve
    // without using them and the slots sit idle; use more than you reserved and you
  6. 6

    Cache: replay, and catch drift

    Targets replay from cache and run once per plan; skip_cache opts a check out. Caching

    import "magus";
    import "magus/spell/go";
    
    magus\project({
        "spells": [go],
        "outputs": ["bin/**", "gen/**"],
        "targets": {
  7. 7

    Globs: gather a target family, minus one

    Name targets by a shared suffix, gather them with one glob, and subtract the odd one out with a negation. Dependencies

    import "magus";
    import "magus/spell/go";
    import "magus/spell/buf";
    
    magus\project({ "spells": [go, buf] });
    
    // One target per generator, all sharing the -generate suffix.
  8. 8

    Charms: check, then apply

    A charm rides down the plan; the built-in rw flips read-only checks to rewrites. Charms

    import "magus";
    import "magus/spell/go";
    
    magus\project({ "spells": [go] });
    
    // Read the active charm set with has_charm, then branch. (Most real spell ops do
    // this flip internally, so you usually get rw for free; here it is spelled out so
  9. 9

    Services: share one across runs

    Author a spell whose op returns a Service; magus supervises one, shared across runs. Services

    import "magus/spell";
    
    // A spell announces its name...
    export fun mgs_getName() > str { return "postgres"; }
    
    // serve returns a Service, so magus treats it as a long-running, shared op:
    //   command   - the process magus forks in the foreground and supervises
  10. 10

    Wards: catch the contradiction

    A detached service trips MGS5002 at resolution, before anything forks. Wards

    import "magus/spell";
    
    export fun mgs_getName() > str { return "postgres"; }
    
    fun serve(t: Target) > Service {
        return Service{
            // The -d is the bug: a service that detaches contradicts its own kind.
  11. 11

    Workspaces: run only what changed

    Projects wire cross-project order with depends_on; affected runs only what changed. CI and affected

    import "magus";
    import "magus/spell/go";
    
    // The shared library project. Nothing depends on the app, so a lib change is what
    // ripples outward through the graph.
    magus\project("lib", {
        "spells": [go],
  12. 12

    Pipelines: assemble the whole build

    The full multi-toolchain pipeline: generate, format, lint, build, test, release. CI

    import "magus";                 // project registration, targets, dependency edges
    import "magus/spell/buf";       // protobuf toolchain: buf-generate, buf-lint, buf-format
    import "magus/spell/go";        // Go toolchain: go-build, go-test, go-vet, go-fmt, ...
    import "magus/spell/typescript";        // TypeScript toolchain: tsc, eslint, prettier, vitest
    import "magus/spell/docker";    // container image: docker-build, hadolint
    
    final VERSION: str = "1.4.0";
  13. 13

    Custom targets: name your own phases

    The canonical names are conventions, not a closed set; export a release or deploy target and drive one from another. Targets

    import "magus";
    import "magus/spell/go";
    
    magus\project({ "spells": [go] });
    
    export fun build(ctx: magus\Context, args: [str]) > void {
        go["go-build"](ctx);
  14. 14

    Standard library: batteries included

    Buzz ships host modules - string casing, semver math, JSON, hashing - you import and call straight from a target. Buzz modules

    import "magus";
    import "semver";
    import "strings";
    
    // A target assembling build config from the stdlib the way a real one would:
    // derive an artifact slug and compare two versions, then print them. `run stamp`
    // computes each value for real.
  15. 15

    Custom charms: patch the argv

    Declare your own charm as a JSON Patch over a spell op's argv; run op:charm to watch the flag splice in. Charms

    import "magus/spell";
    import "magus/charm";
    
    export fun mgs_getName() > str { return "linter"; }
    
    // One op, one base command, two charms that reshape it. `after` splices values in
    // just past an anchor argument; `append` adds to the end. Both resolve to an index
  16. 16

    Guardrails: try to break it

    A deliberate cycle, caught at resolution before anything forks - plus the three other refusals worth knowing. Wards

    import "magus";
    
    magus\project({});
    
    export fun build(ctx: magus\Context, args: [str]) > void {
        // The contradiction: build needs bundle, and bundle needs build.
        ctx.needs(bundle);
  17. 17

    Output refs: what they are, and are not

    A failure mints a handle to that run's exact bytes - not a cache key, not deterministic, and not portable off your machine. Output references

    import "magus";
    import "os";
    
    magus\project({});
    
    export fun build(ctx: magus\Context, args: [str]) > void {
        os\exec("sh", ["-c", "echo 'compiling widget...'; echo 'error: undefined symbol' >&2; exit 1"], ".", {});
  18. 18

    Typed boundaries: keep the data inside

    A target returns void, so structured data belongs in the magusfile - declare the table once, export a verb per action, and let CI supply only the secrets. Tips and tricks

    import "magus";
    import "os";
    
    // The shape, declared once. Note what user_ref holds: a REFERENCE to a credential -
    // under the built-in provider, the name of an environment variable - never the value.
    // That is the whole trick to keeping secrets out of a magusfile: this file describes
    // where to find a credential, something else supplies it, and neither has to know about
  19. 19

    Errors: raise, catch, and branch on a code

    A host call that cannot answer raises rather than returning a blank; catch binds a map with message, and a code and url when the failure carries one, so a target branches on identity instead of matching prose. Diagnostics

    import "magus";
    import "os";
    import "vcs";
    
    magus\project({
        "name": "tour-errors",
        "targets": {
  20. 20

    Footprints: what a target reads and writes

    Two places to put a glob and the difference is not obvious from the names. Declaring the wrong one is the mistake people make most often. Caching

    import "magus";
    import "fs";
    
    magus\project({
        "name": "tour-footprints",
        // Project-wide because EVERY target here reads it. A target-specific input would not
        // belong up here - see report/stamp below.
  21. 21

    Cache identity: what counts as the same work

    A hit means magus decided this run is identical to an earlier one, so every surprise comes from something that is part of that identity without looking like an input. Tool identity

    import "magus";
    
    magus\project({
        "name": "tour-cache-identity",
        "sources": ["magusfile.buzz", "src/**"],
        "targets": {
            // These exist to PRINT, and a replay skips the print - which would hide the very
  22. 22

    Services: when two services are the same service

    Whether your test suite gets one Postgres or four comes down to what magus considers the same config. Services

    import "magus/spell";
    
    export fun mgs_getName() > str { return "pg"; }
    
    // The baseline. Everything in this command that the fingerprint covers - image, tag, port,
    // env - is part of what makes it THIS service rather than another one.
    fun db(target: Target) > Service {
  23. 23

    Charm axes: one question each, and a charm that checks itself

    The shape a real publish target reaches: two charms on separate axes, a guard for the pair that answers one question twice, and magus.raise so a refusal carries a code instead of a sentence. Recommendations

    import "magus";
    import "semver";
    import "std";
    
    magus\project({});
    
    // The version this build would carry. Real magusfiles read vcs\describe(); a literal keeps
Glossary

Glossary

The vocabulary that runs through the rest of the docs. Each entry is a short definition; follow the link for the page that covers the term in depth. Every term has its own anchor, so you can deep-link a single definition (for example glossary/#output-reference).

Core model

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. See workspace.

Project

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

Magusfile

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

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. See targets.

Operation

A single tool-native command a target composes; the middle of the work hierarchy (Spell to Operation to Target). See operations.

Spell

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

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. See charms.

Ward

A coded diagnostic that inspects a resolved op and nudges or blocks an anti-pattern before it runs. See wards.

Module

A magus stdlib namespace a magusfile imports for host capabilities: filesystem, exec, vcs, and more. See the module reference.

Buzz

The language magusfiles are written in (the .buzz engine). See engines.

Engine

The interpreter a magusfile runs on; magus embeds the Buzz engine. See engines.

Execution and caching

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Affected

The set of projects touched by a change; magus affected <target> runs a target only over them. See affected.

Sandbox

The restricted filesystem and environment a target runs in, so builds stay reproducible and side-effect-free. See sandbox.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. See services.

Daemon

The background magus host that owns shared state such as services and the warm knowledge graph. See daemon.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Output reference

A short, shareable id (out1a2b3c, "ref" for short) for one target execution's captured output; it appears on each target's line, and magus query output out1a2b3c prints those exact bytes. In OpenTelemetry terms it corresponds to a span (one target execution) within its trace (the whole magus invocation). See output-refs.

Trace

OpenTelemetry's name for one whole magus invocation; every target it runs is a span beneath it. See telemetry.

Span

OpenTelemetry's name for one unit of work under a trace - a target execution, whose sub-operations are child spans. An output reference points at a span's captured output. See telemetry.

Pool

The concurrency pool: the shared set of slots that caps how many targets run in parallel on one machine. Its capacity defaults to MAGUS_CONCURRENCY, then 4 on GitHub-hosted runners, then min(NumCPU, 8); magus status and the dashboard report it live. See daemon.

Slot

One unit of the pool's capacity. A target acquires the slots it needs to run (most take one) and releases them when it finishes; the pool tracks capacity (total slots), running (acquired), and queued (blocked). See daemon.

Concurrency

How many targets run at once. It is bounded by the pool's capacity and set with --concurrency, MAGUS_CONCURRENCY, or the concurrency config key. See daemon.

Queued

A target that wants a slot while the pool is full; it blocks first-in-first-out until a slot frees. The dashboard colors a sample with queued > 0 accordingly. See daemon.

Pool mode

Which pool a run uses: daemon (one shared pool the background daemon owns across every workspace and client) or proc (a per-process pool for a single one-off invocation). See daemon.

One-off

A single magus invocation that runs a target and exits, using a per-process pool; the opposite of the long-lived daemon or a service. See daemon.

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. See remote-cache.

Snapshot

A point-in-time view of live state - the pool's occupancy or a tick of exported metrics - as opposed to accumulated history. See daemon.

Backfill

The recent history the daemon replays to a dashboard on connect, so its charts start populated instead of empty. It is served from a bounded ring buffer of the last few hundred samples. See daemon.

Telemetry and health

Latency

How long an operation takes. magus records latency as OpenTelemetry histograms per family - target execution, cache op, pool wait, and graph query - and reports each as a count, sum, and percentiles. See telemetry.

Percentile

A latency value at a given rank, interpolated from a histogram's buckets: p50 is the median, p95 and p99 are the tail that most latency budgets care about. See telemetry.

Health

The at-a-glance daemon state derived from the pool: healthy when the pool is reporting, degraded when it reports an error, down when there is no pool. The dashboard color-codes each state. See daemon.

Volatility

A target that fails once and passes on rerun is volatile, as opposed to a regression that started failing and stays failing. magus keeps per-target pass/fail history and a Wilson-score volatility rate to tell them apart and auto-retry the noise. See volatility.

Insight and knowledge

Knowledge graph

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

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. See knowledge.

Insight

The reports magus derives over the graph and history (hotspots, affinity, ownership, trend). See insight.

Hotspot

An insight lens: edit frequency times complexity, the prime refactoring targets. The project view heat-colors the dependency graph by churn; --files ranks individual files. See insight.

Affinity

An insight lens: projects that change together (temporal coupling). A pair that co-changes without either declaring a dependency on the other is a candidate architectural smell. See insight.

Ownership

An insight lens: author concentration - the primary author and their share, the distinct-author count (the bus factor), and abandonment. See insight.

Trend

An insight lens: the recent half of the window against the earlier half. A positive delta is a rising hotspot; a negative one is cooling. See insight.

Diagnostic code

A stable MGSxxxx identifier attached to a magus warning or error, so it can be referenced and looked up; some are guardrails (see wards), others hard errors.

Console

The vocabulary of the browser app. These terms name things you only meet in the console's UI, so they are defined here rather than left to be inferred from it.

Console

The browser app that reads a magus workspace: a tabbed, tiling page hosting the log viewer, graph explorer, dashboard, and activity trail. It is a separate static app, not something the daemon serves - the daemon exposes a loopback API it calls: read-only views plus one bearer-gated job-control service for maintenance jobs. See reference/console.

Console app

One of the console's applications (Log Viewer, Graph Explorer, Dashboard, Activity Trail, Settings). "App" rather than "page" because one is never a document you navigate to: it is mounted into a tab, or into a pane beside another one. Each is single-instance - opening one you already have focuses it instead of duplicating it.

The glossary term is two words on purpose. As a bare "App" the auto-linker matched every unrelated "app" in the corpus - a ChatGPT desktop app, a Postgres app - and pointed each at this definition. See reference/console.

Pane

A split within a tab. Splitting divides the focused pane along its longer side, so the same action tiles side-by-side on a desktop and stacks on a phone; a tab with no split is a single pane. Drag the divider to re-weight the split. See reference/console.

Chord

A key combination bound to a console command, written mod+k - where mod is Cmd on macOS and Ctrl elsewhere, so one binding fits both. Every chord is rebindable (Settings > Keybindings), and a command remains reachable from the command bar whether or not it has one. See reference/console.

Command bar

The console's runner: one searchable list of every command and its chord, opened with mod+k. It is the discoverable route to any action - the menus and chords dispatch the same commands it does. See reference/console.

The URL that points an app at a running daemon. The daemon serves the console from its own loopback origin, so the link is that origin plus the app path and a bearer token in the fragment (http://127.0.0.1:7391/console/graph/#token=...). The daemon prints it; the console consumes the token, stores it, and strips it from the URL, so the secret never lingers in history or a copied link. The origin must be literal loopback - localhost and hostnames are rejected before any request. Without one, an app reads only what rides in the link itself. See reference/console.

See also

Conventions

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