Skip to content

fix(agent): rely on Pi's retry for a dropped stream, track it, explain it - #1363

Draft
gewenyu99 wants to merge 342 commits into
mainfrom
posthog/functional-b6-stream-retry
Draft

gewenyu99 wants to merge 342 commits into
mainfrom
posthog/functional-b6-stream-retry

Conversation

@gewenyu99

@gewenyu99 gewenyu99 commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Wizard runs on Pi sometimes lose their connection to the model mid-response, and one drop fails the whole run. It hits about 0.53% of runs, roughly 53 people over the past couple of months, so it's most likely network related. Pi has its own auto-retry, but the wizard can't see it: nothing records a retry, and a run that fails after one just says upstream closed the stream.

Related: #1320. Seen on main's Smoke Test run 36152887782, which failed task 7/10 on upstream closed the stream.

Changes

This relies on Pi's own retry rather than adding a second one. The wizard now tracks every Pi retry, sends the ones that give up to error tracking, and explains a dropped stream clearly. With that data, you can see how often drops happen and whether Pi recovers them.

  • agent turn retried event. One capture per Pi retry, with the attempt and error.
  • Pi retry give-ups in error tracking. captureException with step: 'pi_auto_retry', skipped on a cancel.
  • Clearer dropped-stream failure. Points at the network or gateway, suggests trying again later.
sequenceDiagram
  participant W as Wizard
  participant P as Pi
  participant G as AI gateway

  W->>P: run the task
  P->>G: stream the reply
  G-->>P: connection dropped

  rect rgba(59, 130, 246, 0.15)
    loop Pi auto-retry, when its pattern matches, up to 3 times
      P-->>W: auto_retry_start, captured as agent turn retried
      P->>G: re-send without the cut-off turn
      G-->>P: reply, or dropped again
    end
  end

  alt recovered
    P-->>W: turn done
  else gave up
    P-->>W: auto_retry_end, sent to error tracking
    W->>W: fail with a clear network or gateway message
  end
Loading

Pi owns the retry. The shaded part is what the wizard now sees and records. The full turn lifecycle, including cancel and the 401 re-mint, is drawn in the Pi harness README.

Area Files
Retry tracking auto-retry.ts, index.ts, task.ts
Failure message gateway.ts
Docs README.md, pi-turn-lifecycle.svg
Details
  • Pi's auto-retry is on by default: 3 attempts at 2s, 4s and 8s. It removes the failed turn and re-sends the same context, the approach Codex and Claude Code use for mid-stream drops.
  • Pi's pattern matches socket hang up and terminated. It doesn't match the gateway's upstream closed the stream or ECONNRESET, so those still fail on the first drop. They now fail with the clear message, and the tracking shows how often.
  • The dropped-stream message covers upstream closed the stream, upstream connection lost, ECONNRESET, socket hang up and terminated: "The connection to the PostHog AI gateway dropped mid-response, and retrying didn't fix it (…). This is usually a network problem or a brief gateway issue. Try again in a few minutes." The existing wrapper then adds "Please report this to: wizard@posthog.com".
  • A cancel during Pi's retry wait ends with finalError: "Retry cancelled", and it isn't error-tracked.
  • Retry tracking runs in both linear runs and orchestrator tasks. withGatewayRemint and the 401 re-mint are unchanged.

Test plan

  • auto-retry.test.ts: a Pi retry is captured with its attempt and error. A retry that gives up goes to captureException, and neither a recovered retry nor a cancelled one does.
  • gateway.test.ts: each dropped-stream error fails with the clear message.
  • Reverting the capture, the error tracking, the cancel check or the message makes a test fail.
  • pnpm typecheck is clean, and vitest run src/agent/runner/harness/pi passes 161 tests.

Created with PostHog Desktop

🤖 Generated with Claude Code

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Merge posthog/functional-a3-entries (1443587) into B1. The two
conflicts are import lines: B1 moved AGENT_ERROR_CODE to
@agent/error-map, so the fix's imports of it from @shared/errors follow.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Merge posthog/functional-b1-pr (1d40129), which carries A3's
2aee250 and 1443587, into B2.

B2 and the A3 fix both built host cancellation. The merge keeps one
design:

- The host signal travels on the agent config, and harnesses return
  A3's typed AgentResult. B2's extra `config.signal` argument and its
  `{}` results are gone.
- A host cancellation replaces a success or the harness's own abort.
  A failure the run already decided stays the outcome. Host
  cancellations use B2's hostAborted() so runAgent, the sequences and
  runProgram report the same code and message.
- runAgent keeps B2's run-installed skill cleanup on every non-success
  ending, inside A3's hardened snapshot and report flush.
- B2's project-skill preflight, inference auth provider, program
  commandments and switchboard data stay, ported to the typed result.
- Every runProgram failure now carries an ErrorCode, as the A3
  contract requires: thrown errors go through classifyRunFailure, and
  declined approvals use AgentAbort.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
The agent, a program and the full TUI each run end to end on their own,
so e2e follows that split:

- `pnpm test:e2e:tui` runs the real TUI in a PTY (scripts/tui-snapshots).
- `pnpm test:e2e:programs` runs `runProgram` with no TUI, store or
  session; the script is the host.
- `pnpm test:e2e:agent` runs `runAgent` on a `quack` skill served from a
  loopback skills server, in an empty directory.

All three read one env contract through `e2e-harness/surface-e2e.ts`.

Retired: the dead jest suite under `e2e-tests/` with its fixture apps,
`bin/test-e2e`, the `NODE_ENV=test` MSW hook in `bin.ts`, and the
`run-live.ts` runner with `e2e-harness/live-e2e-checks.ts`. Fixtures,
app copies and assertions stay in wizard-workbench.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Merge posthog/functional-b-integration (42a506a) into B3. The one
conflict is a test import: B3 keeps its UI reducer import, and the
fix's ErrorCodes import joins it.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci feature-flags
  • /wizard-ci mcp-analytics
  • /wizard-ci replay-vision
  • /wizard-ci revenue
  • /wizard-ci self-driving
  • /wizard-ci warehouse
  • /wizard-ci warehouse-seeded

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/google-adk
  • /wizard-ci ai-observability/groq
Show more apps
  • /wizard-ci ai-observability/manual-capture
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/opentelemetry
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/flutter
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci feature-flags/django
  • /wizard-ci feature-flags/next-js
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci replay-vision/javascript-node
  • /wizard-ci replay-vision/next-js
  • /wizard-ci replay-vision/react-vite
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit
  • /wizard-ci warehouse/monorepo-env
  • /wizard-ci warehouse/multi-source-next
  • /wizard-ci warehouse/stripe-node
  • /wizard-ci warehouse/zero-source
  • /wizard-ci warehouse-seeded/next-stripe
  • /wizard-ci warehouse-seeded/next-stripe-declined

Test against a Context Mill branch:

  • /wizard-ci all context-mill:my-branch

Add context-mill:<branch> to any command above to pin the Context Mill branch. It defaults to main.

Results will be posted here when complete.

gewenyu99 and others added 14 commits September 25, 2026 12:38
The registry only maps integrations to framework configs, so it lives
beside them. Importers now use @programs/frameworks/registry.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…-store imports

DiscoveredFeature moves to src/shared/discovered-feature.ts and the session
re-exports it, so run-program.ts no longer reaches the session and its
allowlist row goes. program-store.ts imports through @agent/types and
@shared/api.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Main's new task-stream sync lands beside the moved task stream, so its
importers use @programs/task-stream/wizard-run-sync and its edges join the
known task-stream rows.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
Main's OAuth session now owns when to refresh. runProgram configures it
with the invocation's login and the refresh-token grant, now in
credentials.ts, and every rotation lands in data through onRefreshed.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…agnostics

- The AI SDK stamp moves to posthog-integration/ai-sdk-stamp.ts, and detect.ts
  re-exports it. runProgram no longer loads detection or the registry.
- A cancel during the pre-run refresh keeps the rotated token in data and in
  the shared OAuth session.
- runSessionProgram logs each runProgram diagnostic.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…abilities

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…Context

RunnerContext now has log.warn, like CiRunnerContext's log, so a helper
shared by run and ciPreRun takes one type. The posthog-cli pre-install takes
the runner's log instead of a callback.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…b4-api-reference

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…data

- The runAgent example removes Write, Edit and Bash instead of adding Read
  and Glob, and the field table says allowedTools adds to the base tools and
  disallowedTools removes them.
- The runProgram field table says data and program snapshots hold tokens.
- The registry link points at frameworks/registry.ts, and every line anchor
  points at its declaration again.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…stream-retry

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
The stream retry now matches only the drops Pi's own auto-retry misses,
`upstream closed the stream` and `ECONNRESET`. Pi already retries
`socket hang up` and `terminated`, so those no longer retry twice.
Before each resume the failed turn leaves the transcript, the way Pi's
retry does, and each wait is jittered by ±20%. A drop that outlasts the
retries now fails with a message that points at the network or the
gateway and suggests trying again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the wizard's own stream retry loop. Pi's auto-retry already
re-sends a failed turn with backoff, so the wizard now only observes it:
each auto_retry_start is logged and captured as `agent turn retried`, and
an auto_retry_end that gives up goes to error tracking. A turn that ends
on a dropped model stream fails with a message that points at the
network or the gateway and suggests trying again in a few minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gewenyu99 gewenyu99 changed the title fix(agent): B6 — resume a Pi turn after a dropped stream fix(agent): rely on Pi's retry for a dropped stream, track it, explain it Sep 25, 2026
The pi harness README gains an SVG state diagram of one turn: streaming,
failure, pi's retry wait, the wizard's 401 re-mint, give up and cancel.
Drawing it surfaced that a cancel during pi's retry wait ends with
`auto_retry_end` and `finalError: "Retry cancelled"`, which was reported
as a give-up. That case is no longer error-tracked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gewenyu99
gewenyu99 marked this pull request as ready for review September 25, 2026 18:43
@gewenyu99
gewenyu99 requested a review from a team as a code owner September 25, 2026 18:43
Two runnable scripts replace the inline examples. tsconfig includes
docs/examples, so typecheck keeps them in step with the code.

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
…stream-retry

Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
An error occurred while trying to automatically change base from posthog/functional-b4-api-reference to posthog/functional-b-host-capabilities September 25, 2026 23:58
Generated-By: PostHog Desktop
Task-Id: d14e92bb-6ee1-49b5-8502-39cb80079589
@gewenyu99
gewenyu99 changed the base branch from posthog/functional-b4-api-reference to main September 27, 2026 15:26
@gewenyu99
gewenyu99 marked this pull request as draft September 27, 2026 15:26
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.

1 participant