Providers
A provider is a spell a magusfile selects to do one job magus knows nothing about. magus calls a few reserved function names on it (a contract) and reads what comes back. Every provider is wired the same way, at the top level of the root magusfile:
import "magus";
import "spells/endoflife-date" as eol;
magus\lifecycle.provider(eol);
| Provider | Selected with | How many | Contract |
|---|---|---|---|
| Remote cache | magus\cache.remote |
one | get_artifact, put_artifact |
| CI | magus\ci.provider |
one | enabled, annotate, last_green_run, ... |
| Secrets | magus\secret.provider |
one | resolve_secret |
| Review | magus\review.provider |
one | find_review, review_threads, ... |
| Workspace | magus\workspace.provider |
many | list_projects |
| Agent harness | magus\harness.provider |
many | harness_config, harness_skills, ... |
| Lifecycle | magus\lifecycle.provider |
one | list_lifecycles |
There is one provider where two would disagree, and many where each answers a separate part. How a failure reads follows what is at stake: a workspace provider that fails stops the load, because the project set would be wrong, while CI, review and lifecycle answers are extra information and degrade to "unknown".
The lifecycle provider
A spell's tool names the product that publishes its release cycles:
Tool{probe = ..., lifecycle = "go"}. The go, typescript, python and rust spells
declare go, nodejs, python and rust. That name carries no dates. The
lifecycle provider supplies them.
With one wired, magus describe tools asks it for every product the workspace's
spells name, in one call, and adds three columns:
lifecycle: endoflife-date, GET https://endoflife.date/api/v1/products/{go,nodejs} (as of 2026-09-24T00:07:56Z)
PROJECT/TOOL INSTALLED WINDOW DECLARED BY VERDICT CYCLE EOL SUPPORT
./go v1.26.6 >= 1.26 spell+ws inside 1.26 - unannounced
console/node v24.19.0 >= 22, < 25 workspace inside 24 2028-04-30 supported
cycleis the longest release line that prefixes the installed version.eolis that line's end-of-life date.supportissupported,eol(on or after the date),unannounced(upstream has named no date) orunknown(nothing to place the version with).
The header line names every URL the provider read, so the network use is never
silent. -o json carries the same facts as a lifecycle object: provider,
state, sources, as_of and fetched_at. The console's Toolchain tile shows
the same columns, and a script reads them with magus\describe.tool(), which returns a
ToolReport.
Nothing here fails a build. The gate is the tools window a project declares;
end of life is a column in a report and a doctor line.
When the provider is not asked
describe tools stores each answer under the cache directory. The stored answer is
what the other readers use:
| State | Meaning |
|---|---|
live |
asked on this call |
offline |
MAGUS_OFFLINE is set, so the spell never ran; the stored answer, or none |
unreached |
the spell threw or ran past 15 seconds; the stored answer, or none |
unwired |
no provider is wired; the columns read - |
cached |
read from the store without asking (doctor) |
With no stored answer, support reads unknown (offline) or
unknown (unreached). A stored answer is used only while the question is the same:
the same provider, the same products, the same built-in spells and the same
workspace spell sources.
magus doctor runs the toolchain-lifecycle check against the stored answer and
never fetches. It gives advice when an installed version, or a project's tools
floor, sits in a cycle past its end of life. With nothing stored it says so and
names magus describe tools as the fix.
A malformed answer is an error that names the spell, the product and the field:
an answer for a product nobody asked for, a missing source, an asOf that is not
RFC 3339, or a date that is not YYYY-MM-DD.
Using endoflife.date in another workspace
The provider this repository wires is spells/endoflife-date. It is not compiled
into the magus binary: it imports http, and the binary carries no release feed
(see Scope). Until remote spells publish, another workspace uses it
by copying it:
-
Copy
spells/endoflife-date/spell.buzztospells/endoflife-date/spell.buzzin your workspace. -
Wire it in the root magusfile:
import "magus"; import "spells/endoflife-date" as eol; magus\lifecycle.provider(eol); -
Run
magus describe tools.
The copy is yours to read and change. It makes one GET per product to
https://endoflife.date/api/v1/products/<product>, with no credential.
Writing your own
A lifecycle provider exports one function:
import "magus/spell";
export fun mgs_getName() > str { return "my-lifecycles"; }
export fun list_lifecycles(target: Target, cb: fun(any)) > [Lifecycle] !> any {
final io = {<str: any>};
cb(io); // io["keys"] is every product the workspace's spells name
return [
Lifecycle{
key = "go",
source = "https://example.internal/lifecycles/go.json",
asOf = "2026-09-24T07:44:41Z", // upstream's last change, never the clock
cycles = [ReleaseCycle{cycle = "1.26", released = "2026-02-10", eol = "", lts = false, latest = "1.26.6"}],
},
];
}
Answer only the keys you were asked for, and leave out a product you do not know.
Throw when the source cannot be reached: magus reads that as unreached, never as
a failure. A workspace wires one lifecycle provider, because a product name has one
meaning; wiring a second is an error.