Skip to content

Reliability push: classify run failures, correlate PostHog with Langfuse, and add safe retries #3408

Description

@yinjialu

Why

We need to drive the observed chat run failure rate from ~15% to <=8% and make every failed run explainable to both the product team and the user.

Source planning doc: https://powerformer.feishu.cn/wiki/KHqiw4QlRiOqBdk5SPAckSWSn0b

Current baseline from the doc and PostHog run_finished analysis:

  • Window: 2026-05-12 to 2026-05-30, n ~= 179k run_finished events.
  • Overall failure rate: 14.75%.
  • Failures are systemic across engines, not isolated to one agent: claude ~14.8%, codex ~14.3%, opencode ~12%, gemini ~16.8%.
  • ~92% of failures are effectively not root-cause readable. For claude / codex / opencode, about 75% of failures collapse into AGENT_EXECUTION_FAILED + AGENT_EXIT_1.
  • Existing diagnostic logic such as apps/daemon/src/claude-diagnostics.ts can produce useful human text, but it is not consistently converted into structured error classes for run_finished or dashboards.

Current Signal Gap

run_finished.error_code is currently too coarse:

  • AGENT_EXECUTION_FAILED mixes auth, quota/rate limit, upstream 5xx, empty output, model/service failures, tool/runtime failures, and other generic execution failures.
  • AGENT_EXIT_1, AGENT_EXIT_41, AGENT_SIGNAL_SIGTERM, etc. describe process termination shape, not the user-meaningful reason.
  • PostHog can show where failure volume concentrates by agent_provider_id, model_id, os, etc., but cannot reliably explain the root cause.
  • Langfuse already carries per-run trace detail and natural-language error/output context. The trace id is the run id, so run_finished.properties.run_id can theoretically correlate directly to the Langfuse trace. However, Langfuse metadata does not yet carry the same structured error_code / failure category fields, and PostHog does not carry the Langfuse-readable failure reason.

Relevant current code paths:

  • apps/daemon/src/server.ts: /api/runs handler emits run_created and schedules run_finished via design.runs.wait(run).
  • apps/daemon/src/run-result.ts: maps run terminal status to result and derives fallback error_code values such as AGENT_EXIT_*.
  • apps/daemon/src/runtimes/auth.ts: already has reusable auth/rate-limit/upstream text classifiers.
  • apps/daemon/src/claude-diagnostics.ts: produces richer Claude failure diagnostics and retryability.
  • apps/daemon/src/langfuse-bridge.ts / langfuse-trace.ts: sends completed run traces to Langfuse with run.id as trace id.
  • apps/web/src/runtime/amr-guidance.ts: maps known failure codes to user actions such as retry, authorize, recharge, or switch to AMR.

Proposal

1. Add a canonical failure classification layer at the run terminal path

At the single child-close / run-finalization choke point, classify failures into user- and dashboard-readable categories before emitting run_finished.

Suggested fields:

failure_category:
  | 'auth'
  | 'rate_limit'
  | 'insufficient_balance'
  | 'model_unavailable'
  | 'prompt_too_large'
  | 'upstream_unavailable'
  | 'timeout'
  | 'empty_output'
  | 'tool_error'
  | 'process_exit'
  | 'user_cancel'
  | 'unknown'

failure_stage:
  | 'preflight'
  | 'spawn'
  | 'session_init'
  | 'model_select'
  | 'prompt_send'
  | 'first_token_wait'
  | 'tool_execution'
  | 'artifact_write'
  | 'child_close'
  | 'finalize'

retryable: boolean
user_action: 'retry' | 'login' | 'recharge' | 'switch_model' | 'reduce_context' | 'install_cli' | 'none'
langfuse_trace_id: run.id

Use the existing classifiers first:

  • classifyAgentServiceFailure(...) for auth / rate limit / upstream.
  • classifyAgentAuthFailure(...) for agent-specific auth guidance.
  • classifyAmrAccountFailure(...) for AMR_AUTH_REQUIRED and AMR_INSUFFICIENT_BALANCE.
  • diagnoseClaudeCliFailure(...) for Claude-specific endpoint/auth/profile problems.
  • Prompt-size guards such as AGENT_PROMPT_TOO_LARGE should classify as prompt_too_large and user_action='reduce_context'.

AGENT_EXIT_* and AGENT_SIGNAL_* should remain as fallback diagnostics but not be the primary root-cause category when stderr/stdout/diagnostics imply a better class.

2. Emit the same classification to PostHog and Langfuse

PostHog run_finished should include the structured fields above. Langfuse trace metadata should include the same values so a PostHog event can be correlated to a trace by run_id == traceId.

Minimum correlation invariant:

  • PostHog: run_finished.properties.run_id
  • Langfuse: trace id = same run.id
  • PostHog should also emit langfuse_trace_id: run.id explicitly for discoverability.

This lets us analyze aggregate failure trends in PostHog, then inspect representative traces in Langfuse without guessing.

3. Add safe automatic retry for transient failures

Automatically retry 1-2 times only for transient classes:

  • rate_limit when the provider signal is retryable and not an account/quota hard stop.
  • upstream_unavailable / 5xx / network errors.
  • short first-token / empty-output failures before any tool call or artifact write.

Do not auto-retry:

  • auth required
  • AMR insufficient balance
  • prompt too large / context overflow
  • model unavailable
  • missing CLI / agent unavailable
  • user cancellation
  • runs that have already performed tool writes or produced artifacts, unless the retry path is explicitly idempotent

Emit retry observability:

run_retry_attempted
run_retry_finished
retry_of_run_id
retry_attempt_index
retry_strategy
retry_result
retry_suppressed_reason

4. Add main-path segment timing

The doc calls out that P90 latency is currently ~566s for successful tasks, but the current data cannot explain where the time goes. Add stage timestamps/durations for:

  • queued
  • process spawn
  • session init
  • model selection
  • prompt send
  • first token
  • generation
  • tool execution aggregate
  • artifact reconciliation/finalization

Populate existing or planned run_finished duration fields where possible, including time_to_first_token_ms and generation_duration_ms.

5. Update user-facing failure guidance

Make the chat error card and AMR guidance consume the new classification fields where available:

  • auth -> login / authorize CTA.
  • rate_limit -> retry when transient, or suggest switching to AMR / another model when quota-like.
  • insufficient_balance on AMR -> recharge CTA.
  • prompt_too_large -> explain context is too large and suggest reducing selected context / switching to stdin-capable adapter.
  • model_unavailable -> choose another model.
  • upstream_unavailable -> retry CTA.

Acceptance Criteria

  • run_finished failed events include failure_category, failure_stage, retryable, and user_action.
  • Langfuse trace metadata includes the same failure fields plus the existing run id correlation.
  • AGENT_EXECUTION_FAILED + AGENT_EXIT_* no longer dominate as the only root-cause fields for known auth/rate-limit/upstream/context failures.
  • Failed runs with result === 'failed' still always carry a non-empty error_code.
  • Unknown/unclassified failure share is measurable and alertable.
  • Safe transient failures retry automatically with bounded attempts and retry telemetry.
  • Non-retryable failures produce clear user-facing guidance.
  • Unit tests cover the failure classifier and the invariant that every failed run_finished has a specific category or explicit unknown fallback.
  • A PostHog dashboard can show failure rate by failure_category, failure_stage, agent_provider_id, model_id, os, and retry outcome.
  • A Langfuse trace can be opened from a failed PostHog run using run_id / langfuse_trace_id.

Suggested Validation

  • Add daemon unit tests around classification helpers and run-result derivation.
  • Replay mock CLI traces for representative failures: auth, rate limit, upstream 5xx, prompt too large, timeout/SIGTERM, empty output.
  • Confirm PostHog receives classified run_finished events in local smoke with telemetry enabled.
  • Confirm Langfuse trace metadata mirrors the same classification.
  • Use a fixed task set as a release guard: success rate, P90, token usage, unknown failure share.

Timeline From Planning Doc

  • 2026-06-01 EOD: failure classification, main-path segment telemetry, automatic retry.
  • 2026-06-03 EOD: 100% failures attributable enough to produce an ROI-ranked optimization list.
  • 2026-06-08 EOD: first high-ROI fixes.
  • 2026-06-10 EOD: first optimization release plus second medium-ROI release.
  • By 2026-06-12: validate first retry effect with new data and put regression gates in place.

Notes

The first engineering move should be to avoid adding more distributed failure emitters. The planning doc explicitly recommends keeping classification and timing at one run-terminal choke point, with a test that prevents new code paths from bypassing structured failure reporting.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions