Agents
magus knows more about a workspace than any agent can rediscover by reading files: the project DAG, every target's inputs and declared outputs, which files are generated, what a diff affects, where churn and coupling concentrate.
The agent surface exposes that through three artifacts, and one rule governs all of them. Every tool answers a question from declared sources; none of them decides, plans, or injects itself into the agent's context. Answering is the tool's job, deciding is the model's.
| artifact | answers | freshness |
|---|---|---|
MAGUS.md |
WHAT is in this workspace (targets, counts, anchors) | regenerated with the workspace (magus describe graph -o markdown) |
| Skills | HOW to use the magus tool surface | ships with the binary; versioned, drift-checked |
| MCP daemon | live answers (query, run, explain, logs) | always current; magus server start |
The split is deliberate: skills never mention workspace specifics, so they go stale only when the tool surface changes, and that staleness is detectable.
MAGUS.md is per-project as well as per-workspace. A nested project can commit
its own, generated by its generate target and scoped to that project's
targets, so an agent working inside one project gets a routing index sized to
it.
Set up your host
A full setup is two steps: install the guidance where the host reads it, then wire the guard hook. Both are on your host's page.
| host | skills | guard |
|---|---|---|
| Claude Code | .claude/skills/ |
two PreToolUse hooks |
| Codex | .agents/skills/ + AGENTS.md |
hooks.json (experimental) |
| Cursor | AGENTS.md only | one self-contained script |
| OpenCode | .opencode/skills/ |
a TypeScript plugin |
| Any other host | wherever it reads them | your own few lines of config |
What you wire is yours. magus ships the rules and the verdict; the host-shaped few lines that carry an event in and a reply back are a template you copy and own, so a host magus has never heard of works the same way and a host that changes next month is your edit rather than a magus release. Doctrine records that trade and what it costs you.
The shared reference pages sit behind those: Skills for the
install surface, The guard for what is denied and why,
Guard hook templates for the two files Claude Code
and Codex run, Attention hooks for magus session notify,
and Leases for the surface an agent uses when it
fans work out across several.
Parity across hosts
Every host gets the same RULES - they come from one binary, and none of them is per-host. What differs is how much of a verdict a host's hook surface can carry.
| command rules | declared-output rule | deny reaches the model |
advise reaches the model |
|
|---|---|---|---|---|
| Claude Code | yes (verified) | yes (verified) | yes | yes (additionalContext) |
| Codex | yes (verified) | yes, per OpenAI's docs (unverified here) | yes | no - rejects the key |
| Cursor | yes (verified) | yes, reported after the write (verified) | yes (user_message + agent_message) |
no - collapses to allow |
| OpenCode | yes (verified) | yes (verified) | yes (thrown) | no - logged for the human |
"Verified" means executed against this binary with a real event on stdin.
Everything else is additive. A host missing a file-write hook still gets every command rule, and adding one later changes no magus code, because the rules and the verdict already exist and only the wrapper is host-shaped.
Project references
A project reference is a workspace-relative path, written bare: pkg/api. That
is the one spelling to read, to write, and to quote back. What a project arg
prints is what a project arg takes, so a path out of any magus command pastes
straight back into the next one.
| form | means |
|---|---|
pkg/api |
that project, measured from the workspace root, from anywhere |
./pkg/api, .. |
measured from the current directory |
. |
the project the current directory is in |
project:pkg/api |
the same project as a GRAPH NODE, for explain, query, path |
Three rules cover the whole surface:
- A bare path is absolute enough. It is measured from the workspace root, so
it means the same project from any directory. Only the dot forms depend on
where you are standing, and when you are standing outside the workspace
entirely,
--root <path>measures them from the workspace it names - so a command written once keeps working when it is run from somewhere else. - Never rewrite a path magus printed. Every surface prints the bare
workspace-relative form -
-o name,-o json, logs, error messages, Mermaid node labels - so it is already in the form the next command wants. Commands that take fuzzy search tokens rather than paths, such asmagus where, take the same bare text. - Quoting a project back to a user: prefer whatever magus printed. The
workspace root is the case that bites, because it is the one project whose
path is a bare
.; human output renders it as the repository's directory name, so a.never leaks into a sentence where it reads as punctuation.
The project: prefix in the last row is not a second path syntax. It is the
kind-prefixed node grammar the graph commands use where kinds mix, alongside
target:, spell:, and doc:; magus explain project:pkg/api disambiguates a
name that could be either. Commands that only ever take a project - run,
affected, ls, describe project - take the bare path.
A workspace://pkg/api reference still parses and resolves to the same
project, with a deprecation warning. It bought no reading the bare path did
not already have, and it is not what magus teaches or prints any more. Drop
the scheme wherever you find one written down.
Incremental review
Answer "I reviewed earlier - what changed since, and what do I need to look at now" without re-reading the whole workspace:
- At review time, record where you stopped:
magus vcs checkpoint -o nameprints the revision, or<revision>+<digest>when the tree was dirty - the digest says which dirty tree was reviewed, since the revision alone reads the same for every dirty tree built on it. - Later, pipe the delta through the annotated view instead of reading a raw
diff:
git diff <revision> | magus diff -reports each changed file's reach, public-surface exposure, and referents - the surrounding code worth a second look, not just the literal hunks.magus diffrefuses a git ref given positionally, on purpose - a swallowed ref once printed the reader's own edits as the answer - so the pipe form above is the only sanctioned spelling, and the refusal message says so. - Reviewing through a diff session carries this further: per-hunk viewed marks key off content digest, not position, so a hunk that has not changed stays marked reviewed and one that has resurfaces on its own.
Leasing work across agents
The same checkpoint identifies a piece of work handed to another agent, and it is one leg of a wider surface: a declared lease ledger, a console Plan surface that draws it, and a spawn recorded but never judged. magus records what an orchestrating agent says it intends and enforces none of it - ownership is settled by diffing against the checkpoint each lease was handed. Leases covers that loop.
The MCP daemon
magus server start brings up the daemon, and the MCP server with it. Agents
connected over MCP get the same verbs as the CLI plus run and log tools;
magus describe mcp-tools lists all of them with parameters. See
MCP for transport and token setup, and
Knowledge graph for the graph the query tools
read.
Skills prefer the MCP tools and fall back to the CLI, so they work in both connected and daemon-less sessions.
One tool carries state across sessions: magus_memory, a user-owned handoff
journal of per-repository records, each pointing at something magus can reopen,
kept in the user state directory outside the repo and shared across branches and
worktrees. It is pull-based - nothing is injected into an agent's context - and
also available through magus memory ls|get|put|delete|verify. Use verify to
surface stale, malformed, or broken linked entries. Captured build output is
addressed by output references.
The CLI works without it
The CLI still reads the workspace, runs targets, uses the cache, and answers graph queries with no daemon running. What it lacks is MCP tool discovery, the warm graph and background indexes, structured output retrieval, and MCP-only capabilities such as the handoff journal.
An agent must not turn that into a blocker. At task start, or after an MCP
error, run magus status --probe=mcp; if it is unavailable, tell the user once
to run magus server start, then use the CLI fallback. The next task picks up
the full surface after the daemon is running and the client is restarted.
Why a daemon, not a wrapper
If an agent can already run magus query in a shell, what does an MCP server
add? If the server only ran the CLI and handed back its stdout, the answer would
be nothing, plus a round trip. The difference is the context the agent has to
work with.
An agent working through a shell falls back on the habits it learned everywhere else: grep and cat over the files. Those return text matches. They do not return the project DAG, the declared outputs, the affected set, or the blast radius of a symbol, because none of that is written in the files - it lives in the graph the daemon keeps warm. So the agent reasons one layer below the structure it is trying to understand, and fills the gap by guessing: this file looks generated, these two packages probably change together. Those guesses are frequently wrong, and the agent has no way to check them.
The tools answer from what the workspace declares. Ask magus_describe_file
about a path and it does not read the filename and infer; it checks the
project's own globs and reports role: output with the note "generated: never
hand-edit, regenerate." In one case an agent spent close to an hour working out
whether a committed gen/ file was safe to edit - running generate repeatedly,
planting sentinel writes, diffing timestamps - when one call to that tool would
have answered it in a line.
So the server adds three things a shell-out leaves on the floor. Discovery: the
tools arrive in the model's context with their descriptions and parameters, so
the agent knows they exist without reading --help first. Shape: results come
back structured and sized for a model, rather than a human-formatted table
wrapped in color codes and pagination it has to scrape and pay for by the token.
Ground truth: the daemon reports what the workspace declares, which the agent
can rely on, rather than what a text pattern happened to match, which it cannot.
None of this comes from the protocol. A server that only shelled out would be a wrapper whether or not it spoke MCP. MCP is how the graph reaches the model; the value is in the graph and the curation.