feat(examples): add experimental pi harness example - #2210
Conversation
…e UI Trim the example-local harness to what the demo and test use: drop the execution-environment type surface, routed calls, lane access, and the follow-up, next-run, cancel-queued, and lane-config transport commands. The WebSocket protocol is now snapshot, subscribe, submit, abort, and steer. Rebuild the client on the same Kumo chat layout as the other examples: a pinned header, a scrolling transcript with user bubbles and assistant tool cards, an Empty state with suggestion buttons, and an InputArea composer with a stop button while a turn runs. Rewrite the README and RFC to describe this as an experimental example on a pinned pi dev build, not a package API, and list what must land before it becomes one. Claude-Session: https://claude.ai/code/session_01KEFnjoMnZBMewsuD9qxGrL
|
There was a problem hiding this comment.
Note
This report is out of date. Scroll down for Devin Review's latest report on this PR.
Devin Review found 4 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| default: | ||
| return; |
| error: PiOperationRejectedError, | ||
| submission?: QueuedSubmission | ||
| ): void { | ||
| this.#rejections.set(operationId, error); |
There was a problem hiding this comment.
🔴 Rejected operations lose terminal results
When pi rejects or withdraws queued work, #reject records the outcome only in memory. Eviction loses it, making later waitForResult calls poll forever.
Prompt for agents
PiHarness deletes a rejected submission from durable intake but stores its terminal PiOperationRejectedError only in #rejections. Persist declined and withdrawn outcomes keyed by operation ID, and make waitForResult and getResult consult that durable record. Preserve results for repeated or concurrent waiters instead of consuming them on the first read. Reconcile the durable result with operation_end stream delivery and any retention policy.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const existing = this.#writers.get(operationId); | ||
| if (existing) return existing; |
There was a problem hiding this comment.
🟡 Concurrent lanes share operation output
When concurrent lanes use the same explicit operation ID, #writerFor returns one shared writer. Their events merge, and either operation can close both output streams.
Prompt for agents
PiHarness stream IDs are lane-scoped, but #writers is keyed only by operationId. The duplicate check in submit also checks current and settled operations only on the requested lane, so the same explicit operation ID can become active on two lanes. Either enforce operation IDs globally across pending, active, and settled operations, matching the receipt contract, or key all writer and waiter state by both lane and operation ID. Add a two-lane concurrency test covering event routing and independent settlement.
Was this helpful? React with 👍 or 👎 to provide feedback.
🟡 agents import sizesMeasured 294 runtime imports as minified bundles. The primary size is gzip; raw minified size is included for diagnosis. An existing import growing by more than 10% is marked red. This report is informational.
Compared Changed imports (135)
All 267 current runtime imports
Reported by agent-think[bot]. |
Build usePiSession on useAgent from agents/react instead of a hand-rolled WebSocket with its own reconnect loop. The protocol over the WebSockets capability is unchanged. Claude-Session: https://claude.ai/code/session_01KEFnjoMnZBMewsuD9qxGrL
There was a problem hiding this comment.
Note
This report is out of date. Scroll down for Devin Review's latest report on this PR.
Devin Review found 4 new potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| this.#reject( | ||
| lane, | ||
| operationId, | ||
| "run", | ||
| new PiOperationRejectedError( |
There was a problem hiding this comment.
🟡 Queued maintenance aborts become runs
Aborting a queued compaction or navigation passes "run" to #reject. Listeners receive the wrong kind, corrupting status displays and operation-specific handling.
Prompt for agents
When aborting a queued operation in PiHarness.abort, recover the deleted submission’s request and derive its kind with requestKind instead of hardcoding run. PiSubmissions.deleteOperation currently returns only a boolean, so adjust the intake API or read the queued row before deletion while preserving atomic withdrawal semantics.
Was this helpful? React with 👍 or 👎 to provide feedback.
| try { | ||
| return ( | ||
| (await routeAgentRequest(request, env, { cors: true })) ?? | ||
| new Response("Not found", { status: 404 }) | ||
| ); |
There was a problem hiding this comment.
| function isClientMessage(value: unknown): value is PiClientMessage { | ||
| return ( | ||
| typeof value === "object" && | ||
| value !== null && | ||
| "type" in value && | ||
| typeof value.type === "string" | ||
| ); |
There was a problem hiding this comment.
| } | ||
| try { | ||
| return ( | ||
| (await routeAgentRequest(request, env, { cors: true })) ?? |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Deploy as pi-harness-example, give the Vite dev server its own inspector port so it can run beside the other harness examples, and note CLOUDFLARE_ACCOUNT_ID for logins with several accounts.
agents/react resolved a second React copy from the agents package, which broke every hook call in the browser. Also load the agents Vite plugin for the bundled skill.
Resync the transcript from the durable snapshot at operation boundaries so the prompt entry appears, and ignore a message event whose id is already in the transcript. Shorten the empty-state copy.
The client resyncs its transcript from the durable snapshot at operation boundaries, so the reply must arrive as a snapshot rather than a generic result.
Summary
Reduces #2197 to an example. Nothing is added to the
agentspackage.examples/next/harnesses/pihosts pi's durableAgentHarnesson a plainDurableObjectcomposed withLifecycle,Tasks,Streams,WebSockets, andagents/skills.PiHarness, the Workers AI provider, and the pi session adapter are example-local code on a pinned pi dev build (c4b0e35a, vendored undervendor/pi-dev).design/rfc-pi-harness-example.mdrecords the composition, the known costs, and what must land before this becomes a package export.Why not a package export yet
agents/sessions(feat(agents): move sessions into a Lifecycle capability #2196) has not landed, so pi's session lives in a namespaced SQLite adapter carrying pi's own schema.Supersedes #2197.