---
title: Spells
order: 4
page_type: overview
description: A spell is a library of tool-native ops (go-build, cargo-clippy, eslint) plus cache metadata that your magusfile composes into runnable targets.
tags: [spells, operations, toolchain, cache, targets, go, rust, magusfile]
---

# Spells

A **Spell** is a _library of tool-native operations_ for one toolchain, plus the cache and affected-set metadata that toolchain needs. The `go` spell exposes `go-build`/`go-test`/`go-vet`/`go-fmt`/`golangci-lint`/...; the `rust` spell exposes `cargo-build`/`cargo-test`/`cargo-clippy`/`cargo-fmt`/.... Each op is named after the CLI command it runs (see [Naming operations](#naming-operations)). A spell is **bound to a project** and **runs nothing on its own**: it contributes operations your magusfile composes into [targets](targets.md), and it tells the cache which files are inputs and outputs.

A spell is _how_ a tool does something (the `go-vet`, `cargo-clippy` ops); a target is _what_ you run (`magus run lint`). You **bind** spells and **invoke** targets. See [Spells vs Targets](#spells-vs-targets).

## Built-in spells

magus ships these spells. Import each with `import "magus/spell/<name>"`; follow a link for its ops, charms, and paste-ready examples you can dry-run in the browser.

<!-- BEGIN SPELL LIST (generated by cmd/magus-spelldocs; edit that generator, not this table) -->
| Spell | Language | Ops | Purpose |
|-------|----------|-----|---------|
| [`bash`](spells/bash.md) | Shell | 1 | Bash spell: shellcheck linting for shell scripts. |
| [`buf`](spells/buf.md) | Protobuf | 5 | Buf spell: protobuf build, lint, format, and code generation. |
| [`buzz`](spells/buzz.md) | Buzz | 3 | Buzz spell: check and test .buzz sources, plus run them through the magus interpreter. |
| [`cosign`](spells/cosign.md) | - | 3 | Cosign spell: keyless sign, attest, and verify for container artifacts. |
| [`docker`](spells/docker.md) | Docker | 5 | Docker spell: image build, build-check, buildx, and hadolint Dockerfile linting. |
| [`go`](spells/go.md) | Go | 13 | Go toolchain spell: build, test, vet, fmt, mod-tidy, golangci-lint, and govulncheck as magus ops. |
| [`markdown`](spells/markdown.md) | Markdown | 3 | Markdown docs spell: markdownlint and prettier for linting and formatting prose. |
| [`python`](spells/python.md) | Python | 6 | Python toolchain spell: pytest, ruff check/format, and uv build/clean as magus ops. |
| [`rust`](spells/rust.md) | Rust | 6 | Rust toolchain spell: cargo build, test, clippy, fmt, and clean as magus ops. |
| [`typescript`](spells/typescript.md) | TypeScript | 11 | TypeScript toolchain spell: tsc, eslint, prettier, and vitest run through the project package manager. |
<!-- END SPELL LIST -->

## Spells vs Targets

These are the two core nouns in magus, on orthogonal axes. Confusing them is the usual reason a build runs and does nothing.

|                         | **Spell**                                                                            | **Target**                                                                              |
| ----------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| **What it is**          | a library of tool-native operations (+ cache metadata)                               | an addressable unit of work you run                                                     |
| **Answers**             | _how_ a tool performs an operation                                                   | _what_ operation runs on _which_ project                                                |
| **Vocabulary**          | the tool's own CLI command (`go-vet`, `cargo-clippy`, `tsc`, `eslint`, `ruff-check`) | magus's lifecycle (`build`, `test`, `lint`, `format`, `clean`, `generate`, `preflight`) |
| **Who declares it**     | a built-in or a spell file (`spells/*.buzz`)                                         | an exported function in your magusfile                                                  |
| **How it enters a run** | **bound** to a project via `magus\project.register`                                  | **invoked** via `magus run <name>`                                                      |
| **Runs on its own?**    | **No**: it only contributes ops + cache inputs                                       | **Yes**: it is the entry point                                                          |
| **Cardinality**         | many ops per spell; many spells per project                                          | one function per target name per project                                                |
| **Cache role**          | declares `needs`/`provides`/`claims` (the inputs/outputs)                            | the unit a cache key is computed and replayed for                                       |
| **Identity**            | a `name` + its ops                                                                   | `Path + Name` (see [targets.md](targets.md))                                            |

The relationship is **compositional**: a target's body calls spell ops.

```buzz
import "magus/spell/go";
magus\project.register(fun(p, cb) > bool { cb({ "spells": [go] }); return true; });   // bind the spell (runs nothing)

// targets are the runnable verbs; their bodies call the spell's ops. Op keys are
// the CLI command, so kebab names are reached by subscript (see Naming operations),
// and every op call takes the target's ctx as its first argument.
export fun build(ctx: magus\Context, args: [str]) > void { go["go-build"](ctx, { "cwd": "." }); }
export fun lint(ctx: magus\Context, args: [str])  > void { go["golangci-lint"](ctx, { "cwd": "." }); }
export fun test(ctx: magus\Context, args: [str])  > void { go["go-test"](ctx, { "cwd": "." }); }
```

```sh
magus run lint .         # runs your `lint` target, which calls go's golangci-lint op
```

### When to use which

- **Reach for a target** when you want a _runnable verb_: the thing a teammate or CI types (`magus run test api`). Targets are your **public surface**; declare one per lifecycle step you want runnable. Until you export a target for an operation, `magus run <op>` is a graceful no-op.
- **Reach for a spell** when you want to _package a toolchain's operations_ and _tell the cache which files matter_. Bind a built-in or load a spell file. Call its ops from inside target bodies.
- **Skip the spell entirely** for a one-off step with arbitrary logic: write the target body directly with the host modules (e.g. `os\exec(...)`). A spell earns its keep when an operation recurs and has cache inputs worth declaring.
- **Use the `::` escape hatch** (`magus run go::go-vet api`) only for ad-hoc runs or introspection. The everyday surface is your composed targets.

magus deliberately does **not** decide what "lint" or "format" means. A spell supplies tool-native operations in the tool's own words; your magusfile decides which op backs each lifecycle target. Toolchain knowledge lives in the spell (reusable, cacheable); policy lives in the magusfile (yours to compose).

## What a spell provides

A bound spell contributes three things to its project. Only operations are "runnable"; the other two are metadata that make caching and the affected set correct.

| Contribution   | Source                                  | Purpose                                                        |
| -------------- | --------------------------------------- | -------------------------------------------------------------- |
| **Operations** | `mgs_listTargets` (or `ops`)            | the tool-native actions a target can call                      |
| **`needs`**    | `mgs_listRequiredGlobs` (or `needs`)    | input globs hashed into the cache key; also affected-set seeds |
| **`provides`** | `mgs_listProvidedGlobs` (or `provides`) | output globs the cache snapshots and replays                   |
| **`claims`**   | `mgs_listClaimedGlobs` (or `claims`)    | files this spell owns for affected-set attribution             |

Binding a spell contributes its `needs`/`claims`/`provides` to that project's cache key and affected set even before you wire any target; it executes nothing until a target calls one of its ops.

### An operation is a command or a service

An op is one of two declarative shapes, and the shape it returns is its **kind**:

- A **command op** returns a `Command`: a `{bin, args, charms}` object naming a program on PATH, its argument vector, and any charm modifiers. magus forks it directly (no shell, no variable expansion) and runs it to completion. This is the default and the vast majority of ops.
- A **service op** returns a `Service`, a long-running process. `command` (required) is the process; `readiness` (a probe polled until it exits 0, the Kubernetes exec-probe model) and `stop` (a graceful-shutdown command) are **optional**, as are `distinct` and `idle` (see [services.md](services.md)). A service run **directly** (`magus run dev`) is forked in the **foreground** and blocked on (Ctrl-C signals it). A service reached as a **dependency** (via `magus\needs`) is instead **supervised in the background**: started, gated on its readiness probe, and shared by configuration fingerprint so several dependents run one instance, and, when a daemon is running, kept warm across invocations. (`Service` is a distinct return type so the op's kind is inferred from what it returns.) A service op's target is **uncached**: magus never replays or snapshots it, so a re-run restarts the process instead of a cache hit doing nothing.

Either way the op is declarative _data_, so its argv is charm-patchable, hashes into the cache key, and previews under `magus describe` without executing. The kind lives on the **op**, not the spell: one spell freely mixes command ops and service ops under one name (a `node` spell builds _and_ serves). Both shapes are static data, unlike the imperative two-op-kinds split magus removed; they differ only in lifecycle (run-to-completion vs long-running).

You author an op as a function (the usual form) or a bare record; the kind is inferred from what it returns. `magus doctor` enforces a doc comment on each function-authored op, captured by the Buzz parser at compile time.

```buzz
// command op: derive the `rw` charm from the arg list
fun goFmt(target: Target) > Command {
    final args = ["-l", "."];
    return Command{bin = "gofmt", args = args, charms = { "rw": set(args, "-l", "-w") }};
}
// service op: a long-running dev server `magus run` blocks on
// (command is required; readiness and stop are optional)
fun nodeServe(target: Target) > Service {
    return Service{ command   = Command{bin = "npm", args = ["run", "dev"]},
                   readiness = Command{bin = "curl", args = ["-sf", "http://localhost:5173"]} };
}
// a map that mixes op kinds is typed `any` (its values have different function types)
export fun mgs_listTargets() > any { return {"go-fmt": goFmt, "serve": nodeServe}; }
```

**In-VM work is still not an op.** Custom logic magus neither forks nor blocks on (HTTP, signing, a remote cache backend's get/put) is not an op at all. A remote cache backend is a separate contract magus's core invokes by name (see [Remote caching](cache/remote.md)); any other one-off logic belongs in a magusfile target body written directly with the host modules (`os\exec`, `http`, `crypto`).

## Binding a spell to a project

A spell only takes effect when its **handle** is passed to `magus\project.register`. Importing a spell is pure; it registers nothing on its own.

### Built-in

Built-in spells are compiled into the magus binary.

```buzz
import "magus/spell/go";
magus\project.register(fun(p, cb) > bool { cb({ "spells": [go] }); return true; });
```

Available built-ins: `go`, `typescript`, `python`, `rust`, `bash`, `buf`, `buzz`, `docker`, `cosign`, `markdown`.

### File spell

A workspace-local `spells/<name>.buzz`, imported by path (`import "spells/ruby"`
resolves `./spells/ruby.buzz` and binds the handle under the basename; `as`
renames it):

```buzz
import "spells/ruby" as rb;
magus\project.register("gems/", fun(p, cb) > bool { cb({ "spells": [rb] }); return true; });
```

## Composing spells

Spells do **not** import one another. There is no spell-to-spell `import`, and a built-in spell may import only the pure-types `magus/spell` module (enforced by `SelfContainedBuiltinSource`). Composition happens one level up, at the **project**: bind several spells to the same project and let your targets call across them.

```buzz
import "magus/spell/go";
import "magus/spell/docker";
magus\project.register(fun(p, cb) > bool { cb({ "spells": [go, docker] }); return true; });   // co-bound

export fun build(ctx: magus\Context, args: [str]) > void {
    go["go-build"](ctx, { "cwd": "." });
    docker.build(ctx, { "cwd": "." });   // one target, ops from two spells
}
```

The go/docker relationship is exactly this **co-binding**, not an import: both are bound to a project and their ops are composed in target bodies. The cache sees the union of every bound spell's `needs`/`provides`/`claims`.

Two magus APIs take a spell handle as an argument, and both are a magus call consuming a spell rather than a spell importing a spell:

- `magus\cache.remote(github)` wires a **cache-backend** spell (e.g. `github-actions`, `aws-s3`) as the remote cache backend. See [Remote caching](cache/remote.md).
- `magus\ci.provider(github)` wires a **CI-provider** spell, which teaches magus one CI system's job-log structure: fold markers around a failure, and annotations that surface on a pull request. See [CI providers](ci-providers.md).

Both are extension points on purpose. magus itself knows neither a cache service's API nor a CI system's log syntax, so supporting one it has never heard of is a spell you write rather than a release you wait for.

## Naming operations

An op's public name is the **map key** in `mgs_listTargets` (or the `ops` table). That key is what a magusfile calls and what `magus run spell::op` invokes. The implementation function is private. Name both after the CLI command, not after a magus lifecycle verb, so the spell is self-documenting and a developer who knows the toolchain can invoke an op without reading the magusfile.

**Op key: the CLI command, kebab-case, lowercase, no flags.** Write the command as you type it and replace spaces with hyphens. The golang spell:

| runs            | op key          | handler        |
| --------------- | --------------- | -------------- |
| `go build`      | `go-build`      | `goBuild`      |
| `go vet`        | `go-vet`        | `goVet`        |
| `go test`       | `go-test`       | `goTest`       |
| `gofmt -l`      | `go-fmt`        | `goFmt`        |
| `go mod tidy`   | `go-mod-tidy`   | `goModTidy`    |
| `golangci-lint` | `golangci-lint` | `golangCILint` |

Naming the op `golangci-lint` (not `lint`) and `go-fmt` (not `fmt`) says exactly which tool runs: there is no `go lint`, and `fmt` here is the `gofmt` binary. Multi-tool spells already work this way: typescript exposes `tsc`/`eslint`/`prettier`/`vitest`, one op per tool.

**Handler: the same command in lowerCamelCase, with Go-style initialisms** (`go-fmt` → `goFmt`, `golangci-lint` → `golangCILint`, `ruff-check` → `ruffCheck`). The handler name is invisible to magus; it exists to tell the reader the exact binary.

**Not every op is a CLI command.** A no-op marker (typescript's `preflight`) or a cache-backend verb (github/s3 `get-entry`) is not a tool invocation, so keep a descriptive name.

**Op keys are matched verbatim** (no kebab/case normalization, unlike target names), so a kebab key is reached by subscript in a magusfile: `go["go-build"](ctx)`, not `go.build(ctx)`. An op whose key is a valid identifier (`pytest`, `eslint`) can use dot: `py.pytest(ctx)`. Either way the first argument is the target's context - the `ctx` the target function received - and any options follow it: `go["go-build"](ctx, { "cwd": "." })`.

This is the one place magus has two name spaces with different rules, so it is worth stating side by side:

|                  | Target names                                   | Op keys                       |
| ---------------- | ---------------------------------------------- | ----------------------------- |
| Matching         | normalized both sides ([how](targets.md#name-normalization-casing--delimiters)) | verbatim                      |
| `goBuild` finds  | `go-build`                                     | nothing                       |
| Magusfile call   | `magus run go-build`                           | `go["go-build"]()`            |
| Dot access       | n/a                                            | only if the key is an identifier |

The practical consequence: a hyphen in a target name costs you nothing, while a hyphen in an op key is what forces subscript notation. That is accepted deliberately (see below) rather than overlooked, because the op key is the tool's own name and magus does not invent a second spelling for it.

### Explicitness over magic (why `go::go-fmt` stutters)

The full-command convention is enforced even for streamlined toolchains like Go, where `go-build`/`go-test`/`go-fmt` all start with `go`. The result reads with a stutter on the CLI (`magus run go::go-fmt`), and that is **by design**:

- **Consistency across a polyglot repo.** Most languages split work across separate binaries (typescript: `tsc`/`eslint`/`prettier`/`vitest`; rust: `cargo`/`clippy`/`rustfmt`). A rule that says "name the op after the binary, always" is one rule for every spell, instead of a special abbreviation for the few single-binary toolchains.
- **No invented vocabulary.** `go-fmt` is `gofmt`; `golangci-lint` is `golangci-lint`. There is no magus-specific `lint`/`fmt` alias a reader has to learn or a magusfile has to map. The op name _is_ the command.
- **The stutter shows up only in the escape hatch.** You type `go::go-fmt` for an ad-hoc op-direct run (see [targets.md](targets.md#cli-extension-spell-qualified-targets)). In normal use you compose `go["go-fmt"](ctx)` into a `format` target and run `magus run format`. magus favors explicitness over magic: the spell says exactly what it runs, and policy (which op is your "format") lives in your magusfile.

## Authoring a custom spell

> For the full contract - every `mgs_` function, the built-in versus workspace-local
> constraint, and the provider variants - see [Writing a spell](../guides/authoring-spells.md).

A spell file exposes the spell contract as `mgs_`-prefixed functions: the required `mgs_getName`, plus optional `mgs_listRequiredGlobs`, `mgs_listProvidedGlobs`, `mgs_listClaimedGlobs`, `mgs_listIgnoreDirs`, `mgs_getVersionProbe`, `mgs_isOpaque`, and `mgs_listTargets`.

MGS functions are discovery-time declarations: they take no arguments and must be
pure, because Magus calls them before it has selected a target or started an
execution. File metadata uses generated `Path` values, not strings. Put
per-invocation inputs and calls to other spell operations on ordinary typed spell
functions, then compose those functions explicitly from a magusfile target.

A spell is the layer that carries logic, so it is also the layer worth testing -
unlike the magusfile that binds it, which should stay thin enough that the
question never arises. See [Testing](../guides/testing.md) for where that line
sits and how to write in-file `test "..." {}` blocks.

`mgs_listIgnoreDirs` names the non-source directories your ecosystem generates (a Rust spell returns `[Path{value = "target", isDir = true}]`; a Node spell, `[Path{value = "node_modules", isDir = true}]`). magus prunes them from the input-hashing walk of any project this spell resolves, so a build tree never counts toward the cache key. Dot-directories are always skipped, so only non-dot names belong here.

Buzz (`spells/ruby.buzz`):

```buzz
export fun mgs_getName() > str { return "ruby"; }
export fun mgs_listRequiredGlobs() > [Path] {
    return [Path{value = "**/*.rb"}, Path{value = "Gemfile"}, Path{value = "Gemfile.lock"}, Path{value = "*.gemspec"}, Path{value = ".rubocop.yml"}];
}
export fun mgs_listProvidedGlobs() > [Path] { return [Path{value = "vendor/bundle/**/*"}]; }
export fun mgs_listTargets() > any {
    return {
        "bundle-install": { "cmd": "bundle", "args": ["install"] },
        "rspec":   { "cmd": "bundle", "args": ["exec", "rspec"] },
        "rubocop": { "cmd": "bundle", "args": ["exec", "rubocop", "--check"],
                     "charms": { "rw": {"ops": [{"op": "replace", "path": "/2", "value": "-A"}]} } },
    };
}
```

Then import it by path, bind it, and compose targets that call its ops:

```buzz
import "spells/ruby" as rb;
magus\project.register("gems/", fun(p, cb) > bool { cb({ "spells": [rb] }); return true; });

export fun test(ctx: magus\Context, args: [str]) > void { rb.rspec(ctx, { "cwd": "gems/" }); }
export fun lint(ctx: magus\Context, args: [str]) > void { rb.rubocop(ctx, { "cwd": "gems/" }); }
```

For cache-correctness rules (declare every input in `needs`, declare `provides` so outputs replay, toolchain-version footguns), see [Spells in the README](../../README.md#custom-spells).

## What magus bounds, and what it does not

A spell is code. That is the design, not a compromise: a build system whose
configuration cannot express a loop or a conditional pushes that logic into shell
scripts nobody can cache, or into the CI provider nobody can run locally. Config
as code means the config is code, and code runs.

So the question is not "how do we stop a spell doing things" - it is "which
things does magus govern, and which are yours". Being explicit about that line is
the point of this section.

### What magus does not restrict

A spell has the whole [host module](../reference/buzz/index.md) surface:
`os\exec`, `http`, `fs`, `crypto`, the lot. It can run any command your shell
can, reach the network, and read and write files. Nothing here is sandboxed
per-spell, and importing a spell is trusting it, the same way adding a dependency
is trusting it.

magus does not attempt to make an untrusted spell safe. It cannot: the feature
being asked for is arbitrary code execution. What it does instead is make the
governed path the convenient one, so a spell that behaves ordinarily is
automatically accounted for.

### What magus bounds

| Bound | Applies to | Default |
| --- | --- | --- |
| Concurrency slot | every `os\exec`, so parallel work respects `-j` | on |
| Own process group | subprocesses, so Ctrl+C reaches magus and not them | on |
| Target deadline | a whole target, subprocesses included | **off** (`target_timeout`) |
| Filesystem sandbox | the magus process, where the platform supports it | per [sandbox](sandbox.md) config |
| CI provider op deadline | each provider spell op | 5s, fixed |

### The runaway guard

A magusfile can express a loop, so a loop that never terminates is something
someone can write by accident. Nothing else reclaims a CI runner that hit one, so
`target_timeout` bounds a single target:

```yaml
# magus.yaml
target_timeout: 30m
```

The Buzz VM samples cancellation on loop back edges, so a spinning target notices
promptly without the interpreter paying for a check on every instruction.

It is **off by default**, deliberately. The deadline covers the whole target,
subprocesses included - cancelling the context kills what the target spawned - so
a value set near a legitimate target's runtime turns a slow compile into a failed
build. Set it well above your slowest target, as a runaway guard rather than a
performance budget.

### A gap worth knowing

The Buzz language stdlib ships its own `os\execute`, separate from magus's
`os\exec`. It spawns a subprocess without taking a concurrency slot, without its
own process group, and without appearing in the run log. It is the one way a
spell can run work that magus does not account for, and telling the two apart by
name alone is not obvious.

Prefer `os\exec`. It is the governed path, and everything in the table above
applies to it.

## Lifecycle: bind → contribute → compose → run

```text
import "magus/spell/<name>" or "spells/<name>"  → a Spell handle (registers nothing)
      │
      ▼
register(fun(p, cb){ cb({spells}) })   → the spell is bound to the project;
      │                                   its needs/provides/claims now feed the
      │                                   project's cache key and affected set
      ▼
export fun <name>(...) > void {}        → a target whose body calls spell ops
      │                                   (spell.op(ctx, {"cwd": ...})); this is the
      │                                   runnable verb
      ▼
magus run <name> <project>             → executes the target; spell ops fork
                                          their commands (cached by needs/provides)
```

Key invariant: **binding is not running.** A bound spell with no target wired is inert at run time but still shapes the cache key. A target with no spell behind it is just a function you wrote (valid; call the host modules directly).

## Glossary

| Term               | Definition                                                                                                                                                                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Spell**          | A library of tool-native operations for one toolchain, plus its cache/affected metadata. Bound to a project; runs nothing on its own.                                                                                                                         |
| **Op (operation)** | One tool-native action a spell exposes, named after its CLI command (`go-vet`, `golangci-lint`). Reached by subscript on the handle (`go["go-vet"](ctx)`) or via `spell::op` on the CLI. See [Naming operations](#naming-operations).                         |
| **Handle**         | The value bound by importing a spell (`import "magus/spell/<name>"` for a built-in, `import "spells/<name>"` for a workspace-local one). Inert until passed to `magus\project.register`.                                                                      |
| **`needs`**        | Input globs (`mgs_listRequiredGlobs`). Hashed into the cache key; also seed the affected set.                                                                                                                                                                 |
| **`provides`**     | Output globs (`mgs_listProvidedGlobs`). What the cache snapshots and replays on a hit.                                                                                                                                                                        |
| **`claims`**       | Files a spell owns (`mgs_listClaimedGlobs`), for affected-set attribution.                                                                                                                                                                                    |
| **Op**             | A command op forks a `Command` (`{bin, args, charms}`) to completion; a service op is a long-running `Service` (`{command, readiness?, stop?, distinct?, idle?}`): foregrounded when run directly, supervised in the background when reached as a dependency. |
| **Op kind**        | Whether an op is a `command` (returns a `Command`, run to completion, the default) or a `service` (returns a `Service`, a long-running process). Inferred from the return type; lives on the op, so one spell mixes both.                                     |
| **Target**         | The runnable unit a spell op is composed into. A separate concept; see [targets.md](targets.md).                                                                                                                                                              |

## Worked examples in this repo

Read these spells under [`spells/`](https://github.com/egladman/magus/tree/HEAD/spells) when you outgrow a plain fork spell:

| Spell                                            | Role            | What it demonstrates                                                                                                                                                                               |
| ------------------------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`buf`](../../spells/buf/spell.buzz)                | fork (built-in) | A **codegen producer**: `needs` (`.proto` + buf config) and `provides` (generated code), so editing a `.proto` reruns codegen and invalidates everything downstream of the generated files.        |
| [`github-actions`](../../spells/github/actions/spell.buzz) | cache backend   | A **remote cache backend** over the GitHub Actions Cache API in pure Buzz: bearer auth, byte-level chunked upload/streamed download (the `http` byte primitives), wired with `magus\cache.remote`. |
| [`aws-s3`](../../spells/aws/s3-cache/spell.buzz)  | cache backend   | A **remote cache backend** for S3/MinIO/R2/B2 that signs every request with **AWS SigV4** via `crypto`'s keyed-hash primitives.                                                                    |

## See also

- [Operations](operations.md): the formal definition of an op and the Spell → Operation → Target work hierarchy.
- [Anatomy of a magus Target](targets.md): the unit you _run_, and the CLI grammar for addressing it.
- [Charms](charms.md): execution modifiers that spell ops and targets both honor.
- [Engines](engines.md): how a magusfile runs on the embedded Buzz VM and the `mgs_` spell contract.
- [Spells (README)](../../README.md#spells): built-ins list, extending a built-in, and custom-spell best practices.
- [`magus` module API](../reference/buzz/magus.md): `magus\project.register`, `magus\cache.remote`.
