Skip to content

spike(observability): run the live tracing backend bake-off and decide #1280

Closed

Description

@mashkovd

Context

mctlhq/mctl-gitops#903 was investigated as a single programme: rubric, paper screen, trace fixtures, collector fan-out, an evaluation namespace, deploying several candidates, a live soak, scoring and an ADR. One implementer PR cannot carry that, and several steps need a merge and a live cluster between them.

The split follows the point where evidence stops being reproducible offline. #903 keeps the rubric, the paper screen, the representative trace fixtures and the fan-out harness, all of which are code and render tests. This issue owns everything that needs candidates actually running.

Scope

  1. Create the evaluation sandbox namespace.
  2. Deploy each candidate that survives the paper screen.
  3. Turn on the collector fan-out to the deployed candidates.
  4. Measure over a fixed, declared soak window: trace reconstruction, search and correlation, token/tool/error/cost visibility, privacy, producer isolation.
  5. Fill every cell of the frozen rubric from a measurement, then promote the decision into the ADR and the runbook.
  6. Tear down every non-selected candidate and the sandbox.

Acceptance

  • Every rubric cell is filled from an observation, and any cell that could not be measured says so instead of being scored.
  • The soak window and the trace volume are declared before the measurements, not chosen afterwards.
  • One backend is selected in the ADR with the evidence that decided it.
  • Nothing from the bake-off is left running.

Depends on

  • mctlhq/mctl-gitops#903 — the rubric, the fixtures and the fan-out harness this measures with.

Related

  • Roadmap epic mctlhq/.github#55

Candidate/approach F — Grafana Agent Observability / agento11y (added 2026-09-24)

Carry candidate F from #903 into the live comparison rather than treating the offline screen as sufficient.

Evaluate two shapes separately:

  1. Self-managed Grafana-native path

    • supported agent/runtime instrumentation via agento11y where feasible;
    • OTLP traces/metrics → existing mctl OTel Collector;
    • Tempo/Grafana as the trace UI/store path;
    • metadata-only capture by default.
  2. Grafana Cloud Agent Observability path

    • conversation/session and coding-agent UX;
    • token/cost/eval visibility;
    • optional guards only as defense-in-depth;
    • explicitly measure Cloud dependency, data egress/residency, pricing and portability.

The acceptance bar is not "it works with interactive Claude Code". The bake-off must state whether agento11y can observe the production claude-agent-sdk DevLoop execution path without becoming a Grafana-specific application dependency. If it cannot, record that limitation and score the approach accordingly.

mctl runtime policy/approval (mctlhq/mctl-agents#197/#198) remains authoritative; Grafana guards are not a replacement governance boundary.

Preconditions / current blockers

Before token/cost evidence is considered valid:

The bake-off may prepare namespaces/manifests while those prerequisites are being closed, but the final scoring must use correctly exported live telemetry.

Activity

  1. added
    agents:intakeFeed this issue to the mctl-agents Temporal pipeline
    on Sep 24, 2026
  2. removed
    agents:intakeFeed this issue to the mctl-agents Temporal pipeline
    on Sep 24, 2026
  3. mctl-agents commented on Sep 24, 2026

    @mctl-agents
    Contributor

    mctl-agents issue-investigator has analyzed this issue and created a proposal:

    https://github.com/mctlhq/mctl-gitops/tree/main/platform-gitops/agents-state/mctl-gitops/proposals/issue-1280-spike-observability-run-the-live-tracing/

    Status: proposed — pending human approval. Review requirements.md, design.md and tasks.md, then approve: signal this issue's DevLoopWorkflow (mctl-api POST /api/v1/agents/dev-loop/dev-loop-mctlhq-mctl-gitops-1280/approve, or python -m orchestrator.temporal.cli approve dev-loop-mctlhq-mctl-gitops-1280 --approver <your-identity>) — the workflow flips .status.yaml to accepted via the mctl-agents-approve operation and runs the Tier 2 implementer. If no DevLoopWorkflow is running for this issue (pre-Temporal proposal), run the mctl-agents-approve operation directly with service=mctl-gitops slug=issue-1280-spike-observability-run-the-live-tracing.

    The approver is recorded in .status.yaml and in the gitops commit, and the implementer refuses a proposal whose control.requires_human_approval carries no named approver — so prefer the mctl-api endpoint, which takes the approver from the authenticated caller rather than from what the caller types.

  4. added a commit that references this issue on Sep 24, 2026
  5. mashkovd commented on Sep 24, 2026

    @mashkovd
    MemberAuthor

    Approved by the owner on 2026-09-24 via the DevLoop approve signal, after the proposal was amended with binding cost and egress constraints in #1351 (c9f3143):

    • PVC quota of 4 / 40Gi, ephemeral storage preferred;
    • one wave at a time, fully torn down between waves;
    • sandbox life of 14 days or less;
    • Grafana Cloud only with an owner-provisioned Vault token, and only for fixture spans;
    • adopting a winner is a separate PR that needs owner approval.

    See the "Owner constraints" section of design.md.

  6. mashkovd commented on Sep 24, 2026

    @mashkovd
    MemberAuthor

    Re-scoped by owner decision (2026-09-24). The first implementer run on this issue ended no-commits, because the proposal mixed reproducible config work with live-cluster measurement.

    Precondition #1332 (token redaction) still gates valid token/cost evidence.

  7. mashkovd commented on Oct 10, 2026

    @mashkovd
    MemberAuthor

    Synthetic span proof — PASSED (soak window starts 2026-10-10T11:55Z)

    Tempo eval deployed by #1764 (Tempo 2.10.8, single binary, R2 bucket tempo-traces). Runbook step 4.3, one submission of otel-trace-fixture (workflow otel-trace-fixture-proof-l8jgm, Succeeded 11:48:57Z):

    Check Result
    collector receiver accepted 240 spans
    collector → Tempo exporter sent / failed all sent, 0 failed
    tempo_distributor_spans_received_total 240
    tempo_ingester_traces_created_total 20
    tempo_discarded_spans_total 0
    tempo_ingester_blocks_flushed_total / failed_flushes_total 1 / 0 (first block in R2 at ~11:55Z)
    R2 backend requests all 2xx
    Grafana datasource "Tempo (eval)" provisioned yes (uid tempo-eval)
    Search through the Grafana datasource proxy 10 devloop.run traces from mctl-agents
    Trace a9486b83e86d32dd2b8942eb731a1cf6 12 spans, 1 root, 0 orphans: complete parent/child tree

    Declared soak (ADR 0001 amendment, accepted with #1764): window from this proof until 2026-10-23 23:59 UTC; volume synthetic only, < 5,000 spans total; real producer traffic none. Teardown date 2026-10-24.

    Defect found during the proof: the collector labels its Tempo exporter telemetry otlp_grpc/eval-tempo, so the two collector-side alerts could never fire. Fix: #1765.

  8. added a commit that references this issue on Oct 10, 2026
  9. mashkovd commented on Oct 11, 2026

    @mashkovd
    MemberAuthor

    Soak record — observed volume reconciled (2026-10-11 06:10Z)

    The soak-start comment above declares "real producer traffic none". That was true at 11:55Z. The ADR's Producer amendment: DevLoop traces (recorded before any producer emitted) then enabled the mctl-agents DevLoop producer at 2026-10-10 20:18:43Z. Counters have read 252 accepted / 252 sent since 20:41Z, so this note accounts for every span instead of leaving 240 vs 252 unexplained.

    Full enumeration of Tempo eval since 2026-10-10 11:00Z: 23 traces, 252 spans, matching tempo_distributor_spans_received_total = 252 exactly.

    Source Traces Spans service.name Workload Span time
    Synthetic fixture otel-trace-fixture-proof-l8jgm 20 (devloop.run, 12 spans each) 240 mctl-agents argo-workflows fixture pod 11:38–11:48Z
    Real producer (amended), DevLoop exec worker 3 12 admins-mctl-agents-worker-exec-base-service pod …-worker-exec-base-service-5bfcc57847-mqgqx 20:19–20:41Z

    The 12 producer spans are 6 RunActivity:submit_and_wait spans and 6 argo.workflow mctl-agents-shepherd submit spans. They come from three DevLoops whose Temporal runs started before enablement but scheduled shepherd activities after it:

    trace_id Temporal workflow Run start (UUIDv7) Spans
    b2cfbbca71c55a264912fd7f9de3002c dev-loop-mctlhq-mctl-api-537 19:32:25Z 4
    45a0ca72449b49d90d899259188691b5 dev-loop-mctlhq-mctl-api-536 19:04:17Z 6
    9be69f5847d13f44928c6cbeb9fe4afc dev-loop-mctlhq-mctl-agent-162 18:57:19Z 2

    Notes:

    Declared vs observed

    Item Value
    Synthetic fixture 240 of the < 5,000 synthetic cap. One submission so far; "at most once a day" allows more.
    Real producer 12 of the 500,000 amendment cap
    No producer spans since 20:41Z
    Eligible investigate/implement DevLoops started since enablement none as of 06:02Z

    Stop rule (amendment), checked 06:05Z — no trigger

    • Cap: 12 of 500,000.
    • Discards: tempo_discarded_spans_total = 0.
    • Collector: refused 0, Tempo exporter queue 0, accepted = sent (252 = 252).
    • Tempo memory: working set ≤ 163 MiB of the 1 GiB limit (about 16%).
    • Ingester: 6 blocks flushed, 0 failed flushes.
    • R2 backend, last 24h: PUT 200 ×488, GET 200/206, DELETE 204, POST 200. The only non-2xx responses are 6 × GET 404, consistent with object-existence probes. No 5xx.
    • Leak check: this runs on the first eligible exported trace (fix(alertmanager): route all alerts through mctl-agent only #195 item 11).

    The soak window and teardown date are unchanged: 2026-10-23 23:59Z, teardown 2026-10-24. Nothing here requires restarting the soak.

  10. added a commit that references this issue on Oct 12, 2026
  11. mashkovd commented on Oct 13, 2026

    @mashkovd
    MemberAuthor

    Soak record: mid-window checkpoint (2026-10-13 12:30Z)

    The soak is still in progress. The window, volume, stop rules and teardown date are unchanged: the window ends 2026-10-23 23:59Z and teardown is 2026-10-24. This is not a verdict. It records evidence in a durable place before teardown removes Tempo.

    Volume: every span accounted for

    Source: Tempo /api/search over 2026-10-10 11:00Z → now, plus /api/v2/traces/<id> for every hit.
    Result: 43 traces, 1,840 spans. That equals tempo_distributor_spans_received_total, Collector otelcol_receiver_accepted_spans{receiver="otlp"}, and otelcol_exporter_sent_spans{exporter="otlp_grpc/eval-tempo"}, all 1,840.

    The Collector pods have been up for about 72.6h, so the counters cover the whole window without a reset.

    Source Traces Spans Counted as
    Synthetic fixture otel-trace-fixture-proof-l8jgm (service.name=mctl-agents) 20 240 synthetic, 240 of < 5,000
    DevLoop runs that started before enablement (2026-10-10 20:18:43Z; admins-mctl-agents-worker-exec-base-service, pre-#1791) 3 12 producer cap, not eligible as evidence
    DevLoop, eligible (mctl-agents-worker 178, mctl-agents-investigator 708, mctl-agents-implementer 691) 17 1,577 producer cap
    mctl-agent incident tickets (service.name=mctl-agent) 3 11 producer cap (#1860 amendment)

    Producer cap: 1,600 of 500,000.

    Accounting notes:

    • The mctl-agent amendment (docs(adr): record mctl-agent producer amendment for #1280 soak #1860, merged 2026-10-12 20:12Z) lists 2 traces / 7 spans as observed before the record. A third ticket trace, 5dfa4b390bc96ff4af79d89819a43c0 (4 spans, 20:01Z), also predates the merge. It is inside the same cap and the same rules; this note only completes the enumeration.
    • There have been no producer spans since 2026-10-12 ~20:05Z. The two mctl-agents-implement runs at 21:04Z and 21:31Z (9cf3d81c, 883394eb) were not submitted by a traced DevLoop, so MCTL_TRACE_REQUIRE_PARENT=true kept them inert, as declared.

    Stop rules (DevLoop + mctl-agent amendments): none triggered

    Rule Observed
    Producer cap 1,600 / 500,000
    tempo_discarded_spans_total increase 0 for every reason (live_traces_exceeded, trace_too_large, unknown_error)
    Collector exporter alerts (TempoEvalCollectorExportFailures, TempoEvalCollectorDroppedSpans, OtelCollector*) never fired over the last 3 days. send_failed = 0, enqueue_failed = 0, refused = 0, queue_size max 0 of 5,000
    Tempo working set > 80% of 1 GiB peak working set 484 MiB (47%), peak RSS 456 MiB (44.5%)
    Leak check 0 hits (method below)

    Operations (rubric operations sources)

    • Tempo pod tempo-0: 0 restarts, uptime about 3d.
    • Ingester: 88 blocks flushed, 0 failed flushes.
    • Block list: 20.
    • tempodb_compaction_errors_total and tempodb_retention_errors_total: 0.
    • tempo_receiver_refused_spans: 0.
    • CPU: 0.009 cores average, 0.28 peak (5m rate), against a 1-core limit.
    • R2 over the last 3d: 200 ×7,971, 204 ×670, 206 ×42,048, 404 ×88, and no 5xx. The 404 count equals the block flush count, which is consistent with existence probes.
    • Quota observability-eval-quota: used 1 pod, 100m/512Mi requests, 1/1Gi limits, 0 PVCs, against a hard limit of 4 pods, 1/1536Mi requests, 2/2Gi limits.
    • TempoEval* and OtelCollector* rules are present in vmalert.

    Trace reconstruction and search (rubric trace_reconstruction)

    • All 17 eligible DevLoop traces have exactly one unresolved parent: the documented virtual root, which is never exported. There are no other orphans.
    • The 20 fixture traces and the 3 mctl-agent traces each have one root and 0 orphans.
    • TraceQL through Tempo's API (over a port-forward, 3-day range):
    Query Traces Latency
    {span.mctl.workflow.id="dev-loop-mctlhq-mctl-telegram-757"} 1 2.6s
    {span.mctl.repository.name="mctlhq/mctl-api"} 10 3.0s
    {span.mctl.issue.number=586} 1 1.8s
    {span.gen_ai.agent.name="issue-investigator"} 17 2.8s
    {status=error} 27 2.9s
    {span.mctl.argo.workflow.name=~"mctl-agents-implement-.*"} 16 2.9s
    {resource.service.name="mctl-agent" && span.mctl.ticket.type="resource_limit"} 2 2.5s
    {span.gen_ai.request.model="claude-opus-5-5"} | sum(span.gen_ai.usage.output_tokens) > 0 17 3.4s
    • Trace by id: 2.6s for 431486061c652efb376a860d9d656f25 (80 spans) and 3.5s for 551bb5d8b7569d152be3ddc5801f8c9 (252 spans).
    • Reference traces, still queryable today:
      • DevLoop end to end: 164ac97d8d7264326c406240d263dcc1 (mctl-agents#195 evidence).
      • Failed-flow correlation: 431486061c652efb376a860d9d656f25 (mctl-agent#97 evidence). The chain is incident argo-mctl-agents-implement-dae24112-1791756910 → workflow mctl-agents-implement-dae24112 → ERROR span ImplementerOrphanedSubagent → Loki [trace] anchor. mctl_get_incident returns this trace_id with trace_source=workflow_audit since mctl-api 4.82.0.
      • mctl-agent diagnosis: 3b54b8b36a8a3334173dc4cad2458e83 (cpu_throttle).

    Privacy: leak check method and result

    • Method: every attribute (resource, span and span-event), every status.message and every span name of all 1,840 spans was scanned with these patterns:
      • Bearer … or any key containing authorization;
      • gh[pousr]_… / github_pat_…;
      • sk-… / sk-ant-…;
      • JWT (eyJ….….);
      • PEM private key headers;
      • password|secret|api_key = …;
      • Slack and Telegram bot token shapes;
      • AKIA…;
      • e-mail addresses.
    • Keys were also checked against a prompt-like name list: prompt, completion, input/output.messages, tool.call.arguments/result, message.text/body/content, *.body, command, args, arguments.
    • Result: 0 pattern hits and 0 prompt-like keys across 77 distinct keys. The longest attribute value is 53 characters (an Argo pod name).
    • Token counts (gen_ai.usage.input_tokens, output_tokens, cache_read.input_tokens, cache_creation.input_tokens) are present as integers, not masked. The token substring in those keys is a usage count, not a credential.
    • Exception events carry only a type and a short message, e.g. ImplementError / "proposal scope too large for one PR".

    Backend neutrality

    • Dependencies: mctl-agent and mctl-api go.mod, and mctl-agents pyproject.toml/uv.lock, contain only go.opentelemetry.io/* / opentelemetry-* and OTLP exporters. There is no Tempo, Grafana or vendor SDK.
    • Code: "Tempo" appears in those repositories only in prose (a tool description, a comment and docs).
    • Endpoints:
      • Every producer exports to http://otel-collector.monitoring.svc.cluster.local:4318: admins-mctl-agent, the mctl-agents-worker-exec/-implement Deployments, and the mctl-agents-investigate/-implement WorkflowTemplates.
      • Tempo is reachable only as the Collector exporter otlp_grpc/eval-tempo, next to debug.
      • So replacing the backend is a Collector/GitOps change.

    Still to measure before the verdict

    • security_privacy: runbook §6 also asks for "a look at what Tempo stores for the poisoned fixture devloop-trace-redaction.json". It has not been submitted yet: the only fixture submission used the default devloop-trace.json. One submission is about 240 spans and fits the declared synthetic volume (at most once a day, under 5,000 in total). Submitting it needs an operator.
    • Producer isolation under a backend fault: no Tempo or R2 fault has occurred during the window, so this cell can only be "not observed". It will not be manufactured.
    • Bucket blocks readable as Parquet (data_ownership_portability): not yet checked directly.
    • Grafana-side checks (datasource search and the Loki ↔ Tempo links) were last verified in the proof and in chore(agents-state): flip mctl-openclaw/upgrade-to-2026-4-27 to rejected #97. They are re-checked before the verdict.

    The rubric cells, the decision rule and the ADR Decision section are filled only after 2026-10-23 23:59Z.

  12. mashkovd commented on Oct 13, 2026

    @mashkovd
    MemberAuthor

    security_privacy synthetic measurement: poisoned fixture (run once)

    Run: Argo workflow otel-trace-fixture-redaction-v96lz (label mctl.ai/purpose=issue-1280-security-privacy), fixture devloop-trace-redaction.json, 2026-10-13 13:26:45Z → 13:27:15Z, Succeeded. Parameters: execution_count=10, 4 spans per trace. The production producers and the collector config were not touched.

    Volume: 10 traces × 4 spans = 40 spans submitted. Synthetic total is now 280 spans, inside the declared < 5000 budget. This is the only run, so the once-per-day limit holds.

    Counter (VM) before 13:26:32Z after 13:28:00Z Δ
    collector accepted spans 1840 1880 +40
    collector refused spans 0 0 0
    exporter otlp_grpc/eval-tempo sent 1840 1880 +40
    exporter debug sent 1840 1880 +40
    exporter send failed / enqueue failed no series no series 0
    Tempo distributor received spans 1840 1880 +40
    tempo_discarded_spans_total 0 0 0
    Tempo flushes / failed flushes 88 / 0 89 / 0 +1 / 0

    Dropped or rejected spans: none. Tempo TraceQL {span.mctl.execution_id="exec-poisoned-redaction-fixture"} returns exactly 10 traces (root devloop.run, service mctl-agents), and fetching all of them gives 40 spans.

    Redaction result: PASS.

    Span Planted attribute Rule expected to fire Stored in Tempo
    gen_ai.chat gen_ai.prompt.0.content (fabricated prompt text) key pattern gen_ai.prompt ****, redaction.masked.count=1
    mcp.tool.call mcp.tool.arguments (fabricated args) key pattern mcp.tool.arguments ****
    mcp.tool.call mctl.artifact.note (fake ghp_-shaped string under a neutral key) value pattern ghp_ ****, redaction.masked.count=2 on the span
    github.request http.request.header.authorization (fabricated bearer) key pattern http.request.header.* / authorization ****, redaction.masked.count=1

    I ran a substring scan over the raw JSON of all 10 stored traces. Each of FABRICATED, ghp_, Bearer, fabricated-not-a-real and the fake token body appears 0 times. The value-pattern case passing matters most: it shows that a secret-shaped value under an innocuous key is still caught. The debug exporter output in both collector pod logs since 13:26Z mentions the execution id 10 times (7 + 3), so the spans did pass through it. The same strings appear 0 times there, and the logs contain no error, drop or refusal lines.

    Secrets: every planted value was fabricated for this fixture. The ghp_ value is a sequential placeholder (A1b2C3…) that has never been a GitHub token, and the prose values say "FABRICATED, NOT A REAL …" in plain text. No real credential, prompt or customer data was used.

    Stop rules after the run: cap 1880 / 500,000. No discard increase, no exporter alert, no leak-check hit. The soak continues unchanged to 2026-10-23T23:59Z. I won't run the fixture again unless the owner approves another run.

  13. mashkovd commented on Oct 22, 2026

    @mashkovd
    MemberAuthor

    Soak closed early: final measurements (cutoff 2026-10-22 17:00Z)

    Caveat first. The owner closed the window on 2026-10-22 for operator capacity, 30 h 59 min before the declared end of 2026-10-23 23:59Z. The actual window was 2026-10-10 11:55Z to 2026-10-22 17:00Z, 293 h 05 min or 90.4% of the declared 324 h 04 min. No stop rule had fired and no measurement prompted the decision. It is recorded as a retro amendment in ADR 0001 ("Window amendment"), and the Decision opens with it.

    Ingestion and the stop rules (counters at 17:00Z)

    • Collector accepted_spans 7,170 = sent_spans to otlp_grpc/eval-tempo 7,170 = Tempo distributor_spans_received_total 7,170. Refused 0. Discarded 0, for every reason. send_failed / enqueue_failed have no series. The exporter queue stayed at 0.
    • Synthetic spans: 240 from the proof, 40 from the poisoned fixture and 1 manual release-tail.export-check span (2026-10-16 13:47Z, origin not recorded) make 281 of the 5,000 allowed. Producer spans were about 6,890 of the 500,000 cap.
    • Tempo flushed 1,266 blocks with 0 failed flushes. The blocklist holds 197. Compaction and retention errors were 0. 0 restarts.
    • Tempo's peak working set in the window was 484 MiB (RSS 456 MiB) against a 1 GiB limit. Average CPU was 0.008 cores, peak 0.28. Quota usage: 1 pod, 100m/512Mi, 0 PVC.
    • R2 over the window: PUT 200 ×18,391, PUT 500 ×1 (2026-10-17 08:37Z, retried), GET 200/206 ×84,472, GET 404 ×1,538, DELETE 204 ×10,477. TempoEvalObjectStorageErrors was pending 08:37–08:41 and never fired. No alert fired inside the window.

    Post-soak checks (read-only)

    • R2 Parquet readability. The bucket holds 874 objects (30.35 MB) and 218 blocks, 200 of them live. Two blocks were downloaded and decoded with pyarrow, without Tempo: the newest and the largest (6 traces, 353 spans, compaction level 3). Both are vParquet4. The bucket lifecycle read returned AccessDenied for the token in use, so the 30-day backstop was not re-read.

    • Grafana tempo-eval search over the window, as two 168 h halves:

      Query Traces Time
      worker 53 7.6–12.1 s (under concurrent load)
      investigator 48 2.8–3.7 s
      {status=error} 42 2.7–3.0 s
      mctl-agent 72 2.5–3.2 s
      repository 18 2.5–2.8 s
      token-usage sum 48 3.3–5.4 s

      Every query shape tried enumerates the same 139 traces, and no query adds a trace.

    • Loki → Tempo. 50 log lines with trace_id in 12 h. 4 opened through the derived field (2–45 spans, 1.6–11.2 s).

    • Tempo → Loki. tracesToLogsV2 (namespace, pod, ±1 m) resolves a DevLoop trace's pods to their log lines.

    • Privacy, final scan. Every attribute key and value of every fetched span, the poisoned fixture's traces included, was scanned. The fixture was not rerun. No credential, token or e-mail was found. The only pattern hits are the fixture's own authorization key with value ****. gen_ai.prompt.0.content ×10 and mcp.tool.arguments ×11 are all ****. The longest value is 63 characters.

    Findings that cut against the result

    • Partial reads. Fetching all 139 traces by id with a bounded range (1 h before to 100 h after the start) returned 5,092 of 6,930 in-window spans. Per UTC day the counts match exactly from 10 to 14 October and fall short from 15 October. 9 of the 48 traces that search matches on the investigator service come back without any investigator span. The unbounded fetch that would settle it timed out, and it was not repeated (next point). The cause is unexplained. The ingest counters show no loss, so this is a read-path completeness limit, and it is why trace reconstruction scores 4, not 5. The privacy scan covers the returned spans only.
    • The read path under load. After the cutoff, those bulk trace-by-id reads throttled the 1-CPU pod to 80% and pushed its working set to 898 MiB, past the 80% stop-rule line, with R2 range reads timing out. There was no restart, and ingestion was unaffected (0 discarded, 0 failed flushes, accepted = sent). This happened outside the window and was caused by the verdict's own reads, but it is the main sizing lesson for adoption.
    • Not observed. Producer isolation under a backend outage: Tempo and R2 never became unavailable, and no outage was staged. Backup/restore, retention deletion and upgrades were not exercised either.

    Verdict

    ADOPT TEMPO / mctl-native only, 315/500 on the Tempo column (4, 2, 4, 3, 4, 0). This is decision-rule branch 3: Tempo's AI/agent score of 2 does not beat the openclaw-llm-usage dashboard baseline, also 2, by more than one point. The other candidates were not deployed (scope amendment), so their cells are cited nulls. The ADR, rubric and runbook are updated in the verdict PR.

  14. added a commit that references this issue on Oct 22, 2026
  15. mashkovd commented on Oct 22, 2026

    @mashkovd
    MemberAuthor

    Closed with the verdict ADOPT TEMPO / mctl-native only (315/500), merged in #2071 (c880817). The soak window was shortened by owner decision for operator capacity: it ended 2026-10-22 17:00Z instead of 2026-10-23 23:59Z, 30 h 59 min short (90.4% of the declared window). This is recorded in ADR 0001's Window amendment and leads the Decision. The evidence is in #1280 (comment).

    Follow-ups:

  16. added 2 commits that reference this issue on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions