Repository navigation
Observability
Two kinds of trace, answering two different questions, plus a spend ledger that outranks both.
| Session trace (JSONL) | OTLP spans | |
|---|---|---|
| Config | [trace] |
[telemetry] |
| Question | what happened in this session — replay and evidence | where did the time and the tokens go — timeline |
| Storage | ~/.codingdude/traces/<session>.jsonl |
your collector |
| Default | on | off |
Every session writes a JSONL trace. It is what the Trace panel replays and
what an evidence check reads back. Every event carries a type — EDIT_APPLIED,
CONTEXT_COMPACTED, DELEGATION_COMPLETED, LONG_RUN, AUTO_RESUMED,
PHASE_START, and the run terminal events.
[trace]
deltas = "compact" # or "full"compact coalesces streaming text deltas into one line per flush window, which
keeps long traces small; full keeps every delta separately. A long run otherwise
writes one line per token.
Large payloads are capped head+tail in the trace with an explicit truncation marker — the file on disk (git) is the source of truth for a file edit, so the full content is recoverable without inflating the trace.
[telemetry]
enabled = false # off by default
otlp_endpoint = "http://127.0.0.1:4318" # any local OTLP/HTTP collector
service_name = "codingdude"One span per model request and per tool call, so a slow or failing run reads as a timeline: which agent, which request, how many tokens, how long.
-
Local only.
send_to_logfire=False— no token, no account, nothing leaves the machine. Run prompts contain your code, so this is a constraint, not a default to relax. -
Off by default, and never fatal. Every malformed value — the boolean as much
as the endpoint — is logged once and turns tracing off. Tracing never stops a run
or a worker:
enabled = "false"(quoted, so not a TOML boolean) stays off,otlp_endpoint = "4318"falls back to the default listener, and an unreachable collector simply produces no traces. -
Everything is named:
main,fix-loop,claims-retry,claims-auditor,compaction,memory-extraction,evaluator,planning-reviewer,vision-inspector, and each delegation asdelegate.<function>. Filter by agent name to see where the time actually went. -
Global only.
[telemetry]lives in~/.codingdude/config.toml; a project.codingdude.tomlcannot enable it, so one collector serves every project.
Any OTLP/HTTP collector works — Grafana Agent, Jaeger (:4318), an OpenTelemetry
Collector, or a locally served logfire.
OpenLIT is the documented self-hosted option: Apache-2.0, no fee, no account, fits the local-only constraint.
services:
openlit:
image: ghcr.io/openlit/openlit:latest
container_name: openlit
ports:
- "3000:3000" # UI
- "4318:4318" # OTLP/HTTP receiver — CodingDude's default endpoint
environment:
- INIT_DB_HOST=127.0.0.1
volumes:
- openlit-data:/app/data
volumes:
openlit-data:docker compose up -dThen flip the switch — the endpoint already points at that receiver:
[telemetry]
enabled = trueRead traces at http://127.0.0.1:3000. A port already in use is the usual first
problem; 4318 is shared with Jaeger and the OpenTelemetry Collector.
Enabling tracing needs your consent, every time. Nothing is exported until you
set enabled = true yourself — no tool, agent or update will flip it for you.
Why the OpenLIT SDK is deliberately not used: openlit.init() instruments
pydantic-ai through the same hooks logfire.instrument_pydantic_ai() already uses,
so running both produces duplicate spans, and only one global TracerProvider can
exist — the second set_tracer_provider is a silent no-op. OpenLIT is used
strictly as an OTLP receiver: no new dependency, no second instrumentation.
Cost views are subordinate to the ledger. OpenLIT computes cost from its own
pricing database plus gen_ai.usage.* attributes. The CodingDude usage ledger
(db.session_usage) remains authoritative and is what the Budget Cap reads. Treat
any dashboard cost number as an independent estimate; where it disagrees with the
ledger, the ledger wins.
Not yet verified: that OpenLIT's dashboard renders spans produced by logfire's pydantic-ai instrumentation. They are standard OTLP spans, so it should — but it has not been run against a live collector here. Start the container and confirm before relying on it.
The ledger is per session and shared with delegations (they inherit the parent's accumulator), so the number you see is the number that was actually spent, not a sum of independent guesses. Per-function tagging is what makes
designer on the strong model, evaluator on the cheap one
a measurement rather than a preference.
| Not adopted | Why |
|---|---|
| OpenLIT Evals | our evaluator grades against a ticket contract, which a generic eval cannot |
| OpenLIT Guardrails | tool policy is registry-locked in functions.py — that lock is the safety property |
| OpenLIT Vault | a runtime availability dependency |
| OpenLIT Prompt Hub | would move prompts out of the repo and break the one-shot cacheable-prefix design |
| GPU monitoring | we do not self-host models |
Start
How it works
Using it
Project