From blank repo to bonded agent, inside OpenCode.
A native OpenCode plugin that scaffolds, wraps, and ships bonded agents without leaving the TUI. Open a scratch directory, run /chio_init, and walk out four minutes later with a signed MCP server, a policy under version control, and receipts on every keystroke.
-
Install the
chioCLI (the runtime behind chio):# from the monorepo cargo install --path crates/chio-cli # or once published brew install chio-protocol/tap/chio
-
Register the plugin in your
opencode.json:{ "plugin": ["@chio/opencode-plugin"] } -
Optional env for daemon mode:
export CHIO_MODE=daemon # or `cli` (default falls back to cli) export CHIO_MCP_EDGE_URL=http://127.0.0.1:8931 export CHIO_TRUST_URL=http://127.0.0.1:8940 export CHIO_TRUST_TOKEN=<your-service-token>
OpenCode's plugin API has no native slash-command surface; it does have a first-class custom-tool API. We expose the /chio-* commands as custom tools:
| Tool | What it does |
|---|---|
chio_init |
Scaffold policy.yaml, agent.md, guards/, receipts.db; bond |
chio_wrap |
chio mcp serve-http -- <cmd>; register the URL in opencode.json |
chio_guard_add |
chio guard new <name>; falls back to a Rust crate scaffold |
chio_policy_lint |
ChioBridge.lintPolicy - unknown keys, shadowing, dead egress |
chio_replay |
Pull + verify receipts in window; deterministic decision trace |
chio_deploy |
Lint -> chio guard pack * -> chio certify |
chio_doctor |
Battery: chio binary, trust plane, policy parse, guards, receipts.db |
chio_status |
Print the BONDED status line on demand |
The model calls these directly; the user invokes them through OpenCode's tool picker.
OpenCode's plugin API does not expose a status-bar primitive (verified against @opencode-ai/plugin@1.14.19 exports: only tui.toast.show, tui.prompt.append, tui.command.execute). We surface the bond status two ways:
tool.execute.afterprefixes the status line (◉ BONDED · $x/$y · 7 guards · A/B allow/block) to every mediated tool result'stitle- the nearest OpenCode equivalent to a status bar.- A dedicated
chio_statustool prints the same line on demand.
chio_init writes four files next to your code:
policy.yaml # HushSpec 0.1.0 ruleset + extensions.chio.* chio-specific knobs
agent.md # role / tools / guardrails (fed to the model)
guards/ # WASM deny predicates, swappable, signable
receipts.db # local SQLite ledger of every allow / deny / cancel
.chio/ # plugin-managed state (future)
tool-agent- wrap an MCP server with deny-by-default rulescode-agent- hand Claude or Codex a repo, gated on fs/shell/patchresearch-agent- read-only fetch, domain allowlist, write to Notionsupport-agent- paid-tier inbox with HITL above $100 refundstrader- paper or live, budgeted, signed, market-hours gatedrelease-engineer- wrapkubectl/argocdwith prod approvals
Every preset parses as HushSpec 0.1.0. Chio-specific knobs (velocity, human_in_loop, market_hours, signing, k8s_namespaces, rollback, capability, budget) live under extensions.chio.* until upstream chio accepts them as first-class rule keys.
Tool calls routed through OpenCode are intercepted by tool.execute.before, which calls ChioBridge.check({ tool, params, policyPath }). Denials are stamped on the args and surfaced in tool.execute.after, which also bumps the status snapshot. Receipts are signed Ed25519 records persisted by the chio kernel; chio_replay verifies them locally.
import type { Plugin } from "@opencode-ai/plugin";
export const ChioPlugin: Plugin = async ({ project, client, $, directory, worktree }) => ({
tool: { chio_init, chio_wrap, /* ... */ },
event: sessionAutobond,
"tool.execute.before": toolExecuteBefore,
"tool.execute.after": toolExecuteAfter,
});
export default ChioPlugin;Docs: https://opencode.ai/docs/plugins/. Source of truth: sst/opencode packages/plugin/src/index.ts.
Apache-2.0.
Workflow: .github/workflows/ci.yml. Runs lint/typecheck (non-blocking in Wave 5.1), unit tests, and a chio-backed smoke pass. Swap owner/... once the GitHub org is live.