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

Tool identity

magus keys a target on its inputs, and the toolchain that built them is one. This page is the three separate questions a spell can answer about a tool, and which of them reach the cache key.

question declared by reaches the key
what version is installed? mgs_getVersionProbe yes
what part of that version matters? mgs_getVersionKey it decides
is the tool usable right now? mgs_getReadinessProbes no

A probe's output is not a version

Every tool wraps its version in prose, and several wrap build identity around it:

go version go1.26.0 linux/amd64
golangci-lint has version 2.5.0 built with go1.25.1 from ff63786c on 2025-09-21T19:04:05Z
Docker version 29.3.1, build c2be9cc

Keying on those strings verbatim makes the key depend on things that are not the tool's version: the commit golangci-lint was built from, the timestamp, the Go version it was compiled with, docker's build hash, the host's OS and arch. Two machines running the identical tool then compute different keys and share no cache, and a distro rebuild moves the key with no version change at all.

Extraction is opt-in

By default the whole probe output keys the cache - what magus has always done. Finding the version inside that output means guessing which number is the version, and a spell asks for that guess explicitly:

export fun mgs_getVersionKey() > VersionKey { return VersionKey{upTo = VersionComponent.patch}; }

upTo is two requests at once: extract a semver, and keep this much of it.

  • patch keeps all three numbers and discards prerelease and build noise. It narrows nothing anyone reasons about, and is the right answer for a tool that pads its version line.
  • minor lets patch releases share one entry.
  • major lets a whole major share one entry.

Narrowing past patch is a claim - that the tool's output does not change across the component you dropped - and it trades cache hits for the risk of replaying an artifact a different version would not have produced. None of the built-in spells narrow past patch.

const covers a tool that cannot report a version at all: the author supplies a string and edits it by hand to invalidate.

Why the default is not extraction

govulncheck -version reports the Go version first, the scanner second, and the vulnerability database's last-modified date last. Extraction would take the Go version and discard the database date - so a newly published CVE could not invalidate a cached pass. It declares no version key, and keys on its whole output.

A spell author knows their tool's output. magus does not.

The host platform is keyed separately

Several toolchains print their platform as part of their version, so before extraction magus keyed on it by accident, and only for projects that happened to bind such a spell. Extraction strips that deliberately, so the platform is stated in the key itself where it covers every step:

os:linux
arch:amd64

They are separate lines because they vary independently: a container image built on linux/amd64 differs from linux/arm64 by arch alone, while a shell test suite differs between macOS and linux by OS alone. One combined switch would make a workspace that cares about one pay for both.

Either can be left out:

cache:
  include:
    os:
      enabled: true
    arch:
      enabled: true

Both default to enabled, and deliberately: being wrong that way costs cache hits, while being wrong the other way replays a foreign artifact out of a shared cache.

A single target can override the workspace answer, using the same nesting so one decision reads the same way wherever it is written:

magus\project({"targets": {
    "image": {"cache": {"include": {"arch": {"enabled": false}}}},
}});

An axis the target does not mention inherits. A misspelled nesting level is a load error rather than a silent inherit - the two are indistinguishable at run time, and one of them is a cache that quietly does the wrong thing.

This is the host platform. The platform an artifact is built for travels as GOOS/GOARCH through the environment allowlist and keys through the env lines.

Readiness never keys the cache

A readiness probe answers whether a tool is usable now:

export fun mgs_getReadinessProbes() > {str: Command} {
    return {"docker": Command{bin = "docker", args = ["info"]}};
}

docker --version is client-only - it succeeds with no daemon - so the version probe structurally cannot detect a stopped daemon. Without readiness the op forked, docker failed, and magus reported a build failure for a project with nothing wrong with it. Now it fails as MGS3004 before forking.

The result is a precondition, not an input. docker info reports running containers and disk usage, so mixing it into a key would invalidate every entry on every run. This is worth stating because the neighboring mechanism does the exact opposite: a version key exists precisely to enter the key. The two probes look alike and mean opposite things.

Probes are keyed by tool and resolved through an op's own bin, so a spell driving both docker and hadolint gates only the former - linting a Dockerfile talks to no daemon. magus doctor lists every gate without running any of them.

cacheversionprobereadinessplatformspellsdeterminism
Last updated (a103255f)
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.

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.

Buzz

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

Cache

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

Sandbox

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

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.

Conventions

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