loop-taskv2.8.2

OpenTelemetry

Built-in OpenTelemetry instrumentation for loop runs, tasks, and agent commands. Configure an OTLP endpoint and traces flow automatically.

loop-task includes built-in OpenTelemetry support that automatically instruments loop runs, tasks, and commands. When enabled, it can also configure supported coding agents (OpenCode, Claude Code) to route their telemetry through the same OTLP destination.

Enabled by default, no endpoint contacted

Telemetry instrumentation is active on new installations, but no data is sent anywhere until you explicitly configure an OTLP endpoint. No connection errors appear when no endpoint is set.

Quick Start

  1. Configure an OTLP endpoint:

    loop-task telemetry endpoint http://127.0.0.1:4318
  2. Run your loops normally. Telemetry is emitted automatically.

  3. View the status:

    loop-task telemetry status

Commands

View status

loop-task telemetry
loop-task telemetry status

Output:

OpenTelemetry
  Enabled:                yes
  Endpoint:               http://127.0.0.1:4318
  Protocol:               http/protobuf
  Service:                loop-task
  Auto-instrument agents: yes
  Capture content:        no
  Headers:                not configured

Enable or disable

loop-task telemetry on
loop-task telemetry off

Configure endpoint

loop-task telemetry endpoint http://127.0.0.1:4318

Configure protocol

loop-task telemetry protocol grpc
loop-task telemetry protocol http/protobuf

Test connection

loop-task telemetry test

Command Palette (Ctrl+P)

Inside the TUI board, press Ctrl+P to access:

  • OpenTelemetry status and connection info — shows enabled state, export status, endpoint, protocol, and privacy settings
  • Enable or disable OpenTelemetry — toggles telemetry at runtime without restarting
  • Test OpenTelemetry connection — sends a diagnostic span to verify the endpoint is reachable

Default Behavior

Telemetry is enabled by default but no endpoint is contacted until one is configured. This means:

  • New installations start with telemetry instrumentation active
  • No data is sent anywhere until you explicitly configure an OTLP endpoint
  • No connection errors appear when no endpoint is set
  • The status command reports "enabled" with "not configured" export state

Privacy

Content capture is disabled by default. The following are never exported unless you explicitly enable content capture:

  • Prompt content
  • Model response content
  • Source code
  • Full stdout/stderr
  • Environment variable values
  • Tool arguments containing arbitrary content
  • API keys, tokens, and authentication headers

Command arguments are sanitized: only the executable name and argument count are recorded, not the full arguments (which may contain prompts).

Agent Auto-Instrumentation

When a task executes a supported coding agent, loop-task automatically configures that agent's native OpenTelemetry support to route telemetry to the same OTLP endpoint.

Supported Agents

AgentDetectionAuto-Configuration
OpenCodeopencode run ...Enables experimental OpenTelemetry, sets OTEL_* environment variables
Claude Codeclaude -p ... or claude --print ...Enables telemetry, sets OTEL_* environment variables

You do not need to configure OpenCode or Claude Code telemetry separately. loop-task handles it automatically.

Disabling Auto-Instrumentation

To disable agent auto-instrumentation while keeping loop-task's own telemetry:

# Not yet available via CLI — use settings directly

Agent Serve Lifecycle

When the daemon starts, it manages a persistent opencode serve sidecar process to eliminate per-task cold-start time. The serve process loads skills once, initializes the OTLP SDK once, and stays warm for all subsequent opencode run tasks.

How it works

  1. Daemon boots → AgentServeManager starts opencode serve --port 4096 with all static OTEL env vars
  2. Manager polls GET http://localhost:4096/global/health until healthy (120s timeout, 1s poll interval)
  3. Recipe task invokes opencode run --agent fullstack "do X"
  4. Command-runner detects the opencode integration and auto-injects:
    • --format json (for structured output parsing)
    • --attach http://localhost:4096 (when serve is alive)
    • --dir <cwd> (so serve works in the right directory)
    • --session <id> (when prior opencode task in chain produced a session)
    • --model <model> (when chaining sessions from prior opencode task)
  5. opencode run --attach connects to the warm serve instance, delegates the work, returns with a normal exit code
  6. Loop-task sees normal exit code + stdout → chain routing, context parsing, and telemetry all work unchanged
  7. On daemon shutdown → serve process receives SIGTERM (5s grace), then SIGKILL

Telemetry configuration split

When serve is alive, static OTEL config lives in the serve process env (set once). Per-task dynamic context stays on the run client.

ScopeWhereSet whenWhat
Staticserve process envOnce at daemon bootOTEL_EXPORTER_OTLP_*, OTEL_*_EXPORTER, OTEL_RESOURCE_ATTRIBUTES, OPENCODE_EXPERIMENTAL_OPEN_TELEMETRY
Dynamicrun --attach client envPer taskTRACEPARENT, TRACESTATE

Fallback behavior

If the serve process is not running (crash, port in use, opencode not installed), loop-task falls back to the current opencode run model — no --attach injection, all OTEL env injected per-task as before.

Troubleshooting

  • Check serve health: curl http://localhost:4096/global/health
  • Check serve logs: look for opencode-serve in the daemon log
  • Verify --attach injection: enable debug logging, check command-runner output

Observing Loop Task Agent Sessions

The persistent serve instance supports multiple clients. While loop-task dispatches work headlessly via opencode run --attach, you can observe live sessions from another terminal.

Attach a TUI to the running serve

# Attach a full TUI to the serve instance
opencode attach http://localhost:4096

# List sessions created by recipe tasks
opencode session list

# Attach to a specific session
opencode -s <session-id> --attach http://localhost:4096

Multi-client architecture

┌─────────────────────────────────────────────────────────┐
│  opencode serve :4096  (managed by loop-task daemon)   │
│                                                         │
│  Session A (created by recipe task: "implement #158") │
│  Session B (created by recipe task: "fix CI")         │
│  Session C (created by recipe task: "apply feedback") │
│                                                         │
└──────────┬──────────────────────┬──────────────────────┘
           │                      │
           ▼                      ▼
  ┌────────────────┐    ┌────────────────────┐
  │ loop-task      │    │ your terminal      │
  │ opencode run   │    │ opencode attach    │
  │ --attach :4096 │    │ http://localhost:  │
  │ (headless,     │    │ 4096               │
  │  exits)        │    │ (TUI, observe)    │
  └────────────────┘    └────────────────────┘

SSH / headless VM considerations

On headless VMs accessed via SSH, opencode attach requires an interactive SSH session with a proper terminal. On the Numa VM, connect via ssh azureuser@<vm-ip> and run opencode attach http://localhost:4096 directly.

OpenCode JSON Output and context.opencode.*

When the detected agent integration is opencode, loop-task auto-injects --format json into the run args. After the command completes, the JSONL stdout stream is parsed into a structured context object available to subsequent tasks in the same loop chain.

Extracted context keys

KeySource eventExample
opencode.session.idstep_startsessionIDses_abc123
opencode.session.messageIdstep_startpart.messageIDmsg_xyz
opencode.tokens.inputstep_finish (accumulated)671
opencode.tokens.outputstep_finish (accumulated)8
opencode.tokens.cache.readstep_finish (accumulated)21415
opencode.tokens.cache.writestep_finish (accumulated)0
opencode.coststep_finish (accumulated)0.042
opencode.tools.counttool_use (counted)3
opencode.tools.namestool_use (unique)["bash","read","write"]
opencode.gitSnapshotstep_start / step_finish09dd05d...
opencode.errorerror event{"name":"APIError",...}
opencode.textlast text before step_finish(reason=stop)Agent's final summary
opencode.modelcaptured from --model argplainconcepts/glm-5-1

Interpolation behavior

  • Scalar values: {{opencode.cost}}0.042
  • Nested scalar: {{opencode.tokens.input}}671
  • Object values: {{opencode.tokens}} renders as indented JSON:
    {
      "input": 671,
      "output": 8,
      "reasoning": 0,
      "cache": { "read": 21415, "write": 0 }
    }

Text capture policy

Only the last text event before step_finish with reason == "stop" is captured (the agent's final summary). Intermediate reasoning text is not captured.

Auto session chaining

When a chain has multiple opencode run tasks, loop-task automatically injects --session and --model from the prior opencode task. No need to write {{opencode.session.id}} in recipes:

- id: implement
  command: opencode
  commandArgs: [run, --agent, fullstack, --model, plainconcepts/glm-5-1, "implement #158"]
  # After: context.opencode.session.id = ses_abc, context.opencode.model = plainconcepts/glm-5-1

- id: fix-tests
  command: opencode
  commandArgs: [run, --agent, fullstack, "fix the tests"]
  # Auto-injected: --format json --session ses_abc --model plainconcepts/glm-5-1
  # Agent remembers Task 1, uses same model, tokens accumulate

- id: pr
  command: sh
  commandArgs: [-c, 'gh pr create --title "Resolve #{{number}}" --body "Tokens: {{opencode.tokens}} Cost: ${{opencode.cost}}"']
  # Shows total session usage

Session chaining is within the same loop chain only. Context is discarded between iterations.

Environment Variables

loop-task respects the standard OpenTelemetry environment variables:

  • OTEL_EXPORTER_OTLP_ENDPOINT — fallback endpoint if not configured in settings
  • OTEL_EXPORTER_OTLP_PROTOCOL — fallback protocol (grpc or http/protobuf)
  • OTEL_EXPORTER_OTLP_HEADERS — authentication headers for the OTLP endpoint
  • OTEL_RESOURCE_ATTRIBUTES — additional resource attributes (merged with loop-task correlation attributes)

The precedence order is:

  1. Explicit loop-task CLI settings
  2. Daemon telemetry settings (configured via loop-task telemetry endpoint)
  3. Standard OTEL_* environment variables
  4. No exporter configured

Local Collector Example

Here's a minimal OpenTelemetry Collector configuration for local development:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]
    metrics:
      receivers: [otlp]
      exporters: [debug]

Start with Docker:

docker run --rm -p 4317:4317 -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otelcol/config.yaml \
  otel/opentelemetry-collector:latest

Then configure loop-task:

loop-task telemetry endpoint http://127.0.0.1:4318
loop-task telemetry test

Span Names

loop-task uses stable span names that do not include dynamic identifiers:

SpanName
Loop runloop_task.loop.run
Task executeloop_task.task.execute
Command executeloop_task.command.execute
Agent executeloop_task.agent.execute

Dynamic identifiers (run ID, task ID, issue numbers) are recorded as span attributes, not in the span name.

Architecture

loop-task
├── loop-task instrumentation
├── OpenCode instrumentation (auto-configured)
└── Claude Code instrumentation (auto-configured)
        |
        | OTLP
        v
  Configured OTLP destination
        |
        v
  User's observability backend

loop-task supports any OTLP-compatible destination including: OpenTelemetry Collector, Grafana Alloy, Jaeger (via Collector), Grafana Tempo, Honeycomb, Datadog, New Relic, Azure Monitor, and other compatible platforms.