> ## Documentation Index
> Fetch the complete documentation index at: https://tracevell.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenCode observability

> Trace OpenCode coding agent sessions: every model turn and tool call, grouped per session and readable as a conversation in Tracevell.

## Overview

[OpenCode](https://opencode.ai/) is an open-source AI coding agent. You can use it as a CLI or run it as a server and drive sessions from your own code to build products on top of the agent. Tracevell makes those runs observable with the `@lmnr-ai/opencode-plugin` plugin, which works with OpenCode V2 and with OpenCode V1 (1.4 and newer).

What Tracevell captures:

* Every conversation turn, one trace per turn.
* LLM prompts, responses, token counts, latency, and cost.
* Tool calls (`bash`, `read`, `write`, `edit`, custom tools) with arguments and results.
* Subagents, nested under the tool call that started them.
* When you send a prompt from inside `observe`, the server-side turn nests under your caller span, so the entire call stack lands in a single trace.

Each trace carries the OpenCode session ID as its Tracevell session ID, so every turn in the same OpenCode conversation shows up under one session in Tracevell.

## Calling OpenCode from TypeScript

Tracevell ships two pieces that cooperate:

* **`@lmnr-ai/opencode-plugin`** runs inside the OpenCode server and emits the turn, LLM, and tool-call spans.
* **`@lmnr-ai/lmnr`** runs in your code. Your prompt carries the current span context to the server, and the plugin parents the turn under it. On V2 you pass the context in the prompt's `metadata`; on V1, `@lmnr-ai/lmnr` patches `@opencode-ai/sdk` and injects it for you.

The result: one trace per turn that spans from your `observe` block into the server's `opencode turn` span and all the way down to each LLM call.

**1. Add the Tracevell plugin to your OpenCode config**

The plugin is what actually emits the turn and tool spans. Add it to the OpenCode config next to your code. The key differs between versions:

> [tab] OpenCode V2
>
> ```json opencode.jsonc theme={null}
> {
>   "$schema": "https://opencode.ai/config.json",
>   "plugins": ["@lmnr-ai/opencode-plugin"]
> }
> ```

> [tab] OpenCode V1
>
> ```json opencode.json theme={null}
> {
>   "$schema": "https://opencode.ai/config.json",
>   "plugin": ["@lmnr-ai/opencode-plugin"]
> }
> ```

V2 reads `plugins` and V1 reads `plugin`. Using the other version's key means the plugin never loads.

> [!NOTE]
>
> OpenCode reads config from several locations, and a file in the current project takes precedence over the global one in `~/.config/opencode/`. If you want tracing on by default everywhere, put the plugin entry in the global file. See OpenCode's plugin docs for [V2](https://opencode.ai/v2/docs/plugins/) and [V1](https://opencode.ai/docs/config/#plugins) for the full rules.

**2. Set LMNR\_PROJECT\_API\_KEY**

The plugin reads `LMNR_PROJECT_API_KEY` from the environment of the process that runs the OpenCode server, so export it in the shell where you start the server. On V1, `opencode.createOpencode()` starts the server from your TypeScript process, so that is the same shell. The plugin also reads `.env` files in the project directory.

```bash theme={null}
export LMNR_PROJECT_API_KEY=your-tracevell-project-api-key
```

To get the project API key, go to the Tracevell dashboard, click the project settings,
and generate a project API key. This is available both in the cloud and in the self-hosted version of Tracevell.

Specify the key at `Tracevell` initialization. If not specified,
Tracevell will look for the key in the `LMNR_PROJECT_API_KEY` environment variable.

> [!TIP]
>
> Self-hosting Tracevell? Also set `LMNR_BASE_URL` and `LMNR_GRPC_PORT` to point at your instance. For the OSS default, that's `http://localhost` and `8001`. On V2 you can instead pass `projectApiKey` and `baseUrl` as plugin options: `{ "package": "@lmnr-ai/opencode-plugin", "options": { "baseUrl": "..." } }` in place of the plain plugin name.

**3. Send prompts from inside observe**

> [tab] OpenCode V2
>
> Install the Tracevell SDK and OpenCode's [JavaScript client](https://opencode.ai/v2/docs/build/client/), and start the server with `opencode serve`:
>
> ```bash theme={null}
> npm install @lmnr-ai/lmnr@latest @opencode/client@latest
> ```
>
> Pass the current span context as `lmnrSpanContext` in the prompt's `metadata`:
>
> ```typescript {1,4,11,15,21} theme={null}
> import { Tracevell, observe } from "@lmnr-ai/lmnr";
> import { OpenCode } from "@opencode/client";
>
> Laminar.initialize();
>
> const client = OpenCode.make({ baseUrl: "http://localhost:4096" });
> const session = await client.session.create({
>   location: { directory: "/workspace" },
> });
>
> await observe({ name: "my-agent-step" }, async () => {
>   await client.session.prompt({
>     sessionID: session.id,
>     text: "Create a Python factorial function and test it.",
>     metadata: { lmnrSpanContext: Laminar.serializeLaminarSpanContext() },
>   });
>   // Wait for the turn to finish so it stays inside this span.
>   await client.session.wait({ sessionID: session.id });
> });
>
> await Laminar.shutdown();
> ```

> [tab] OpenCode V1
>
> Install the Tracevell SDK and `@opencode-ai/sdk`:
>
> ```bash theme={null}
> npm install @lmnr-ai/lmnr@latest @opencode-ai/sdk@latest
> ```
>
> Initialize Tracevell with the `opencode` module. The instrumentation injects the span context into every `client.session.prompt` call made inside `observe`:
>
> ```typescript {1,2,5-9,16} theme={null}
> import * as opencode from "@opencode-ai/sdk";
> import { Tracevell, observe } from "@lmnr-ai/lmnr";
>
> // Initialize Tracevell once at the entry point.
> Laminar.initialize({
>   instrumentModules: {
>     opencode,
>   },
> });
>
> const { client, server } = await opencode.createOpencode();
>
> try {
>   const sessionRes = await client.session.create({ body: { title: "agent run" } });
>
>   await observe({ name: "my-agent-step" }, async () => {
>     await client.session.prompt({
>       path: { id: sessionRes.data.id },
>       body: {
>         model: { providerID: "anthropic", modelID: "claude-haiku-4-5" },
>         parts: [{ type: "text", text: "Create a Python factorial function and test it." }],
>       },
>     });
>   });
> } finally {
>   server.close();
>   await Laminar.shutdown();
> }
> ```
>
> > [!NOTE]
> >
> > If you import `@opencode-ai/sdk` before `Laminar.initialize()` (common when the project has `"type": "module"` in `package.json`), your local reference won't be auto-wrapped. Pass the module via `instrumentModules` as shown above; the instrumentation attaches to the SDK's `Session` class and every new `OpencodeClient` picks it up.

## Trace structure

Because your prompt hands the span context to the server-side plugin, the trace comes back as a single tree: your `observe` span is the root, the server's turn span is its child, and every model call and tool call hangs off the turn. There is no separate trace for the server: everything the OpenCode process does on this turn shows up nested under the TypeScript code that kicked it off.

On V2 the turn span is `opencode turn` and each model call is an `ai.llm <provider>:<model>` span. The screenshots below come from a V1 run, where the turn wraps `session.llm` spans instead.

The tree view makes that nesting obvious:

![OpenCode SDK trace in Tracevell, tree view](https://mintcdn.com/laminarai/RPYdoMdfsv6y-QUL/images/traces/opencode-sdk-tree.png?fit=max\&auto=format\&n=RPYdoMdfsv6y-QUL\&q=85\&s=49b3d3f299b3b9ac0c003f9e555dfa22)

Tree view: the outer my-agent-step span from the TypeScript caller is the parent of the server's turn, which wraps every session.llm, model call, and tool call (write, bash) the agent made on this turn.

Switch to transcript view on the same trace to read the conversation: user prompt, model output, tool calls, and tool results read as a dialogue instead of a span tree. More on the trace UX: [Viewing traces](https://tracevell.com/docs/platform/viewing-traces.md).

![OpenCode SDK trace in Tracevell, transcript view](https://mintcdn.com/laminarai/RPYdoMdfsv6y-QUL/images/traces/opencode-sdk.png?fit=max\&auto=format\&n=RPYdoMdfsv6y-QUL\&q=85\&s=a02fcf07cc031eaf77bf414bd5b72e31)

Same trace in transcript view: the turn reads as a dialogue with the model responses and tool invocations inline.

## Using the OpenCode CLI

The plugin works the same way on a standalone `opencode` CLI run: add it to your OpenCode config, export `LMNR_PROJECT_API_KEY`, and every conversation turn gets traced. There's no TypeScript caller to be the parent span, so each turn becomes its own top-level trace.

**1. Install the OpenCode CLI**

Follow [OpenCode's install guide](https://opencode.ai/docs/#install). You do not need to install the plugin package yourself; OpenCode resolves it from npm.

**2. Configure the plugin and key**

Use the same plugin entry as in the section above (`plugins` on V2, `plugin` on V1), and export `LMNR_PROJECT_API_KEY` in the shell you launch `opencode` from. You can also put the key in a `.env` file in the project directory; the plugin loads it automatically.

**3. Run opencode**

```bash theme={null}
opencode
```

When the plugin starts, it logs `[tracevell] tracing initialized → https://api.lmnr.ai` on V2, or `Tracevell tracing initialized → https://api.lmnr.ai` on V1. On V1 the plugin also turns on OpenCode's experimental OpenTelemetry flag for you, so you don't need to touch the OTel config. V2 doesn't have that flag, and the plugin builds its spans from OpenCode's event stream instead.

![OpenCode CLI trace in Tracevell](https://mintcdn.com/laminarai/RPYdoMdfsv6y-QUL/images/traces/opencode.png?fit=max\&auto=format\&n=RPYdoMdfsv6y-QUL\&q=85\&s=2f162a740c3e78eee8490498b5a40352)

CLI run traced on its own: the turn span is the root, wrapping the model response, the write tool call, and the bash tool call for a Python factorial task.

## Track outcomes with Signals

Traces answer *what happened on this turn*. **[Signals](https://tracevell.com/docs/signals/introduction.md) answer the cross-trace question**: *how often does the agent edit a file it wasn't asked to, when does the bash tool run a destructive command, which turns burn tokens without touching a file*. A Signal pairs a plain-language instruction with a JSON output schema. Tracevell runs it live on new traces (Triggers) or backfills it across history (Jobs) and records a structured event every time it matches. From there you [query](https://tracevell.com/docs/platform/sql-editor.md), [cluster](https://tracevell.com/docs/signals/clusters.md), and [alert](https://tracevell.com/docs/signals/alerts.md) on events across every trace.

> [!NOTE]
>
> Every new project ships with a **Failure Detector** Signal that categorizes issues on any trace over 1000 tokens. Open it from the Signals sidebar to see events as soon as your OpenCode traces arrive.

## Query across traces

* **[SQL editor](https://tracevell.com/docs/platform/sql-editor.md)** for ad-hoc queries across traces, spans, signals, and evals.
* **SQL API** for programmatic access from scripts and pipelines.
* **[CLI](https://tracevell.com/docs/platform/cli.md)** (`lmnr-cli sql query`) for terminal-driven queries and piping JSON into shell tools or coding agents.
* **[MCP server](https://tracevell.com/docs/platform/mcp.md)** to query Tracevell directly from OpenCode, Claude Code, Cursor, or Codex.

## Troubleshooting

> [accordion] I don't see any traces in Tracevell
>
> * `LMNR_PROJECT_API_KEY` must be set in the environment of the process that launches the OpenCode server, not just in the process that creates the OpenCode client. The plugin runs on the server side and reads the key there.
> * Check the OpenCode log for `tracing initialized → ...`. If you see `LMNR_PROJECT_API_KEY not set` instead, the plugin loaded but had no key.
> * Check the config key for your OpenCode version: V2 reads `"plugins"`, V1 reads `"plugin"`. The other version's key silently does nothing.

> [accordion] Traces from my TypeScript caller aren't linked to the server turn
>
> * Make sure both sides are on a current `@lmnr-ai/lmnr` (install with `@latest`). The caller-side instrumentation and the plugin's context extraction need to match up.
> * The server process also needs the plugin in its OpenCode config. Without it, only your caller-side span reaches Tracevell.
> * Send the prompt from inside `observe` (or another active span). There is no context to pass otherwise.
> * On V2, pass `metadata: { lmnrSpanContext: Laminar.serializeLaminarSpanContext() }` with the prompt. Only V1's `@opencode-ai/sdk` gets the context injected automatically.

> [accordion] Self-hosting Tracevell
>
> Export `LMNR_BASE_URL` and `LMNR_GRPC_PORT` in the same environment as `LMNR_PROJECT_API_KEY`. For a local OSS instance, that's `LMNR_BASE_URL=http://localhost` and `LMNR_GRPC_PORT=8001`. The plugin passes these through when it initializes Tracevell.

## What's next

> [card] [Viewing traces](https://tracevell.com/docs/platform/viewing-traces.md)
>
> Read the transcript view, filter, and search across traces.

> [card] [Signals](https://tracevell.com/docs/signals/introduction.md)
>
> Detect behaviors and failures across every OpenCode turn, then query, cluster, and alert on them.

> [card] [SQL editor and MCP server](https://tracevell.com/docs/platform/sql-editor.md)
>
> Query traces programmatically from the UI, API, or your IDE.

> [card] [Tracing structure](https://tracevell.com/docs/tracing/structure/overview.md)
>
> Sessions, metadata, and tags for deeper control.

## Related integrations

> [card] [Claude Agent SDK](https://tracevell.com/docs/integrations/claude-agent-sdk.md)
>
> Running OpenCode alongside the Claude Agent SDK? Trace both here.

> [card] [Claude Code](https://tracevell.com/docs/integrations/claude-code.md)
>
> Trace Claude Code sessions with a one-command plugin.

> [card] [Codex](https://tracevell.com/docs/integrations/codex.md)
>
> Trace Codex CLI sessions with a one-command plugin.

> [card] [Pi](https://tracevell.com/docs/integrations/pi.md)
>
> Trace Pi coding agent sessions with a one-command extension.

> [card] [Anthropic](https://tracevell.com/docs/integrations/anthropic.md)
>
> Trace the Anthropic SDK directly in TypeScript and Python.

> [card] [All integrations](https://tracevell.com/docs/integrations.md)
>
> Browse every provider, framework, coding agent, and browser integration Tracevell supports.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.