Telemetry-grounded autofix¶
Status: MVP. Operator-flagged off per source. Sentry / GlitchTip and GitHub Actions failure adapters ship with full dispatch wiring. Datadog, Loki, and custom-JSONL adapters ship as stubs - the audit chain records the would-be escalation but no agent is spawned.
The telemetry-grounded subsystem extends the autofix daemon so an error or alert from your observability stack can open a PR through the same ladder, cost cap, and audit primitives the CI-failure path already uses. The dispatch loop is identical to the CI flow:
- Webhook arrives at
/webhooks/telemetry/<source>/. - Adapter parses the payload into a
TelemetryEvent. - Grounding retriever pulls a window of recent log lines around the event fingerprint.
- A grounded goal is built and handed to the existing autofix dispatch hook.
- The outcome is recorded in the audit log.
Sources¶
| Source | Status | Endpoint | Secret env (default) |
|---|---|---|---|
sentry | full | /webhooks/telemetry/sentry/ | BERNSTEIN_SENTRY_WEBHOOK_SECRET |
gha_failure | full | /webhooks/telemetry/gha_failure/ | BERNSTEIN_GHA_WEBHOOK_SECRET |
datadog | stubbed | /webhooks/telemetry/datadog/ | BERNSTEIN_DATADOG_WEBHOOK_SECRET |
loki | stubbed | /webhooks/telemetry/loki/ | BERNSTEIN_LOKI_WEBHOOK_SECRET |
custom_jsonl | stubbed | /webhooks/telemetry/custom_jsonl/ | BERNSTEIN_CUSTOM_JSONL_WEBHOOK_SECRET |
The sentry adapter covers both Sentry SaaS and the Sentry-compatible self-hosted GlitchTip - the issue-alert webhook envelope is identical.
Configuration¶
Telemetry sources live under autofix.telemetry_sources in bernstein.yaml. Each entry is opt-in:
autofix:
cost_cap_per_pr: 1.0
telemetry_sources:
- source: sentry
enabled: true
endpoint: /webhooks/telemetry/sentry/
secret_env: BERNSTEIN_SENTRY_WEBHOOK_SECRET
fingerprint_path: "" # default: data.issue.id
cost_cap_usd: 0.20
- source: gha_failure
enabled: true
endpoint: /webhooks/telemetry/gha_failure/
secret_env: BERNSTEIN_GHA_WEBHOOK_SECRET
cost_cap_usd: 0.20
Fields:
| Field | Purpose |
|---|---|
source | One of sentry, gha_failure, datadog, loki, custom_jsonl. |
enabled | Master switch. The receiver still accepts the request when disabled, but the dispatcher records skipped. |
endpoint | Mount path the upstream should POST to. |
secret_env | Env var holding the shared HMAC secret. Empty disables signature checks (test-only). |
fingerprint_path | Optional dotted path into the payload that overrides the adapter's default fingerprint extraction. |
cost_cap_usd | Hard per-event cap. Zero refuses to spawn. |
Setup: Sentry / GlitchTip¶
- In Sentry / GlitchTip, create an Internal Integration or a project-level webhook with the Issue alert created trigger.
- Set the webhook URL to
https://<your-host>/webhooks/telemetry/sentry/. - Copy the integration's shared secret into the env var named in
secret_env(defaultBERNSTEIN_SENTRY_WEBHOOK_SECRET). - Flip
enabled: trueon thesentryentry inbernstein.yaml.
The adapter expects the standard issue-alert envelope:
{
"action": "created",
"data": {
"issue": {
"id": "12345",
"title": "ZeroDivisionError: division by zero",
"culprit": "app.handlers.divide in src/app.py",
"shortId": "BACKEND-1A",
"permalink": "https://sentry.example/issues/12345/",
"metadata": {"value": "Specific arithmetic failure"}
}
},
"project_slug": "backend"
}
data.issue.id becomes the fingerprint by default. Set fingerprint_path to override.
Setup: GitHub Actions failure¶
- In repo settings, add a webhook for the
workflow_runevent. - Set the URL to
https://<your-host>/webhooks/telemetry/gha_failure/. - Set the GitHub webhook secret and store it in
BERNSTEIN_GHA_WEBHOOK_SECRET. The adapter verifies via theX-Hub-Signature-256header. - Flip
enabled: trueon thegha_failureentry.
The adapter only dispatches when conclusion is failure, timed_out, or action_required. Success conclusions are accepted but skipped.
Setup: stubbed sources (Datadog / Loki / custom JSONL)¶
The webhook endpoints exist but every event is recorded with outcome stubbed until follow-up PRs land the production wiring. You can still configure the endpoints today to exercise the audit chain and verify your upstream signature setup:
- source: datadog
enabled: true
endpoint: /webhooks/telemetry/datadog/
secret_env: BERNSTEIN_DATADOG_WEBHOOK_SECRET
cost_cap_usd: 0.20
The stub adapter normalises the top-level fingerprint, title, message, repo, environment, and url fields, so a hand-rolled caller can POST a JSON object with those keys and watch the audit log fill in.
Grounding retrieval¶
This MVP ships one retriever: RecentJsonlLogRetriever. It tails a JSONL log file and returns the most-recent lines containing the event fingerprint. The retriever is bounded:
max_linesdefaults to 50 (one pytest traceback's worth).scan_bytesdefaults to 256 KiB; larger files are read from the tail.
Plug the retriever in at bootstrap:
from pathlib import Path
from bernstein.core.autofix.telemetry_grounded import (
RecentJsonlLogRetriever,
load_telemetry_settings,
)
from bernstein.core.routes.telemetry_webhooks import configure_receiver
settings = load_telemetry_settings()
retriever = RecentJsonlLogRetriever(Path(".sdd/traces/events.jsonl"))
configure_receiver(
app_state=app.state,
settings=settings,
retriever=retriever,
dispatch_hook=my_dispatch_hook,
audit=audit_log,
)
Trace and commit retrievers are tracked as follow-up tickets.
Cost cap¶
Every event consults its source's cost_cap_usd before the dispatch hook fires. A zero cap refuses to spawn. The dispatch hook is also re-checked post-hoc: if the underlying agent reports spending past the cap, the dispatcher flips the outcome to cost_capped so the operator can intervene without losing the audit trailer.
Audit trail¶
Every terminal outcome emits a single audit event:
event_type: autofix.telemetry.dispatch
actor: autofix-telemetry
resource_id: <source>:<fingerprint>
details: outcome, source, fingerprint, retriever_id,
cost_usd, commit_sha, reason, log_lines,
event_url, event_repo, event_environment
The outcome field is one of:
| Outcome | Meaning |
|---|---|
dispatched | Hook was invoked, dispatch succeeded under the cap. |
stubbed | Adapter parsed event but dispatch wiring is deferred. |
skipped | Source disabled or event had no fingerprint. |
cost_capped | Per-event cap refused the dispatch. |
errored | Dispatch hook raised; caught for daemon hygiene. |
The audit shape is identical across sources so the existing bernstein audit query tooling consumes it without modification.
Follow-ups¶
- Full Datadog Logs Webhook adapter (parses the embedded event body and pulls the log query URL).
- Loki Alertmanager adapter (parses the Alertmanager v2 envelope and groups on
groupKey). - Custom JSONL tail adapter (watches a file rotation instead of taking webhook deliveries).
- Multi-source fusion (correlate Sentry + Datadog into one event).
- Trace-store + git-history retrievers to complement the log retriever.