anomalyco / anomalyco/opencode
[FEATURE]: Add stable agent lifecycle telemetry
@jlongster is already working on this.
Since Sep 7, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
We already collect Claude Code telemetry for enterprise audit. We use it to review prompts, tool inputs, permission decisions, MCP use, token consumption, and cost.
OpenCode exports internal Effect logs and spans, but those records do not provide stable agent lifecycle events. We cannot reconstruct the same activity without depending on internal span names or incomplete plugin events.
Basic OTLP transport already exists. This request is about the lifecycle data sent through that transport.
Requested decision
Would the core team accept a stable lifecycle event contract for prompts, model requests, tools, permissions, MCP calls, skills, and subagents?
One option is a typed core event contract that feeds OpenTelemetry logs, traces, and metrics. Exporters and compatibility mappings can remain separate changes.
Why this needs core support
A plugin can observe public events, but it cannot reliably observe every runtime boundary or preserve active trace context.
Core owns these facts:
- Model request start, result, refusal, retry, and cancellation
- Tool input, permission wait, execution, result, and cancellation
- Provider-executed tools
- MCP connection and tool activity
- Skill and plugin loading
- Foreground and background subagent relationships
- Token usage and cost
The existing public event stream can remain unchanged.
Common event data
Each lifecycle event needs:
- A schema version, event ID, timestamp, and session sequence number
- Applicable session, turn, message, request, tool, permission, MCP, and subagent IDs
- Trace and span IDs when tracing is active
- OpenCode version, client, and process run ID
- The applied content policy
OpenCode does not know an enterprise user or organization by default. Operators can add optional resource metadata:
OTEL_RESOURCE_ATTRIBUTES='user.email=user@example.com,organization.id=example'
OpenCode would treat these values as operator-supplied metadata. It would not infer identity from Git settings, paths, or prompts.
Proposed event coverage
| Area | Event | Main data |
|---|---|---|
| Session | opencode.session.created |
location, agent, model |
| Session | opencode.session.resumed |
reason |
| Session | opencode.session.forked |
parent session, reason |
| Session | opencode.session.moved |
old and new locations |
| Session | opencode.session.ended |
reason, duration, result |
| Prompt | opencode.user_prompt.submitted |
delivery, agent, model, attachments |
| Prompt | opencode.user_prompt.admitted |
queue duration, delivery |
| Response | opencode.assistant_response.completed |
finish reason, duration |
| Model | opencode.model_request.started |
provider, model, attempt |
| Model | opencode.model_request.completed |
duration, usage, cost, finish reason |
| Model | opencode.model_request.failed |
duration, category, retryable |
| Model | opencode.model_request.refused |
duration, refusal category |
| Model | opencode.model_retry.scheduled |
attempt, delay, category |
| Model | opencode.model_retries.exhausted |
attempts, category |
| Tool | opencode.tool.input.completed |
name, source |
| Permission | opencode.tool.permission.requested |
action, resources, matched rule |
| Permission | opencode.tool.permission.decided |
decision, source, wait duration |
| Tool | opencode.tool.execution.started |
name, source, provider-executed flag |
| Tool | opencode.tool.execution.completed |
duration, result summary, changed files |
| Tool | opencode.tool.execution.failed |
duration, error category |
| MCP | opencode.mcp.connection.started |
server, transport, scope |
| MCP | opencode.mcp.connection.completed |
server, duration |
| MCP | opencode.mcp.connection.failed |
server, duration, category |
| MCP | opencode.mcp.disconnected |
server, reason |
| MCP | opencode.mcp.tool.discovered |
server, tool |
| Skill | opencode.skill.discovered |
name, source, version, digest |
| Skill | opencode.skill.loaded |
name, source, version, result |
| Skill | opencode.skill.activated |
name, activation source |
| Plugin | opencode.plugin.loaded |
name, source, version, result |
| Plugin | opencode.plugin.failed |
name, source, error category |
| Subagent | opencode.subagent.started |
agent, model, parent tool, foreground flag |
| Subagent | opencode.subagent.completed |
duration, usage, cost, result |
| Subagent | opencode.subagent.failed |
duration, error category |
| Subagent | opencode.subagent.cancelled |
duration, reason |
| Compaction | opencode.compaction.started |
reason, input tokens |
| Compaction | opencode.compaction.completed |
reason, duration, token counts |
| Permission | opencode.permission_mode.changed |
old mode, new mode, trigger |
| Authentication | opencode.auth.completed |
action, method, result |
| Internal | opencode.internal_error |
component, category, recoverable |
Tool events would identify core, plugin, mcp, or provider as their source. MCP tool events would also identify their server.
Privacy requirements
Prompts, responses, reasoning, tool data, model bodies, paths, and errors can contain sensitive information.
Each content class needs an independent none, metadata, or full policy. Every class would default to none.
The publisher would apply the policy before export. Raw content would never appear in metric attributes or span names.
Trace shape
Each admitted prompt would create one bounded interaction trace:
opencode.interaction
├── opencode.model_request
├── opencode.tool
│ ├── opencode.tool.blocked_on_user
│ └── opencode.tool.execution
├── opencode.mcp.request
└── opencode.subagent
Turns would use span links instead of one session-long trace. Background subagents would link to their parent tool span.
Model spans would use applicable OpenTelemetry gen_ai.* conventions. OpenCode-specific attributes would use opencode.* names.
Metrics
The first metrics would cover request count and duration, tokens, cost, tool results, permission decisions, MCP connections, and subagent results.
Metric attributes would exclude identifiers, paths, commands, prompts, and tool content.
Claude Code as a compatibility target
Claude Code provides the level of coverage we need. Exact claude_code.* compatibility can live in a Collector transform.
OpenCode can keep native names. The transform would omit events for Claude Code features that OpenCode does not have.
Delivery scope
Normal OpenTelemetry export is best-effort. Documentation must not describe it as complete or durable audit delivery.
A bounded local outbox can be future work. The external audit system would still own retention, access control, search, and legal holds.
Suggested first change
The first change could define events for prompts, model requests, tools, and permissions. It could include an in-memory sink and privacy tests.
Later changes could add OpenTelemetry projections, MCP coverage, skill events, and subagent correlation.
Minimum acceptance criteria
- Every admitted prompt, model request, and accepted tool call has one terminal result.
- Every permission request has one decision.
- Every MCP call identifies its server and tool.
- Every subagent identifies its parent.
- Every usage and cost value identifies its model request.
- The content policy applies before export.
- An end-to-end test proves the event order.
Related work
- #12142, #14246, and #14697 cover basic OTLP and earlier telemetry requests.
- #32920 covers prompt details and trace boundaries.
- #40384 covers attributable session metadata.
- #46685 covers subagent event lineage.
- #46856 covers MCP trace propagation.
- #47351 covers administrator-enforced OTLP settings.
- #34633, #35935, and #36179 contain unmerged metrics and trace work.
I would like agreement on core ownership before implementation starts. If the direction is acceptable, I can split it into smaller proposals.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.