|
| 1 | +(adr-agentic-structured-logging)= |
| 2 | + |
| 3 | +# ADR 0009: Agentic structured logging |
| 4 | + |
| 5 | +## Status |
| 6 | + |
| 7 | +Accepted. |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +ADR 0008 established local OpenTelemetry as an opt-in observability surface for |
| 12 | +agentgrep. It also requires exported logs to be trace-linked and sanitized |
| 13 | +because agentgrep reads local prompt-history stores. |
| 14 | + |
| 15 | +The next gap is usefulness. A trace tree can show that work happened, but |
| 16 | +agentic debugging loops often need a small stream of structured status records: |
| 17 | +which surface ran, which boundary completed, how many sources or results were |
| 18 | +involved, whether a handoff failed, and which trace to open next. |
| 19 | + |
| 20 | +The logging systems studied for this decision repeat a few durable patterns. |
| 21 | +Operational status is separated from result payloads. Sensitive or |
| 22 | +security-adjacent decisions use typed, stable fields rather than prose blobs. |
| 23 | +OpenTelemetry treats trace and span identifiers as first-class log correlation |
| 24 | +fields. Those patterns fit agentgrep if logs stay sparse, schema-oriented, and |
| 25 | +content-free. |
| 26 | + |
| 27 | +## Decision |
| 28 | + |
| 29 | +agentgrep uses structured Python logging as a low-volume status stream for |
| 30 | +critical operation boundaries. Logs complement spans and metrics; they do not |
| 31 | +replace them and they do not carry search result content. |
| 32 | + |
| 33 | +Critical boundaries include CLI command lifecycle, MCP request/tool lifecycle, |
| 34 | +TUI session and worker lifecycle, engine search and find lifecycle, profiling |
| 35 | +and benchmark runs, OTel acceptance smoke work, and sensitive decisions such as |
| 36 | +environment/config path handling or MCP argument summarization. |
| 37 | + |
| 38 | +Each telemetry-oriented log record should carry stable `agentgrep_*` fields. |
| 39 | +The preferred base fields are: |
| 40 | + |
| 41 | +- `agentgrep_surface` |
| 42 | +- `agentgrep_operation` or the established surface-specific operation key |
| 43 | +- `agentgrep_outcome` |
| 44 | +- safe counts such as source, planned-source, result, record, or batch counts |
| 45 | +- elapsed duration when the surrounding boundary has an obvious timer |
| 46 | +- bounded enums such as scope, strategy, method, tool, adapter, or error type |
| 47 | + |
| 48 | +Logs are emitted from inside active project spans when they are meant for OTel. |
| 49 | +That keeps Loki records correlated with Tempo traces and avoids orphan log |
| 50 | +events. Library code still does not configure handlers, levels, console output, |
| 51 | +or exporters; `agentgrep._telemetry` owns opt-in export. |
| 52 | + |
| 53 | +Telemetry logs describe operation shape, not local content. They must not |
| 54 | +record raw prompts, query terms, raw argv, raw MCP arguments, environment |
| 55 | +values, file contents, full exception text that may contain content, or local |
| 56 | +absolute paths. Use booleans, counts, enums, redacted path metadata, and |
| 57 | +length/digest summaries instead. |
| 58 | + |
| 59 | +High-volume detail belongs in spans, metrics, profiles, or bounded aggregate |
| 60 | +fields. Per-record, per-line, per-keypress, render-frame, and hot-loop logs are |
| 61 | +not accepted signal. |
| 62 | + |
| 63 | +## Consequences |
| 64 | + |
| 65 | +### Positive |
| 66 | + |
| 67 | +- Loki becomes useful for local agentic loops without requiring operators to |
| 68 | + reconstruct every status transition from spans. |
| 69 | +- Logs link directly to traces, so a Grafana drilldown can move from a status |
| 70 | + event to the exact CLI, MCP, TUI, or engine execution. |
| 71 | +- Normal users still see no logging output unless an application configures it. |
| 72 | +- Prompt-history content and local machine paths stay out of exported logs. |
| 73 | + |
| 74 | +### Tradeoffs |
| 75 | + |
| 76 | +- Boundary logs add maintenance surface because field names become |
| 77 | + compatibility-sensitive once dashboards or alert queries rely on them. |
| 78 | +- Some spans and logs will describe the same operation. This duplication is |
| 79 | + accepted when the log is the searchable status record and the span is the |
| 80 | + timing/trace context. |
| 81 | +- Sparse logging means deep per-record investigation still requires local |
| 82 | + reproduction, targeted debug logs, or a profiling artifact. |
| 83 | + |
| 84 | +### Rejected options |
| 85 | + |
| 86 | +- Logging every parsed record or match: rejected because it risks content |
| 87 | + leakage, high cardinality, and hot-loop overhead. |
| 88 | +- Exporting logs outside active spans: rejected because unlinked Loki events |
| 89 | + are hard to use and were explicitly disallowed by ADR 0008. |
| 90 | +- Console logging for local development by default: rejected because CLI and |
| 91 | + MCP stdout/stderr are public behavior. |
| 92 | +- Free-form message-only logs: rejected because agentic loops need filterable |
| 93 | + fields and stable dashboards. |
| 94 | + |
| 95 | +## Tests and verification |
| 96 | + |
| 97 | +Unit tests for new telemetry logs should assert on structured attributes, not |
| 98 | +formatted text. Use the in-memory telemetry backend when trace/span linkage is |
| 99 | +part of the contract, and use `caplog.records` for ordinary logging schema |
| 100 | +tests. |
| 101 | + |
| 102 | +Live verification belongs in `just otel-acceptance`. The acceptance check must |
| 103 | +continue to prove that current-run Loki logs have trace and span identifiers. |
0 commit comments