From Logs to Fixes: Build a Production Incident Co‑Pilot with Code Debugging AI, eBPF, and OpenTelemetry
Most teams have nailed the basics of observability: logs, metrics, traces. Yet the bridge from signal to fix is still manual. Pager rings, Slack war room forms, someone digs through dashboards, the expert who remembers a similar incident chimes in, a tentative revert is pushed, and, hours later, the dust settles. Mean-time-to-diagnose has improved, but the handoff from diagnosis to remediation is stubbornly human-heavy.
This article walks through building an incident co‑pilot that doesn’t stop at alarms. It ingests real-time traces with OpenTelemetry, enriches them with deep kernel context via eBPF, and funnels the evidence into a code debugging AI that proposes a minimal, test-backed change. It then auto-bisects to find the offending commit, opens a safe PR behind a feature flag, and does all of this without exposing secrets. The goal is not to replace engineers but to cut the cold-start time and ship the first reasonable fix attempt in minutes rather than hours.
You’ll get:
- A reference architecture that connects OpenTelemetry, eBPF, and a code-aware AI loop.
- Concrete configuration snippets, BPF examples, and integration glue.
- A practical approach to secret redaction and privacy-preserving AI prompts.
- An auto-bisect pipeline that generates a failing test or replay harness from your traces.
- Guardrails to ensure changes are auditable, reversible, and safe to ship.
Opinion: if your co‑pilot cannot tie a trace to code and a code change to a canaried rollout, it’s just another dashboard. The focus here is causality and action, not more charts.
Design Principles
Before we wire components together, set the bar for what a co‑pilot must do in production:
- Real-time prioritization over exhaustiveness. When the error budget is burning, you don’t need complete data, you need the right 5%. Use tail-based sampling and on-demand deep capture.
- Correlate user-visible failures to a concrete code location. Every hop from symptom to code loses intent; instrument aggressively where users feel pain.
- Secret safety by construction. Assume all telemetry might be prompt fodder. Redact at the edge, mark sensitive fields as structured tokens, and use a narrow context window to minimize leakage risk.
- Machine-curated, human-approved changes. The AI proposes. Humans approve. CI proves. Canary verifies. Undo is one click away.
- End-to-end auditability. Every step leaves a trail: prompt, model version, inputs, outputs, diffs, test artifacts, rollout timeline.
Architecture Overview
Think pipeline, not silo. The co‑pilot looks like a streaming system with a sidecar for evidence capture and an async agent for code reasoning.
Flow:
- Instrument services with OpenTelemetry SDKs. Emit traces, metrics, and structured events using semantic conventions. Add resource attributes that encode service name, version, git sha, and deploy environment.
- Deploy an eBPF agent on nodes to capture low-level events: syscall errors, TCP retransmits, DNS timeouts, GC pauses, CPU throttling, kernel-level latency histograms. Aim for targeted, low-overhead captures.
- Join eBPF events to OTel spans by time window, PID/TID, cgroup, and container metadata. Enrich spans with kernel context such as error codes, socket addresses, and syscall counts.
- Detect incident candidates with rules and models: surge in error rate, anomalous latency, new top offending code path, regressions tied to a recent deploy.
- For a high-priority candidate, fetch code context: build index of repositories, symbol maps, and commit history. Use tree-sitter or LSIF for a navigable code graph.
- Construct a privacy-safe prompt and feature bundle for a code debugging AI: failing stack trace, enriched span sequence, diff of config and flags, suspected files, and commit range. Redact secrets at the source and pass structured tokens in-line.
- The AI proposes: likely root cause, minimal patch or revert, associated test changes, and a risk summary.
- Auto-bisect in CI to validate: generate a reproducer or failing test from the trace, bisect the suspect range, confirm the culprit commit, and verify the patch flips the test from red to green.
- Open a safe PR: small diff, link to incident, added test, flag guard, rollout plan, and metrics to watch. Block merge behind code owners and green checks.
The heart of this system is a join: correlating what the app thinks it is doing (trace spans) with what the kernel observes (eBPF). The rest is carefully curating that evidence so an AI model can reason about code, not raw logs.
OpenTelemetry Setup: Traces You Can Fix From
You cannot fix what you cannot trace to code. OTel gives the structure.
- Use semantic conventions consistently, especially the HTTP, DB, RPC, and messaging spans. Populate attributes like http.method, http.route, net.peer.name, db.statement (sanitized), rpc.system, messaging.operation.
- Add resource attributes for versioning: service.version, deployment.environment, and git.sha. These are critical for linking to commit history and bisect ranges.
- Use parent-child spans to capture causality across network boundaries. Inject and extract context via the OTel propagators.
- Emit high-cardinality span events for failures, but keep payloads small. Do not embed secrets; rely on a redaction library in the SDK middleware.
Minimal OTel Collector pipeline with tail-based sampling, attributes, and an exporter might look like this:
yamlreceivers: otlp: protocols: grpc: http: processors: batch: tail_sampling: decision_wait: 2s policies: - name: error-spans type: status_code status_code: status_codes: [ERROR] - name: high-latency type: latency latency: threshold_ms: 1000 attributes: actions: - key: git.sha from_attribute: service.version action: upsert - key: redaction.version value: v1 action: insert exporters: otlphttp: endpoint: http://observability-gateway:4318 service: pipelines: traces: receivers: [otlp] processors: [batch, tail_sampling, attributes] exporters: [otlphttp]
Note the tail sampler: we retain error spans and tail-latency spans for deep analysis, reducing noise for the AI and cost for storage.
eBPF Enrichment: Seeing What the Kernel Sees
Why eBPF? Because you need ground truth when users say the app is slow or broken. eBPF lets you attach to kernel and user-level probes with near-zero friction and minimal overhead. Use it to capture:
- Syscall errors: open failures, permission denied, too many files, EAGAIN loops.
- TCP health: retransmits, zero window events, SYN timeouts, RTO spikes.
- DNS and connect latency: measure where network time is really spent.
- CPU throttling and scheduler contention: why p99 stalls.
- GC pauses and page faults: memory pressure symptoms.
Strategy for correlation: tag each eBPF event with PID/TID, cgroup id, container id, and timestamps. In the agent, join to OTel spans by nearest timestamp within a small window and matching pid or container metadata. When available, use cgroup inode id to avoid pid reuse ambiguity.
Here is a simplified BPF program that counts syscall errors by process and publishes them via a perf ring buffer. It attaches to the sys_enter and sys_exit tracepoints and reports errno for selected syscalls.
c// file: ebpf/sys_errs.bpf.c #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h> #include <linux/ptrace.h> #include <linux/sched.h> struct event { u64 ts; u32 pid; u32 tid; u32 cgroup_id_low; int syscall_nr; int errno_val; char comm[16]; }; struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); } events SEC(".maps"); SEC("tracepoint/raw_syscalls/sys_exit") int on_sys_exit(struct trace_event_raw_sys_exit* ctx) { int ret = ctx->ret; if (ret >= 0) return 0; // only errors struct event ev = {}; ev.ts = bpf_ktime_get_ns(); ev.pid = bpf_get_current_pid_tgid() >> 32; ev.tid = (u32)bpf_get_current_pid_tgid(); ev.cgroup_id_low = (u32)bpf_get_current_cgroup_id(); ev.syscall_nr = ctx->id; ev.errno_val = -ret; // errno positive bpf_get_current_comm(&ev.comm, sizeof(ev.comm)); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev)); return 0; } char LICENSE[] SEC("license") = "GPL";
The userspace agent reads this stream and correlates with spans. Using Go, you can load the BPF object and forward enriched events to your OTel pipeline as span events or logs.
go// file: agent/main.go package main import ( `context` `time` `log` ) // Imagine we have bindings that produce Event structs from the perf ring buffer. type Event struct { Ts uint64 Pid uint32 Tid uint32 CgroupLow uint32 SyscallNr int ErrnoVal int Comm string } func main() { ctx := context.Background() // initBPF() loads the object, attaches, and returns a channel of events evCh := initBPF() // spanIndex lets us query recent spans by pid/tid/time window index := NewSpanIndex(5 * time.Second) for ev := range evCh { span := index.FindNearest(ev.Pid, ev.Tid, ev.Ts) if span == nil { continue } // Enrich span with kernel error metadata as an OTel event attrs := map[string]any{ `kernel.syscall_nr`: ev.SyscallNr, `kernel.errno`: ev.ErrnoVal, `proc.comm`: ev.Comm, `cgroup.low`: ev.CgroupLow, } emitSpanEvent(ctx, span, `kernel.error`, attrs) } }
In practice, you will run more programs: TCP RTT histograms per remote address, DNS lookup latency, page fault counters, and JVM or Go runtime probes via USDT or uprobe. Keep overhead in check: prefer CO-RE BPF for portability, use ring buffer backpressure, limit per-event payload, and sample aggressively outside incident windows.
Empirically, well-written eBPF probes add sub-1% CPU overhead for the kinds of counters above, but measure in your context. Use BPF maps to aggregate at the kernel and export aggregates instead of every event when possible.
Joining the Worlds: Span Enrichment
The join logic should be modular and cheap:
- Build a time-indexed cache of spans keyed by pid, tid, and container id.
- For each eBPF event, pick the closest span in a small time window that matches pid or container and same host; if ambiguous, attach to the parent request span rather than a child to avoid misattribution.
- Prefer adding eBPF data as span events or additional attributes with a well-known prefix such as kernel.* or net.kernel.* to keep the schema clean.
- Forward enriched spans to an analysis topic or storage for retrieval by the AI.
By the time an incident is declared, you want a complete conversation of spans plus kernel proofs: for example, a 2xx HTTP span with 4 seconds of server processing but also a cluster of TCP zero-window events and CPU throttling spikes on the same pid. Now a code reasoning agent has data to hypothesize: blocking on disk IO, head-of-line blocking in a worker pool, or a bad retry loop on a flaky backend.
Privacy and Secret Safety by Construction
A co‑pilot that leaks secrets is worse than useless. Adopt a layered approach:
- Redact at source. Add SDK middleware that removes or hashes known secret patterns from spans: Authorization headers, cookies, tokens, credentials in DB statements. Use a high-entropy detector for unknown tokens.
- Structured tokenization. Replace sensitive values with typed placeholders like SECRET:AWS_ACCESS_KEY or PII:EMAIL. Store a reversible mapping in your vault if you need to rehydrate in a secured path. The AI gets the placeholder, not the secret.
- eBPF redaction. The eBPF agent should never capture raw payloads. Stick to metadata: error codes, counts, durations, socket tuples. If you must sample payloads for an incident, do it behind a feature flag and redact in-kernel where possible.
- Context diet. Keep the AI prompt to minimal evidence: stack frames, file names, function signatures, diff snippets, and sanitized metrics. Avoid shipping raw logs wholesale.
- Policy guardrails. Enforce an allowlist of fields that can cross the AI boundary. Log every prompt and mask in your audit trail.
A useful trick is to use a Bloom filter of known secret prefixes in the agent and drop any attribute value that matches. Combine this with entropy thresholds and regex signatures for common keys (cloud provider keys, OAuth tokens).
Building the Code Context Engine
Even the best traces won’t fix code in the abstract. The AI must see your code graph, not just text blobs.
Ingredients:
- Repository mirrors with commit metadata and tags. Index service.version and git.sha for each deploy so you can map spans to code versions.
- Parse code with tree-sitter or language servers to build a symbol graph: functions, types, call edges, and file paths.
- Index test files and link them to source files by import or path conventions.
- Build an embedding store for stack traces and code snippets to power retrieval. Avoid storing raw secrets; store redacted text.
- Symbol maps for native binaries and debug info for symbolication if you run native code.
For many stacks, LSIF or LSP output can give you enough cross-references. For stacktrace-to-code mapping, maintain a map from function names in frames to source files and line ranges. For minified or optimized builds, preserve source maps where relevant.
The Code Debugging AI Loop
Treat the AI as a specialized pair programmer with three sequential tasks:
- Hypothesis. Given spans and kernel context, produce a ranked list of plausible failure modes and the code areas most likely responsible.
- Proposal. Produce a minimal patch or revert that aligns with the hypothesis, including a failing test that the patch should fix.
- Risk framing. Summarize possible side effects, configuration knobs that interact with the change, and telemetry to watch post-merge.
Prompt construction should be templated and deterministic. Example template with strict placeholders:
textSystem: You are a production incident co‑pilot focused on code reasoning. You work with redacted data. Do not hallucinate APIs; prefer code snippets from the provided context. Propose minimal, reversible changes with tests. Incident summary: - Service: {service_name} - Version: {service_version} - Git SHA: {git_sha} - Time window: {start_ts}..{end_ts} - Impact: {impact_summary} Evidence: - Top stack trace (redacted): {stack_trace} - Enriched spans (selected): {span_evidence} - Kernel context summary: {kernel_summary} - Recent changes (git log short): {git_log_recent} - Relevant code snippets: {code_snippets} Tasks: 1) Diagnose likely root cause. 2) Propose a minimal patch (diff) and a failing test that this patch makes pass. 3) Outline risks and metrics to watch. Constraints: - Do not include any secrets. - Only modify files under: {allowed_paths} - Use feature flag {flag_name} if needed to gate behavior.
Use a retrieval step to feed only small, targeted snippets. The model should not see the entire repo. Keep prompts short and focused; long prompts increase cost and risk of leakage. If you need chain-of-thought internally, keep it private on the server side; the audit log should record inputs and final outputs.
You can run an open model fine-tuned for code, or a hosted service with strong privacy guarantees. In both cases, wrap the call with your own guardrails: schema validation on outputs (patch shape, test files touched), token count limits, and regex checks to ensure no secrets slipped in.
Generating a Reproducer From Traces
A smoking-gun fix starts with a reliable reproducer. The enriched spans give you inputs to reconstruct the faulty path.
For HTTP services:
- Extract method, route, headers (sanitized), query params, and a synthetic body if available from span attributes.
- Reconstruct the call sequence across services by following trace links and build a minimal local harness that exercises the failing service directly.
- If downstreams are involved, stub them with recorded responses or run ephemeral mocks.
For message consumers:
- Extract topic, partition, offset, and payload schema. Produce a minimal message with the same shape and metadata.
Example Python harness that replays a failing HTTP path using reconstructed data:
python# file: reproduce/test_replay.py import os import requests def test_replay_failing_route(): base = os.environ.get('SERVICE_BASE', 'http://localhost:8080') route = '/api/v1/checkout' # derived from span headers = { 'x-request-id': 'redacted', 'user-agent': 'incident-replay', } params = { 'cart_id': 'abc123', 'coupon': 'SUMMER2025', } # Body is optional and redacted if sensitive resp = requests.post(base + route, headers=headers, params=params, json={'items': [{'sku': '42', 'qty': 1}]}) assert resp.status_code == 200 # Add assertions that mirror the failing behavior if we can detect it
The AI can generate or tweak this test from the span evidence. The key is to run it in CI against the code version range and flip it red for the bad commits.
Auto-Bisect: Find the Culprit Commit
Once you have a failing reproducer or test, bisecting is straightforward automation.
Algorithm:
- Determine the suspect commit range using deploy metadata. If service.version and git.sha are in spans, you know exactly which build regressed and the last known good.
- Check out the repo, run the reproducer against the current head; it should fail.
- Run git bisect between last known good and current bad with a small script that runs the replay test.
- Record the culprit commit and its diff. Feed that to the AI for patch generation or validation.
Here is a minimal bisect runner script:
bash#!/usr/bin/env bash set -euo pipefail # file: scripts/bisect_run.sh # Build and run the test; exit 0 if fixed, 1 if reproducer fails (bad) make build >/dev/null 2>&1 || exit 1 SERVICE_BASE='http://localhost:8080' docker compose up -d --build >/dev/null 2>&1 sleep 2 pytest -q reproduce/test_replay.py >/dev/null 2>&1 && exit 0 || exit 1
And a GitHub Actions job to perform the bisect on demand:
yamlname: Incident Bisect on: workflow_dispatch: inputs: good_sha: description: 'Last known good sha' required: true bad_sha: description: 'Current bad sha' required: true jobs: bisect: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup run: | sudo apt-get update sudo apt-get install -y docker-compose python -m pip install --upgrade pip pytest requests - name: Run bisect run: | git bisect start ${{ inputs.bad_sha }} ${{ inputs.good_sha }} git bisect run bash scripts/bisect_run.sh || true echo Culprit: $(git rev-parse HEAD) git bisect reset
When the bisect finishes, you have a concrete commit that introduced the failure. The AI can now propose either a localized fix or a temporary revert, along with a targeted test ensuring this class of failure doesn’t recur.
Proposing and Opening a Safe PR
A safe PR is small, reversible, and observable.
Checklist for the co‑pilot’s PR:
- Patch modifies only allowed paths. No drive-by refactors.
- Includes a failing test that passes with the patch.
- Wrapped behind a feature flag side-path if behavior change is risky.
- Linked to an incident ticket and includes evidence summary.
- Adds or updates SLO-related metrics for the code path.
- Has a rollout plan: canary steps, metrics to watch, and an auto-rollback condition.
Use your VCS API to open the PR. A simple Python snippet using a CLI can keep it short and avoid leaking secrets in logs by relying on pre-configured credentials.
bash# file: scripts/open_pr.sh set -euo pipefail branch="incident/fix-${INCIDENT_ID}" git checkout -b "$branch" git add -A git commit -m "Incident ${INCIDENT_ID}: minimal fix with test and flag" git push -u origin "$branch" gh pr create \ --title "Incident ${INCIDENT_ID}: minimal fix with test" \ --body "This PR addresses incident ${INCIDENT_ID}.\n\n- Hypothesis: ${HYPOTHESIS}\n- Culprit commit: ${CULPRIT_SHA}\n- Reproducer: see reproduce/test_replay.py\n- Fix: minimal change guarded by flag ${FLAG_NAME}\n- Rollout: canary 5% -> 25% -> 50% with metrics ${METRICS_TO_WATCH}\n\nAll inputs redacted and audited." \ --label incident,autofix \ --draft
GitHub fine-grained tokens or OIDC with short-lived credentials reduce risk. Record the PR URL and link it in the incident timeline. Require code owners on the paths touched. CI should block merge until tests, static analyzers, and policy checks pass.
Example End-to-End: A Latency Spike With Silent Kernel Errors
Imagine a checkout service where users report intermittent timeouts. Traces show HTTP 200 with server.duration around 3 seconds, no apparent database slowdowns, and normal GC. eBPF data tells a different story: a spike in TCP zero-window events and sys_exit with errno EMFILE for the process. The agent enriches spans with kernel.error events.
The AI receives a prompt with:
- Top stack showing a retry loop in a payment client.
- eBPF summary: file descriptor exhaustion events and TCP backpressure.
- Recent commits: a pool size change for a shared HTTP client and a missing close in a newly added code path.
Hypothesis: a missing close on error path leaks connections, exhausting the fd limit and inducing backpressure. Proposal: ensure close on error return, add a connection upper bound guard, extend pool idle timeout, and expose a metric for open fd count.
Reproducer: simulate a wave of concurrent requests with injected failures to trigger the leak path, assert p99 and open fd metrics.
Bisect confirms the commit that introduced the missing close. The PR changes two lines and adds a test. Rollout is guarded by a flag that toggles the new close behavior and tightens pool limits. Canary shows p99 returns to baseline; kernel errors vanish.
Guardrails and Governance
Put hard limits on what the co‑pilot can do:
- Allowed file paths. Restrict write access to code, tests, and safe configs; no infra or secrets.
- Change budget. Limit diffs to a small number of lines. Larger refactors go to humans.
- Model output schema. Enforce JSON or diff format with strict validation. Reject if it touches disallowed areas.
- Security scans. Run static analyzers, SAST, dependency scanners, and secret scanners on the PR.
- Rollout policy. Canary with automatic rollback on regression in key SLOs.
- Kill switch. A single flag to disable automated PR creation globally.
All artifacts and prompts go to an immutable audit log with timestamps, model versions, and hashes of inputs and outputs.
Performance and Cost Management
No free lunches. Keep overheads and cloud bills in check:
- Use tail-based sampling plus on-demand deep capture during incidents. For example, sample 1% normally, jump to 20% for 10 minutes when a rule triggers.
- Push aggregation into BPF. Export histograms and counters, not raw events, unless debugging a live incident.
- Keep prompts short. Summaries beat raw logs. Prefer high-signal features (stack frames, error codes, commit diffs).
- Bound concurrency of AI calls. Queue and deduplicate similar incidents.
- Cache code embeddings and parsed graphs; update incrementally on new commits.
Measure the co‑pilot with its own SLOs: detection-to-diagnosis latency, false positive rate, first fix proposal latency, PR merge latency, rollback frequency.
Security Model for eBPF and Agents
eBPF runs with privileges; treat it as part of your trusted computing base.
- Least privilege. Load only the programs you need. Use read-only maps where possible. Ship CO-RE and drop root where supported.
- Container isolation. Run the agent with a minimized profile, seccomp, and namespaces. Restrict host mounts.
- Data policy. The agent never exports payloads by default. Only metadata leaves the node. Add a feature flag for incident deep capture with a review step.
- Source authenticity. Sign BPF objects and verify signatures at load time.
- Monitoring. Self-instrument the agent and alert on unusual event rates or buffer overruns.
A Minimal Kubernetes Deployment Sketch
An outline of how the pieces sit in a cluster:
- OTel Collector as a DaemonSet for reception and tail sampling.
- eBPF agent as a DaemonSet with node-level privileges, exporting to a local gRPC endpoint.
- Joiner service as a Deployment that subscribes to both streams and emits enriched spans.
- Co‑pilot brain as a Deployment with access to code indices and CI credentials for bisect.
- A queue (e.g., NATS, Kafka) between joiner and brain to buffer incident jobs.
High-level manifest fragments:
yamlapiVersion: apps/v1 kind: DaemonSet metadata: name: ebpf-agent spec: template: spec: hostPID: true containers: - name: agent image: yourorg/ebpf-agent:latest securityContext: privileged: true env: - name: EXPORT_ENDPOINT value: http://otel-collector:4318
yamlapiVersion: apps/v1 kind: Deployment metadata: name: copilot-brain spec: replicas: 1 template: spec: containers: - name: brain image: yourorg/copilot-brain:latest env: - name: CODE_INDEX_URL value: http://code-indexer - name: MODEL_ENDPOINT value: http://local-model-gateway - name: CI_TOKEN_SECRET valueFrom: secretKeyRef: name: ci-token key: token
Testing the Co‑Pilot With GameDays
Do not wait for production to be the first exercise. Run Chaos GameDays to validate end-to-end:
- Faults to inject: connection leaks, slow DNS, partial outages, permission errors, file descriptor exhaustion, TLS handshake failures.
- Observed signals: enriched spans should clearly reflect the injected fault. The AI should rank the right hypothesis in the top 3.
- Time to first patch: measure from incident trigger to draft PR.
- Safety checks: ensure no secrets cross the AI boundary during chaos.
Make GameDays a regular part of your reliability calendar. Over time, tune sampling, prompts, and guardrails based on the outcomes.
Extending Beyond HTTP: Databases, Queues, and Native Services
- Databases: instrument DB spans with sanitized statements and use eBPF to measure syscall patterns like fsync rates and page faults. Combine to detect hot shards or missing indexes with proof.
- Queues: capture message ack times and consumer lag; use kernel network telemetry to spot broker issues vs consumer bugs.
- Native services: add symbolication and use uprobes and USDT to capture runtime metrics. For C++ services, enable frame pointers or use LBR-based unwinding where available.
The principle remains: combine application-level intent with kernel-level truth, then reason about code with both.
What This Is Not
- It is not a fully autonomous code writer. Engineers remain in the loop.
- It is not an excuse to under-instrument applications. OTel and clean code still matter more than any AI.
- It is not a replacement for postmortems. You still need root cause analysis and long-term fixes.
Common Pitfalls and How to Avoid Them
- Over-collection. If you feed the AI a firehose, you get higher costs and worse answers. Curate hard.
- Weak version mapping. Without reliable service.version and git.sha in spans, bisecting is guesswork.
- Secret leakage via correlation IDs. Treat all opaque IDs as potentially sensitive; tokenize them.
- Unbounded patch surface. Without an allowlist, the AI might propose changing infra code or config. Enforce path policies.
- Flaky reproducers. If your test is flaky, bisect results are meaningless. Invest in determinism for the harness.
A Realistic Adoption Plan
Phase 1: Enrichment only
- Deploy OTel and an eBPF agent.
- Join spans and kernel data; build dashboards and alerts.
- Establish secret redaction policies.
Phase 2: Assistant for diagnosis
- Add code indexing and a code-aware AI.
- Generate hypotheses and risk summaries, but no code changes.
Phase 3: PR suggestions
- Generate patches and tests as drafts. Humans review and copy if desired.
Phase 4: Auto-bisect and draft PRs
- Automate bisect and open draft PRs gated by code owners and CI.
Phase 5: Guarded merge and canary
- Allow merges that satisfy all policies and green checks, with controlled rollouts and auto-rollback.
At every phase, measure impact on time-to-diagnosis, PR lead time, and regression rates.
Conclusion
A production incident co‑pilot that pushes from logs to fixes is not magic. It is a careful integration of three capabilities:
- Observability that preserves causality and context via OpenTelemetry.
- System-level truth via eBPF to separate signal from speculation.
- A code reasoning loop that turns evidence into minimal, test-backed changes, with auto-bisect to ground truth and guarded PRs to keep humans in control.
Built correctly, this co‑pilot gives your team back their peak hours: less shovel work, faster first fixes, and cleaner audit trails. Most importantly, it reduces the distance between a user’s pain and the line of code that causes it. Start with enrichment, add the code brain, and keep safety first. The rest is glue.
Further reading:
- Dapper, Google’s distributed tracing paper, for span semantics and causality.
- eBPF references and CO-RE practices for safe, portable kernel instrumentation.
- OpenTelemetry semantic conventions for consistent, analyzable telemetry.
- LSIF and tree-sitter docs for building code graphs.
Ship the pipeline, run GameDays, and iterate. Your future incidents will thank you.