Distribute · OpenCode
OpenCode
Instrument OpenCode with the observability plugin so sessions and tool calls reach Observe and risk policies are enforced on OpenCode traffic.
OpenCode has no plugin marketplace, so the observability plugin ships as a self-contained package that is copied into an OpenCode configuration directory. OpenCode discovers it on the next start. This page covers installing that package, choosing where it lands, and verifying that events arrive.
Once installed, the plugin captures OpenCode sessions and tool calls for Observe and enforces risk policies on prompts, tool calls, MCP calls, and permission requests.
OpenCode has no OTEL exporter
Claude Code and Codex export OpenTelemetry alongside their hooks. OpenCode has no equivalent, so every OpenCode signal arrives through the observability plugin. Sessions, tool calls, token counts, cost, and policy enforcement all work; the OTEL-derived detail described in What the plugin captures does not apply. No Claude-side OTEL configuration is needed for OpenCode.
Access requirements
Section titled “Access requirements”Downloading the observability plugin requires the org:admin scope, and so
does creating the API key the CLI path uses. Both are held by the default
Admin role.
Download the plugin package
Section titled “Download the plugin package”On the dashboard’s Distribute > Plugins page, open the Platform Plugins section, select opencode, and click Download Plugin. The download is a ZIP with a hooks-scoped API key already embedded, so no key needs to be created or exported separately.
Extract it into the target directory:
unzip observability-opencode.zip -d .opencodeThe package contains plugin/agenthooks.ts, which maps OpenCode’s events to the platform, speakeasy.json, which carries the deployment identity and the embedded key, and the bootstrap scripts that fetch the hooks runtime on first run.
Install with the CLI
Section titled “Install with the CLI”Use this path when the package needs to be rendered from a script or checked into a repository template. Install the speakeasy-hooks binary:
curl -fsSL https://raw.githubusercontent.com/speakeasy-api/gram/main/hooks/install.sh | shCreate an API key with the Hooks scope on the API keys page, then run the install from the target directory:
GRAM_HOOKS_ORG_KEY="<hooks-scoped-api-key>" \speakeasy-hooks install --provider=opencode --dir=. --project=<project-slug>Useful flags:
--provider— the agent to render for,opencodehere--dir— where the package is written--project— the project slug events are attributed to, defaulting todefault--browser-login— let each developer sign in through the browser so events record under their own identity rather than the organization key
Choose an install location
Section titled “Choose an install location”OpenCode loads plugins from both a global and a per-repository configuration directory:
~/.config/opencode/instruments every repository on the machine- A repository’s
.opencode/instruments that repository only
Install globally for real rollouts. Per-repository installs suit evaluation, or a single repository that needs a different project attribution.
The plugin never edits opencode.json. Removing the copied files is the
complete uninstall.
Roll out across a fleet
Section titled “Roll out across a fleet”The device agent installs and maintains the OpenCode package on every enrolled machine and reapplies it every minute, so organizations running it can skip the manual steps above. It writes into ~/.config/opencode/, tracks the files it owns, and leaves entries a developer added alone.
OpenCode is managed by default wherever it is installed. To exclude it on a given fleet, set the platforms key in the agent’s managed configuration:
{ "platforms": { "opencode": false }}See the device agent reference for the full managed configuration.
Verify the installation
Section titled “Verify the installation”Restart OpenCode, then run any tool call — an MCP tool, or a prompt such as echo hi there. Confirm both surfaces:
- The call appears in Tool Logs. For a local tool call, set the Type filter to include local tools.
- The conversation appears in Agent Sessions with Agent type filtered to opencode. Transcripts require Agent Session Capture on the Logging & Telemetry page.
Connect MCP servers
Section titled “Connect MCP servers”MCP servers reach OpenCode through its own mcp configuration block rather than through a plugin bundle. Take the name and URL from the server’s hosted install page:
{ "$schema": "https://opencode.ai/config.json", "mcp": { "<server-name>": { "type": "remote", "enabled": true, "url": "<mcp-server-url>", "headers": { "Authorization": "Bearer {env:MCP_SERVER_API_KEY}" } } }}The observability plugin reads this block at startup and reports the configured servers as the session’s MCP inventory.
What the plugin captures
Section titled “What the plugin captures”The plugin translates OpenCode’s native events into the platform’s canonical hook events:
| OpenCode event | Recorded as |
|---|---|
session.created, server.instance.disposed | Session start and end |
chat.message | User prompt |
tool.execute.before, tool.execute.after | Tool call and result |
permission.asked | Permission request |
session.idle | Assistant response, with the turn’s token and cost usage |
Prompt submission, tool calls, permission requests, and stop events are blocking, so a risk policy set to block denies the action in OpenCode rather than only recording it. Tool results, errors, and notifications are recorded without blocking.
Because there is no OTEL stream, a few details available for Claude Code are not available for OpenCode:
- No stable per-turn identifier, so message and turn counts in Agent Sessions are approximate and the per-turn cost badge does not appear
- No model attribution on individual requests
- No skill source resolution or prompt attachment capture
- MCP inventory reflects OpenCode’s configuration rather than a live probe of the running servers
- OTEL forwarding copies OTEL payloads only, so OpenCode events are not forwarded to an external collector
Token and cost totals are unaffected: OpenCode reports them at the end of each turn and the plugin forwards them, so Costs attributes OpenCode spend normally.