The Academy pack (packs:academy)
packs:academy
OpsChain Academy teaches OpsChain by having you do real work in an OpsChain server of your own, and attests that work from what the server records. The OpsChain Academy agent serves the learning path — tracks, modules and exercises, each with a scenario seeded for you alone — reads the server's state and audit events to decide whether an exercise was done, and seals the result as a signed, metadata-only evidence bundle for the Academy Server hosted by LimePoint, which keeps the ledger and issues the badge. The agent never writes to OpsChain; you do the work through the CLI, the API or the GUI exactly as you would in production, and no quiz answer ever stands in for the record.
packs:academy is the OpsChain packaging of that agent, and the gate on
any academy content: it is in the Basic tier (the embedded defaults
carry it, and so does the baseline union on the
entitlements page, so every tier has it) and unlocks
the Foundations track only. Every other track is owned by the pack
that ships its surface and opens with that pack's entitlement — what the
licence scopes is the curriculum the agent serves, since an exercise about
a surface your estate is not entitled to cannot be performed on it.
What the licence unlocks
The agent reads what the targeted server's licence carries (the
licence_status features /api/info reports) and resolves the path
against it:
- No
packs:academy— nothing is served: no path, no exercise, and every page names the entitlement that unlocks the academy. packs:academy(the Basic tier and up) — the Foundations track, the platform's own.- A pack's entitlement — the track that pack owns. The first
pack-owned tracks are
change-governance(packs:change-governance),security-governance(packs:security-governance),agent-governance(packs:agent-governance) andagents(packs:agents); each is the seed of its track, and the pack's own content lands there in lockstep with the release that changes the surface.
The learner always sees the whole path: a track whose pack the
licence lacks is shown locked with the pack that unlocks it ("Azure —
needs packs:azure"), its exercises cannot start and hand out no hints,
and the ladder skips it so the tracks after it open as if it were absent.
Nothing is hidden, and nothing can be started that the licence does not
carry. The gate is the agent's own, read from this server — the launcher
adds none, and a licence applied or removed is read back on the agent's
next page.
How the agent ships
The agent is a separate binary, built on the LimePoint Academy Agent
SDK in its own repository exactly as every other product's Academy agent
is, so that the learning experience and the evidence model are the same
across LimePoint products. The OpsChain binary does not embed it. Each
agent release is published to the Academy Server's release catalogue
— the one place every LimePoint product's agent is served from, to the
organisations entitled to it — and opschain academy install resolves
the agent through that catalogue, verifies the signed digest and
extracts the binary under the OpsChain home directory (academy/bin/),
recording what was installed beside it. This keeps a 21 MB agent out of a
product binary that most installations never run, lets the agent release
on its own cadence, and keeps the installed bytes verifiable against a
key the learner already trusts.
How the agent is installed
Two lanes. The default is the Academy Server; --from is the offline
lane for air-gapped estates and development builds.
| Lane | Command | What is trusted |
|---|---|---|
| the Academy Server lane (default) | opschain academy install --grant-file <grant> (or OPSCHAIN_ACADEMY_RELEASE_TOKEN), [--version x.y.z] [--channel stable|preview] | the server's signed catalogue: an Ed25519 signature by the Academy signing key over the document naming each version's archives and SHA-256 digests; the download is verified against that digest, and nothing is written when it differs. The credential — the organisation's registration grant, an installed agent's deployment token or a learner token — decides the entitlement; every download is identified and recorded on the server |
| the file lane — a release archive | opschain academy install --from <academy-opschain-agent_<version>_<os>_<arch>.tar.gz> (.zip on Windows) | the digest committed in this build: the archive is verified against the pin for this platform |
| the file lane — a built binary | opschain academy install --from <path to academy-opschain-agent> | you — a development build or a copy carried onto an air-gapped machine; its digest and origin are recorded in installed.json as version local |
The build names the server and the channel it follows (release.json:
server, channel; opschain academy show reports them; --academy-server
/ ACADEMY_AGENT_SERVER override the server for a development stack).
The catalogue's newest version on the channel is installed unless an
exact --version is asked for. A version its publisher yanked —
withdrawn from new installs after a regression, never deleted — is
withheld from the channel's newest and refused by name to an unpinned
install, but still served to an estate pinning it by exact version
(rollback, reproducibility). The signing key is taken from the server
(GET /v1/signing-keys) and its id recorded in installed.json; a build
that pins the hosted key (signing_key in release.json) refuses a
server whose current key differs. The hosted server's key is pinned once
the hosted lane is provisioned.
The pin, the floor and the file lane
The build still carries a pinned release — the version and the SHA-256
digest of each platform's archive — with two meanings: it is the digest
--from <archive> verifies against, and the floor the server lane
accepts (a catalogue offering only older versions is refused by name; a
newer stable is never stopped). A build that pins no release takes the
channel's newest through the server, and only a built binary through
--from. The pin moves with a product release
(scripts/academy-release-pin.sh <tag>, LimePoint's release
engineering); an agent version is never re-published or moved — a broken
release is followed by the next patch tag, and the broken one is yanked.
Where the agent runs
The same binary runs in two lanes, and opschain academy serve starts
either:
- Co-located — one agent beside a shared OpsChain server serves a
whole team: learners open its pages on the lab network, sign in with
their company address, and the agent attests on their behalf with its
own deployment key, registered once with the Academy Server through a
registration grant. Each learner's exercises are seeded inside a project
of their own (
/projects/academy_<seed>), so learners on one server never collide. - Learner-local (
--single-learner) — the agent on your own machine beside your own server; you enrol once from its page and your evidence is signed with a key generated there.
The agent needs two things from OpsChain, and the launcher supplies both
from the ordinary CLI context: the server it reads is the targeted
server (--server, OPSCHAIN_SERVER or the active context — never local
mode, there is nothing to read), and the credential it reads with is
your opschain login session for that server, or OPSCHAIN_ACADEMY_TOKEN
when set. For a co-located agent that runs unattended, mint an API token
for a read-only user (opschain tokens create) and set
OPSCHAIN_ACADEMY_TOKEN: the agent only ever reads, and a credential that
could write is a liability it never needs. The agent's data directory
(its store, deployment key and pack cache) defaults to <opschain home>/academy. Every agent flag maps onto an ACADEMY_AGENT_* environment
variable; opschain docs cli/academy-serve lists them.
What the exercises need from the server
Exercises are checked against the server's identity read (/api/me), the
node-path reads, the properties and change reads, and the audit events
feed. A server on the embedded SQLite store has no users and no event
writer — identity and audit-event checks cannot be evidenced on it — so
the Foundations pack requires a PostgreSQL-backed server, and the agent
says so honestly in its verdicts rather than passing a check it cannot
observe. The zero-configuration training posture is a PostgreSQL of your
own beside the server (opschain docs storage).
Evidence and privacy
An evidence bundle carries check verdicts and the identifiers that back them — event ids, types, timestamps, node paths when they carry your seed, principal ids — hashed and signed, chained to the bundle before it. It never carries event payloads, usernames, e-mail addresses or hostnames, and the agent's probe never projects the events feed's username. The Academy Server verifies the signature against the enrolled key, the seed, the chain and the pacing before it counts anything.
The tutor's three lanes
The agent includes an optional tutor: a model that explains, diagnoses a failed check from the examiner's own verdicts and answers questions. The tutor never grades — a badge is earned only against the deterministic checks — and its model cost is the customer's, never LimePoint's and never the estate's by default. There are three lanes, and the agent starts with the first:
| Lane | Who pays | What it is |
|---|---|---|
| no model (the default) | nobody | the deterministic examiner plus the pack's own hints; slower, never blocked — the agent starts with no model configured and no key anywhere |
| bring your own provider | the estate | opschain academy serve --tutor <model-provider code>: the estate's model-provider catalog supplies the kind and endpoint (Anthropic, OpenAI, OpenAI-compatible — a local model server is an OpenAI-compatible endpoint); the key stays in the agent's environment (ACADEMY_AGENT_TUTOR_KEY, or the variable --tutor-key-env names), held once per agent, never per learner, never read from the catalog |
| learner-side MCP | the learner, on their own subscription | opschain academy mcp: the agent exposes a read-only Model Context Protocol server over its standard streams for the learner-local lane's learner — the current exercise and its seeded scenario, the hints, the examiner's last verdicts with their reasons, the path with every locked track named — so the learner's own Claude Code, Claude Desktop or any MCP client tutors them; the conversation never touches the agent |
Every bring-your-own-provider turn is redacted of addresses and
bearer-looking tokens before it reaches the provider or a transcript, and
transcripts are kept per learner in the agent's store. The catalog lane
refuses an entry that is disabled or whose data handling the estate has
not approved before the agent starts. The platform's brokered model
gateway (the key never leaving the server) is an agent-plane lane the
Academy agent does not ride: it is not a registered platform agent. The
examiner is untouched by all three lanes: whatever is tutoring, only the
checks decide. opschain docs cli/academy-mcp shows how a client
connects.
Failure modes
| Situation | What happens |
|---|---|
install without an Academy credential (no --grant-file / ACADEMY_AGENT_GRANT_FILE, no OPSCHAIN_ACADEMY_RELEASE_TOKEN) | refused by name, naming the three credential classes and the --from lane for an air-gapped machine |
install by an organisation not entitled to the agent | refused by name (not_entitled); the server records the refusal; nothing is written |
install when the channel's newest was yanked, or --version names a yanked version without pinning it | the channel reports no installable version / the download is refused release_yanked with the publisher's reason; --version <that version> still installs it |
| the catalogue's signature does not verify, or the server's key is not the one this build pins | refused by name; nothing is written |
| the download's digest differs from the signed catalogue's | refused by name; nothing is written |
| the catalogue's version is below the floor this build pins | refused by name, naming both versions |
| the Academy Server is unreachable | refused, naming the server and pointing at --from <archive-or-binary> |
install --from with --version, --channel or --academy-server | refused by name: --from is a file lane |
install --from <archive> with no release pinned in this build | refused by name: the archive cannot be verified, so the built binary is the lane |
| the archive's digest differs from the pin | refused by name; nothing is written |
| the archive holds no agent binary for this platform | refused, naming the binary it looked for |
serve without an installed agent | refused, naming opschain academy install --grant-file (or --from) |
serve in local mode | refused: the agent reads a running server |
serve without a login for the targeted server and no OPSCHAIN_ACADEMY_TOKEN | refused, naming both ways to supply one |
serve, register, sync or rotate-key without --academy-server and no ACADEMY_AGENT_SERVER | refused by name in the launcher's spelling (the agent's own --server is the OpsChain server here); status and mcp work over the data directory and are not asked for one |
the targeted server's licence lacks packs:academy | the agent serves no path; every page names the entitlement that unlocks the academy |
| a track's pack is not on the licence | the track is shown locked, naming the pack; starting it or asking for its hints is refused by name; the tracks after it open regardless |
mcp over a shared (co-located) agent, or before a learner enrolled | refused by name: the lane serves the learner-local lane's one learner |
| the launcher receives SIGINT, SIGTERM or SIGHUP | relayed to the agent (a hangup as a termination), which shuts down on its own terms and flushes its queue; the launcher waits, kills the agent after 30 s if it is still running, and never exits ahead of it |
| the agent exits | its exit status is this command's (128 plus the signal number when a signal ended it) |
See also
opschain docs cli/academy— the command family.opschain docs cli/academy-mcp— the learner-side tutor lane and how a client connects.opschain docs entitlements— the Basic tier and the baseline union that grantpacks:academy.opschain docs model-providers— the catalogacademy serve --tutorreads.