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

Operations

This document defines an Operation, magus's smallest named unit of tool work, and fixes its place in the hierarchy between Spells, Targets, and the result a run produces. It also disambiguates the two words magus overloads, op and Target.

Status. The hierarchy, the ExecResult value type, and the spell.Op (née spell.Target) Operation type all exist today. A per-op OpResult/TargetResult consolidation was prototyped and removed as speculative (no consumer); the run path reports at the target level only, via the run.target.result event (internal/report). The Operation-layer rows below are kept as the conceptual model, marked (not built).

What an operation is

An Operation (op) is one tool-native action a Spell exposes, named after the CLI command it runs: go-build, go-vet, golangci-lint, cargo-clippy, eslint. It is the unit a Target composes: a target body calls ops, ops do the tool work.

An Operation is one of two declarative shapes, and the shape is its kind. A command op returns a Command ({bin, args, charms}) magus forks directly (no shell, one process, run to completion), the default. A service op returns a Service ({command, readiness?, stop?, distinct?, idle?}), a long-running process. Run directly (magus run dev) a service forks in the foreground and magus blocks on it; reached as a dependency (magus\needs) it is instead supervised in the background: started, readiness-gated, and shared across dependents (see services). You author either as a function that returns it (fun(Target) > Command or fun(Target) > Service) or as a bare record; the kind is inferred from the return (see An operation is a command or a service). Because both are declarative data, the argv is charm-patchable, cache-keyable, and previewable without running. The kind lives on the op, so one spell mixes command and service ops. In-VM work that magus neither forks nor blocks on (a remote cache backend) is not an op at all; it is a separate contract magus's core invokes by name.

An Operation is how a tool performs an action; a Target is what you run. You bind spells (which contribute ops) and invoke targets (which call ops). A target with no ops of its own, like ci, is pure composition: it only needs other targets.

The work hierarchy

Spell ──exposes──▶ Operation (op)              go-build, eslint, golangci-lint
  │                   │  a command → one process (no shell)
  │                   ▼ runs
  │                Process  ──yields──▶  ExecResult
  │
Target (export fun) ──composes──▶ Operations  +  ──needs──▶ other Targets
  │
  ▼ run by the dispatcher (cacheable, charm-modified)
run.target.result event   (per target; no per-op breakdown)
Layer Entity Cardinality Identity
Spell a library of operations many spells per project name + its ops
Operation one tool-native action many ops per spell spell + op name
Process one forked command 1 per op (0 for a no-op marker) argv
Target a runnable export fun one per name per project Path + Name (Target)

Charms (charms) sit orthogonal to this stack: a charm rewrites an Operation's argv (in what manner it runs), it is not a layer of its own.

Results: what each layer produces

Result Layer Shape Returned or emitted Status
ExecResult Process {stdout, stderr, code, ok} returned by proc\exec, magus\cmd/run/describe/insight/doctor, a Capture op exists
OpResult Operation ExecResult + op identity (spell, op) would be returned by the op handler (not built)
run.target.result Target {project, target, status, cache_hit, duration_ms} emitted by the dispatcher (internal/report) exists
  • ExecResult exists in both worlds. It is the Go spawn.ExecResult and the spell-op capture record a Capture: true op returns "instead of void": the same {stdout, stderr, code, ok} shape proc\exec returns.

  • The target result is emitted, not returned. A target is cacheable, and on a cache hit the body never runs: outputs are replayed without executing the export fun. A return value cannot exist on a hit, yet a cache hit is exactly what you most want to report. So the dispatcher assembles and emits a run.target.result event from cache.OnResult, which fires for both the ran and the cached case. It reports at the target level; a per-op [OpResult] breakdown was prototyped and removed as speculative (no consumer, and it misattributed ops across the cross-project boundary).

Disambiguating "op" and "target"

magus overloads two words. Formalizing Operation fixes the first and exposes a latent misnaming in the second.

Three unrelated "op"s. Only the first is the Operation defined here:

Term Type Shape Domain
Operation Operation spell + op name + impl the spell op defined in this doc
PatchOp PatchOp {op, path, value, from} RFC 6902 charm patch (charms)
RemoteOp RemoteOp {op, outcome, duration, bytes} remote-cache backend call (telemetry)

PatchOp and RemoteOp keep their names; they are genuinely different operations. magus never names anything just op in the spell API, for exactly this reason.

Two "Target"s. These are distinct and must not be conflated:

  • types.Target: the addressable work-unit Path + Name, plus charms and changed files. This is the Target (targets).
  • spell.Op (formerly spell.Target): "a single dispatchable entry point of a spell," i.e. an Operation. It was named Target, colliding with the work-unit above; renamed to Op to formalize this vocabulary.

Naming decision (done): spell.Target → spell.Op

spell.Target was an Operation misnamed as a Target. It is now spell.Op (Spec.Ops, OpNames, and the resolve/fork/bind paths followed), wire formats preserved. The docs warn against substituting "Operation" for a work-unit Target (targets); that warning is about types.Target and never protected spell.Target, which was the actual offender.

Relationship to the value types

The serializable Buzz value types model the nouns around this hierarchy:

Value type Models Layer it touches
Target a resolved work-unit (Path + Name + charms + files) plus its per-target policy (skip_cache, exclusive, slots, ...) Target
ExecResult the {stdout, stderr, code, ok} outcome of one process Process

A Target is run as a set of Operations; each Operation yields an ExecResult. A magus\needs edge points straight at another Target's function; there is no intermediate query value.

Glossary

Term Meaning
Spell A library of tool-native Operations for one toolchain (spells).
Operation (op) One tool-native action a spell exposes, named after its CLI command. The unit a target composes.
Target A runnable export fun; the work-unit Path + Name you invoke (targets).
Charm A named modifier of an Operation's argv (charms).
ExecResult The result of one process: {stdout, stderr, code, ok}.
run.target.result The dispatcher-emitted report event for one target run: project, target, status, cache hit, duration.

See also

  • spells: anatomy of a spell, the two op shapes, naming operations.
  • targets: the work-unit Target, Path + Name, the seven lifecycle names.
  • charms: how a charm patches an Operation's argv.
operationsopshierarchyspellstargetsciwork-modelexecution
Last updated (29d8b3de)
Earlier changes on this page (7)

Full history ↗ · Blame source ↗

Glossary

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. Project in full.

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. Target in full.

Op

A single tool-native command a target composes (long form: operation); the middle of the work hierarchy (Spell to Op to Target). Op in full.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. Spell in full.

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. Charm in full.

Buzz

The language magusfiles are written in (the .buzz engine). Buzz in full.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. Cache in full.

Service

A long-running or shared process magus manages across runs, distinct from a one-shot target. Service in full.

CI

An ordinary target you compose yourself with ctx.needs; magus does not hardcode its stages. It is the anchor magus affected ci keys off, and ci strips the rw charm. CI in full.

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). Slot in full.

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. Remote cache in full.

Insight

The reports magus derives over the graph and history (hotspots, affinity, ownership, trend, volatility, unreferenced). Insight in full.

Tool

An action a model can call through its host: a name, a description, a typed input and a result. magus serves its tools over MCP, each a read of the workspace model or a run of the CLI it already has. Tool in full.

Run

One target executing under one magus invocation, such as magus run test web or magus affected ci. A run keeps its captured output behind an output reference. Every magus run is a run whether or not any job asked for it; see Job for how the two relate. Run in full.

Conventions

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