Skip to content

feat(examples): Harness capability with a remote Claude Code runtime in a Container - #2285

Open
mattzcarey wants to merge 9 commits into
cloudflare:mainfrom
mattzcarey:feat/harness-capability
Open

mattzcarey wants to merge 9 commits into
cloudflare:mainfrom
mattzcarey:feat/harness-capability

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

Summary

One developer API for every harness, one Harness capability over a pluggable runtime, and Claude Code running in a Cloudflare Container as a remote runtime driven over Cap'n Web. Closes the direction of #1829 without adopting @ai-sdk/harness: the SDK's own Tasks, Streams and Sessions stay the durability model.

Design: design/rfc-harness-capability.md (registered in the design indexes).

What is in the PR

  • examples/next/harnesses/shared (@cloudflare/agents-next-harness). The Harness Lifecycle capability: durable inbox admission, operation and request rows, one Streams log per operation plus one per session, a Tasks driver that wakes the runtime, the browser link and the useHarnessSession React hook. Runtimes implement HarnessRuntime (drive(ctx) plus messages()) and add their own vocabulary through a HarnessProtocol type parameter. Example-local on purpose; nothing is exported from agents yet.
  • ContainerHarnessRuntime (shared/src/remote.ts). A runtime whose engine runs as a daemon in a Container: dialled through getTcpPort().fetch(upgrade) and newWebSocketRpcSession, the Durable Object exports nothing, events arrive on a ReadableStream of batches from a durable outbox in the container. Generation-scoped wire cursor committed in the same transaction as the frames, a doorbell for waking an evicted object, an idle policy that closes the socket (detachAfterIdleMs), keeps the container alive with setInactivityTimeout and destroys it (stopContainerAfterIdleMs), and a transcript mirror so a new container resumes the engine's own session.
  • examples/next/harnesses/claude-code. One Durable Object per coding session, one container per object. container/ is harnessd: Node 24, Cap'n Web over ws, a node:sqlite outbox, the Claude Agent SDK in streaming-input mode (one long-lived query(), PreToolUse gate plus canUseTool for permissions as durable requests, a SessionStore mirror), and an echo engine for keyless smoke deploys (HARNESS_ENGINE=echo). The daemon is a workspace package so its typecheck, tests and build run in CI; the Dockerfile builds from the harnesses directory.
  • pi, codex, self-modifying become PiRuntime, CodexRuntime, SelfModifyingRuntime behind the shared Harness. Their bespoke transports, hooks and intake tables are deleted (net 6.1k lines removed); every client uses the shared hook; host-only demo features moved to plain HTTP routes on the object.

The developer API

const session = this.harness.session();               // a handle, no I/O
const { operationId } = await session.prompt(text);   // durable before it resolves
for await (const e of session.events({ previews: true })) {
  if ("preview" in e) render.delta(e.body);
  else if (e.body.type === "request_raised") render.ask(e.body.request);
  else if (e.body.type === "operation_settled") break;
}
await session.reply(requestId, { type: "permission", decision: "allow" });

compact, fork, rewind, configure, cancelQueued are always present and throw unless status().capabilities advertises them.

Verified in production

Deployed on two accounts and driven over the shared wire with shared/scripts/drive.mjs:

  • pi, codex and self-modifying each complete real turns on the shared API.
  • Claude Code on the real engine through AI Gateway (BYOK team gateway, cf-aig-metadata project tag): a Write turn in under 10 s warm, a Bash turn with a permission round trip, usage and cost frames.
  • The RFC's headline bet: setInactivityTimeout keeps the container alive across a Durable Object eviction. POST .../restart evicts the object mid-turn; the fresh incarnation re-attaches within 4 s and the turn settles once.
  • Container death: POST .../kill destroys the container mid-conversation; the next container resumes the Claude Code session from the transcript mirrored into the Durable Object (signed thinking included, because Claude Code replays its own entries) and answers a question about the earlier turn.
  • Idle policy observed end to end: socket detached at ~20 s, container stopped at ~140 s, old instances inactive.
  • Doorbell verified container to Worker to object; a subscribe-before-deliver bug it exposed is fixed.

Tests

Shared 25 (harness API, replay from any cursor, live tails with previews, idempotent ids, interrupt with drain, request round trip and timeout, multiple sessions, eviction between and during operations; a fake daemon over a WebSocketPair covers the wire: reconnect from the cursor, generation change, redelivery, restore chunking, tool-result projection), pi 4, codex 5, self-modifying 10, claude-code 6 (a live-daemon suite that drives the real ContainerHarnessRuntime against a local harnessd, skipped when none listens), daemon 44 (outbox, wire, projection fixtures, session mirror).

Known edges

  • The SDK's transcript mirror is best-effort: a batch it drops after retries surfaces as a mirror_error event and a later restore resumes a transcript with a hole; nothing detects the hole on the wire yet.
  • Entries of superseded engine sessions are kept until the harness session is deleted.
  • The container receives the gateway token in its environment (documented in the example README); the tokenless interceptOutboundHttps mode is future work.
  • messages() has no paging in any example; pi's sessions.delete() cannot delete a pi lane.
  • Host tools are declared but not exercised end to end.
  • After a deploy, the platform can hand a session a pre-warmed instance of the previous image; info reports the daemon build digest for that reason.

Decisions this PR takes (from the RFC's "The decision")

  1. The three local harnesses become runtimes behind one Harness; the examples are ported, not kept.
  2. rfc-coding-agent.md's HarnessEngine path over @ai-sdk/harness is closed.
  3. messages(), requests() and reply() stay in the core even for harnesses that return one page and an empty list.
  4. The daemon and the Claude Code engine ship inside the example (container/), not as packages/ entries, until the wire stabilises.
  5. getAgentByName and RoutedAgents are untouched: a container-backed session is one top-level object and the example has no hub.

Devin Review

… runtime

One developer API for every harness (session().prompt/interrupt/requests/reply/
messages/status/result/wait/events), one Harness capability over a HarnessRuntime
port, and a remote runtime that drives Claude Code in a Container over Cap'n Web.
Registered in the design indexes.
examples/next/harnesses/shared (@cloudflare/agents-next-harness): the one Harness
Lifecycle capability every harness example composes. The base owns admission (a
durable inbox), operation and request rows, one Streams log per operation plus one
per session, the Tasks driver that wakes the runtime, the browser link and the
useHarnessSession React hook. A HarnessRuntime owns the agent loop and the
transcript; runtimes add vocabulary through a HarnessProtocol type parameter.

Also ContainerHarnessRuntime: a runtime whose engine runs as a daemon in a
Cloudflare Container, dialled over getTcpPort and Cap'n Web, with a durable
outbox cursor, a doorbell for waking an evicted object, an idle policy that
parks and stops the container, and a transcript mirror so a new container
resumes the engine's own session from entries held in the Durable Object.
…nto the shared Harness

PiHarness, CodexHarness and SelfModifyingHarness stop being capabilities and become
HarnessRuntime implementations (PiRuntime, CodexRuntime, SelfModifyingRuntime)
behind the shared Harness. Their bespoke WebSocket transports, React hooks and
intake tables are deleted; every client uses useHarnessSession over the shared
browser link, and host-only demo features moved to plain HTTP routes on the
object. Tests are ported and extended (eviction mid-turn, steer, interrupt).
examples/next/harnesses/claude-code: one Durable Object per coding session, one
container per object. The container runs harnessd, a Node 24 daemon (Cap'n Web
over ws, a node:sqlite outbox) hosting the Claude Agent SDK in streaming-input
mode, plus an echo engine for keyless smoke deploys. Permissions round-trip as
durable requests, the transcript is mirrored to the Durable Object through the
SDK SessionStore so a killed container resumes the conversation, and AI Gateway
credentials enter through ANTHROPIC_BASE_URL plus a gateway token. The daemon is
a workspace package so its typecheck, tests and build run in CI.
@changeset-bot

changeset-bot Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d92fa75

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@agent-think

agent-think Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

⚪ agents import sizes

Measured 343 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.

Red Yellow Green Unchanged New Removed
0 0 0 343 0 0

Compared a7b29135 with d92fa757. Open workflow run.

No import sizes changed.

All 343 current runtime imports
Status Import Gzip Raw minified
⚪ agents#__DO_NOT_USE_WILL_BREAK__agentContext 261.1 KiB 1141.0 KiB
⚪ agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 261.1 KiB 1141.0 KiB
⚪ agents#Agent 261.1 KiB 1141.0 KiB
⚪ agents#AGENT_TOOL_MILESTONE_PART 261.1 KiB 1141.0 KiB
⚪ agents#AGENT_TOOL_PROGRESS_PART 261.1 KiB 1141.0 KiB
⚪ agents#buildAgentPath 261.7 KiB 1143.3 KiB
⚪ agents#buildAgentUrl 261.8 KiB 1143.7 KiB
⚪ agents#callable 261.1 KiB 1141.0 KiB
⚪ agents#camelCaseToKebabCase 261.1 KiB 1141.0 KiB
⚪ agents#createHeaderBasedEmailResolver 261.3 KiB 1141.4 KiB
⚪ agents#DEFAULT_AGENT_STATIC_OPTIONS 261.1 KiB 1141.0 KiB
⚪ agents#DurableObjectOAuthClientProvider 261.1 KiB 1141.0 KiB
⚪ agents#getAgentByName 261.1 KiB 1141.0 KiB
⚪ agents#getCurrentAgent 261.1 KiB 1141.0 KiB
⚪ agents#getSubAgentByName 261.4 KiB 1141.6 KiB
⚪ agents#isDurableObjectCodeUpdateReset 261.1 KiB 1141.0 KiB
⚪ agents#isDurableObjectMemoryLimitReset 261.1 KiB 1141.0 KiB
⚪ agents#isDurableObjectStorageReset 261.1 KiB 1141.0 KiB
⚪ agents#isPlatformTransientError 261.1 KiB 1141.0 KiB
⚪ agents#MCP_SERVER_ID_MAX_LENGTH 261.1 KiB 1141.0 KiB
⚪ agents#MessageType 261.2 KiB 1141.3 KiB
⚪ agents#normalizeServerId 261.1 KiB 1141.0 KiB
⚪ agents#parseSubAgentPath 261.1 KiB 1141.0 KiB
⚪ agents#routeAgentEmail 261.4 KiB 1141.7 KiB
⚪ agents#routeAgentRequest 261.7 KiB 1142.9 KiB
⚪ agents#routeSubAgentRequest 261.3 KiB 1141.5 KiB
⚪ agents#SqlError 261.1 KiB 1141.0 KiB
⚪ agents#StreamingResponse 261.1 KiB 1141.0 KiB
⚪ agents#SUB_PREFIX 261.1 KiB 1141.0 KiB
⚪ agents#unstable_callable 261.2 KiB 1141.2 KiB
⚪ agents/agent-tools#agentTool 112.5 KiB 538.2 KiB
⚪ agents/browser#BrowserConnector 50.5 KiB 176.6 KiB
⚪ agents/browser#browserContent 36.3 KiB 127.4 KiB
⚪ agents/browser#browserExtract 36.3 KiB 127.4 KiB
⚪ agents/browser#browserLinks 36.3 KiB 127.4 KiB
⚪ agents/browser#browserMarkdown 36.3 KiB 127.4 KiB
⚪ agents/browser#browserPdf 36.3 KiB 127.3 KiB
⚪ agents/browser#BrowserRenderingError 36.0 KiB 126.7 KiB
⚪ agents/browser#browserScrape 36.3 KiB 127.4 KiB
⚪ agents/browser#browserScreenshot 36.3 KiB 127.3 KiB
⚪ agents/browser#browserSnapshot 36.3 KiB 127.4 KiB
⚪ agents/browser#CdpSession 37.2 KiB 129.8 KiB
⚪ agents/browser#CodemodeRuntime 39.6 KiB 139.0 KiB
⚪ agents/browser#connectBrowser 37.8 KiB 131.4 KiB
⚪ agents/browser#connectBrowserSession 37.5 KiB 130.4 KiB
⚪ agents/browser#connectUrl 37.6 KiB 130.5 KiB
⚪ agents/browser#createBrowserSession 36.3 KiB 127.5 KiB
⚪ agents/browser#DEFAULT_EXEC_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#DEFAULT_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#deleteBrowserSession 36.1 KiB 126.9 KiB
⚪ agents/browser#DurableBrowserSessionStore 36.4 KiB 127.6 KiB
⚪ agents/browser#getBrowserRecording 36.2 KiB 127.1 KiB
⚪ agents/browser#listBrowserTargets 36.1 KiB 126.9 KiB
⚪ agents/browser#loadCdpSpec 36.6 KiB 128.3 KiB
⚪ agents/browser#runQuickAction 36.0 KiB 126.6 KiB
⚪ agents/browser/ai#createBrowserRuntime 147.0 KiB 632.8 KiB
⚪ agents/browser/ai#createBrowserTools 147.0 KiB 632.9 KiB
⚪ agents/browser/ai#createQuickActionTools 122.5 KiB 554.3 KiB
⚪ agents/browser/tanstack-ai#createBrowserTools 162.7 KiB 701.7 KiB
⚪ agents/channels#ChannelHost 3.3 KiB 9.1 KiB
⚪ agents/channels#consumeChunks 237 B 321 B
⚪ agents/channels#createUserIdentityStore 1.3 KiB 3.2 KiB
⚪ agents/channels#fallback 155 B 160 B
⚪ agents/channels#fallbackChannel 893 B 1.9 KiB
⚪ agents/channels#fanout 151 B 156 B
⚪ agents/channels#fanoutChannel 875 B 1.9 KiB
⚪ agents/channels#identityKey 178 B 223 B
⚪ agents/channels#isChannelMessageSurface 271 B 452 B
⚪ agents/channels#linkChannelIdentities 113 B 99 B
⚪ agents/channels#matchesPath 110 B 96 B
⚪ agents/channels#routes 307 B 507 B
⚪ agents/channels#UserIdentityConflictError 248 B 335 B
⚪ agents/channels/ai-sdk#createSendMessageTool 112.2 KiB 537.4 KiB
⚪ agents/channels/ai-sdk#toChannelChunks 112.5 KiB 538.4 KiB
⚪ agents/channels/email#email 23.5 KiB 75.4 KiB
⚪ agents/channels/email#inboundEmail 22.3 KiB 72.3 KiB
⚪ agents/channels/slack#slack 5.5 KiB 14.6 KiB
⚪ agents/channels/slack#slackWebhook 2.2 KiB 5.1 KiB
⚪ agents/channels/tanstack-ai#createSendMessageTool 16.2 KiB 68.4 KiB
⚪ agents/channels/telegram#telegram 3.8 KiB 10.3 KiB
⚪ agents/channels/telegram#telegramWebhook 1.6 KiB 3.6 KiB
⚪ agents/channels/voice#browserVoice 1010 B 2.2 KiB
⚪ agents/chat#AbortRegistry 2.5 KiB 8.9 KiB
⚪ agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#AgentToolProgressEmitter 2.7 KiB 9.5 KiB
⚪ agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#aiSdkRecoveryCodec 2.3 KiB 8.2 KiB
⚪ agents/chat#applyAgentToolEvent 3.2 KiB 11.0 KiB
⚪ agents/chat#applyChunkToParts 2.3 KiB 8.2 KiB
⚪ agents/chat#applyToolUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#AutoContinuationController 2.3 KiB 8.2 KiB
⚪ agents/chat#awaitWithDeadline 2.4 KiB 8.4 KiB
⚪ agents/chat#broadcastTransition 3.2 KiB 11.4 KiB
⚪ agents/chat#buildChatRecoveringFrame 2.4 KiB 8.4 KiB
⚪ agents/chat#buildInClauseStrings 2.4 KiB 8.4 KiB
⚪ agents/chat#bumpChatRecoveryProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#byteLength 2.5 KiB 8.5 KiB
⚪ agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#ChatRecoveryEngine 4.4 KiB 15.3 KiB
⚪ agents/chat#chatRecoveryTaskRunOptions 2.4 KiB 8.6 KiB
⚪ agents/chat#ChatStreamStalledError 2.4 KiB 8.3 KiB
⚪ agents/chat#classifyAgentToolChildRecovery 2.4 KiB 8.5 KiB
⚪ agents/chat#clearChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#clientResolvableToolNames 2.3 KiB 8.3 KiB
⚪ agents/chat#ContinuationState 2.7 KiB 9.8 KiB
⚪ agents/chat#createAgentToolEventState 2.3 KiB 8.3 KiB
⚪ agents/chat#createChatFiberSnapshot 2.5 KiB 8.6 KiB
⚪ agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 9.0 KiB
⚪ agents/chat#createChatStreams 6.4 KiB 22.1 KiB
⚪ agents/chat#createChatTurnTaskDefinition 2.7 KiB 8.9 KiB
⚪ agents/chat#createToolsFromClientSchemas 114.3 KiB 545.4 KiB
⚪ agents/chat#crossMessageToolResultUpdate 2.4 KiB 8.6 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 8.3 KiB
⚪ agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 9.9 KiB
⚪ agents/chat#drainInteractionApplies 2.3 KiB 8.3 KiB
⚪ agents/chat#enforceRowSizeLimit 3.5 KiB 11.2 KiB
⚪ agents/chat#hasIncompleteToolBatch 2.4 KiB 8.6 KiB
⚪ agents/chat#interceptAgentToolBroadcast 2.5 KiB 8.7 KiB
⚪ agents/chat#isPlatformFailure 2.6 KiB 8.9 KiB
⚪ agents/chat#isReplayChunk 2.4 KiB 8.7 KiB
⚪ agents/chat#iterateWithStallWatchdog 2.6 KiB 8.8 KiB
⚪ agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 8.2 KiB
⚪ agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 8.4 KiB
⚪ agents/chat#MAX_BOUND_PARAMS 2.3 KiB 8.2 KiB
⚪ agents/chat#MessageType 2.4 KiB 9.0 KiB
⚪ agents/chat#normalizeToolInput 2.3 KiB 8.2 KiB
⚪ agents/chat#parseProtocolMessage 2.5 KiB 9.0 KiB
⚪ agents/chat#partAwaitsClientInteraction 2.4 KiB 8.6 KiB
⚪ agents/chat#pausedExecutionUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#pendingChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#persistReconstructedOrphan 3.1 KiB 11.0 KiB
⚪ agents/chat#PreStreamTurns 2.6 KiB 9.3 KiB
⚪ agents/chat#readChatRecoveryProgress 2.3 KiB 8.3 KiB
⚪ agents/chat#reconcileMessages 2.8 KiB 9.6 KiB
⚪ agents/chat#reconcileOrphanPartial 2.4 KiB 8.5 KiB
⚪ agents/chat#recordChatTerminal 2.4 KiB 8.3 KiB
⚪ agents/chat#repairInterruptedToolParts 2.6 KiB 9.1 KiB
⚪ agents/chat#resolveChatRecoveryConfig 2.6 KiB 8.9 KiB
⚪ agents/chat#resolveToolMergeId 2.4 KiB 8.5 KiB
⚪ agents/chat#ResumableStream 5.1 KiB 16.8 KiB
⚪ agents/chat#ResumeHandshake 3.0 KiB 10.4 KiB
⚪ agents/chat#ROW_MAX_BYTES 2.3 KiB 8.2 KiB
⚪ agents/chat#runChatRecoveryExhaustion 2.6 KiB 8.9 KiB
⚪ agents/chat#sanitizeMessage 2.5 KiB 8.8 KiB
⚪ agents/chat#sendIfOpen 2.4 KiB 8.4 KiB
⚪ agents/chat#setChatRecovering 2.5 KiB 8.5 KiB
⚪ agents/chat#shouldCreditStreamProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 8.2 KiB
⚪ agents/chat#StreamAccumulator 2.9 KiB 10.7 KiB
⚪ agents/chat#StreamProgressCreditThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#SubmitConcurrencyController 2.9 KiB 10.3 KiB
⚪ agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 8.5 KiB
⚪ agents/chat#TextSegmentJoiner 2.7 KiB 9.2 KiB
⚪ agents/chat#TIMED_OUT 2.3 KiB 8.2 KiB
⚪ agents/chat#toolApprovalUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#toolPartHasSettledResult 2.3 KiB 8.4 KiB
⚪ agents/chat#toolResultUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#truncateOlderMessages 3.2 KiB 10.4 KiB
⚪ agents/chat#TurnQueue 2.6 KiB 9.2 KiB
⚪ agents/chat#unwrapChatFiberSnapshot 2.4 KiB 8.5 KiB
⚪ agents/chat#wrapChatFiberSnapshot 2.3 KiB 8.2 KiB
⚪ agents/chat-sdk#ChatSdkStateAdapter 263.5 KiB 1152.6 KiB
⚪ agents/chat-sdk#ChatSdkStateAgent 262.8 KiB 1150.0 KiB
⚪ agents/chat-sdk#createChatSdkState 263.5 KiB 1152.6 KiB
⚪ agents/chat-sdk#defaultKeyShard 261.2 KiB 1141.2 KiB
⚪ agents/chat-sdk#defaultThreadShard 261.1 KiB 1141.0 KiB
⚪ agents/chat/react#detectToolsRequiringConfirmation 3.3 KiB 8.3 KiB
⚪ agents/chat/react#extractClientToolSchemas 3.2 KiB 8.3 KiB
⚪ agents/chat/react#getAgentMessages 3.4 KiB 8.6 KiB
⚪ agents/chat/react#getToolApproval 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolCallId 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolInput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolOutput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolPartState 3.2 KiB 8.2 KiB
⚪ agents/chat/react#useAgentChat 132.9 KiB 609.7 KiB
⚪ agents/chat/react#WebSocketChatTransport 5.7 KiB 17.1 KiB
⚪ agents/chat/transport#WebSocketChatTransport 2.8 KiB 9.2 KiB
⚪ agents/client#AgentClient 21.8 KiB 75.2 KiB
⚪ agents/client#AgentConnectionError 16.3 KiB 57.8 KiB
⚪ agents/client#agentFetch 19.7 KiB 69.0 KiB
⚪ agents/client#createStubProxy 16.4 KiB 57.8 KiB
⚪ agents/client#DEFAULT_CALL_TIMEOUT_MS 16.2 KiB 57.6 KiB
⚪ agents/client#isTerminalCloseEvent 16.3 KiB 57.6 KiB
⚪ agents/client#nativeCall 16.5 KiB 58.3 KiB
⚪ agents/client#NativeCallQueue 16.7 KiB 58.9 KiB
⚪ agents/client#splitCallOptions 16.3 KiB 57.7 KiB
⚪ agents/context#AgentContextProvider 412 B 792 B
⚪ agents/context#AgentSearchProvider 640 B 1.3 KiB
⚪ agents/context#ContextBlocks 87.4 KiB 429.7 KiB
⚪ agents/email#createAddressBasedEmailResolver 193 B 227 B
⚪ agents/email#createCatchAllEmailResolver 110 B 97 B
⚪ agents/email#createHeaderBasedEmailResolver 334 B 492 B
⚪ agents/email#createSecureReplyEmailResolver 718 B 1.3 KiB
⚪ agents/email#DEFAULT_MAX_AGE_SECONDS 56 B 39 B
⚪ agents/email#isAutoReplyEmail 201 B 249 B
⚪ agents/email#signAgentHeaders 424 B 812 B
⚪ agents/experimental/webmcp#registerWebMcp 85.2 KiB 295.8 KiB
⚪ agents/lifecycle#getCurrentAgent 376 B 798 B
⚪ agents/lifecycle#Lifecycle 8.4 KiB 26.1 KiB
⚪ agents/lifecycle#LifecycleCapability 497 B 999 B
⚪ agents/mcp#createLegacyMcpHandler 378.6 KiB 1583.3 KiB
⚪ agents/mcp#createMcpHandler 390.9 KiB 1628.5 KiB
⚪ agents/mcp#DurableObjectEventStore 345.1 KiB 1441.8 KiB
⚪ agents/mcp#ElicitRequestSchema 345.1 KiB 1441.8 KiB
⚪ agents/mcp#experimental_createMcpHandler 378.7 KiB 1583.6 KiB
⚪ agents/mcp#getMcpAuthContext 345.1 KiB 1441.9 KiB
⚪ agents/mcp#MCP_SERVER_ID_MAX_LENGTH 345.1 KiB 1441.9 KiB
⚪ agents/mcp#McpAgent 345.1 KiB 1441.8 KiB
⚪ agents/mcp#normalizeServerId 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPC_DO_PREFIX 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPCClientTransport 345.1 KiB 1441.8 KiB
⚪ agents/mcp#RPCServerTransport 345.1 KiB 1441.8 KiB
⚪ agents/mcp#SSEEdgeClientTransport 345.2 KiB 1442.1 KiB
⚪ agents/mcp#StreamableHTTPEdgeClientTransport 345.2 KiB 1442.1 KiB
⚪ agents/mcp#WorkerTransport 348.5 KiB 1458.7 KiB
⚪ agents/mcp/client#getNamespacedData 62.9 KiB 240.0 KiB
⚪ agents/mcp/client#MCP_SERVER_ID_MAX_LENGTH 62.9 KiB 239.9 KiB
⚪ agents/mcp/client#MCPClientManager 158.6 KiB 702.6 KiB
⚪ agents/mcp/client#normalizeServerId 63.0 KiB 240.2 KiB
⚪ agents/mcp/do-oauth-client-provider#DurableObjectOAuthClientProvider 2.1 KiB 6.6 KiB
⚪ agents/mcp/server#createMcpHandler 80.5 KiB 307.2 KiB
⚪ agents/mcp/server#getMcpAuthContext 64.0 KiB 245.5 KiB
⚪ agents/observability#channels 259 B 549 B
⚪ agents/observability#genericObservability 470 B 1.2 KiB
⚪ agents/observability#subscribe 324 B 668 B
⚪ agents/observability/ai#wrapAISDK 8.8 KiB 30.5 KiB
⚪ agents/queue#Queue 3.1 KiB 9.6 KiB
⚪ agents/react#_testUtils 19.3 KiB 66.4 KiB
⚪ agents/react#useAgent 27.1 KiB 89.9 KiB
⚪ agents/react#useAgentToolEvents 21.1 KiB 73.7 KiB
⚪ agents/routing#getAgentByName 795 B 1.7 KiB
⚪ agents/routing#routeAgentRequest 1.6 KiB 3.6 KiB
⚪ agents/routing#RoutedAgents 2.4 KiB 6.2 KiB
⚪ agents/schedule#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedule#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedule#unstable_getSchedulePrompt 85.9 KiB 424.9 KiB
⚪ agents/schedule#unstable_scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedules#Scheduler 6.8 KiB 22.0 KiB
⚪ agents/schedules/parser#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedules/parser#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/sessions#COMPACTION_PREFIX 147 B 160 B
⚪ agents/sessions#createCompactFunction 1.7 KiB 4.0 KiB
⚪ agents/sessions#isCompactionMessage 177 B 200 B
⚪ agents/sessions#Session 2.0 KiB 6.3 KiB
⚪ agents/sessions#Sessions 9.6 KiB 34.3 KiB
⚪ agents/skills#fromManifest 309.8 KiB 1084.0 KiB
⚪ agents/skills#parseSkillFrontmatter 328.4 KiB 1146.2 KiB
⚪ agents/skills#parseSkillMarkdown 328.6 KiB 1146.5 KiB
⚪ agents/skills#r2 330.2 KiB 1150.4 KiB
⚪ agents/skills#runner 369.0 KiB 1297.8 KiB
⚪ agents/skills#SkillRegistry 416.4 KiB 1581.7 KiB
⚪ agents/skills/compile#compileSkillScript 15.4 KiB 43.4 KiB
⚪ agents/skills/compile#isCompilableSkillScript 15.4 KiB 43.3 KiB
⚪ agents/state#State 1.1 KiB 2.4 KiB
⚪ agents/streams#DEFAULT_MAX_CHUNK_BYTES 94 B 96 B
⚪ agents/streams#sseResponse 857 B 1.6 KiB
⚪ agents/streams#StreamClosedError 175 B 212 B
⚪ agents/streams#StreamNotFoundError 215 B 276 B
⚪ agents/streams#Streams 4.3 KiB 13.9 KiB
⚪ agents/streams#StreamSerializationError 171 B 201 B
⚪ agents/tasks#DuplicateTaskStepError 328 B 463 B
⚪ agents/tasks#MAX_SERIALIZED_BYTES 190 B 232 B
⚪ agents/tasks#MissingTaskDefinitionError 358 B 536 B
⚪ agents/tasks#NonRetryableError 238 B 308 B
⚪ agents/tasks#TaskReplayDivergedError 341 B 483 B
⚪ agents/tasks#Tasks 8.9 KiB 31.8 KiB
⚪ agents/tasks#TaskSerializationError 258 B 339 B
⚪ agents/types#MessageType 211 B 365 B
⚪ agents/vite#default 353.8 KiB 1356.1 KiB
⚪ agents/voice#addSFUTracks 324 B 424 B
⚪ agents/voice#createSFUSession 255 B 306 B
⚪ agents/voice#createSFUWebSocketAdapter 331 B 429 B
⚪ agents/voice#decodeVarint 157 B 160 B
⚪ agents/voice#downsample48kStereoTo16kMono 238 B 324 B
⚪ agents/voice#encodePayloadToProtobuf 189 B 279 B
⚪ agents/voice#encodeVarint 129 B 122 B
⚪ agents/voice#extractPayloadFromProtobuf 271 B 425 B
⚪ agents/voice#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice#renegotiateSFUSession 329 B 428 B
⚪ agents/voice#SentenceChunker 550 B 1.1 KiB
⚪ agents/voice#sfuFetch 294 B 358 B
⚪ agents/voice#upsample16kMonoTo48kStereo 211 B 272 B
⚪ agents/voice#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice#withVoice 9.5 KiB 32.7 KiB
⚪ agents/voice#withVoiceInput 4.8 KiB 16.0 KiB
⚪ agents/voice#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice#WorkersAITTS 682 B 1.4 KiB
⚪ agents/voice/client#VOICE_PROTOCOL_VERSION 473 B 768 B
⚪ agents/voice/client#VoiceClient 10.1 KiB 31.6 KiB
⚪ agents/voice/client#WebSocketVoiceTransport 4.6 KiB 13.3 KiB
⚪ agents/voice/errors#logVoiceError 132 B 173 B
⚪ agents/voice/errors#toVoiceError 94 B 80 B
⚪ agents/voice/errors#voiceErrorMessage 125 B 111 B
⚪ agents/voice/errors#VoiceProviderError 190 B 345 B
⚪ agents/voice/react#useVoiceAgent 13.6 KiB 42.5 KiB
⚪ agents/voice/react#useVoiceInput 13.3 KiB 41.4 KiB
⚪ agents/voice/react#WebSocketVoiceTransport 7.4 KiB 21.3 KiB
⚪ agents/voice/sfu#addSFUTracks 323 B 424 B
⚪ agents/voice/sfu#createSFUSession 254 B 306 B
⚪ agents/voice/sfu#createSFUWebSocketAdapter 329 B 429 B
⚪ agents/voice/sfu#decodeVarint 155 B 160 B
⚪ agents/voice/sfu#downsample48kStereoTo16kMono 236 B 324 B
⚪ agents/voice/sfu#encodePayloadToProtobuf 188 B 279 B
⚪ agents/voice/sfu#encodeVarint 129 B 122 B
⚪ agents/voice/sfu#extractPayloadFromProtobuf 270 B 425 B
⚪ agents/voice/sfu#renegotiateSFUSession 329 B 428 B
⚪ agents/voice/sfu#sfuFetch 292 B 358 B
⚪ agents/voice/sfu#upsample16kMonoTo48kStereo 209 B 272 B
⚪ agents/voice/text#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice/text#SentenceChunker 549 B 1.1 KiB
⚪ agents/voice/types#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice/workers-ai#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAITTS 682 B 1.4 KiB
⚪ agents/websockets#CAPNWEB_TRANSPORT_QUERY 12.4 KiB 43.3 KiB
⚪ agents/websockets#CAPNWEB_TRANSPORT_VALUE 12.4 KiB 43.3 KiB
⚪ agents/websockets#capnWebTransportUrl 12.5 KiB 43.5 KiB
⚪ agents/websockets#CF_NO_PROTOCOL_KEY 12.4 KiB 43.3 KiB
⚪ agents/websockets#CF_READONLY_KEY 12.4 KiB 43.3 KiB
⚪ agents/websockets#isCapnWebTransportUpgrade 12.5 KiB 43.4 KiB
⚪ agents/websockets#registerInternalConnectionKeys 12.4 KiB 43.4 KiB
⚪ agents/websockets#WebSockets 20.0 KiB 69.3 KiB
⚪ agents/workflows#AgentWorkflow 262.4 KiB 1145.7 KiB
⚪ agents/workflows#WorkflowRejectedError 261.2 KiB 1141.2 KiB
⚪ agents/x402#normalizeNetwork 14.7 KiB 61.1 KiB
⚪ agents/x402#withX402 23.0 KiB 89.2 KiB
⚪ agents/x402#withX402Client 104.1 KiB 346.5 KiB

Reported by agent-think[bot].

devin-ai-integration[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2285

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2285

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2285

hono-agents

npm i https://pkg.pr.new/hono-agents@2285

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2285

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2285

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2285

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2285

commit: d92fa75

- daemon: record a delivery key as applied only after the engine took the
  row, with an in-flight guard for a duplicate racing the first; a daemon
  that dies between the two no longer leaves evidence of an input the
  engine never received
- harness: seed the session seq from every log that can hold the tail
  (settled operations' last_seq, running operations' streams, the session
  log), not the newest-created stream; a steer that settles a newer
  operation while an older one keeps appending no longer reuses seqs
- harness: delete a session's streams page by page until none remain
- harness: sessions.list() cursors carry the (created_at, session_id) key,
  so sessions created in one millisecond page without skips
- remote: the launch digest covers env values as well as keys (the
  per-generation runtime id excepted), so a rotated credential relaunches
- react: the hook resets its cursor when the agent or sub path changes
- outbox: byte accounting follows the commit, so a rolled-back batch
  counts nothing

Tests: seqs stay strictly increasing across an eviction mid-turn; sessions
page without skips; the outbox applied-key API.

@devin-ai-integration devin-ai-integration 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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 12 new potential issues.

Devin Review

Comment thread examples/next/harnesses/claude-code/container/src/outbox.ts
Comment thread examples/next/harnesses/shared/src/remote.ts Outdated
Comment thread examples/next/harnesses/shared/src/harness.ts
Comment on lines +522 to +527
const stop = new AbortController();
await Promise.race([
done.then(() => stop.abort()),
ctx.inbox.wait(stop.signal)
]);
if (!outcome) await this.#deliverSteer(ctx, lane, context);

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.

🔍 Steered prompts can miss the running turn

#untilSettled() waits without peeking for a steer admitted after the previous check. Since ctx.inbox.wait() observes only future rows, the steer can remain queued until another wake.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +99 to +106
function promptText(payload: JsonValue): string {
// SAFETY: the base writes `HarnessPromptPayload` into every prompt row.
const input = (payload as unknown as HarnessPromptPayload).input;
const text = typeof input === "string" ? input : (input?.text ?? "");
if (text.trim() === "") {
throw new SelfModifyingInputError("Turn prompt must not be empty");
}
return text;

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.

🔍 Parts-only prompts are treated as empty

promptText() ignores text entries in input.parts. Valid parts-only input becomes empty and fails before the editable harness runs.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +180 to +193
if (request.method === "POST" && segments.at(-1) === "restart") {
// Evict this object after replying, so a reader can watch a turn the
// container is still running survive the eviction: the outbox holds
// the frames, the doorbell wakes a fresh incarnation, and reconcile
// replays them.
setTimeout(() => this.ctx.abort("restart requested from the demo"), 50);
return new Response(null, { status: 202 });
}
if (request.method === "POST" && segments.at(-1) === "kill") {
// Destroy the container, workspace and all. The next prompt launches a
// fresh one and hands the engine back the transcript this object kept,
// so the model remembers the conversation the dead container ran.
await this.runtime.stop("killed from the demo");
return new Response(null, { status: 202 });

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.

🟥 Unauthenticated routes allow container destruction

Any caller can invoke restart or kill for a named session. These routes evict the object or destroy its container and workspace.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +243 to +245
return (
(await routeAgentRequest(request, env, { cors: true })) ??
new Response("Not found", { status: 404 })

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.

🟥 Unauthenticated sockets control named sessions

The harness socket accepts caller-selected sessions without authorization. Attackers can read transcripts, submit prompts, interrupt turns, and answer permission requests.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +232 to +237
const url = new URL(request.url);
if (url.pathname.startsWith(HARNESS_DOORBELL_PATH)) {
// The daemon cannot address a Durable Object; this entrypoint can.
return (ctx as ExportsContext).exports
.HarnessDoorbell({ props: { namespace: "ClaudeCodeSession" } })
.fetch(request);

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.

🟥 Doorbell routing accepts arbitrary object names

Public doorbell requests select any Durable Object through ?name= before authentication. Attackers can force object creation and lifecycle startup at scale.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +46 to +52
async onRequest(request: Request): Promise<Response> {
const { pathname } = new URL(request.url);
if (request.method !== "GET" || !pathname.endsWith("/snapshot")) {
return new Response("Not found", { status: 404 });
}
await this.lifecycle.start();
return Response.json(this.runtime.snapshot());

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.

🟨 Host routes expose session internals

Unauthenticated routes return editable source, journals, workspace output, and operation snapshots. Anyone with an object name can inspect private session state.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread examples/next/harnesses/shared/src/transport.ts Outdated
- Outbox persists its high-water seq in meta so a fully pruned outbox does
  not restart at seq 1 on a reopened daemon under the same runtime id.
- ContainerHarnessRuntime treats a failed attach (container starting, port
  not listening) as a retry with backoff instead of failing the running
  operation; a handshake the runtime already settled still propagates, and
  attach failures give up after five minutes.
- interrupt({ operationId }) only targets operations in the caller's session.
- begin() marks the row running in the same transaction that writes
  operation_started.
- The daemon's request timeout and shutdown replies use each request's own
  reply shape, through a shared harnessTimeoutReply helper.
- pi delivers steers already in the inbox before waiting for the next row.
- The self-modifying runtime reads prompt parts when there is no text.
- The browser transport validates each client message's fields.
- The doorbell bounds the object name it will route to.
- READMEs say plainly that the examples and the socket are unauthenticated.

Tests: outbox reopen after prune, cold-port dial retries, interrupt scoping,
request timeout shapes.
devin-ai-integration[bot]

This comment was marked as resolved.

…n delete

- ContainerHarnessRuntime no longer advertises resumePolicy: nothing read
  it, and a lost operation always settles as E_ENGINE_LOST while the next
  prompt continues the session on the restored transcript. The RFC says
  the same and marks the SIGTERM-continuation item as the blocker.
- ContainerHarnessRuntime.delete() and the Codex runtime's delete() clear
  the Sessions transcript they projected, so a recreated session starts
  empty.
- A begin or settle frame for an operation the base no longer has is
  dropped with a warning instead of failing the drive pass.
- The remote test fixture ends the fake daemon's generation when the
  container is destroyed, as the platform does.
devin-ai-integration[bot]

This comment was marked as resolved.

…t have

An event frame for an unknown operation reached ctx.begin() through the
handle lookup and threw on every retry, so the drain never advanced past
it. The guard now wraps the whole frame in the batch loop: a frame the
base cannot place is dropped with a warning and the cursor moves on.
The fake daemon can leave a stray turn in its outbox to test it.
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