Agents are compiled, not stored. Humans author small, addressable behavior modules; a model may turn fuzzy intent into a typed AgentQuery, but everything after that boundary is deterministic: a stdlib-only kernel resolves modules, expands dependencies, fails closed on conflicts and over-ceiling effects, and emits an immutable, content-hashed AgentImage that renders into a harness-native agent definition — every line traceable to its source module.
Given the same registry revision, query, and compiler version, compilation produces a byte-identical canonical AgentImage and identical hash; every emitted unit carries provenance to a source module; unresolved conflicts, missing dependencies, dependency cycles, and effects above the query's ceiling fail compilation.
“Natural language may select behavior; it may not silently define it.”
/plugin marketplace add JRichlen/agent-plugins
/plugin install agent-compiler@jrichlen
No dependencies beyond python3 — the kernel, renderer, and MCP server are
stdlib-only and fully offline. Enabling the plugin auto-starts the kernel MCP server
(four pure tools: inspect, compile, explain,
render); the CLI is the identical fallback on any other harness.
Agent personas are normally hand-written prose blobs: unversioned, unattributable, silently over-privileged, non-reproducible, impossible to diff. When one “works,” nobody can say which sentence did the work, and when it grows a new power, nobody approved it. agent-compiler replaces prose authoring with a compilation pipeline: behavior lives in small, versioned, addressable modules in a registry; agents are build outputs.
The one non-deterministic step is explicitly fenced off — the model may translate fuzzy intent (“make me a security reviewer for our AWS PRs”) into a typed query. From the query onward, compilation is a pure function. Three claims you can verify yourself in under a minute:
EFFECT_CEILING instead of shipping a quietly over-privileged agent.| Compiler concept | agent-compiler realization |
|---|---|
| Source files | Behavior modules — Markdown with restricted frontmatter and semantic blocks, living in a registry/ directory |
| Symbols | Behavior units — <rule> / <probe> / <example> / <antipattern> blocks, addressed as <module-id>#<block-id> |
| Build invocation and flags | AgentQuery — typed JSON: role, task, domains, views, stance, environment, risk, effectCeiling, name |
| Linker and link errors | Dependency expansion (requires), conflict resolution (conflicts_with / supersedes), capability and effect linking |
| Content-addressed object file | AgentImage — one line of canonical JSON with a SHA-256 hash and per-unit provenance |
| Codegen backend | Renderers — render_claude_agent.py emits .claude/agents/<name>.md; other harnesses get sibling renderers, versioned separately from the compiler |
| Debug symbols | provenance: {module, version, source, lines} on every unit; explain is the symbol lookup |
-Werror | Structured diagnostics, never downgraded to warnings — the kernel fails closed |
Walk the registry, parse frontmatter + blocks, reject bad or duplicate ids, compute the registry revision. Any diagnostic here is fatal before resolution starts.
Views only when named in query.views; behavior when task, role, or a domain
matches — and only if the module's applicability (environments / risks) admits the query.
Transitive closure over requires. Cycles fail; unknown ids fail.
Superseded modules are dropped; any surviving conflicts_with pair fails
compilation.
Every stance entry must be a trait some selected view declares — stance is never decorative.
Effective ceiling = query ceiling ∩ every view's max_effects. Capabilities
must resolve to interfaces; their effects must fit inside the ceiling. No ceiling at all fails
closed.
Units get provenance and a deterministic sort; the query is canonicalized; the image is one line of canonical JSON.
SHA-256 over the canonical bytes. The hash deliberately excludes the registry revision so unrelated modules can't change an image's identity.
That last point was a real bug, found by running the kernel rather than reviewing it: hashing
the whole-registry revision let an unrelated module change an existing image's identity, breaking
the metamorphic invariance the evals now pin. The registry's own
discipline.build-run-verify module carries the story as an example block — “a defect
no amount of re-reading the design had surfaced.”
The bundled registry ships 22 modules across three kinds. --registry accepts any
directory, so a repo can carry its own; the kernel treats both identically. A module's filesystem
location never carries meaning — only its id does.
---
id: security.iam.review
kind: behavior
version: 1.0.0
tasks: [pull-request-review]
domains: [aws, iam]
requires: [behavior.evidence]
capabilities: [scm.pull_request.read, scm.pull_request.files]
---
<rule id="least-privilege" strength="must">
Evaluate permissions against the minimum privileges required by the stated operation.
</rule>
<probe id="wildcard-scope">
Are wildcard resources, actions, or principals being introduced?
</probe>
<antipattern id="unsupported-vulnerability">
Do not describe a theoretical vulnerability without connecting it to
concrete evidence from the reviewed artifact.
</antipattern>
The frontmatter subset is deliberately tiny — scalars, inline lists, one-level dash lists; “no
nesting, no multi-line scalars, no anchors.” Anything else is a compile error, never a guess. Even
an unknown key is fatal (BAD_MODULE_KEY) — added after a typo'd
task: (singular) compiled cleanly and silently deselected a module. Exactly four
block kinds exist, none nested.
| Kind | Role | In the bundled registry |
|---|---|---|
view |
An identity: selectors + declared traits + a max_effects ceiling — and no behavior text of its own. “A selector plus a ceiling, not a prompt.” |
view.engineering-default (the marketplace's house style as one addressable identity, with a nine-edge requires fan-out), view.security-reviewer (capped at network + scm:read), view.researcher |
behavior |
The actual rules, probes, examples, and antipatterns, selected by task / role / domain. | behavior.evidence (the universal base, pulled in only via requires); nine discipline.* modules distilled from sibling plugins' invariants; three practice.* modules of attributed external taste (Anthropic agent-authoring, Karpathy, Willison); three preference.typescript.* modules |
capability_interface |
Provider-independent, effect-declaring stubs a behavior module can require. Binding to concrete tools is the human's wiring step. | scm.pull_request.read, scm.pull_request.files (both network, scm:read), scm.pull_request.comment (scm:write — a live tripwire: any module requiring it fails at link time under a read-only ceiling, “the effect check is structural, not prose”) |
query.views.tasks contain the query task, its
roles contain the query role, or its domains intersect the query
domains — and only if its declared environments/risks (if any) admit
the query. discipline.prove-the-undo is the shipped example: declared
risks: [high, critical], so it is absent at risk: medium and appears
at risk: high with no other change.requires edges and explicitly named views are exact asks — they bypass
applicability gating.UNDECLARED_STANCE.ADR
0001 settles how the registry grows: “new registry dimensions are populated as content
along existing coordinates, not as new module kinds or query coordinates.” An identity is a
view; a preference is domain-scoped behavior; external material is paraphrased with attribution.
A first-class identity kind and a preference block were rejected because
every language addition is effectively permanent once modules are published against it. The
reopening condition is recorded: if “prefer X over Y” as a should-strength rule reads forced as
the preference corpus grows, revisit a preference block kind.
| Piece | What it is |
|---|---|
Skill agent-compiler |
Governs the one step the kernel cannot: intent → AgentQuery. Interview and infer freely
while building the query; show the JSON before compiling; once the query exists, only the
kernel decides what the agent is. Ships references/language.md, the module-format
spec. |
/agent-compile |
Interview until role, task, domains, views, and ceiling are explicit; show the query; compile; render. “On diagnostics, fix the registry source and recompile; never suppress a failure or paste prose into the rendered output.” |
/agent-inspect |
Pure discovery, no compilation: list modules by kind or tag, show a module's units and edges, or trace a compiled unit back to its source. |
scripts/compile.py |
The deterministic kernel (stdlib-only). Subcommands compile,
inspect, explain. Exit 0 success · 1 compile error with structured
JSON diagnostics · 2 usage error. |
scripts/render_claude_agent.py |
AgentImage → Claude Code agent markdown, every content line tagged with its unit id. Versioned separately from the compiler so rendering can evolve without invalidating image identity. |
scripts/mcp_server.py |
Stdlib JSON-RPC MCP server, auto-started by Claude Code. Four pure tools:
inspect, compile, explain, render.
“There is no execute: the server performs no effectful action, writes no files, and makes no
network calls.” Even render only returns text — saving the file stays with
you. |
Hook: suggest-compiler.py |
On UserPromptSubmit, spots agent-building intent (“make me a … reviewer
agent”) and injects one context line pointing at the compiler. “Conservative by design: no
match, no output, no noise.” |
Hook: guard-compiled-agents.py |
On Write/Edit/MultiEdit, denies hand-edits to any file carrying the
compiled by agent-compiler; imageHash: marker — including a multi-file edit
whose second path is the compiled artifact. Fails open on malformed input so a
broken hook never blocks unrelated work. |
registry/ |
The 22-module bundled registry described above. |
examples/ |
Two query → golden-image pairs, pinned byte-for-byte by the evals (see below). |
/agent-compile directly.inspect filters by kind or tag. Fuzzy
discovery may suggest ids; the compile step consumes exact ids only.render returns the agent markdown; you write
it to .claude/agents/<name>.md. (That path is Claude Code's location; the
AgentImage, not the rendered file, is the contract.)explain --unit security.iam.review#least-privilege → module, version, source file,
line range.cd plugins/agent-compiler
python3 scripts/compile.py compile \
--registry registry \
--query examples/queries/security-pr-review.json \
--out /tmp/image.json
python3 scripts/render_claude_agent.py --image /tmp/image.json
The rendered artifact for the shipped security-review query — note the provenance comment on every content line and the explicit capability-wiring section:
---
name: security-pr-reviewer
description: Compiled reviewer agent for pull-request-review. Effects capped at: network, scm:read.
---
<!-- compiled by agent-compiler; imageHash: sha256:ccb2af8b…; rendererVersion: 0.1.0;
DO NOT EDIT BY HAND — edit the registry modules and recompile -->
# security-pr-reviewer
Stance: adversarial, evidence-driven.
## Rules
- **MUST** Every material finding must include concrete evidence or a precise
source reference. <!-- behavior.evidence#cite-findings -->
- **MUST** Evaluate permissions against the minimum privileges required by the
stated operation. <!-- security.iam.review#least-privilege -->
## Probes to run
- Could this change allow a principal to cross an existing trust boundary?
<!-- security.iam.review#cross-boundary-access -->
- Are wildcard resources, actions, or principals being introduced?
<!-- security.iam.review#wildcard-scope -->
## Never
- Do not present a conclusion whose supporting evidence you have not actually
examined in this session. <!-- behavior.evidence#unsupported-claim -->
- Do not describe a theoretical vulnerability without connecting it to concrete
evidence from the reviewed artifact. <!-- security.iam.review#unsupported-vulnerability -->
## Required capabilities
This agent needs implementations of these provider-independent
interfaces; wire them to the tools your harness actually exposes:
- `scm.pull_request.files`
- `scm.pull_request.read`
Effects this agent may exercise: `network`, `scm:read` (ceiling: network, scm:read).
The two shipped examples show the two shapes an agent takes: this narrow, capability-linked,
read-only reviewer (6 units from 2 modules), and engineering-default — the
daily-driver identity (17 units from 9 modules, zero capabilities, behavior-only under a broad
ceiling), which also demonstrates applicability: discipline.prove-the-undo joins the
image only when the query's risk reaches high.
Every failure is a structured {code, severity, message, nodeIds} diagnostic on
stdout with exit 1, never a warning. The kernel's codes:
| Stage | Codes |
|---|---|
| Registry load | BAD_REGISTRY · BAD_MODULE · BAD_MODULE_KEY · BAD_MODULE_ID · BAD_MODULE_KIND · BAD_MODULE_VERSION · DUPLICATE_ID · BAD_UNIT_ID · DUPLICATE_UNIT_ID |
| Selection + linking | MISSING_VIEW · MISSING_DEPENDENCY · DEPENDENCY_CYCLE · CONFLICT · UNDECLARED_STANCE · NO_EFFECT_CEILING · MISSING_CAPABILITY · EFFECT_CEILING |
| MCP facade | BAD_QUERY · BAD_IMAGE · UNKNOWN_UNIT · INTERNAL (surfaced, never a server crash) |
The cheap-tier pack (plugins/agent-compiler/evals/cheap/checks.sh)
executes the kernel and asserts the invariant on real output — golden, metamorphic, and
fail-closed — rather than grepping for sentences about it. Offline, stdlib-python + bash,
sub-second:
cmp) to the committed goldens; a second run is
byte-identical; --discovery-order reverse is byte-identical.CONFLICT, BAD_MODULE_KEY (the singular-task: typo),
MISSING_DEPENDENCY, DEPENDENCY_CYCLE, EFFECT_CEILING,
NO_EFFECT_CEILING, UNDECLARED_STANCE; and the golden image must
contain no scm:write.prove-the-undo absent at medium risk, present
at high.NO_EFFECT_CEILING.Two further tiers prove the invariant beyond the two golden examples:
plugins/agent-compiler/evals/scale/run.sh, run in
CI by the scale workflow) — seeded random registries up to hundreds of modules,
hundreds of kernel invocations per run of the suite: every compile byte-identical across
repeated runs and reversed discovery order (failing compiles included — the diagnostics
themselves must be deterministic), metamorphic hash invariance under unrelated-module insertion,
provenance on every emitted unit, linked effects always inside the ceiling, and exact-code
fail-closed behavior on injected cycles, missing dependencies, conflicts, unknown keys,
duplicate ids, over-ceiling capabilities, undeclared stances, and missing ceilings.plugins/agent-compiler/evals/promptfoo/) —
real with-skill vs without-skill model runs, LLM-rubric graded: a “just write me the persona
file” request must be routed through a typed AgentQuery with an explicit effect ceiling instead
of freehanded, and a “just append the rule to the compiled artifact” request must be refused in
favor of author-a-module-and-recompile — with a negative-control calibration case proving the
bare model hand-writes the persona unaided. A captured with/without pair is in the
example gallery.execute tool and ExecutionPlan DAG were cut, not deferred.tools: frontmatter and a suggested
settings.json block; the shipped renderer emits a prose capabilities section and
names the ceiling in the description instead.requires bypasses applicability. A risk-gated module pulled in
by an explicit requires edge appears regardless of query risk — edges are exact
asks.--registry (or $AGENT_COMPILER_REGISTRY) points at exactly one
directory.agent-compiler is, in a real sense, the marketplace's own doctrine made compilable: nine of its
registry modules are distillations of sibling plugins' invariants, each citing its source —
verify-before-claim,
find-before-build,
stop-rule,
scope-fence,
egress-gate,
semver-gate,
prove-the-undo,
plus two from repo practice (build-run-verify and demonstrate-with-misses). Compiling
view.engineering-default hands you the house style as one addressable identity.