Skip to content

feat(tasks): inject agent OTLP telemetry into cloud sandboxes - #71100

Merged
tatoalo merged 26 commits into
masterfrom
posthog-code/agent-run-otel-telemetry
Jul 23, 2026
Merged

feat(tasks): inject agent OTLP telemetry into cloud sandboxes#71100
tatoalo merged 26 commits into
masterfrom
posthog-code/agent-run-otel-telemetry

Conversation

@tatoalo

@tatoalo tatoalo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Cloud task agent telemetry needs an opt-in path from the sandbox to PostHog Logs and Tracing. The backend did not have safe configuration plumbing for the OTLP endpoints and project token, and fresh runs and snapshot resumes needed identical behavior.

Changes

Before:

flowchart LR
    Agent{{Cloud agent}} --> API[Run log API]
    API --> Session[(Session log)]
    classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    class Agent phBlue;
    class API phRed;
    class Session phGray;
Loading

After:

flowchart LR
    Settings[Backend settings] --> Agent{{Cloud agent}}
    Agent --> API[Run log API]
    API --> Session[(Session log)]
    Agent --> Logs[OTLP logs]
    Agent --> Traces[OTLP traces]
    classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    class Agent phBlue;
    class API,Logs,Traces phRed;
    class Settings phYellow;
    class Session phGray;
Loading
  • Add optional sandbox settings for the OTLP logs URL, traces URL, and project token. Telemetry remains disabled unless the logs URL and token are both configured.
  • Inject the agent-specific environment variables into fresh and snapshot-resumed sandboxes.
  • Reserve the variables so task-provided sandbox configuration cannot override them.
  • Support local agentsh firewall allowlisting and Docker host URL rewriting.
  • Document the local configuration and add coverage for gating, reservation, and both sandbox creation paths.

Also carried on this branch: scout run log mirror (#71094)

This branch now incorporates the scout run log mirror from #71094 (credit @andrewm4894), squashed from that PR's final state so both delivery paths ship together:

  • TaskRun.append_log emits one structured stdout line per persisted entry (event=task_run_log); the per-cluster OTel collector that already ships container stdout delivers them into the region's internal project's Logs, with request_id (the run uuid) becoming the trace id.
  • Scoped by TASK_RUN_LOGS_MIRROR_ORIGIN_PRODUCTS (default: signals_scout only; empty disables). Mirror failures are logged and never break the run's log write.
  • On top of feat(tasks): mirror scout run logs into posthog logs #71094, this branch adds a direct OTLP delivery leg (TASK_RUN_LOGS_MIRROR_OTLP_URL/_TOKEN, default unset). The token pins the destination to PostHog's own internal logs project — scout runs execute for customer teams, and their mirrored transcripts must never land in (or bill) a customer's project. It is also the only delivery path in local dev, where append_log runs in the host Django process whose stdout the dev collector (docker containers only) never tails.
  • Complementary postures: the mirror carries readable bodies for PostHog-authored scout runs into the internal project, while the OTLP path above stays metadata-only and covers customer-driven cloud runs.
  • Protocol setup envelopes and metadata-only updates are mirrored as type labels rather than raw payloads, so configuration headers never enter Logs.

If #71094 merges separately first, the duplicated commit here should be dropped on the next rebase.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Playwright — all passed

All tests passed.

View test results →

⚠️ Backend snapshots — 11 updated (11 modified, 0 added, 0 deleted)

Query snapshots: Backend query snapshots updated

Changes: 11 snapshots (11 modified, 0 added, 0 deleted)

What this means:

  • Query snapshots have been automatically updated to match current output
  • These changes reflect modifications to database queries or schema

Next steps:

  • Review the query changes to ensure they're intentional
  • If unexpected, investigate what caused the query to change

Review snapshot changes →

⚠️ Backend coverage — 82.0% of changed backend lines covered — 76 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ████████████████░░░░ 82.0% (371 / 447)

File Patch Uncovered changed lines
products/tasks/backend/temporal/process_task/tests/test_utils.py 0.0% 3, 1072–1073, 1077, 1081–1082, 1087, 1089–1091, 1093, 1097, 1101–1102, 1107, 1109, 1111, 1115, 1119–1120, 1125, 1127
products/tasks/backend/temporal/process_task/tests/test_provision_sandbox.py 0.0% 202–205, 226, 229, 231, 236, 238, 241–245, 247, 252, 254
products/tasks/backend/temporal/process_task/activities/provision_sandbox.py 0.0% 348–349
products/tasks/backend/temporal/process_task/activities/tests/test_get_task_processing_context.py 0.0% 42–43, 54–56
products/tasks/backend/temporal/process_task/utils.py 11.1% 1152–1153, 1155, 1165–1167, 1171–1172
products/tasks/backend/temporal/process_task/activities/get_task_processing_context.py 25.0% 381–382, 384–386, 388–389, 394, 795
products/tasks/backend/feature_flags.py 72.7% 46–48
products/tasks/backend/temporal/client.py 88.9% 124, 152–153
products/tasks/backend/logic/services/run_log_mirror.py 94.9% 190–192, 198, 208–209, 281

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30028222937 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.6% 8,414 / 21,249
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks ██████████████░░░░░░ 69.4% 30,000 / 43,235
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
signals ████████████████░░░░ 79.8% 21,628 / 27,098
cdp ████████████████░░░░ 81.0% 3,144 / 3,881
data_modeling █████████████████░░░ 82.7% 5,547 / 6,708
wizard █████████████████░░░ 84.8% 935 / 1,102
notebooks █████████████████░░░ 85.3% 7,275 / 8,531
agent_platform █████████████████░░░ 86.4% 3,807 / 4,405
actions █████████████████░░░ 86.6% 717 / 828
cohorts ██████████████████░░ 87.8% 4,488 / 5,114
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
exports ██████████████████░░ 88.4% 6,949 / 7,861
data_warehouse ██████████████████░░ 88.9% 11,887 / 13,378
business_knowledge ██████████████████░░ 89.0% 4,391 / 4,936
conversations ██████████████████░░ 89.3% 16,820 / 18,834
engineering_analytics ██████████████████░░ 89.4% 6,371 / 7,130
dashboards ██████████████████░░ 89.4% 5,983 / 6,693
visual_review ██████████████████░░ 89.4% 5,818 / 6,505
error_tracking ██████████████████░░ 89.7% 10,174 / 11,342
alerts ██████████████████░░ 89.9% 4,054 / 4,508
early_access_features ██████████████████░░ 90.1% 1,031 / 1,144
mcp_analytics ██████████████████░░ 90.1% 2,763 / 3,065
streamlit_apps ██████████████████░░ 90.4% 2,501 / 2,767
links ██████████████████░░ 90.6% 183 / 202
slack_app ██████████████████░░ 90.7% 9,028 / 9,951
marketing_analytics ██████████████████░░ 91.0% 11,792 / 12,964
stamphog ██████████████████░░ 91.1% 4,056 / 4,450
mcp_store ██████████████████░░ 91.9% 4,257 / 4,634
notifications ███████████████████░ 92.6% 1,011 / 1,092
product_analytics ███████████████████░ 92.7% 5,811 / 6,268
ai_observability ███████████████████░ 92.7% 14,995 / 16,168
surveys ███████████████████░ 93.0% 5,730 / 6,163
managed_migrations ███████████████████░ 93.1% 1,424 / 1,530
web_analytics ███████████████████░ 93.1% 14,398 / 15,461
posthog_ai ███████████████████░ 93.2% 1,326 / 1,422
approvals ███████████████████░ 93.3% 3,437 / 3,682
reminders ███████████████████░ 93.4% 468 / 501
workflows ███████████████████░ 93.6% 6,390 / 6,826
legal_documents ███████████████████░ 94.1% 1,568 / 1,667
endpoints ███████████████████░ 94.1% 8,640 / 9,177
messaging ███████████████████░ 94.3% 2,682 / 2,845
tracing ███████████████████░ 94.5% 2,670 / 2,826
revenue_analytics ███████████████████░ 94.6% 3,651 / 3,858
skills ███████████████████░ 94.6% 3,035 / 3,207
review_hog ███████████████████░ 94.7% 6,814 / 7,199
logs ███████████████████░ 95.4% 9,935 / 10,416
experiments ███████████████████░ 95.7% 25,036 / 26,159
growth ███████████████████░ 96.1% 3,245 / 3,376
annotations ███████████████████░ 96.2% 732 / 761
replay_vision ███████████████████░ 96.2% 15,094 / 15,691
feature_flags ███████████████████░ 96.5% 17,110 / 17,734
user_interviews ███████████████████░ 96.5% 2,638 / 2,734
access_control ███████████████████░ 96.8% 851 / 879
customer_analytics ███████████████████░ 97.1% 9,047 / 9,317
warehouse_sources ███████████████████░ 97.1% 329,165 / 338,906
data_catalog ███████████████████░ 97.4% 2,367 / 2,429
analytics_platform ████████████████████ 98.0% 2,153 / 2,197
metrics ████████████████████ 98.2% 2,491 / 2,536
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

ℹ️ Docs preview — preview build triggered

Docs from this PR will be published at posthog.com.

Project Preview Updated (UTC)
posthog.com Open preview Jul 23, 2026, 5:10 PM

The preview should be ready in about 10 minutes. Open the preview at /handbook/engineering/.

@tatoalo
tatoalo marked this pull request as ready for review July 23, 2026 11:38
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 23, 2026 11:39
Comment thread products/tasks/backend/logic/services/run_log_mirror.py
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Security Review

Customer-controlled sandbox subprocesses inherit the reusable internal telemetry project key, allowing arbitrary writes to that project's logs and traces.

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
products/tasks/backend/temporal/process_task/utils.py:1167-1168
**Telemetry credential reaches user processes**

When telemetry is configured, this helper places its project API key in the sandbox environment inherited by customer-controlled commands, allowing those commands to exfiltrate the key and submit arbitrary logs or traces that corrupt internal telemetry and consume its ingestion allowance.

### Issue 2 of 2
products/tasks/backend/logic/services/run_log_mirror.py:175-180
**OTLP request blocks log appends**

When the configured OTLP endpoint is slow or unavailable, this synchronous request blocks the Django worker handling `append_log` for the connect and read timeout, delaying or timing out the agent's persisted log-delivery path during telemetry-ingest degradation.

Reviews (1): Last reviewed commit: "refactor(tasks): simplify run log mirror..." | Re-trigger Greptile

Comment thread products/tasks/backend/temporal/process_task/utils.py
Comment thread products/tasks/backend/logic/services/run_log_mirror.py
Comment thread products/tasks/backend/models.py
@veria-ai

veria-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 4 · PR risk: 0/10

Comment thread REPORT.md Outdated
Comment thread products/tasks/backend/temporal/process_task/utils.py
tatoalo added a commit to PostHog/code that referenced this pull request Jul 23, 2026
…g it

The sandbox delivers POSTHOG_AGENT_OTEL_LOGS_TOKEN via the container
environment, which agent-spawned tool subprocesses inherit - so any `env`
dump in a persisted transcript or PR body would leak the key (flagged by
review bots on PostHog/posthog#71100).

bin.ts is the only consumer: parse it into the server config, then delete
it from process.env so children never see it. Defense in depth, not a
boundary - same-UID processes can still read the container's initial env
via /proc; the key itself remains a write-only capture token scoped to a
dedicated telemetry project.

No test: a one-line env mutation at the CLI entry with no cheap seam;
exercising it would mean spawning the CLI end to end.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
tatoalo added 14 commits July 23, 2026 17:06
Companion to PostHog/code#posthog-code/agent-run-otel-telemetry, which adds
OTLP export of cloud-run metadata (logs + APM spans) to the agent-server.

New optional settings SANDBOX_AGENT_OTEL_LOGS_URL/_TOKEN and
SANDBOX_AGENT_OTEL_TRACES_URL are mapped to POSTHOG_AGENT_OTEL_* sandbox env
vars by a shared helper called from both env assembly paths (fresh
provisioning and snapshot resume). The names are deliberately not standard
OTEL_* so OTel SDKs in user code running in the sandbox never auto-export
into the telemetry project; the keys are reserved so user sandbox env vars
cannot override them. Local dev is covered by adding the URLs to the agentsh
debug firewall settings and the Docker localhost rewrite list; prod egress is
already allowed via *.posthog.com. Telemetry stays off unless URL + token are
both set; the traces URL additionally enables APM spans.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…t and root-status fixes

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Mirrors the PostHog/code telemetry changes: run errors export provenance
only (generic body, error_source/stop_reason attributes - the raw message
stays in the session log and the run's error_message), flush/shutdown are
best-effort and per-signal independent with a 5s export timeout, a run
error marks still-open tool spans as interrupted, and the deprecated
OtelTransportConfig stubs are kept for published-package compatibility.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…minal-point fix

Mirrors the PostHog/code change: the task_run root span is now ended and
exported at the run's in-process terminal point (background prompt
settled, terminal failure, or interactive close) instead of relying on
sandbox teardown, which never delivers SIGTERM to the exec'd agent-server
process. Documents the interactive hard-teardown limitation.

Stacked on posthog-code/agent-run-otel-telemetry (pure fast-forward).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Incorporates the scout run log mirror from PR #71094 (credit: Andrew
Maguire, @andrewm4894) into the agent-run telemetry branch, squashed from
the PR's final state so both delivery paths ship together.

Persisted task-run log entries are mirrored into the PostHog Logs product,
scoped to scout runs. There is no transport of its own: TaskRun.append_log
emits one structured stdout line per persisted entry (event=task_run_log)
and the per-cluster OTel collector that already tails container stdout
ships them into the region's internal project's Logs, parsing JSON keys
into queryable attributes and request_id (the run uuid) into a trace id so
one run groups as one trace.

- run_log_mirror.py translates each ACP JSONL entry: readable bodies for
  agent messages / tool calls / sandbox output / turn ends (8k char cap,
  bounded entries per call), severity mapping, and run-identity fields as
  log attributes
- TaskRun.append_log calls the mirror after the S3 write, guarded so any
  failure is logged and never breaks the run
- TASK_RUN_LOGS_MIRROR_ORIGIN_PRODUCTS setting gates which task origins
  mirror (default signals_scout; empty disables)

Complementary to the OTLP run-telemetry export on this branch: the mirror
carries full readable bodies for PostHog-authored scout runs into the
internal project, while the OTLP path stays metadata-only and covers
customer-driven cloud runs.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…rror

Documents the incorporated scout run log mirror (#71094) alongside the
OTLP run-telemetry export: architecture, the two pipelines' differing
privacy postures and scopes, changed files, and the new
TASK_RUN_LOGS_MIRROR_ORIGIN_PRODUCTS setting in the configuration
reference. Also applies ruff formatting to test_utils.py after the
rebase conflict resolution.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Local testing showed the mirrored scout-run logs (readable agent message
bodies, tool calls, sandbox output) never reach the Logs UI in dev: the
mirror emits structured stdout lines for the cluster OTel collector to
pick up, but locally append_log runs in the host Django process and
otel-collector-config.dev.yaml only tails docker-compose container
stdout - so the lines land in the phrocs pane and go nowhere. Only the
agent-server's own OTLP metadata records were visible in /logs.

Add an optional direct delivery leg: when TASK_RUN_LOGS_MIRROR_OTLP_URL
and _TOKEN are set, each mirrored batch is also POSTed as OTLP JSON to
that logs endpoint (service.name=task-run-log-mirror, run uuid as the
trace id - the same request_id -> trace mapping the production collector
applies - and the structlog fields as attributes). Both settings default
unset, so production keeps the zero-transport stdout path; delivery
failures are logged and never raise into the run.

Tests: payload shape (severity mapping, trace id, int attribute
encoding) and a parameterized guard that no HTTP call happens unless
both settings are set - protecting the hot append_log path from
accidental sync network calls in production.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…y leg

Drops the mirror-specific TASK_RUN_LOGS_MIRROR_OTLP_URL/_TOKEN settings:
in local dev there is one logs destination anyway, and the agent
telemetry settings (SANDBOX_AGENT_OTEL_LOGS_URL/_TOKEN) already point at
it - so the mirror's direct delivery leg now reuses them, gated on
DEBUG. With the agent telemetry configured locally, mirrored scout runs
reach /logs with zero extra configuration.

The DEBUG gate is what makes the reuse safe: in production those
settings point at the customer-facing telemetry project and the
collector daemonset already delivers pod stdout to the internal project,
so an unconditional reuse would double-deliver scout bodies into the
wrong project the moment the telemetry rollout sets them. A test pins
that production never posts even with the settings configured.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
The mirror's direct OTLP leg is meant to run in production, not just
dev: it delivers scout run transcripts into PostHog's own internal logs
project. The dedicated TASK_RUN_LOGS_MIRROR_OTLP_URL/_TOKEN settings are
restored (replacing the short-lived DEBUG-gated reuse of the agent
telemetry settings) and the DEBUG gate is dropped.

The token is the mechanism that keeps customer projects out of it: scout
runs execute for customer teams, but capture-logs routes records by the
Bearer key, so a centrally configured internal-project key means the
mirrored transcripts only ever land in - and bill - our own project,
never the customer's. That destination differs from the agent-telemetry
project SANDBOX_AGENT_OTEL_LOGS_TOKEN addresses, which is why the
settings stay separate.

Unset disables the direct leg; the stdout emission for the collector
remains either way. Delivery is one POST per persisted batch, scout runs
only, 3s timeout, and never raises into the run.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
tatoalo added 7 commits July 23, 2026 17:06
Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…d input

Review findings from a subagent pass over the mirror module:

- The OTLP POST runs synchronously inside append_log, and a scalar
  requests timeout applies to connect and read independently - a slow
  connect followed by a stalled read could block the append for ~6s.
  Use a (1, 3) connect/read tuple to bound the worst case.
- acp_method, acp_session_update, and entry_timestamp were forwarded
  uncapped from the semi-trusted append_log request; an oversized value
  could push the stdout line past the collector's 100 KB drop threshold,
  silently discarding the whole record. Cap them at 200 chars.
- _extract_text recursed without a depth bound, so an adversarially
  nested content list would RecursionError and abort mirroring for the
  entire batch. Bound it at 10 levels.

Tests: identifier-cap and deep-nesting regressions, an OTLP-leg privacy
test pinning that protocol payloads never reach the payload and that the
attribute set is closed, and a negative gating row for the
snapshot-resume env path (logs pair missing -> no OTel keys).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Simplify-pass cleanups, no behavior change:

- pass the already-derived session update dict into _body instead of
  re-parsing the notification per entry
- hoist the batch-constant trace fields and event attribute out of the
  per-record loop in _post_otlp
- drop the Z-suffix replace in _time_unix_nano (fromisoformat handles it
  natively on our Python)

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
REPORT.md was a working artifact for the agent session, not part of the
change itself.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
origin_product was API-settable to signals_scout (only image_builder was
rejected), so any task:write caller could forge a scout task whose
appended run logs would be mirrored into PostHog's internal Logs project
once the mirror ships - flagged by the security review bots on the PR.

Scout tasks are created exclusively server-side by the signals scout
harness via the tasks facade, so treat signals_scout like image_builder:
an internal-only origin the public serializer rejects. With creation
server-attested, mirroring_enabled() can keep gating on origin_product.
The mirror is new in this PR, so no forged tasks can predate the check
where it matters.

Test: parameterized serializer-level SimpleTestCase over the internal-only
origins (verified to fail without the fix); the existing image_builder
endpoint test remains the wiring guard.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
@tatoalo
tatoalo force-pushed the posthog-code/agent-run-otel-telemetry branch from 154de31 to 82042b7 Compare July 23, 2026 16:06
Comment thread products/tasks/backend/models.py
Comment thread products/tasks/backend/logic/services/run_log_mirror.py
tatoalo added a commit to PostHog/code that referenced this pull request Jul 23, 2026
…g it

The sandbox delivers POSTHOG_AGENT_OTEL_LOGS_TOKEN via the container
environment, which agent-spawned tool subprocesses inherit - so any `env`
dump in a persisted transcript or PR body would leak the key (flagged by
review bots on PostHog/posthog#71100).

bin.ts is the only consumer: parse it into the server config, then delete
it from process.env so children never see it. Defense in depth, not a
boundary - same-UID processes can still read the container's initial env
via /proc; the key itself remains a write-only capture token scoped to a
dedicated telemetry project.

No test: a one-line env mutation at the CLI entry with no cheap seam;
exercising it would mean spawning the CLI end to end.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Telemetry was env-gated only (SANDBOX_AGENT_OTEL_* /
TASK_RUN_LOGS_MIRROR_ORIGIN_PRODUCTS): all-or-nothing per region, with a
deploy needed to change anything - no gradual rollout and no runtime
kill switch.

Add tasks-agent-run-otel-telemetry, evaluated org-targeted once at
dispatch and stamped into run state, following the
sandbox-event-ingest pattern (_capture_sandbox_event_ingest_flag is
generalized to _capture_run_feature_flags, stamping both flags in one
fetch + one atomic mutate). The stamp keeps the decision stable across
retries and resumes.

The flag gates both legs, AND'ed with the existing env settings:
- sandbox OTel env injection - fresh provisions read the new
  TaskProcessingContext.agent_otel_telemetry_enabled field (state
  override -> DEBUG -> flag), snapshot resumes pass it into
  build_sandbox_environment_variables
- the scout run-log mirror - reads the state stamp directly, so the
  append_log hot path never evaluates a flag remotely

Fail-closed everywhere: missing stamp, missing flag, or evaluation
error means telemetry stays off. DEBUG bypasses the flag (the analytics
SDK is disabled locally), so local dev remains purely env-gated.
Deploys are inert until the flag is created and ramped.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Comment thread products/tasks/backend/models.py
Comment thread products/tasks/backend/temporal/client.py
tatoalo added 3 commits July 23, 2026 17:31
Two Python code quality (mypy) failures on the branch:

- run_log_mirror.mirror_entries typed entries as list[dict], making the
  defensive non-dict guard "unreachable" under warn_unreachable. The
  guard is load-bearing (entries arrive from a semi-trusted request
  payload), so type the parameter list[Any] to match reality.
- the flag-gating commit accidentally injected
  AGENT_OTEL_TELEMETRY_STATE_KEY as the first positional argument of the
  keep-stream-open feature_enabled call (an over-broad substring
  replacement), which mypy flagged as a duplicate distinct_id. Restore
  the call.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Review bots flagged that agent_otel_telemetry_enabled was not in
_PROTECTED_RUN_STATE_KEYS, so a same-team task:write caller could PATCH
a run's state to force the stamp true and bypass the org rollout flag -
injecting the internal OTLP capture token into their sandbox on resume
and re-enabling the run-log mirror with the rollout off.

Add the shared AGENT_OTEL_TELEMETRY_STATE_KEY constant to the protected
set, and sandbox_event_ingest_enabled alongside it - the sibling rollout
stamp had the same gap (a caller could flip the event transport branch
mid-run). Dispatch (_capture_run_feature_flags) remains the sole writer.

Extends the existing protected-state endpoint test: both stamps in the
attack payload and assertions, and the telemetry stamp in the
state_remove_keys leg.

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
Three counters so the cloud-background-agents-runs Grafana dashboard can
verify the telemetry rollout:

- posthog_tasks_agent_otel_telemetry_stamped_total{enabled} - first-time
  rollout stamps at dispatch; the enabled=true share tracks the
  tasks-agent-run-otel-telemetry flag ramp on new runs
- posthog_tasks_run_log_mirror_entries_total{origin_product} - entries
  the run-log mirror actually emitted
- posthog_tasks_run_log_mirror_otlp_batches_total{outcome} - direct-OTLP
  mirror delivery outcomes (local-dev leg; stays flat in cloud)

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
…annotations

Three fixes for the review comment + red CI on the branch:

- _is_agent_otel_telemetry_enabled checked the state stamp before DEBUG,
  but local dev always stamps False (the analytics SDK is disabled
  there), so the DEBUG bypass was dead and local sandboxes would never
  get the OTel env even with SANDBOX_AGENT_OTEL_* set - flagged by
  graphite-app, and inconsistent with the mirror's
  agent_otel_telemetry_enabled_for_state. DEBUG now wins first at both
  gates; pinned by a parametrized precedence test.
- test_captures_sandbox_event_ingest_flag_before_starting_workflow
  patched the shared posthoganalytics module attribute and asserted a
  single feature_enabled call; the dispatch capture now evaluates two
  flags, so the test asserts both stamps and both calls.
- TestAppendLogMirroring needed class-level organization/team
  annotations for the setUpTestData attributes (mypy attr-defined,
  the "Python code quality" failure).

Generated-By: PostHog Code
Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
@tatoalo
tatoalo enabled auto-merge (squash) July 23, 2026 17:49
@tatoalo
tatoalo merged commit 48d972a into master Jul 23, 2026
332 of 334 checks passed
@tatoalo
tatoalo deleted the posthog-code/agent-run-otel-telemetry branch July 23, 2026 18:03
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-23 18:41 UTC Run
prod-us ✅ Deployed 2026-07-23 19:10 UTC Run
prod-eu ✅ Deployed 2026-07-23 19:14 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants