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

Made to be debuggable at 2am.

magus is a task orchestrator: it builds, tests and lints the projects in a repository, works out which ones a change reached, and calls the tools you already run instead of replacing them. One project or two hundred, in whatever languages you already use. It caches what those tools produce, so the same work never runs twice. Ask it what it is going to do and it prints the exact command, arguments and all. When something breaks (and something always breaks), the error comes from your tool, not from ours.

It informs; it does not decide. Everything it knows about your repository, handed over as answers - and the call stays yours. It was not built for agents, either: they drive it well because an interface legible to a person is legible to anything, and that ordering is the design.

magus is early software. The parts you see here work, and I have put care into them, but I am still shaping the project. Expect change, rough edges, and uneven platform coverage as it grows. It is pre-1.0; CI tests linux/amd64 only, and the Windows builds have never been run here (what is tested, per platform). One binary, GPL-3.0, on Linux, macOS and Windows, whatever languages your repository holds. No account, no paid tier, and nowhere to phone home: magus runs no hosted service, and the OpenTelemetry exporter stays off until you point it at a collector of your own.

What makes it different

The magus mascot: a gopher in a wizard's hat and robe, a staff in one hand and a small cube offered in the open palm of the other.

Affected sets and a build cache are settled ground, and I am not trying to reinvent them. There is no new idea on this page. The work went into refining old ones and then stopping: nothing here invents vocabulary for something that already has a name, and nothing here hands you a second way to do what one verb already does. What is rarer than a new idea is a build tool you can still follow six months in. Boring is the point, because boring means consistent, and inconsistency is where footguns come from: every arbitrary pattern and special case is one more thing you have to remember instead of derive.

It also runs where you are standing. magus run test from inside a project works on that project, the way Make has behaved since 1976 and most command-line tools have since, and the way an agent already expects when it walks up the tree looking for its own instructions. Context is not a feature anybody had to invent; it is the default behavior of a command line, and it survived fifty years because it is right.

Which is why a project here is its path, and has no second name. Give it one and you have created a mapping: something has to store it, keep every entry unique, and update it each time a directory moves. Allow arbitrary characters in that name and you have also decided, permanently, what quoting every command in your repository needs. None of it was a problem anyone had before the naming scheme invented it, and the tool that invented it then hands you the ceremony for living with it: name the project every time, because it can no longer work out the one thing the shell already knew, which is where you were standing when you asked.

Built for humans. Agents welcome.

A tool for people who do this for a living. It assumes you know what you are doing.

Every tool that boxes you in was built by someone who believed they had enumerated the cases. Nobody can. The moment you need the one thing they did not anticipate, the answer is that you cannot do that here - and then you are stuck, and so is whoever you escalate to. So the constraints here live where they can be enforced, in the sandbox and the content-addressed cache, rather than in a schema that has to predict you. What is left is a real language with an escape hatch, because sooner or later you need one. It can hand you everything it knows and tell you what it thinks; it cannot make the call for you, and it should not try. A tool you stand this close to has no business being a walled garden.

None of which makes the enforcement soft. What is enforced is enforced by the engine and cannot be talked past: ci strips the write charms before it dispatches, so the pipeline cannot mutate your tree even when someone explicitly asks for ci:rw. That is a rule magus keeps, not a convention a plugin author might remember to honor. The freedom is in what you are allowed to want, not in whether the rules apply to you.

Easy and permissive are not opposites, whatever the tools that claim to solve everything for you have taught you to expect. Something can meet a person on their first day and still not pen in the one who has been doing this for fifteen years. You get both by informing rather than deciding: the newcomer does what it told them, and the veteran reads the same output and does something else with it.

It was not designed for agents, which is worth saying plainly given how much tooling now is. They drive it well anyway, and that is a side effect: an interface legible to a person is legible to them.

Nothing that makes it drivable was invented for a model. --output is a global flag, so any command will hand back json, yaml, a bare list of names, or one field through a Go template, and nobody has to shred a screen of text to get a value out of it. --dry-run prints the exact argument vector before anything runs. A verb means the same thing everywhere it appears, and an error magus raises itself carries a code you can look up. A command line has owed you all of that since long before anything else was reading its output on your behalf. A tool that only got there once machines started reading it was late rather than clever, and giving that old obligation a new name does not make it a new idea.

So an agent gets exactly what you get, out of the same flags, for the same reason. What it does not get is a say in the design. The day the graph is shaped for a model first is the day this took a wrong turn, and the damage would not announce itself: output tuned for a reader who never gets tired and never gets confused quietly stops being output a person can follow, and you would find that out at 2am.

What ships for agents, and what does not

Twelve skills, compiled into the binary, installed with one command into .claude/skills, .agents/skills, .opencode/skills, or anywhere else your shell can reach. They teach the tool: how to run a target, query the graph, read a diff, resolve a generated-file conflict. Documentation with a table of contents, not a plugin reaching into your repository.

What it will not do is write your AGENTS.md. That file is yours, and an installer that edits a file you own leaves bytes you did not write and cannot audit, so magus agent install prints the block for you to paste and only mentions it when yours is missing or stale. The whole command is a data generator: it writes to stdout, and you decide where it lands.

The skills are one shared body installed identically everywhere, which means they cannot know that a target here is slow, that a directory is off limits, or that one command keeps going wrong in this repository. That part is yours, and it belongs in your repository rather than in mine. The installed copies are written for you to commit; a local skill sits beside them under a name magus does not ship, so an install never overwrites it; the notes store is checked-in content like any other source. All of it is version-controlled, reviewed as an ordinary diff, and meant to be rewritten as you learn - the same way you would maintain any other note to the next person. The one piece deliberately kept out of the tree is the handoff journal, which is yours alone and not the team's. A skill that claimed to know your workspace would be lying about where the knowledge came from.

There is also a guard: magus session hook takes one proposed command or file path and returns deny, advise, or pass, so an agent can be told no before it runs something rather than after. And a daemon-hosted MCP endpoint, which I will describe accurately rather than enthusiastically: it works, it is bearer-gated over loopback, and it is the piece of this surface I am least sure earns its place. If it is removed it will go through the same compatibility contract as anything else, but do not build on it expecting it to be load-bearing forever.

None of this is the reason the graph exists, and it is worth repeating that the day it becomes the reason is the day something went wrong. Pointing an agent at it has the full list.

What you write

A magusfile.buzz at each project root. Every exported function is a runnable target. It is typed, so a misspelled op is a compile error rather than a failure twenty minutes into CI.

magusfile.buzz
import "magus";
import "magus/spell/go";

magus\project({ "spells": [go] });

// every exported function is a runnable target
export fun build(ctx: magus\Context, args: [str]) > void { go["go-build"](ctx); }
export fun test(ctx: magus\Context, args: [str])  > void { go["go-test"](ctx); }
export fun lint(ctx: magus\Context, args: [str])  > void { go["golangci-lint"](ctx); }

// read-only by default. format:rw writes, format:relock re-resolves go.mod
export fun format(ctx: magus\Context, args: [str]) > void {
    go["go-fmt"](ctx);
    go["go-mod-tidy"](ctx);
}

export fun ci(ctx: magus\Context, args: [str]) > void {
    ctx.needs(build, test, lint, format);
}

A spell names a tool

A spell is not an abstraction over your toolchain, it is a name for it. go["go-build"] runs go build; the spell decides the arguments and nothing else. It supplies the operations a target can call, named after the thing they run, so reading a magusfile tells you which binaries will be invoked.

A charm modifies a run

A charm says in what manner. rw turns a check into a write, so format reports by default and format:rw rewrites. relock is separate because re-resolving dependencies is a different risk from reformatting a file.

Underneath it is an RFC 6902 JSON Patch over the command's argument vector, so a charm can replace an argument, remove one, or insert at a position - not merely append a flag. The SET you name is additive and stacks (format:rw,relock), and every run prints the set in effect before it starts; what each one does to the argv is as precise as it needs to be.

Together they are the whole model

Neither invents behavior your tools do not have. That is deliberate: a tool that abstracts your toolchain away leaves you helpless the first time it breaks, and it breaks eventually. This one expects you to keep the knowledge you already have and hands it straight back when something goes wrong, rather than standing between you and the answer. Adopting it should not cost you anything you already know.

It also will not write code for you. Boilerplate a tool generates into your repository is toil you inherit: now you have to know whether you may edit it, what breaks when you do, and whether the next version overwrites it. magus scaffolds one magusfile with magus init and never touches it again, and nothing it writes into your tree has to exist for a build to run.

What you type

Every verb answers one question and stops. Four words carry the whole surface - target, spell, charm, op - so a new capability never earns a new subcommand, and the CLI does not grow into a dialect you have to keep relearning.

magus ls
which projects exist
magus run test
run a target, cache the result
magus affected ci
the pipeline, over only what your diff reaches
magus query kind:spell
what the graph knows
magus describe file <path>
is this generated, and by what

The flags are the same everywhere too

They are global, not per-command, which is the part that makes them worth learning once. Each one answers a different question about a run you have not committed to yet: what would happen, how much do I want to see, and who is going to read the output.

--dry-run
print the exact argument vector and stop
--output
json, yaml, name, or a Go template
-v / -vv / -vvv
detail, then live tool output, then tracing
--silent
errors only, with the failing log's path
--no-cache
run it again for real, then refresh the entry
--explain / --impact
why is this project affected, and what does my diff reach

One command, whichever shape you need it in

The same question, asked three ways. Nothing here is a second code path bolted on for scripts: it is one command with one answer, rendered for whoever is reading. The template fields are the -o json keys, so you find a field name by running the command rather than by reading source.

magus ls targets apps/api
project: apps/api (3 targets)
  generate  regenerates the client from the schema.
  lint      [buf: buf-lint]
  ci        [needs: generate, lint]
magus ls targets apps/api -o name
ci
generate
lint
magus ls targets apps/api -o json
{
  "workspace": "/home/dev/src/acme",
  "count": 1,
  "projects": [
    {
      "path": "apps/api",
      "name": "api",
      "count": 3,
      "targets": [
        { "name": "generate", "reads_secrets": false },
        { "name": "lint", "reads_secrets": false },
        { "name": "ci", "dependencies": ["generate", "lint"] }
      ]
    }
  ]
}

The magusfile that produced those targets is typed, so the mistakes land at the right time. export fun ci(ctx: magus\Context, args: [str]) > void is a signature the checker enforces, and a misspelled op is a compile error before anything runs rather than a failure twenty minutes into a pipeline.

An error you have to read twice is a bad error

One you have to interpret is worse. The bar is that you read it once, understand what happened, and know what to do next: no ambiguity to resolve, nothing to look up, nobody to ask. An error that fails that test has handed its job to the reader.

So a diagnostic magus raises itself carries a code and a page of its own, saying what tripped it and how to get past it. There are sixty-odd of them and the number keeps going up, which is the point rather than a milestone - the whole catalogue is here.

What does not get a code is the error your tool produced. A compiler's complaint, a non-zero exit, a stack trace out of the interpreter: those reach you as they were written, and putting a magus number on someone else's message would be inventing a diagnostic rather than reporting one. So the rule is narrower than "everything is documented", and more useful: if magus is the one objecting, it owes you a code and a page. If it is your tool objecting, it owes you the message unedited.

The documentation came before the announcement rather than after it. Two hundred-odd maintained source pages render into more than five hundred alongside the generated reference. I used AI throughout to move faster, then reviewed and revised the material repeatedly. Generated pages carry an auto-generated tag, so you can tell when code, rather than maintained prose, is the source of truth.

You can understand, operate, and improve magus without a private tour from its author. Maintainers can use the docs and diagnostics to keep it healthy.

The graph is a byproduct, not a product

Every few months there is a new name for this and a new tool to go with it: graph engineering, context engineering, fanning work out across subagents. The tactics keep changing and the same requirement sits under all of them, which is a model of your repository you can actually trust. Most of what ships to provide one is a third-party observer: it scans from the outside and infers the structure, and an inference can be wrong in ways nothing warns you about.

magus is not observing your repository. It is the thing that builds it, so it already has to know every project, every target's declared inputs and outputs, and what a diff reaches. Get any of that wrong and builds break, loudly, today. The graph is that same knowledge handed back to you, and every run checks it. A scan can only happen to agree.

Knowing is the easy half. A graph nobody can put a question to is a data structure, not an answer, and collecting more of it does not help. So every verb is a question with a plain answer: what depends on this, is this file generated and by what, where is this symbol used, what does my diff touch. If it cannot be asked in one command and read in one screen, it does not earn its place.

One of those verbs is not like the others, and the difference is worth stating rather than glossing. Symbols come from a SCIP index, and magus does not contain an indexer. The scip op forks the one your language already has - scip-go, scip-typescript, scip-python, rust-analyzer - and magus reads what it emits. So that layer really is a second index: it is as fresh as its last run and no fresher, it is the one part of the graph a scan produced rather than the build, and if the indexer is not installed you do not get it. Everything else on this page - projects, targets, declared inputs and outputs, what a diff reaches - is the build's own bookkeeping and needs nothing installed to be true.

Everything gets a reference. It is not a random id.

Every run prints one, on success and on failure alike, and it keeps working long after the terminal is gone. The reference is the content-addressed cache key with its tail cut off, so it is not an identifier magus made up and wrote down: the same inputs produce the same reference on your laptop, on a colleague's, and on CI. A reference from a CI log is a reference you can resolve at home.

Which is what makes a failure tractable. A failing target does not just say what went wrong; it hands you the reference for the output, the exact command to read it, and the exact command to run it again.

magus run ci-shard .
[fail] magus ci-shard:rw (ran, 2.8s)
  cause: serialize.jsonDecode: invalid character 'o' in literal null
  output: outb513e99c9eea
  inspect: magus query output outb513e99c9eea
  reproduce: magus run ci-shard:rw .

That reference resolves days later, to the exact captured bytes, or to a record with -o json, or into a browser log viewer with --open. --attempts lists every stored execution behind it, because a flaky target has a history rather than a result. And --publish uploads one run's output to the remote cache as a signed bundle so a colleague can resolve the same reference you are looking at; failing runs are never shared automatically, because a stack trace from your machine is yours until you decide otherwise.

None of this is an audit log and none of it is a security feature; nothing here is trying to catch anyone. It answers the two questions you actually ask after the fact, both otherwise guesswork: what did that command really run, and why did this rebuild when I did not change anything.

magus query invocation invmsnkhkml1
command: magus run lint apps/web
cwd:     ~/src/acme
started: 2026-08-10 14:30:49 (3.488s)
status:  pass

secrets: no credential reads during this run

events:  19
  14:30:49  started
  14:30:51  exec      pnpm install --frozen-lockfile
  14:30:51  exec      pnpm run typecheck
  14:30:52  exec      pnpm run biome
  14:30:53  result    pass

The second question is the one a cache normally cannot answer. A content-addressed cache decides by hashing its inputs, so "why did this run again" has an exact answer, and magus will show you the key broken into the parts that composed it. One digest differs from last time; that is the input that changed.

magus query output out840a2b0bbffb --meta
target:  lint:rw
status:  pass (1.843s)
inv:     invmsnkhkml1  (magus run lint apps/web)
key:     840a2b0bbffb9be6...553bfe4d (keyVersion 5)
rev:     9f3c1d7a4b2e (dirty: uncommitted changes at capture time;
         the revision alone may not reproduce it)

key components:
  os               b6d8d425bd8a  1 line
  arch             f67e9ac7c9ea  1 line
  projectPath      6495552683f0  1 line
  target           b8e6454d1495  1 line
  charm            2c5d84332584  1 line
  src              200bc8e89b6e  10 lines
  spellDefVersion  cbb77e117681  1 line
  tool             fb3166df8e7d  3 lines

Two lines there are worth pointing at. rev says the tree was dirty and that the revision alone may not reproduce the result, rather than printing a commit hash and letting you assume it would. And secrets reports that nothing was read, because the useful thing to know is whether a run touched a credential at all, which is a question about how your build behaves and not an accusation about who ran it.

There is a GUI. You never have to open it.

It is called the Console, and the name is overloaded enough to be worth pinning down: this is a graphical app in your browser, not a terminal. Some tools use the word for the terminal itself. magus is CLI-first and stays that way; the Console reads the same daemon and adds nothing you cannot already get from a prompt.

It is optional by design, and I mean that as someone who lives in a terminal and runs his editor in one. The CLI is the product. magus is fully featured with a browser never involved, and nothing in the console is a place where work happens only there.

A GUI earns its place at the things a terminal is bad at. A dependency graph is the obvious one: you can print it, and you will still want to look at it. The rest is for what you are curious about, not what you do all day. A dashboard of daemon health and running targets is frankly an older idea, a remnant of a different era of continuous integration, and it is still pleasant to leave open on a spare screen when you want half an eye on things.

It cannot quietly become a dependency, and that is structural rather than a promise: the binary serves no HTML. The pages are a separate static site. Over loopback the daemon exposes read-only views, one bearer-gated job service for maintenance work such as reindexing the graph, and the MCP endpoint - and the whole schema is published as reference documentation, so that is checkable rather than something you take from me. Your data does not leave the machine.

The magus console dashboard: a workspace switcher, an all-clear banner reading five targets running and nothing failing, counters for failing, running and queued, and a live activity list of magus run commands with their durations.
The dashboard, running on the synthesized demo data rather than a real daemon - which is also what you get if you open it without installing anything.

Open the live demo - no install and no daemon; it runs on synthesized activity so you can see all four apps before deciding whether you want any of them.

Version control, and the toil around it

Git is the one everyone has, and it is not the only one magus speaks. There are backends for Mercurial and Jujutsu as well, with the caveat stated plainly: git is what gets exercised daily, and the other two will have rough edges I have not hit yet. None of the three is reimplemented here. Each backend drives that system's own command, so what you get back is what git, hg or jj said, and magus has no opinion it could quietly substitute.

The part worth having either way is what happens around a merge. Generated files conflict constantly and resolving them by hand is the wrong move every time, because the answer is not a merge of two outputs - it is whatever the generator produces from the merged source. magus vcs resolve settles the conflicted generated files and regenerates once. magus vcs add stages a change the way this workspace's own declarations say it should be staged, so nobody has to remember which paths are outputs.

The same knowledge answers the question you actually have on a pull request: what does this change reach, and is something else in flight about to make me rebase. magus can post that as advice on the pull request itself - blast radius, generated-file drift, a conflict you are heading toward - and it says nothing at all when it has nothing to say.

Your CI provider is a detail

magus fans work out across runners without knowing whose runners they are. magus affected ci --plan emits a provider-neutral matrix: which projects a change reached, split into shards. It will not drop into a provider untouched, and nobody should pretend otherwise. Every provider wants that same information in its own shape, so something has to reshape it, and the interesting question is what that something is allowed to be.

Here it is magus. Structured output is not a flag you bolt on for scripting: --output is global, so it works on every command, and it takes json, yaml, name, or a Go template, and the transform itself is Buzz, the same language your magusfile is already written in. No jq in your pipeline, no sed holding your CI together, no shelling out to a text tool to fix up a data structure that was structured when it left. Switch the tab to see the same plan land in a different provider.

What magus emits. No provider knows about this shape, and that is deliberate: it is the answer, before anyone has an opinion about how to spell it.

magus affected ci --plan --max-shards 3
{
  "count": 3,
  "max_parallel": 3,
  "source": "git diff vs origin/main",
  "matrix": [
    { "shard": "0", "projects": ". apps/api libs/schema" },
    { "shard": "1", "projects": "apps/web libs/ui" },
    { "shard": "2", "projects": "services/worker libs/testing" }
  ]
}

Actions wants key=value lines written to $GITHUB_OUTPUT, with the matrix as a JSON string it will parse back out with fromJSON. That is the glue, and here is all of it. gha is not a declared thing you register anywhere: a charm is a name you attach to a run, and the target decides what it means.

magusfile.buzz
export fun ci_shard(ctx: magus\Context, args: [str]) > void {
    if (proc\stdinIsTerminal()) { throw "ci-shard: pipe a plan in"; }
    final plan = serialize\jsonDecode(io.stdin.readAll());

    // The plan names its own outputs, so this never lists them.
    var block = "";
    foreach (o in plan.q("outputs").listValue()) {
        block = block + o.q("name").stringValue() + "=" + o.q("value").stringValue() + "\n";
    }

    // Without the charm this only previews. The charm is the whole difference.
    if (!ctx.has_charm("gha")) { magus\info(block); return; }
    fs\writeFile(os\env("GITHUB_OUTPUT"), block);
}
magus affected ci --plan | magus run ci-shard:gha
matrix={"include":[{"shard":"0","projects":". apps/api libs/schema"},{"shard":"1","projects":"apps/web libs/ui"},{"shard":"2","projects":"services/worker libs/testing"}]}
count=3
max_parallel=3
inherit=false

The workflow reads them back, and these three lines are the only ones that know whose runners these are.

.github/workflows/ci.yaml
max-parallel: $
matrix:      $

GitLab wants parallel:matrix, a list of variable sets. Different shape, same plan, and the transform is a dozen lines of Buzz you keep in your own repository.

ci-shard-gitlab.buzz
final plan = serialize\jsonDecode(io.stdin.readAll());
var yaml = "shard:\n  parallel:\n    matrix:\n";
foreach (row in plan.q("matrix").listValue()) {
    yaml = yaml + "      - SHARD: \"" + row.q("shard").stringValue() + "\"\n"
                + "        PROJECTS: \"" + row.q("projects").stringValue() + "\"\n";
}
std\print(yaml);
magus affected ci --plan | magus buzz ci-shard-gitlab.buzz
shard:
  parallel:
    matrix:
      - SHARD: "0"
        PROJECTS: ". apps/api libs/schema"
      - SHARD: "1"
        PROJECTS: "apps/web libs/ui"
      - SHARD: "2"
        PROJECTS: "services/worker libs/testing"

Nothing in magus is compiled against a provider. GitHub Actions and GitLab CI are spells, written in Buzz, and moving between them changes a spell and a workflow file rather than anything about how your build works. Git was meant to be distributed; a great deal of tooling has quietly made one host load-bearing anyway, and the last few years have been a reminder of what that costs when the host has a bad week.

Add what you need. Keep what you write.

There is no CI provider compiled into the binary. GitHub Actions, GitLab, S3 - none of that lives in the Go source. They are spells, written in Buzz, and magus loads them from your own workspace as readily as from its own.

That boundary is the point: the thing you are most likely to need to change is the thing you can change without touching magus at all. Adding a tool, a provider or an op is not a pull request you have to get merged and then wait for a release to use. Write the spell, keep it in your repository, bind it. If it turns out to be useful to other people, contribute it back; if it is specific to your company, never do.

There is a failure mode this avoids by construction. When the extension point is "write code that runs the tool", every plugin author ends up re-solving the same problems - caching, environment, failure handling, secret redaction - and solving each of them slightly differently, so the ecosystem accumulates the same footguns once per plugin and everyone downstream maintains the consequences of a leak they did not write. A spell does not run anything. It declares a command: a binary, its arguments, which charms modify it, which secrets it needs. Running that declaration - caching it, sandboxing it, applying the charms, capturing and redacting the output - is the engine's job, and it happens identically for every spell ever written.

The same holds for the browser apps. The daemon speaks protobuf, and its schema is published as reference documentation. The Console is a reference frontend: a working client that shows what the API supports. I do not work in TypeScript often, so AI helped me move quickly while I reviewed and refined it. You can build your own application on the same API.

That boundary is a licensing one too, which is worth being precise about because magus is GPL-3.0-or-later and people reasonably assume the worst. A spell is a Buzz file that lives in your repository and that magus reads and runs, the way make reads a Makefile and a shell runs a script. It is your work. It is not part of magus, it is not linked into anything, and nothing in the license asks you to publish it. Keep the spell that wires up your company's internal artifact store closed forever. That is the intended use, not a loophole.

What the license does reach is magus's own source. Change that and pass the result on, and whoever you passed it to is owed the same freedoms and the same source you had. The obligation attaches to handing it on, not to editing it. A patch you build and run inside your own company and never distribute is yours as well. The practical point is that you are unlikely to need one, because the extension point was deliberately put outside the binary.

Recent writing

Why things are the way they are, at more length than a documentation page should go to.

Where to next

Three, in order. The first is the only one you need today.

Already working with an agent? Pointing an agent at it covers the structured output, the installed skills and the graph queries it can use, and what it deliberately does not get.

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.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op 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.

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, volatility, unreferenced). 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.

Design and scope

Two rules named often enough elsewhere to need a definition of their own.

Scope test

The question every proposed capability has to answer: does it read the model magus already had to build, or does it make magus learn something new about the world? Reads stay small; acquisitions are where a tool loses its shape. See scope.

One-vocabulary rule

Each concept gets one name, used everywhere: target, spell, charm, op. A second word for the same thing is a house dialect, and it costs every reader (and every agent) a lookup that never ends. See doctrine.

Sessions and leases

The vocabulary of magus watching work happen: who ran what, what an agent is blocked on, and which agent owns which paths. The policy behind these terms lives in doctrine.

Session

One magus process's recorded facts - the targets it finished, their outcomes, and the lease it acted as - kept in a repo-scoped store every worktree shares. magus session lists them; the store prunes itself by last-fact age.

Attention request

A durable "an agent is blocked" record, opened when a magus session notify event carries the waiting or permission outcome and held until a person disposes it. magus session attention lists what is open. Nothing closes one on its own - see doctrine.

Dispose

The human act of closing an attention request: a judgment rendered, recorded with who and why. Distinct from resolving a review thread or a merge conflict - a disposition answers a request; it does not merge anything.

Lease

One row of the lease ledger: a piece of work an orchestrating agent handed out, with its goal, the checkpoint it was cut against, and the paths it owns or must not touch. The ledger records; the agent guard is what reads those facts back when grading a write. See doctrine.

Lease id

The short identifier a worker carries (the --lease flag, or the magus.lease member of the W3C BAGGAGE environment channel) so its runs, journal facts, and guard verdicts attribute to its lease. Letters, digits and -_./: only.

Spawn claim

What a spawning tool said about itself in the environment: TRACEPARENT (the W3C trace and the parent span this process runs under) and the magus.spawner baggage member (a label for whoever spawned it). magus records each verbatim beside the session's own minted span id, and no verdict reads any of them - the ancestry is a relation between recorded sessions, the way a process tree is a relation between pids.

Advisor

One read-only check from the advice suite: it reads the changeset through magus and writes one titled section of findings. The same advisors run as a pull request comment in CI and inside magus diff --impact locally.

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 (Runs, 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.