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

# Send traces and logs

> Wire an agent's OpenTelemetry output into a XORCISE run, with the exact configuration for Claude Code, Codex, OpenHands, and everything else.

XORCISE turns your agent's OpenTelemetry output into the replay you watch and the evidence the judge reads. This page is the wiring: the endpoint, both signals, and the exact configuration each harness needs. It is optional — a run without telemetry is still graded.

**Time:** about 10 minutes · **You need:** a live run ([Connect any agent](/guides/connect-your-agent)) · **Interface:** CLI

This page uses the agent `scout` and the mission `chrono-canary`. `<run_id>` stands for the 32-character run id XORCISE printed when you created the run.

## What XORCISE ingests

XORCISE runs an embedded OTLP/HTTP receiver with two routes, `POST /v1/traces` and `POST /v1/logs`. Both matter. Claude Code's assistant prose arrives on logs, and Codex's entire narrative is logs-only, so a harness configured for traces alone produces a gutted replay or none at all. There is no gRPC receiver; JSON and protobuf are both accepted, and gzip is decompressed for you.

Every batch must carry the run id. XORCISE reads the `xorcise.run_id` OpenTelemetry **resource** attribute first — a span attribute does not correlate — and falls back to grepping the `xorcise.run_id=` marker out of the payload, which only works for the batches that happen to echo the connect prompt. A batch carrying neither is dropped, never blended into another run.

The port is the one `xorcise up` printed: `4318`, unless it was busy and XORCISE moved up. Read the endpoint from the run's launch profile rather than assuming it. The `otel` field on an agent record is stored and never read.

## Running without telemetry

A run with no telemetry is fully valid and quantifiably safe. Your agent still joins the tailnet, submits artifacts over run-control, and calls `POST /complete`; grading runs at completion. Deterministic checks are unaffected, because they read submitted artifacts and observed facts, not traces. The judge still runs, with an explicit note that no execution trace is available, and the criteria it cannot evidence score `unknown` and are renormalised out of the weighted average rather than scored zero. What you lose is the replay — the timeline, terrain map, and event feed are empty — and any rubric criterion that depends on *how* the agent worked.

## 1. Read the run's launch profile

The endpoint is computed per run and served by the brain, so no harness has to guess it. `launch-cmd` takes the launch mode; ask for `host` when your agent runs in a terminal. To keep the variables in a file, use `xorcise run launch-profile <run_id> > launch.env` — it writes the same values as dotenv lines, but takes no launch mode and returns the container profile for a custom `kind`.

<CodeGroup>
  ```bash launch-cmd theme={"theme":"css-variables"}
  xorcise run launch-cmd <run_id> host
  ```

  ```bash launch-profile theme={"theme":"css-variables"}
  xorcise run launch-profile <run_id> > launch.env
  ```
</CodeGroup>

You should see the exports for the `kind` you registered. For an agent with no built-in harness, that is the generic three:

```text Output theme={"theme":"css-variables"}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp
```

`OTEL_EXPORTER_OTLP_ENDPOINT` is a base URL. A standard OpenTelemetry SDK appends `/v1/traces` and `/v1/logs` itself; Codex does not, which is why its configuration looks different.

## 2. Export the variables before your agent starts

A bare `KEY=VALUE` paste is not inherited by the child process, so the agent starts with no exporter configured and nothing arrives.

```bash theme={"theme":"css-variables"}
set -a; source launch.env; set +a
```

You should see the endpoint in the environment your agent will inherit:

```bash theme={"theme":"css-variables"}
printenv OTEL_EXPORTER_OTLP_ENDPOINT
```

```text Output theme={"theme":"css-variables"}
http://127.0.0.1:4318
```

If you already export `OTEL_RESOURCE_ATTRIBUTES` for your own service metadata, append `xorcise.run_id=<run_id>` to it comma-separated rather than replacing it.

## 3. Configure your harness

XORCISE ships three built-in harness adapters plus a generic floor. You should see the block below that matches the `kind` on your agent record; `xorcise run launch-cmd` emits it for you, already filled in with this run's id and endpoint.

### Claude Code

`kind: claude-code`. The strongest coverage of the three: traces and logs, with `resource-attr` correlation. Claude Code's SDK honours the standard OpenTelemetry variables, so the whole configuration is environment.

```bash theme={"theme":"css-variables"}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_LOG_USER_PROMPTS=1
export OTEL_LOG_TOOL_DETAILS=1
export OTEL_LOG_TOOL_CONTENT=1
export OTEL_TRACES_EXPORT_INTERVAL=1000
export OTEL_LOGS_EXPORTER=otlp
export OTEL_LOG_ASSISTANT_RESPONSES=1
export OTEL_LOGS_EXPORT_INTERVAL=1000
export OTEL_RESOURCE_ATTRIBUTES=xorcise.run_id=<run_id>
claude --permission-mode auto -p '<connect prompt>'
```

Four of these are load-bearing. `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` is the beta gate: without it there are no spans at all. `OTEL_LOGS_EXPORTER=otlp` opens the logs signal, which is where the assistant's text and the user prompt live — a headless run never exports the interaction span, so logs are the only source. `OTEL_LOG_ASSISTANT_RESPONSES=1` is what stops the response arriving as `<REDACTED>` and being dropped. `OTEL_RESOURCE_ATTRIBUTES` is the correlation. The 1000 ms export intervals exist so a short headless run flushes before it exits.

### Codex

`kind: codex`. Codex's OpenTelemetry is driven by its own config, and it **ignores every `OTEL_EXPORTER_*` environment variable**. The exporter configuration rides `-c` overrides on the command, layered in memory per invocation — nothing is written to `~/.codex/config.toml` and there is nothing to clean up.

```bash theme={"theme":"css-variables"}
export OTEL_RESOURCE_ATTRIBUTES=xorcise.run_id=<run_id>
codex exec --skip-git-repo-check --sandbox danger-full-access \
  -c otel.trace_exporter.otlp-http.endpoint='"http://127.0.0.1:4318/v1/traces"' \
  -c otel.trace_exporter.otlp-http.protocol='"binary"' \
  -c otel.exporter.otlp-http.endpoint='"http://127.0.0.1:4318/v1/logs"' \
  -c otel.exporter.otlp-http.protocol='"binary"' \
  -c otel.metrics_exporter='"none"' \
  -c otel.log_user_prompt=true \
  '<connect prompt>'
```

Five details decide whether this works. The two exporters live under **different** keys: traces under `otel.trace_exporter.otlp-http.*`, logs under `otel.exporter.otlp-http.*`. Each endpoint carries the **full per-signal path**, because Codex does not append `/v1/traces` or `/v1/logs` itself. `protocol = "binary"` means http/protobuf. `otel.log_user_prompt=true` is Codex's analogue of `OTEL_LOG_USER_PROMPTS`; without it the prompt logs as `[REDACTED]` and the replay cannot show the task. And `OTEL_RESOURCE_ATTRIBUTES` is the only environment variable that matters — the run id must be a resource attribute, since a span attribute does not correlate.

<Note>
  Codex's replay is built entirely from the logs signal. Its trace adapter returns nothing by design, suppressing several hundred contentless runtime spans, so a Codex run with the logs endpoint misconfigured produces an empty replay even though spans are arriving. Codex's telemetry also carries no assistant prose or reasoning text at all — expect the full action narrative without the model's answers.
</Note>

### OpenHands

`kind: openhands`. OpenHands has a replay adapter and a launch command but **no telemetry provider**, so it falls back to the generic environment and to prompt-sentinel correlation. It is the code-level default kind, but the register form preselects nothing — an agent registered without a harness runs through `generic`, not through this.

```bash theme={"theme":"css-variables"}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp
openhands --headless -t '<connect prompt>'
```

Because correlation is best-effort, only the batches that echo the connect prompt attach to the run; deleting the `xorcise.run_id=` marker from the prompt means nothing attaches. OpenHands contributes traces only — it has no logs mapping. Configure its model and credentials before you launch, and expect its exporter to gzip every export, which XORCISE accepts.

### Any other harness

A blank or unrecognised `kind` gets the generic adapter, the generic three variables, and prompt-sentinel correlation. The generic adapter classifies spans by name and attributes and still produces a readable feed.

```bash theme={"theme":"css-variables"}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp
```

<Tip>
  Nothing stops you from setting `OTEL_RESOURCE_ATTRIBUTES=xorcise.run_id=<run_id>` yourself. XORCISE reads the resource attribute regardless of which harness produced the batch, so adding it upgrades a custom agent from best-effort correlation to the same strong tier the built-ins get. Add `OTEL_LOGS_EXPORTER=otlp` too if your SDK supports it.
</Tip>

## 4. Confirm events are arriving

Check the receiver first, then the run.

```bash theme={"theme":"css-variables"}
curl http://127.0.0.1:4318/healthz
```

```text Output theme={"theme":"css-variables"}
{"status":"ok","service":"otel"}
```

Then read what has landed against this run.

```bash theme={"theme":"css-variables"}
xorcise run traces <run_id>
```

You should see numbered records and a count:

```text Output theme={"theme":"css-variables"}
seq 0: resourceSpans
seq 1: resourceSpans

2 record(s)
```

On the live run page the same signal shows as the phase leaving **Awaiting agent** and the event count climbing.

## Troubleshooting

### No events appear

Work down this list in order.

* **Wrong host.** This is the single most common cause. Container mode bakes `host.docker.internal`, which a plain terminal cannot resolve, so the exporter fails silently and nothing arrives. Ask for host mode: `xorcise run launch-cmd <run_id> host`. An agent that really does run in a container needs `--add-host host.docker.internal:host-gateway`.
* **The variables were not exported.** A pasted `KEY=VALUE` line is not inherited by the child process. Use `set -a; source launch.env; set +a`, or run the whole block `launch-cmd` printed.
* **The profile was empty.** If `xorcise run launch-profile` prints nothing, no collector host is configured for this instance.
* **Codex only.** Setting `OTEL_EXPORTER_OTLP_ENDPOINT` does nothing; Codex ignores it. The endpoints must be the `-c` flags, with the full `/v1/traces` and `/v1/logs` paths.
* **Claude Code only.** Without `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` there are no spans at all.
* **The receiver is unreachable.** `GET /healthz` on the OTLP port should return `{"status":"ok","service":"otel"}`. A `500` with `{"code": 12, ...}` means the protobuf decoder is missing; reinstall with `pip install --force-reinstall xorcise`. A `400` with `{"code": 3, "message": "malformed OTLP body"}` means the payload itself is bad.

### Events appear on the wrong run, or on no run

The receiver tells you directly. An export that cannot be routed comes back with a partial-success body:

```text Output theme={"theme":"css-variables"}
{"partialSuccess": {"rejectedSpans": 34, "errorMessage": "missing/unknown xorcise.run_id or run is sealed"}}
```

The logs route reports the same thing as `rejectedLogRecords`. Causes, in order of likelihood:

* **The run id is a span attribute, not a resource attribute.** Only `resource.attributes` is read. Set it through `OTEL_RESOURCE_ATTRIBUTES`.
* **You overwrote your resource attributes.** Append `xorcise.run_id=<run_id>` comma-separated instead of replacing `service.name` and friends.
* **The prompt marker was stripped.** For OpenHands and any generic harness the `xorcise.run_id=` line in the connect prompt is the only correlation channel.
* **The run is already sealed.** Sealing closes the control plane immediately and keeps OTLP admissible for only five more seconds, so a harness that batches slowly loses its tail after `POST /complete`. Late spans are rejected by design, not merged — rerun rather than trying to backfill.
* **The prompt came from an older run.** Its marker names that run's id.

### Only tool calls, no reasoning

* **Claude Code.** Missing `OTEL_LOG_TOOL_DETAILS=1` or `OTEL_LOG_TOOL_CONTENT=1` gives tool cards with no content. Missing `OTEL_LOG_ASSISTANT_RESPONSES=1` makes the response arrive as `<REDACTED>`, and XORCISE drops it. Missing `OTEL_LOGS_EXPORTER=otlp` removes the assistant text and the user prompt together.
* **Codex.** Assistant prose and reasoning content are never available — its telemetry carries token counts, not text. Without `otel.log_user_prompt=true` you also lose the task itself.
* **OpenHands.** It emits no logs mapping at all, and drops a duplicate tool call when a dedicated action span already covers the command.
* **The wrong `kind`.** If events render as raw span names with no grouping, the generic adapter was selected. Slugs are hyphenated: `claude-code`, not `claude_code`. A friendly label in `xorcise agent list` is not an adapter — `gemini`, `goose`, `cai`, and `langchain` render a display name and nothing more.

## Next steps

<Card title="Traces and events" icon="eye" href="/concepts/traces">
  How XORCISE replays what arrives — the timeline, the terrain map, and the event feed.
</Card>
