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

Coming from Nx

This page is for a team that already knows Nx and wants to map that mental model onto magus. Nx and magus solve the same core problem - a task graph, an affected set, and a content-addressed cache for a monorepo - with different philosophies: Nx infers, reading package.json/project.json and plugin conventions to build its graph; magus declares, reading exactly what a magusfile says and nothing it guesses. Nothing below is written to make either tool look bad; where Nx has something magus does not, that is stated plainly, and vice versa.

Terminology map

Nx magus
workspace (nx.json) workspace (magus.yaml)
project (project.json / package.json) project (a directory whose magusfile.buzz registers it)
target (project.json targets) target (an exported fun in the magusfile; seven canonical names plus custom - see targets)
executor / plugin spell op (a spell is a library of tool-native ops)
nx:run-commands os\exec(...) in a target body
dependsOn: ["^build"] ctx.needs(...) (target-level; the ^-upstream semantics come from depends_on plus same-target ordering - see dependencies)
implicitDependencies depends_on in magus\project
inputs / namedInputs a spell's needs globs, plus a project's own sources
outputs outputs / a spell's provides globs
targetDefaults[t].cache: true nothing - caching is already on; see Caching is on by default
targetDefaults[t].cache: false the skip_cache target policy, but only when replay would be wrong (see cache)
--skip-nx-cache --no-cache (one invocation; still snapshots afterward)
nx affected magus affected
nx graph magus graph / magus affected --graph / magus graph open
Nx Cloud remote cache (Nx Replay) magus remote cache (self-hosted backends, Ed25519-signed artifacts)
Nx Cloud DTE / Nx Agents magus affected --plan (a provider-neutral JSON shard plan; you bring the runners)
generators / scaffolding fixed, not extensible: magus init writes a starter magusfile, magus init spell <name> scaffolds one spell stub; there is no generator framework for custom, pluggable scaffolds
task pipeline (targetDefaults in nx.json) composed magus\needs calls in the magusfile

Model differences

Config is code, not JSON. A magusfile is Buzz, a small embedded scripting language, not a JSON/YAML document a plugin interprets. There is no schema to look up; a target is a function, and its dependencies are calls you can trace by reading top to bottom.

Explicit declarations, not plugin inference. Nx plugins read your package.json/config files and infer targets, inputs, and dependencies for you - powerful, but the inference is only as good as the plugin's understanding of your setup. magus caches exactly what you declare: a spell's needs and a project's depends_on are the whole story, and under-declaring an input is the one way to get a stale cache hit (see dependencies). Nothing is inferred from source-code analysis.

Caching is on by default; there is nothing to opt into

In Nx, caching is a per-target opt-in: a target caches because something set "cache": true for it in targetDefaults, and forgetting that is why a task you expected to be instant runs in full. magus inverts this. Every target caches, keyed by the inputs it declares, with no policy to write. Arriving from Nx, the instinct is to hunt for where caching gets switched on. There is no such switch, and its absence is the feature: magus affected ci is fast because you declared inputs correctly, not because you remembered a flag.

The mirror-image trap matters more. cache: false in Nx is an ordinary performance or correctness dial people reach for freely, and its nearest-looking neighbor here is the skip_cache target policy. They are not equivalents. skip_cache is a claim that replaying this target would produce a wrong result - it signs a fresh artifact, records a screen capture, mutates go.mod, or never returns. Reaching for it because a target "should feel fresh" disables replay permanently, for every user, on every machine.

If you only distrust the cache for one run, that is --no-cache, which still refreshes the entry afterward. And if a target seems to need skip_cache because it produces no files, it does not: a pure orchestration target caches correctly with no policy at all. See Opting out and busting for the full set of controls and their scopes.

A canonical target vocabulary, not free-form names. Nx targets are whatever string a plugin or project.json names them. magus has seven canonical names (build, test, lint, format, clean, generate, preflight) plus ci, with a stated litmus test for adding an eighth - custom names are allowed, but the vocabulary is deliberately small so magus run lint means the same thing in every project.

Read-only by default, not mutate-by-default. Every magus run is read-only unless you add the rw charm (magus run format:rw); Nx targets run whatever their executor does, with no equivalent default-safe mode.

Sandboxed execution. On Linux, magus confines spell subprocesses with the kernel's landlock LSM (see sandbox); Nx has no equivalent process-level sandbox.

Single static binary, no Node runtime required. magus is a compiled Go binary; running it does not require Node, npm, or any JS toolchain, even for a non-JS workspace. Nx is an npm package and requires Node to run.

What Nx has that magus does not

Said plainly, no hedging:

  • A large plugin ecosystem and community, covering most popular frameworks out of the box.
  • An extensible generator framework (nx generate, local generators) for scaffolding new projects and files with custom, pluggable templates - magus has only the two fixed scaffolds above, not a generator system.
  • First-party editor extensions (Nx Console) with rich UI for running tasks and visualizing the graph.
  • Nx Cloud's managed distributed task execution (Nx Agents) and a flaky-task retry service, as a hosted product.
  • Years of production maturity across a very wide range of ecosystems.

What magus has that Nx does not

  • A signed remote-cache trust model: every remote artifact carries a detached Ed25519 signature verified against a configured trust set, not just an access-token-gated store (see remote-cache).
  • Kernel-level sandboxing of spell subprocesses on Linux (landlock).
  • Services as a first-class declarative op kind, with readiness probes, shared-instance dedup, and idle teardown (see services), rather than a run-commands invocation of a script you write yourself.
  • A knowledge graph and MCP agent surface: magus query / explain / path let an agent (or you) navigate the project/target/spell domain instead of grepping.
  • Volatility detection: magus tracks and reports non-deterministic ("flaky") targets from run history, distinct from a hosted retry service.
  • Language-neutral single binary, no Node runtime dependency.

A porting sketch

An Nx project.json composing a build that depends on its upstream's build, with declared inputs and outputs:

{
  "targets": {
    "build": {
      "executor": "@nx/js:tsc",
      "dependsOn": ["^build"],
      "inputs": ["{projectRoot}/src/**/*.ts", "{projectRoot}/tsconfig.json"],
      "outputs": ["{projectRoot}/dist"]
    },
    "test": {
      "executor": "@nx/vite:test",
      "inputs": ["{projectRoot}/src/**/*.ts", "{projectRoot}/vite.config.ts"]
    },
    "lint": {
      "executor": "@nx/eslint:lint"
    }
  }
}

The equivalent magusfile, in the same project directory - the upstream dependency is declared once, at the target that needs it, via a project import (see Dependencies):

import "magus";
import "project/../shared-lib" as shared;
import "magus/spell/ts";

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

export fun build(ctx: magus\Context, args: [str]) > void {
    ctx.needs(shared.build);   // folds into depends_on automatically
    ts["tsc-build"](ctx);
}

export fun test(ctx: magus\Context, args: [str]) > void { ts["vitest"](ctx); }

// tsc composes into lint alongside eslint - not a bespoke `typecheck` target.
export fun lint(ctx: magus\Context, args: [str]) > void {
    ts["tsc"](ctx);
    ts["eslint"](ctx);
}

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

ts["tsc-build"]'s needs/provides globs and ts["eslint"]'s claimed files are already declared by the spell - see the ts spell reference for the full op list, and Getting started for a from-scratch walkthrough.

See also

  • Getting started: install to first ci pipeline, magus-native.
  • Dependencies: the magus\needs / depends_on model this page's dependsOn row maps to.
  • Remote caching: the signed trust model behind the Nx Cloud comparison row.
  • Spells: the executor/plugin equivalent, and the built-in spell list.
nxmigrationmonorepoterminologycomparisonporting
Last updated (a170f9b2)
Earlier changes on this page (3)

Full history ↗ · Blame source ↗

Glossary

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.

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.

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.

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.

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.

Trace

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

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.

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.

Knowledge graph

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

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.