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
ExecResultvalue type, and thespellruntime.Op(néespellruntime.Target) Operation type all exist today. A per-opOpResult/TargetResultconsolidation was prototyped and removed as speculative (no consumer); the run path reports at the target level only, via thetarget.resultevent (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 |
-
ExecResultexists in both worlds. It is the Gorun.ExecResultand the spell-op capture record aCapture: trueop returns "instead of void": the same{stdout, stderr, code, ok}shapeos\execreturns. -
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 atarget.resultevent fromcache.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-unitPath + Name, plus charms and changed files. This is the Target (targets).spellruntime.Op(formerlyspellruntime.Target): "a single dispatchable surface of a spell," i.e. an Operation. It was namedTarget, colliding with the work-unit above; renamed toOpto formalize this vocabulary.
Naming decision (done): spellruntime.Target → spellruntime.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. |