@agentspec/claude-plugin
v0.2.0
Published
AgentSpec plugin for Claude Code — live spec↔runtime bridge, policy enforcement, and status line, with no fork of Claude Code
Downloads
145
Maintainers
Readme
@agentspec/claude-plugin
A Claude Code plugin that makes a
Claude Code session live-observed and policy-governed by AgentSpec — with no
fork of Claude Code. Your agent.yaml becomes the source of truth; the plugin
rides Claude Code's documented extension surface (hooks + MCP + statusline).
What it does
| Capability | Mechanism |
|---|---|
| Live spec↔runtime gap | A per-user bridge daemon serves GET /agentspec/health. The AgentSpec sidecar control-plane polls it and flips /gap · /explore · /health/ready to live mode (source: agent-sdk) — the exact endpoint the sidecar expects from an SDK-integrated agent. |
| Policy enforcement (opt-in) | With AGENTSPEC_ENFORCE set, a PreToolUse hook denies Task spawns whose subagent_type isn't in spec.subagents, denies tools not in spec.tools, and asks-for-approval on destructiveHint tools when spec.humanInTheLoop requires it. Off by default — see Enforcement. |
| Live tool/health status | PreToolUse/PostToolUse/SubagentStop hooks feed the bridge, which synthesizes the tool health category from the real event stream. |
| Status line | agentspec-statusline renders ● healthy · gap 3 · tools 11/12 from cached daemon data. |
| Self-query | Bundles the AgentSpec MCP server so Claude can call agentspec_gap/agentspec_audit in-session. |
How it works
claude (closed binary)
│ hooks (SessionStart / PreToolUse / PostToolUse / SubagentStop / SessionEnd)
▼
agentspec-bridge daemon (127.0.0.1, per-user, lockfile-guarded)
├─ :8000 GET /agentspec/health ← the sidecar polls this (the "hinge")
│ POST /events ← hook observation sink
│ POST /decide ← enforcement decision (from agent.yaml)
└─ :4001 control-plane → /gap /explore /health/ready (live)The bridge sets UPSTREAM_URL=http://127.0.0.1:8000 before importing the
sidecar control-plane (@agentspec/sidecar/embed), so the control-plane's
probeAgent() targets the bridge and serves live data. Claude Code is never
introspected — the bridge reconstructs runtime state from config + the hook
event stream.
Quick start
# 1. Derive agent.yaml from your .claude/ config (deterministic, no LLM):
agentspec scan --dir . --profile claude-code
# 2. Install + enable the plugin.
# Published: npm i -g @agentspec/claude-plugin && /plugin marketplace add agents-oss/agentspec && /plugin install agentspec
# Local/dev: pnpm -F @agentspec/claude-plugin build && (cd packages/claude-plugin && npm link)
# /plugin marketplace add /abs/path/to/agentspec && /plugin install agentspec
# (or commit .claude/settings.json → { "enabledPlugins": { "agentspec": true }, "statusLine": { "type": "command", "command": "agentspec-statusline" } })
# 3. Start a session. SessionStart backgrounds the bridge; the sidecar shows live state:
curl 127.0.0.1:4001/gap # → { "source": "agent-sdk", ... }Distribution status: the repo ships
.claude-plugin/marketplace.json, so the local/dev route works today.npm i -g @agentspec/claude-pluginrequires the first npm publish (tracked intask.md).
The daemon is a per-user background process (like a language server): the
SessionStart hook does an idempotent ensure-running via a lockfile under
.claude/agentspec/, it binds loopback only, and it is left running across
sessions.
Enforcement (opt-in)
The plugin is observe-only by default — it records events, serves health, and
drives the status line, but never blocks a tool call. Turn gates on with
AGENTSPEC_ENFORCE (a comma list) in your environment or .claude/settings.json
env:
| AGENTSPEC_ENFORCE | Effect |
|---|---|
| (unset) | Observe only. Every tool call is allowed. |
| subagents | Deny Task spawns not declared in spec.subagents. |
| tools | Deny tools not in spec.tools; ask-for-approval on destructiveHint tools when spec.humanInTheLoop requires it. |
| tools,subagents | Both gates. |
Why opt-in: scan --profile claude-code builds spec.tools from
.claude/settings.json permissions.allow, which is Claude Code's auto-approve
list — not an exhaustive capability list. Enforcing it by default would deny
tools the session would normally permit. Likewise, unspecified spec.subagents
(the scan default when there is no .claude/agents/) means unrestricted; only an
explicit spec.subagents: [] means "deny all delegation."
Compose into an existing status line
Claude Code allows only one statusLine command, so to keep your existing bar
(e.g. ccstatusline, claude-hud) and show AgentSpec, run agentspec-statusline
with --segment: it emits only AgentSpec ● healthy · gap N · tools x/y (dropping
model/cost, which the host already shows) and prints nothing when the daemon is
down — a natural on/off. Wrap it with your existing status line:
#!/usr/bin/env bash
input="$(cat)"
host="$(printf '%s' "$input" | your-existing-statusline 2>/dev/null || true)"
seg="$(printf '%s' "$input" | agentspec-statusline --segment 2>/dev/null || true)"
[ -n "$host" ] && printf '%s' "$host"
[ -n "$host" ] && [ -n "$seg" ] && printf '\n'
[ -n "$seg" ] && printf '%s' "$seg"Point statusLine at that wrapper. Either half can be absent and the bar still works.
Fidelity note
Infra checks (env/model/mcp/memory/service) are real reachability
probes from the SDK. The tool category is reconstructed from the hook stream:
a tool that ran is pass/fail by its outcome; a declared tool not yet invoked
this session is skip (no behavioral evidence yet — the same blind spot the
in-process Reporter has for a registered-but-uncalled tool).
Layout
.claude-plugin/plugin.json plugin manifest (references hooks + mcp)
hooks/hooks.json + *.mjs lifecycle hooks (dependency-free, fail-open)
.mcp.json registers @agentspec/mcp for the session
src/policy.ts enforcement decision (pure, tested)
src/event-log.ts hook event → tool observations (pure, tested)
src/health-overlay.ts synthesize the `tool` category (pure, tested)
src/bridge.ts the daemon (bin: agentspec-bridge)
src/statusline.ts the status line (bin: agentspec-statusline)