magus v0.3.0 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 spellruntime.Op (née spellruntime.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 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)
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 os\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)
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 run.ExecResult and the spell-op capture record a Capture: true op returns "instead of void": the same {stdout, stderr, code, ok} shape os\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 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).
  • spellruntime.Op (formerly spellruntime.Target): "a single dispatchable surface 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): spellruntime.Targetspellruntime.Op

spellruntime.Target was an Operation misnamed as a Target. It is now spellruntime.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 spellruntime.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}.
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 (38e9171e)
Earlier changes on this page (3)

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. 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.

Operation

A single tool-native command a target composes; the middle of the work hierarchy (Spell to Operation to Target). See operations.

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.

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.

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.

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). See daemon.

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.

Insight

The reports magus derives over the graph and history (hotspots, affinity, ownership, trend). See insight.

Conventions

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