feat(otlp): capture coding-agent behavioral events into agent_telemetry - #548
Conversation
Claude Code emits four non-usage log events (tool_result, tool_decision, user_prompt, api_error) on the existing /v1/logs OTLP receiver; they were dropped for lacking token/model attributes. Map them, content-free, into a new agent_telemetry table kept separate from usage_logs. The full table schema (including the metric columns the /v1/metrics receiver will use) lands here, so the follow-up metrics PR is purely additive with no second migration. Refs mozilla-ai#429. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe change adds agent telemetry storage, maps allow-listed behavioral events from OTLP logs, persists validated records with deduplication, adds capture controls, and provides protected deletion with user cleanup. ChangesAgent telemetry lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/gateway/services/agent_telemetry_service.py (1)
158-167: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftBatch telemetry inserts instead of flushing each record.
ingest()flushes oneAgentTelemetryrow at a time inside the record loop and also opens a savepoint per row. Large OTLP exports will create sequential database round trips; batch the compatible rows and keep duplicate counting separate. PostgreSQL can useon_conflict_do_nothing; handle a single bulk insert failure into accepted/duplicate counts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/gateway/services/agent_telemetry_service.py` around lines 158 - 167, Update ingest() to collect compatible AgentTelemetry rows and perform one bulk PostgreSQL insert with on_conflict_do_nothing instead of flushing and creating a nested transaction per record. Preserve separate accepted and duplicate counts, and handle a bulk insert failure by assigning those counts appropriately rather than processing each row with individual database round trips.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/gateway/services/agent_telemetry_service.py`:
- Around line 84-97: Update event_dedup_key so zero-valued duration_ms,
status_code, and prompt_length remain distinct from absent values in the hashed
key. Replace the truthiness-based fallbacks for these fields with explicit None
handling, preserving 0 while still mapping None to the empty-string
representation.
---
Nitpick comments:
In `@src/gateway/services/agent_telemetry_service.py`:
- Around line 158-167: Update ingest() to collect compatible AgentTelemetry rows
and perform one bulk PostgreSQL insert with on_conflict_do_nothing instead of
flushing and creating a nested transaction per record. Preserve separate
accepted and duplicate counts, and handle a bulk insert failure by assigning
those counts appropriately rather than processing each row with individual
database round trips.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7fe57fb0-fabc-41f9-a847-a8cee8a880e1
📒 Files selected for processing (7)
alembic/versions/e8a7c6b5d4f3_add_agent_telemetry.pysrc/gateway/api/routes/otlp.pysrc/gateway/models/entities.pysrc/gateway/services/agent_telemetry_service.pytests/integration/otlp_helpers.pytests/integration/test_otlp_logs_behavioral.pytests/unit/test_agent_telemetry_mapping.py
Collapse 0 onto "" for duration_ms, status_code, and prompt_length in event_dedup_key so a zero-valued field no longer hashes identically to an absent one. Zero is a legitimate value and must remain distinct to avoid dropping rows on the ON CONFLICT dedup key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three defects in the new agent_telemetry ingest path, each reproduced against POST /v1/logs: - Claude Code emits tool_result's success as the string "true"/"false", not an OTLP boolValue, so the strict isinstance(bool) check stored NULL for the outcome field on every tool result. Read both encodings. - The user lookup did not filter deleted_at, so a live key whose user was soft-deleted kept storing telemetry while the usage path on the same request rejected its events. Use get_active_user, the same gate the usage path applies. - Behavioral events bypassed _MAX_EVENTS_PER_EXPORT: an 8 MiB body holds roughly 25k of them, each inserting a row inside its own savepoint. Apply the same per-export bound the usage path carries. Adds integration coverage for the first two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/gateway/api/routes/otlp.py (1)
476-477: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftBatch telemetry inserts in
agent_telemetry_service.ingest.This call invokes
ingest, which opens a nested transaction and flushes once for every telemetry record. A large valid export therefore creates one database round trip per row. Use a batched, conflict-tolerant insert while preserving accepted, duplicate, and rejected counts.As per coding guidelines, do not execute queries or deletes inside row loops; batch with
IN, bulk operations, or eager loading.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/gateway/api/routes/otlp.py` around lines 476 - 477, Update the telemetry ingestion flow called by the route around ingest_telemetry and agent_telemetry_service.ingest to collect records first, then perform a single batched conflict-tolerant insert instead of opening transactions and flushing per row. Preserve the existing accepted, duplicate, and rejected counts, and ensure no database queries or deletes occur inside the record loop.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/gateway/api/routes/otlp.py`:
- Around line 469-472: Update the OTLP export validation around the existing
pairs and telemetry ingestion flow to enforce _MAX_EVENTS_PER_EXPORT against
len(pairs) + len(telemetry), before either ingestion call; remove the
telemetry-only limit and add an integration test covering a mixed export that
exceeds the combined bound.
---
Outside diff comments:
In `@src/gateway/api/routes/otlp.py`:
- Around line 476-477: Update the telemetry ingestion flow called by the route
around ingest_telemetry and agent_telemetry_service.ingest to collect records
first, then perform a single batched conflict-tolerant insert instead of opening
transactions and flushing per row. Preserve the existing accepted, duplicate,
and rejected counts, and ensure no database queries or deletes occur inside the
record loop.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 27fb59c4-33c0-41fb-9b25-813480c459f7
📒 Files selected for processing (3)
src/gateway/api/routes/otlp.pysrc/gateway/services/agent_telemetry_service.pytests/integration/test_otlp_logs_behavioral.py
🚧 Files skipped from review as they are similar to previous changes (1)
- src/gateway/services/agent_telemetry_service.py
njbrake
left a comment
There was a problem hiding this comment.
Note: this review was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.
Thanks for splitting this out, the shape is right. I pushed three fixes to your branch (c693211) and CI is green on them. The rest below is yours to decide.
What I pushed
tool_result.successwas never persisted. Claude Code emitssuccessas the string"true"/"false"(Tool result event, in the Claude Code monitoring docs), soisinstance(success, bool)stored NULL for the outcome field on every tool result. Reproduced throughPOST /v1/logs. Every other field goes through_bounded_int, which tolerates string encodings;successwas the strict one, and it is the field documented as a string.- The user lookup in
ingest()did not filterdeleted_at, so a live key whose user was soft-deleted kept storing telemetry while the usage path on the same request rejected its events. It now usesget_active_user. - Behavioral events bypassed
_MAX_EVENTS_PER_EXPORT. An 8 MiB body holds roughly 25k of them, each inserting a row inside its own savepoint.
Integration coverage added for the first two; both fail on 3c40d80.
What I would like your read on
Dedup key. It hashes the timestamp plus the value fields, but Claude Code ships tool_use_id on tool_result and tool_decision, and event.sequence on every event. Without them, two parallel Read calls with the same duration in one timestamp bucket collide and the second is dropped as a duplicate. Worth settling before merge: dedup_key is stored under a unique constraint, so changing the derivation later leaves existing rows on the old format.
Metric-only columns. kind is always "event", and value, temporality, series_start, series_key are always NULL, with an index on series_key. Also cheaper to decide now. ADD COLUMN <nullable> is metadata-only in PG 11+, so "avoid a second migration" is a weaker argument than it looks.
Batching. Agreeing with CodeRabbit; external_usage_service._insert_rows is the existing pattern. Not blocking now that the cap bounds it.
Docs and opt-out. docs/use-with-claude-code.md tells operators the export carries no content, which stops being true here. There is also no flag to disable capture and no purge path: DELETE /v1/usage does not cover agent_telemetry, and user deletion SET NULLs the owner rather than removing rows. Fine to land with the metrics PR if the two merge together, not fine if this one lands alone.
Usage and behavioral events are disjoint by event.name, so checking each list against _MAX_EVENTS_PER_EXPORT on its own let one export persist twice the intended number of rows. Bound the total instead. The narrower check inside _ingest stays for /v1/traces, which has no behavioral path. Reported by CodeRabbit on c693211. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/use-with-claude-code.md`:
- Around line 137-138: Update the DELETE /v1/agent-telemetry documentation to
include the api_key_id filter and state that filter-based deletion requires
by_filter: true. Ensure the documented request contract or example enables valid
API-key-scoped purges alongside the existing ids and user/date filters.
In `@src/gateway/services/agent_telemetry_service.py`:
- Around line 188-215: Update _insert_same_source_batch at
src/gateway/services/agent_telemetry_service.py:188-215 and its fallback
handling at src/gateway/services/agent_telemetry_service.py:246-250 to use async
with db.begin_nested() and await db.flush() for every tentative batch or
individual-row insert instead of committing. Commit only once after all source
groups in ingest succeed, preserving duplicate detection and rollback behavior;
update commit-based tests and add regression coverage proving a later
SQLAlchemyError leaves no earlier telemetry rows persisted.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 77c9b2d7-4938-4a43-bc17-6feb3868ae85
⛔ Files ignored due to path filters (1)
docs/public/openapi.jsonis excluded by!docs/public/openapi.json
📒 Files selected for processing (17)
alembic/versions/e8a7c6b5d4f3_add_agent_telemetry.pydocs/public/otari.postman_collection.jsondocs/use-with-claude-code.mdscripts/sdk_codegen/sdk-endpoints.txtsrc/gateway/api/main.pysrc/gateway/api/routes/agent_telemetry.pysrc/gateway/api/routes/keys.pysrc/gateway/api/routes/otlp.pysrc/gateway/api/routes/users.pysrc/gateway/core/config.pysrc/gateway/models/entities.pysrc/gateway/services/agent_telemetry_admin_service.pysrc/gateway/services/agent_telemetry_service.pytests/integration/test_agent_telemetry_admin.pytests/integration/test_otlp_logs_behavioral.pytests/unit/test_agent_telemetry_ingest.pytests/unit/test_agent_telemetry_mapping.py
🚧 Files skipped from review as they are similar to previous changes (2)
- src/gateway/models/entities.py
- src/gateway/api/routes/otlp.py
|
Note: this comment was drafted by Claude. Thanks, this was helpful. I took the four follow-ups as:
I also added coverage for the new dedup behavior, capture override, purge paths, user cleanup, metric-column removal, and batched ingest behavior. |
A record repeated inside one export fails the bulk insert on (source, dedup_key), survives the re-query because nothing is stored yet, fails the retry, and drops the whole batch into the row-at-a-time fallback. Measured on a 201-event export carrying one repeat: 204 commits and 3 rollbacks, against 2 commits for a clean batch of 200. At the per-export ceiling that is roughly 10,000 sequential commits in one request. external_usage_service guards this with a seen_in_batch set before calling _insert_rows; the port carried _insert_rows over but not the guard. The stored projection is lossy by design, so two records can collapse onto one dedup key more readily than on the usage path, where source_event_id is a real unique id. Also document capture_agent_telemetry in the configuration settings table and the PATCH /v1/keys field list, and complete the documented DELETE /v1/agent-telemetry contract with api_key_id and by_filter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
njbrake
left a comment
There was a problem hiding this comment.
Note: this review was drafted by Claude Opus 5 via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.
Approving. I pushed one commit to this branch (064e30c) with a performance fix and three doc updates; nothing else is changed.
The fix. A record repeated inside a single export fails the bulk insert on (source, dedup_key), survives the re-query because nothing is stored yet, fails the retry, and drops the whole batch into the row-at-a-time fallback. Measured on a 201-event export carrying one repeat: 204 commits and 3 rollbacks, against 2 commits for a clean batch of 200. At the per-export ceiling that is roughly 10,000 sequential commits in one request. external_usage_service guards this with a seen_in_batch set before calling _insert_rows; the port brought _insert_rows over but not the guard. Added a regression test.
Docs. capture_agent_telemetry was missing from the settings table in configuration.md and from the PATCH /v1/keys field list in api-reference.md, and the documented DELETE /v1/agent-telemetry contract omitted api_key_id and by_filter.
Verified locally against PostgreSQL 18: the telemetry suites plus otlp, keys, users, config, and docs; make lint, make typecheck, make openapi-check, make postman-check; and the migration up, down, and up again with no autogenerate drift.
Two things left for you, neither blocking.
- Behavioral events take their timestamp from
time_unix_nanoonly, while usage events go through_resolve_timestamp, which prefers theevent.timestampattribute. An export with notime_unix_nanotherefore drops every behavioral event silently while usage still lands, and the two families sit on different clocks, which matters for the cost-per-outcome joins in #429. Changing it changes every dedup key, so it is your call. capture_agent_telemetryhas no dashboard surface, whilereject_user_mismatchhas a picker on the Keys page. Fine to land with the read view if that is the plan.
Needs a rebase for a one-line conflict in scripts/sdk_codegen/sdk-endpoints.txt.
On CodeRabbit's single-transaction comment: I disagree in scope. _insert_same_source_batch deliberately reproduces external_usage_service._insert_rows, which commits per batch, and telemetry rows carry no budget or spend. If savepoint semantics are wanted, both paths should move together.
Resolves the conflict in scripts/sdk_codegen/sdk-endpoints.txt by keeping both entries: main's GET /v1/usage/in-flight in the usage section, and this branch's DELETE /v1/agent-telemetry in its own section below it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@arthuursantos thank you for your work on this, nice job! |
…of spend Closes the remaining half of issue mozilla-ai#429. PR mozilla-ai#548 added the behavioral events on the logs signal; this adds the metrics signal Otari had no receiver for, plus the read API that gives recorded spend a denominator. - POST /v1/metrics: records the four outcome counters an agent reports and Otari has no other source for (lines of code, commits, pull requests, active time), content-free and non-billable. token.usage and cost.usage are skipped as already billed from the api_request usage event, and code_edit_tool.decision as already captured by tool_decision, so neither spend nor edit acceptance is ever double counted. Unrecognized names are accepted and skipped, so a newer agent version never breaks reception. - agent_telemetry gains five nullable metric columns (kind, value, temporality, series_start, series_key) and a (series_key, timestamp) index, the ones spec 002 deferred to this feature's own migration. Points are stored exactly as OTLP reported them; the cumulative to delta arithmetic happens at read time, split per series generation, so a re-exported total adds nothing and a counter reset never reads as negative work. - GET /v1/agent-telemetry/summary, /count, and /series, master key only, mirroring the /v1/usage family. Only summary joins usage, reporting cost per commit, per pull request, per line changed, spend per active hour, tool acceptance rate, turns per session, and error rate. It filters by session as well as user and API key, matching the session on both sides of the join (session_label against usage_logs.source_label). - Ingestion reuses the existing capture_agent_telemetry toggle and the batched ingest() pipeline: 1,000 data points cost one INSERT, asserted in the tests. The existing purge and user-deletion cleanup already cover metric rows. - Docs now tell operators to enable OTEL_METRICS_EXPORTER alongside OTEL_LOGS_EXPORTER, which are independent settings, and list exactly which metric fields are captured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…of spend Closes the remaining half of issue mozilla-ai#429. PR mozilla-ai#548 added the behavioral events on the logs signal; this adds the metrics signal Otari had no receiver for, plus the read API that gives recorded spend a denominator. - POST /v1/metrics: records the four outcome counters an agent reports and Otari has no other source for (lines of code, commits, pull requests, active time), content-free and non-billable. token.usage and cost.usage are skipped as already billed from the api_request usage event, and code_edit_tool.decision as already captured by tool_decision, so neither spend nor edit acceptance is ever double counted. Unrecognized names are accepted and skipped, so a newer agent version never breaks reception. - agent_telemetry gains five nullable metric columns (kind, value, temporality, series_start, series_key) and a (series_key, timestamp) index, the ones the behavioral-events PR reserved for this receiver rather than adding then. Points are stored exactly as OTLP reported them; the cumulative to delta arithmetic happens at read time, split per series generation, so a re-exported total adds nothing and a counter reset never reads as negative work. - GET /v1/agent-telemetry/summary, /count, and /series, master key only, mirroring the /v1/usage family. Only summary joins usage, reporting cost per commit, per pull request, per line changed, spend per active hour, tool acceptance rate, turns per session, and error rate. It filters by session as well as user and API key, matching the session on both sides of the join (session_label against usage_logs.source_label). - Ingestion reuses the existing capture_agent_telemetry toggle and the batched ingest() pipeline: 1,000 data points cost one INSERT, asserted in the tests. The existing purge and user-deletion cleanup already cover metric rows. - Docs now tell operators to enable OTEL_METRICS_EXPORTER alongside OTEL_LOGS_EXPORTER, which are independent settings, and list exactly which metric fields are captured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…of spend (mozilla-ai#567) * feat(telemetry): receive OTLP outcome metrics and read them per unit of spend Closes the remaining half of issue mozilla-ai#429. PR mozilla-ai#548 added the behavioral events on the logs signal; this adds the metrics signal Otari had no receiver for, plus the read API that gives recorded spend a denominator. - POST /v1/metrics: records the four outcome counters an agent reports and Otari has no other source for (lines of code, commits, pull requests, active time), content-free and non-billable. token.usage and cost.usage are skipped as already billed from the api_request usage event, and code_edit_tool.decision as already captured by tool_decision, so neither spend nor edit acceptance is ever double counted. Unrecognized names are accepted and skipped, so a newer agent version never breaks reception. - agent_telemetry gains five nullable metric columns (kind, value, temporality, series_start, series_key) and a (series_key, timestamp) index, the ones the behavioral-events PR reserved for this receiver rather than adding then. Points are stored exactly as OTLP reported them; the cumulative to delta arithmetic happens at read time, split per series generation, so a re-exported total adds nothing and a counter reset never reads as negative work. - GET /v1/agent-telemetry/summary, /count, and /series, master key only, mirroring the /v1/usage family. Only summary joins usage, reporting cost per commit, per pull request, per line changed, spend per active hour, tool acceptance rate, turns per session, and error rate. It filters by session as well as user and API key, matching the session on both sides of the join (session_label against usage_logs.source_label). - Ingestion reuses the existing capture_agent_telemetry toggle and the batched ingest() pipeline: 1,000 data points cost one INSERT, asserted in the tests. The existing purge and user-deletion cleanup already cover metric rows. - Docs now tell operators to enable OTEL_METRICS_EXPORTER alongside OTEL_LOGS_EXPORTER, which are independent settings, and list exactly which metric fields are captured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): count a cumulative counter's first reading, and bound the summary scan Review fixes on the OTLP metrics receiver. A cumulative series was diffed pairwise, so the first reading of every generation was dropped. An OTel counter exports no data point until its first measurement, so that reading already carries work: three commits read as two, and a session whose only export carried one commit read as zero, inflating cost per commit and cost per line. A generation whose series_start falls inside the window is now diffed from its own zero, which is what a cumulative counter reads at its series start. One that began earlier has no known baseline, so its first in-window reading stays a level, as before. Also: - Bound /summary's metric scan at 200k data points and fail closed past it. The delta arithmetic runs in Python, so unlike the aggregates beside it this read grows with the number of exports rather than with the window. - Return None from _point_value when a NumberDataPoint sets neither value arm, instead of proto3's default 0, which inside a cumulative series reads as a reset the series start never announced. - Pin offset-less date bounds to UTC in core.sql.utc_bound, shared by the telemetry read filters and the purge selection so the count an operator confirms and the delete that re-derives it mean the same instant. asyncpg encodes timestamptz with astimezone, which reads a naive datetime as local. - Apply _SKIPPED_METRICS, which was defined but never read, so adding an already-billed metric to the recorded set cannot double count spend. - Note in the /summary docstring that the spend side is every usage row in scope, not only the agent's. - Update the capture_agent_telemetry row in docs/configuration.md, which still described logs-signal behavioral events alone, list the OTLP metrics receiver in docs/external-usage.md, and account for claude_code.session.count in the skipped-metrics paragraph. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Nathan Brake <NathanBrake@users.noreply.github.com>
Description
Claude Code emits four non-usage log events (
tool_result,tool_decision,user_prompt,api_error) on the OTLP logs signal Otari already receives at/v1/logs. They were dropped because they carry no token/model attributes. ThisPR maps them, content-free, into a new
agent_telemetrytable kept separate fromusage_logs.Because behavioral events ride the existing receiver, this PR is cheap to land and
settles the full
agent_telemetryschema up front (including the columns themetrics receiver will use), so the follow-up metrics PR is purely additive with no
second migration.
Only allow-listed, typed fields are persisted (tool name, decision, success,
duration, status code, prompt length); prompts, responses, and user identity are
never stored. Ingestion is idempotent via a natural dedup key.
PR Type
Relevant issues
Part of #429 (paired with the metrics receiver PR, which closes it).
Checklist
tests/unit,tests/integration).make lint,make typecheck,make test)./v1/logs, so the spec is unchanged.)AI Usage
AI Model/Tool used: Claude Code (Claude Opus 4.8)
Any additional AI details you'd like to share:
Used to split an existing feature branch into two stacked PRs and rebase the new migration onto the current
mainAlembic head.NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)
Summary
/v1/logsreceiver.agent_telemetrytable.This preserves useful agent activity data without storing sensitive content. Usage logging and billing remain unchanged.