OpenGuardrailsOpenGuardrailsBlog

2026-06-29

Guarding an opencode agent with OpenGuardrails

Guard opencode — a TypeScript coding agent — behind one neutral contract, as a pure plugin. No fork, no core patch.

OpenGuardrails (OGR) turns each agent action into a GuardEvent, runs it past detectors you choose, and returns a Verdictallow, block, or require approval — before the action runs. opencode is TypeScript, so the binding is the npm package openguardrails-instrumentation-opencode, built on @openguardrails/core — the TS port of the same runtime the Python Hermes integration uses.

How it connects: one plugin hook

opencode already exposes a plugin hook that fires before a tool runs: tool.execute.before. The OGR plugin binds it and needs nothing else — no fork, no proxy, no core change:

// .opencode/plugins — the OGR guardrails plugin
export const guardrails = {
  "tool.execute.before": async (input, output) => {
    const verdict = await runtime.evaluate(toGuardEvent(input))   // GuardEvent → Verdict
    if (verdict.decision === "block" || verdict.decision === "require_approval") {
      throw new GuardrailError(verdict.reasons.join("; "))        // deny-and-continue
    }
    // allow / modify / redact → proceed
  },
}

The policy is a file the agent itself can author — .opencode/guardrails.json, an OGR policy. A default ships out of the box: curl | bash, rm -rf /, credential-file reads, and | sudo are caught immediately.

What "block" means here: deny-and-continue

When a verdict is block (or require_approval), the plugin throws — opencode turns that into a tool error the agent reacts to rather than a hard halt. The agent sees "that was blocked by policy" and looks for a safer path. That's the right shape for an autonomous coding loop: you stop the dangerous action without killing the run.

The judge can be your own model. own-model.ts is an OpenAI-compatible backend — "use the model I already pay for as the guardrail" — composed alongside the deterministic config rules under deny-wins.

Honest limits (and the upstream fix)

Today this integration is restrict-only: it can block, but it can't turn a would-auto-approve call into a first-class interactive ask the human. The reason is structural — opencode's permission system (PermissionV2) doesn't route its plugin permission.ask hook through the tool path, so a plugin can deny but not prompt. We opened anomalyco/opencode#34329 (tracking #34327) to add an optional gate in PermissionV2 for the allow → ask path; once it lands, require_approval becomes a real prompt instead of a deny.

opencode also has no built-in syscall sandboxbash runs on the host. OGR's sandbox altitude would mean wrapping the spawner (a core change) or running opencode inside a container / srt. The agent_hook altitude above, though, works today and stops the download-and-execute class.


Same GuardEvent → Verdict model as every OGR integration — only the binding differs. Spec · GuardEvent & Verdict · @openguardrails/core.