Skip to content

feat: add durable cloud Pi runtime sessions - #3719

Merged
trunk-io[bot] merged 31 commits into
mainfrom
feat/pi-cloud-runtime-task-sessions
Jul 29, 2026
Merged

feat: add durable cloud Pi runtime sessions#3719
trunk-io[bot] merged 31 commits into
mainfrom
feat/pi-cloud-runtime-task-sessions

Conversation

@jonathanlab

@jonathanlab jonathanlab commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a native Pi runtime path alongside ACP while adapting both runtimes to the shared AgentConversationEvent / ChatThread UI
  • add browser-safe native Pi RPC transport, local pooled sessions, cloud session routing, model/thinking/queue controls, retries, cancellation, compaction, bash, and tool rendering
  • use Django-owned task runtime selection and the shared task-creation saga for ACP and Pi
  • restore cloud Pi sessions through Pi's native SessionManager/RPC flow and wait for pi_run_started before startup commands
  • persist translated Pi conversation events for UI replay while keeping native TaskSession JSONL authoritative for runtime restoration
  • resume completed cloud Pi runs before reconnecting and clear/recreate bound session transports
  • share cloud task/domain types and host-router implementations across desktop and web
  • upgrade the Pi packages to 0.81.0

Backend dependency: PostHog/posthog#72928

Architecture

Pi does not impersonate ACP. ACP and Pi independently adapt to AgentConversationEvent; ChatThread renders that runtime-neutral model. Native Pi RPC remains canonical, while TaskSession JSONL is restored by Pi itself rather than parsed or rewritten by product code.

Diff distribution

The current +5,027 / -1,762 diff is split between:

  • 2,606 production-code additions (52%)
  • 2,341 test additions (47%)
  • 80 build/package configuration additions (1%)

Testing

  • full workspace typecheck after rebasing onto current main
  • agent focused tests, build, and package verification
  • core Pi session/provider/controller and task-creation tests
  • workspace-server and host-router focused tests/typechecks
  • full core and harness suites during development
  • Biome and git diff --check
  • real Electron + local Django/Temporal/Docker E2E:
    • fresh cloud Pi task
    • model response and follow-up
    • replacement-sandbox native JSONL restore
    • full app restart and conversation restore
    • follow-up after restart
    • durable event projection and TaskSession revision promotion

Rollout

Merge/deploy the backend dependency first, then publish the updated @posthog/agent and rebuild the production sandbox image before enabling the client path behind pi-harness.

@trunk-io

trunk-io Bot commented Jul 22, 2026

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

React Doctor found 7 issues in 2 files · 7 warnings.

7 warnings

src/features/pi-sessions/PiSessionView.tsx

src/features/sessions/components/CloudSessionLifecycle.tsx

Reviewed by React Doctor for commit 589d56f.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/ui/src/features/task-detail/components/TaskDetail.tsx:58-63
**Resumed Run Keeps Stale ID**

When a completed cloud Pi run is resumed, `taskId` stays the same while `latest_run.id` changes. This ref is only refreshed for a new task, so `PiSessionView` remains connected to the terminal run and subsequent commands can be routed to that stale run instead of the resumed one.

### Issue 2 of 3
packages/agent/src/server/pi-agent-server.ts:528-529
**Live Session Read Can Truncate**

The synchronization timer reads the JSONL file while the Pi child process can still be writing it. If the read occurs during a rewrite or between appending a record and its final bytes, the incomplete content is promoted as an authoritative revision and a later restore can load a truncated session.

### Issue 3 of 3
packages/agent/src/server/pi-agent-server.ts:440-475
**Delivery Outlives Session Replacement**

An in-flight user-message delivery retains the runtime selected before awaiting the RPC call, but replacing `this.session` does not cancel that delivery. If the run is recreated while a delivery is pending, the message can complete against the old Pi process and disappear from the newly active session.

Reviews (1): Last reviewed commit: "feat: add durable cloud Pi runtime sessi..." | Re-trigger Greptile

Comment thread packages/ui/src/features/task-detail/components/TaskDetail.tsx Outdated
Comment thread packages/agent/src/server/pi-agent-server.ts
Comment thread packages/agent/src/server/pi-agent-server.ts
Comment thread packages/core/src/task-detail/taskService.ts Outdated
@veria-ai

veria-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

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: 1 · PR risk: 0/10

Comment thread packages/agent/src/pi/remote-rpc-client.ts
Comment thread packages/agent/src/pi/rpc-client.ts Outdated
Comment thread packages/agent/src/pi/rpc-transport.ts
Comment thread packages/agent/src/pi/types.ts
Comment thread packages/agent/src/server/bin.ts Outdated
Comment thread packages/agent/tsup.config.ts Outdated
Comment thread packages/core/src/pi-runtime/cloudPiSessionClient.ts Outdated
Comment thread packages/core/src/pi-runtime/cloudPiSessionClient.ts Outdated
Comment thread packages/core/src/pi-runtime/piSessionProvider.ts Outdated
Comment thread packages/core/src/pi-runtime/piSessionController.ts Outdated
@jonathanlab
jonathanlab force-pushed the feat/pi-cloud-runtime-task-sessions branch from a6ede65 to f56b0d0 Compare July 23, 2026 15:39
@tatoalo

tatoalo commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Stress-tested this a bit in cloud runs (replicating this also on the backed PR):

  • attachments are not working
  • during task log hydration and cold reload I saw that the renderer combined duplicate copies of persisted and live Pi events even though BE storage contained one (correct)
  • reconnect snapshots leave RPC calls hanging
  • user-message idem is not durable, they seem to stay in the process memory
  • every pi/rpc command is passed directly to sendPiRpcCommand so no durable request identity is enforced

@posthog

posthog Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Visual changes approved by @jonathanlab — baseline updated in a77aaae.

View this run in PostHog

12 new, 12 removed.

jonathanlab and others added 2 commits July 28, 2026 12:56
12 updated, 12 removed
Run: 9e2a836d-06fa-4eb0-80ab-9cafd1241ac9

Co-authored-by: jonathanlab <32547391+jonathanlab@users.noreply.github.com>
@posthog

posthog Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🦔 ReviewHog reviewed this pull request

Found 3 must fix, 15 should fix, 9 consider.

Published 27 findings (view the review).

@posthog

posthog Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReviewHog Report

Business logic

Issues: 1 issue

Files (9)
  • packages/agent/src/pi/rpc-transport.ts
  • packages/agent/src/pi/remote-rpc-client.ts
  • packages/agent/src/pi/rpc-client.ts
  • packages/agent/src/pi/rpc-host.ts
  • packages/agent/src/pi/types.ts
  • packages/agent/src/pi/queue-persistence.ts
  • packages/agent/src/pi/runtime.ts
  • packages/agent/package.json
  • packages/agent/tsup.config.ts
What were the main changes
  • Adds browser-safe piRpcCommandSchema/parsePiRpcResponse transport and a RemotePiRpcClient adapter that maps typed Pi RPC methods onto it
  • Adds IPC-based queue get/clear host requests to SecurePiRpcClient and rpc-host.ts, plus JSONL-persisted queue restoration
  • PiRuntime now tracks in-flight user-message ids, emits direct-bash tool-call events, and exposes sendCommand for generic RPC dispatch
  • Publishes new package export paths (pi/rpc-transport, pi/remote-rpc-client, pi/model-catalog) and switches server/bin build to ESM

Business logic

Issues: 6 issues

Files (6)
  • packages/agent/src/pi/conversation/translatePiConversation.ts
  • packages/agent/src/pi/conversation/translatePiMessage.ts
  • packages/agent/src/pi/model-catalog.ts
  • packages/harness/src/extensions/posthog-provider/models.ts
  • packages/harness/src/extensions/posthog-provider/provider.ts
  • packages/harness/src/runtime.ts
What were the main changes
  • Adds direct-bash live translation (beginDirectBash/completeDirectBash/failDirectBash) and queue_update event translation
  • Generic tool result content fallback and inline image fileName support in translatePiMessage
  • New resolvePosthogPiModelCatalog/fetchPosthogPiModelCatalog exposing PostHog gateway models with thinking levels for Pi
  • Harness runtime migrated to pi-ai CredentialStore/ModelRuntime APIs replacing AuthStorage/modelRegistry

Feature

Issues: 6 issues

Files (5)
  • packages/agent/src/server/pi-agent-server.ts
  • packages/agent/src/server/bin.ts
  • packages/agent/src/server/types.ts
  • packages/agent/src/server/event-stream-sender.ts
  • packages/agent/src/posthog-api.ts
What were the main changes
  • New PiAgentServer: cloud-hosted Pi runtime with JWT-authed SSE/command endpoints, session restore from TaskSession storage, and pi_run_started signaling
  • Persists translated Pi conversation events as pi_event log entries and syncs native JSONL sessions back to Django on settle/shutdown/crash
  • bin.ts wires POSTHOG_AGENT_RUNTIME/SANDBOX_ID env vars to select PiAgentServer vs AgentServer and resolve the packaged Pi RPC host path
  • posthog-api.ts adds getTaskSession/downloadTaskSession/syncTaskSession for durable TaskSession revision storage

Business logic

Issues: 2 issues

Files (4)
  • packages/core/src/pi-runtime/piSessionController.ts
  • packages/core/src/pi-runtime/piSessionStore.ts
  • packages/core/src/pi-runtime/pi-runtime.module.ts
  • packages/core/src/pi-runtime/piRunner.ts
What were the main changes
  • PiSessionController rewritten to route through PiSessionProvider, track session versions, and unify local/cloud session lifecycle
  • Adds retry/restart, queue capture-and-restore, optimistic user messages, auth-restoration waiting, and structured PiSessionError/PiOperationError reporting
  • Session store gains queue snapshot, stats, cloudStatus, thinkingLevels and richer connection/error state

Business logic

Issues: 2 issues

Files (3)
  • packages/core/src/pi-runtime/cloudPiSessionClient.ts
  • packages/core/src/pi-runtime/piSessionProvider.ts
  • packages/core/src/pi-runtime/piSessionUsage.ts
What were the main changes
  • New CloudPiSessionClient adapts cloud task snapshot/log updates into RemotePiRpcClient RPC responses, with terminal-run read-only fallback and pi_run_started readiness
  • RoutingPiSessionProvider selects cloud vs local Pi session factory based on task.latest_run.environment
  • piSessionUsage.ts maps Pi session stats into shared ContextUsage for the chat footer

Infrastructure

Issues: 3 issues

Files (16)
  • apps/code/src/renderer/di/bindings.ts
  • apps/code/src/renderer/di/container.ts
  • apps/web/src/web-container.ts
  • apps/web/src/web-host-router.ts
  • packages/host-router/package.json
  • packages/host-router/src/cloud-task-client.ts
  • packages/host-router/src/pi-runner.ts
  • packages/host-router/src/pi-session-client.ts
  • packages/host-router/src/pi-session-factory.ts
  • packages/host-router/src/routers/agent.router.ts
  • packages/host-router/src/routers/cloud-task.router.ts
  • packages/host-router/src/routers/pi-session.router.ts
  • packages/workspace-server/src/services/agent/agent.ts
  • packages/workspace-server/src/services/agent/schemas.ts
  • packages/workspace-server/src/services/pi-session/pi-session.ts
  • packages/workspace-server/src/services/pi-session/schemas.ts
What were the main changes
  • Replaces TrpcPiSessionClient with TrpcPiSessionFactory (generic RPC pass-through) and adds TrpcCloudTaskClient/TrpcPiRunner shared between desktop and web DI containers
  • host-router gains a cloud-task router (context/watch/retry/sendCommand) and slims pi-session.router to rpc/health/getQueue/clearQueue/stop
  • workspace-server pi-session.ts replaces per-RPC-method methods with a generic request(command) dispatch, active-request tracking, and safer hot-pool eviction
  • agent.ts renames getGatewayModels to getPiModelCatalog using the new model-catalog helper

Frontend

Issues: 6 issues

Files (7)
  • packages/ui/src/features/pi-sessions/PiQueuedMessagesDock.tsx
  • packages/ui/src/features/pi-sessions/PiSessionControls.tsx
  • packages/ui/src/features/pi-sessions/PiSessionView.tsx
  • packages/ui/src/features/task-detail/components/TaskDetail.tsx
  • packages/ui/src/features/task-detail/components/TaskInput.tsx
  • packages/ui/src/features/settings/settingsStore.ts
  • packages/ui/src/shell/GlobalEventHandlers.tsx
What were the main changes
  • PiSessionView reworked for cloud reconnect/retry/restart banners, queued-message dock, auth-restoration and usage-limit handling, and follows the live task.latest_run.id
  • New PiQueuedMessagesDock renders/edits/removes the Pi steering/follow-up queue
  • TaskInput gains a Pi runtime toggle with model/thinking-level selectors sourced from the new Pi model catalog query
  • GlobalEventHandlers retries unhealthy cloud Pi sessions on window focus alongside ACP sessions

Frontend

Issues: 1 issue

Files (12)
  • packages/ui/src/features/sessions/components/CloudInitializingView.tsx
  • packages/ui/src/features/sessions/components/CloudSessionLifecycle.tsx
  • packages/ui/src/features/sessions/components/ContextBreakdownPopover.tsx
  • packages/ui/src/features/sessions/components/SessionView.tsx
  • packages/ui/src/features/sessions/components/UserMessageAttachments.tsx
  • packages/ui/src/features/sessions/components/buildConversationItems.ts
  • packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx
  • packages/ui/src/features/sessions/components/chat-thread/ChatThreadFooter.tsx
  • packages/ui/src/features/sessions/components/session-update/ProgressGroupView.tsx
  • packages/ui/src/features/sessions/components/session-update/QueuedMessageView.tsx
  • packages/core/src/sessions/contextUsage.ts
  • packages/core/src/sessions/promptContent.ts
What were the main changes
  • Extracts CloudConnectionBanner/CloudStreamDisconnectedBanner/ConnectingToAgent into a shared CloudSessionLifecycle component reused by ACP and Pi
  • buildConversationItems handles the new progress and queue_update AgentConversationEvent types runtime-neutrally
  • ChatThread/ChatThreadFooter accept an externally supplied context-usage override so Pi can report its own stats
  • QueuedMessageView's drag handle becomes optional and promptContent supports inline base64 image attachments

Other findings (outside the changed lines)

Valid issues on this PR's files that sit on lines GitHub won't let us comment on inline.

ChatThread hardcodes footerEvents=[] for Pi, discarding lastTurnInfo/isCompacting/completedToolCallCount

Priority: should_fix | File: packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx:974-985, 1133-1145 | Category: bug

Why we think it's a valid issue
  • Checked: the full Pi render chain — PiSessionView.tsx:207-212ChatThread.tsx:972-983ChatThreadRenderer (ChatThread.tsx:1130-1141) → ChatThreadFooter.tsx:39-64SessionFooter.tsx:98-121,164-168, plus buildAgentConversationItems (buildConversationItems.ts:293-312) and both footer-source hooks (useAgentConversationItems.ts, useConversationItems, useContextUsage.ts).
  • Found: ChatThread calls useAgentConversationItems(events, ...), whose BuildResult already carries correct lastTurnInfo/isCompacting/completedToolCallCount for Pi, but destructures only { items } (ChatThread.tsx:973) and renders footerEvents={[]} (ChatThread.tsx:980). ChatThreadFooter then re-derives those three fields via useConversationItems(events, ...) over that empty array (ChatThreadFooter.tsx:40-41). AcpChatThread instead passes footerEvents={events} (ChatThread.tsx:996), confirming Pi is the broken branch. PiSessionView supplies no overrides (PiSessionView.tsx:207-212).
  • Impact: Confirmed live for every Pi cloud session — the footer permanently shows lastGenerationDuration: null (so the "Generated in Xs" summary never renders, SessionFooter.tsx:98-121), completedToolCallCount: 0 (so GeneratingIndicator's activityKey never changes and the thinking word never advances, GeneratingIndicator.tsx:164-168), and isCompacting: false. Deterministic, always-on across the whole Pi runtime path, not an edge case.
  • Found (correction): In this checkout ChatThreadFooter has NO usage override prop — usage is also derived from the passed events via useContextUsage(events) (ChatThreadFooter.tsx:39, useContextUsage.ts:11-17). So the issue's claim that usage was 'already patched via an override at L42-43' does not match the tree; all four footer signals are wired to the always-empty footerEvents. The fix should thread the already-computed BuildResult fields through uniformly rather than mirror a usage override that isn't present.
  • Priority: Lowering to should_fix — the defect is real and always-present, but it degrades only the footer status line (duration summary, thinking-word cadence, compacting indicator); Pi sessions, streaming, and tools all function, and the path is gated behind the pi-harness flag, so it is a non-blocking quality regression rather than a correctness/data/security blocker.
Issue description

ChatThread (the Pi-runtime entry point) calls useAgentConversationItems(events, props.isPromptPending), which returns a full BuildResult{ items, lastTurnInfo, isCompacting, completedToolCallCount } — computed correctly from Pi's native AgentConversationEvent[] via buildAgentConversationItems. But the component destructures only items and then renders <ChatThreadRenderer ... footerEvents={[]} />, permanently passing an empty array. ChatThreadRenderer forwards footerEvents verbatim to ChatThreadFooter (events={footerEvents}), which re-derives lastTurnInfo, isCompacting, and completedToolCallCount via its own useConversationItems(events, ...) call (ChatThreadFooter.tsx L44-45) — always over an empty array for Pi. The PR explicitly patched this same class of bug for context usage (usage prop override, ChatThreadFooter.tsx L43), but the equivalent turn/compaction/tool-count data was left wired to the always-empty footerEvents instead of the already-computed values. I confirmed this is live: packages/ui/src/features/pi-sessions/PiSessionView.tsx renders <ChatThread events={session.events} ... usage={contextUsage} /> with no override for the other three fields. As a result, for every Pi cloud session the footer will always show lastGenerationDuration: null, lastStopReason: undefined, isCompacting: false, and completedToolCallCount: 0 regardless of what actually happened — the 'last generation took Xs' summary, the compacting indicator, and the tool-call progress word in GeneratingIndicator/SessionFooter never reflect Pi's real state.

Suggested fix

Have ChatThread keep the full BuildResult from useAgentConversationItems (not just items) and thread lastTurnInfo, isCompacting, and completedToolCallCount down to ChatThreadFooter the same way usage was added — e.g. add optional override props to SharedChatThreadProps/ChatThreadFooterProps (lastTurnInfo?: LastTurnInfo | null, isCompacting?: boolean, completedToolCallCount?: number) and use value === undefined ? derivedFromEvents : value for each, mirroring the usage pattern already in ChatThreadFooter.tsx L42-43.

Comment thread packages/agent/src/server/bin.ts
Comment thread packages/agent/src/pi/conversation/translatePiConversation.ts
Comment thread packages/agent/src/pi/conversation/translatePiConversation.ts
Comment thread packages/core/src/pi-runtime/cloudPiSessionClient.ts
Comment thread packages/agent/src/server/pi-agent-server.ts
Comment thread packages/agent/src/server/pi-agent-server.ts
Comment thread packages/core/src/pi-runtime/cloudPiSessionClient.ts
Comment thread packages/workspace-server/src/services/pi-session/pi-session.ts Outdated
Comment thread packages/ui/src/shell/GlobalEventHandlers.tsx
Comment thread packages/ui/src/features/pi-sessions/PiSessionView.tsx
@jonathanlab

Copy link
Copy Markdown
Contributor Author

ReviewHog outside-diff finding — ChatThread hardcodes footerEvents=[] for Pi: valid — fixed in 3890211965. The Pi thread now passes its already-computed lastTurnInfo, isCompacting, and completedToolCallCount through to ChatThreadFooter, while ACP continues deriving those values from ACP events.

@jonathanlab jonathanlab added the Create Release This will trigger a new release label Jul 29, 2026
@jonathanlab

Copy link
Copy Markdown
Contributor Author

/trunk merge

1 similar comment
@jonathanlab

Copy link
Copy Markdown
Contributor Author

/trunk merge

@jonathanlab

Copy link
Copy Markdown
Contributor Author

/trunk cancel

…-task-sessions

# Conflicts:
#	pnpm-lock.yaml
@jonathanlab

Copy link
Copy Markdown
Contributor Author

/trunk merge

@trunk-io
trunk-io Bot merged commit baaaa7f into main Jul 29, 2026
34 checks passed
@trunk-io
trunk-io Bot deleted the feat/pi-cloud-runtime-task-sessions branch July 29, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Create Release This will trigger a new release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants