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.
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_finishedanalysis:run_finishedevents.AGENT_EXECUTION_FAILED+AGENT_EXIT_1.apps/daemon/src/claude-diagnostics.tscan produce useful human text, but it is not consistently converted into structured error classes forrun_finishedor dashboards.Current Signal Gap
run_finished.error_codeis currently too coarse:AGENT_EXECUTION_FAILEDmixes 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.agent_provider_id,model_id,os, etc., but cannot reliably explain the root cause.run_finished.properties.run_idcan theoretically correlate directly to the Langfuse trace. However, Langfuse metadata does not yet carry the same structurederror_code/ failure category fields, and PostHog does not carry the Langfuse-readable failure reason.Relevant current code paths:
apps/daemon/src/server.ts:/api/runshandler emitsrun_createdand schedulesrun_finishedviadesign.runs.wait(run).apps/daemon/src/run-result.ts: maps run terminal status toresultand derives fallbackerror_codevalues such asAGENT_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 withrun.idas 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:
Use the existing classifiers first:
classifyAgentServiceFailure(...)for auth / rate limit / upstream.classifyAgentAuthFailure(...)for agent-specific auth guidance.classifyAmrAccountFailure(...)forAMR_AUTH_REQUIREDandAMR_INSUFFICIENT_BALANCE.diagnoseClaudeCliFailure(...)for Claude-specific endpoint/auth/profile problems.AGENT_PROMPT_TOO_LARGEshould classify asprompt_too_largeanduser_action='reduce_context'.AGENT_EXIT_*andAGENT_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_finishedshould include the structured fields above. Langfuse trace metadata should include the same values so a PostHog event can be correlated to a trace byrun_id == traceId.Minimum correlation invariant:
run_finished.properties.run_idrun.idlangfuse_trace_id: run.idexplicitly 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_limitwhen the provider signal is retryable and not an account/quota hard stop.upstream_unavailable/ 5xx / network errors.Do not auto-retry:
Emit retry observability:
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:
Populate existing or planned
run_finishedduration fields where possible, includingtime_to_first_token_msandgeneration_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_balanceon 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_finishedfailed events includefailure_category,failure_stage,retryable, anduser_action.AGENT_EXECUTION_FAILED+AGENT_EXIT_*no longer dominate as the only root-cause fields for known auth/rate-limit/upstream/context failures.result === 'failed'still always carry a non-emptyerror_code.run_finishedhas a specific category or explicitunknownfallback.failure_category,failure_stage,agent_provider_id,model_id,os, and retry outcome.run_id/langfuse_trace_id.Suggested Validation
run-resultderivation.run_finishedevents in local smoke with telemetry enabled.Timeline From Planning Doc
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.