The Cloudflare OS agent is a code-execution agent that writes and runs snippets to complete tasks; it respects gatekeeper approval gates, recovers from restarts via durable storage, and accesses reachable resources through a bounded AgentCatalog. Agent chat state and bindings are stored server-side in AiChatAgentContext; the system validates binding names uniformly, preserves reasoning across turns with StoredAssistantMessage, and compacts chat history into immutable checkpoints that retain proposed changes only in registry rows. A gatekeeper mediates what resources and actions an agent session can access: it controls approval gates and exposes the AgentCatalog, establishing the trust boundary between the agent and the broader system. The keyboardEvent.ts module in workshop-frontend centralises IME composition state detection; its guards must be used by all keyboard-shortcut handlers and onKeyDown listeners to prevent actions from firing during CJK input method composition.
The Cloudflare OS coding agent is a "Code Mode" agent — it performs tasks by writing and immediately executing code snippets, and can be used for general-purpose tasks beyond building Gadgets.[1] The agent loop honors the ActionDescription.awaitDecision field, pausing execution until the user resolves gatekeeper approval when set.[2] Agents can be resumed after a server restart; in-progress sessions are recovered from durable storage.[3] The agent runtime in workshop-backend/src/agent.ts treats responsiveness as an explicit design constraint when generating or scaffolding gadgets. In packages/workshop-backend/src/overseer.ts, the overseer startup sequence awaits completion of all pending connection requests before allowing the agent to resume execution, preventing requests from being dropped or handled out of order during startup. In packages/workshop-backend/src/overseer.ts, a metrics call records the size of the message-replay payload emitted when a client subscribes to an existing chat session, enabling visibility into historical data volume per subscription for diagnosing reconnect-time latency and planning around Durable Object storage reads. All action-log read paths in workshop-backend/src/overseer.ts and workshop-shared/src/api.ts are bounded with explicit pagination limits, preventing arbitrarily large logs from being loaded into memory. The frontend hooks useActions.ts, useActionHistory.ts, Activity.tsx, ActivityNotifications.tsx, ChatInterface.tsx, and GadgetEditor.tsx consume paginated action-log responses. The resume cursor allows action-stream delivery to survive WebSocket disconnects: on reconnect, the client sends its last-acknowledged position and overseer.ts replays only the log tail from that point forward, avoiding full-log retransmission. The resume cursor is implemented in useActions.ts, useActionHistory.ts, and useWorkspaceOpen.ts; the cursor field is carried in the shared API contract in workshop-shared/src/api.ts; Activity.tsx and ChatInterface.tsx thread it through the render path. otClient.ts in workshop-frontend applies ordering guarantees to prevent concurrent operational-transform operations from overwriting the result of an already-completed chat metadata rebuild with a stale snapshot. packages/workshop-backend/src/overseer.ts logs return values from executeCode calls, surfacing results in runtime logs for debugging agent code-execution flows and observability tooling. The executeCode return-value contract for the gatekeeper scheduler is defined in packages/gatekeeper-scheduler/src/types.d.ts.
The Agent Catalog (AgentCatalog) is bounded discovery metadata a gatekeeper exposes via Gatekeeper.getAgentCatalog() so the agent can see what is reachable through a session — for example, Context Library collection titles — without reading everything upfront; entries are shown to the agent as untrusted data, carry no authority, and are size-capped.[4] The Workshop enforces hard caps on AgentCatalog entries: max 25 entries (AGENT_CATALOG_MAX_ENTRIES), max 256-character IDs (AGENT_CATALOG_MAX_ID_LENGTH), max 100-character titles (AGENT_CATALOG_MAX_TITLE_LENGTH), and max 400-character descriptions (AGENT_CATALOG_MAX_DESCRIPTION_LENGTH).[4]
validateBindingName() in packages/workshop-shared/src/api.ts is the single shared validator for all binding names in the system, applied at gadget binding edges, workspace default binding lists, chat binding maps, spawner env configs, and agent tools.[5] validateBindingName() rejects names that are not ASCII JavaScript identifiers matching /^[A-Za-z_][A-Za-z0-9_]*$/ (the $ character is deliberately excluded), ECMAScript reserved words, prototype, or any name that exists on Object.prototype such as __proto__, constructor, and hasOwnProperty.[5]
AiChatAgentContext in packages/workshop-backend/src/agent.ts stores additional per-chat-thread info needed by the AI agent but not exposed to clients, including the chat ID, an optional spawner config, and a frozen initial binding set (bindings).[6] The bindings map in AiChatAgentContext is frozen after the chat starts; new bindings introduced by changes messages in the chat log are not added to it — the log must be replayed to find the current binding set.[6] alwaysAvailableCapsuleIds in AiChatAgentContext is a legacy field that predates per-chat named bindings; its contents are now folded into bindings and it persists for old-chat migration and as a record of which bindings came from ambient gatekeepers.[6] A ChatBindingEntry in packages/workshop-backend/src/agent.ts resolves a name in the agent's executeCode env to either a workpiece (gadget or gatekeeper, distinguished at env-build time) or the value arguments of an agent callback.[6]
The changes summary message is generated at the end of a turn rather than at the end of each step, reducing noise in the chat history.[7] File uploads are supported in chat messages, and chat attachment types are validated on submission.[8] CompactionCheckpoint in packages/workshop-backend/src/agent.ts is an immutable record of a compacted chat prefix; a chat keeps every checkpoint it has published so history reads and reverts can select the newest checkpoint below any given sequence.[6] Provisional gadget creations and binding additions from before a compaction boundary are deliberately absent from CompactionCheckpoint.proposedChanges because registry rows (GadgetRecord.pending, BindingRecord.pending) already record them; duplicating them in the checkpoint would create a second source of truth.[6] Chat and gadget composer draft state is stored in sessionStorage via the composerDraft.ts module; drafts survive page navigation within a session but are cleared on send or tab close. composerDraft.ts is the canonical reference for draft lifecycle semantics; work on auto-save, multi-window sync, or draft migration must account for its sessionStorage persistence layer. ChatInterface.tsx renders a copy-to-clipboard button inside markdown code blocks; the button's styling is defined in ChatInterface.module.css. Chat composer attachment handling lives in packages/workshop-frontend/src/features/chat/composer/ and is split across three modules: useComposerAttachments.ts, useComposerAttachmentDrop.ts, and prepareChatAttachment.ts. ChatComposer.tsx in packages/workshop-frontend/src/features/chat/composer/ is a self-contained component extracted from ChatInterface.tsx; it owns dedicated sub-modules for drafts, attachments, inline items, submission, and layout. Chat composer draft logic resides in packages/workshop-frontend/src/features/chat/composer/draft/composerDraft.ts and useComposerDraft.ts, extracted from ChatInterface.tsx.
StoredAssistantMessage in packages/workshop-backend/src/agent.ts is stored server-side only and never sent to clients; it preserves reasoning across turns and restarts by keeping thinking blocks with their provider signatures (including encrypted/redacted payloads) and the message's true api/provider/model provenance, so the provider's cross-model conversions apply correctly when the user switches models.[6] The StoredAssistantMessage schema is intentionally subtractive — it copies everything from pi's AssistantMessage and deletes only what is provably redundant — so fields pi adds in the future are retained by default, preventing silent fidelity loss and prompt-caching breakage.[6] StoredToolCall in packages/workshop-backend/src/agent.ts omits the arguments field from pi's ToolCall type because arguments are already stored in the step's AiToolCall record as input and rehydrated at replay time; this avoids duplicating large payloads such as writeFile/executeCode content.[6]
IME composition guards from keyboardEvent.ts are applied in ChatInterface.tsx, Connections.tsx, FileSidebar.tsx, GadgetEditor.tsx, SettingsPage.tsx, ShareModal.tsx, WorkpiecePicker.tsx, CommandPalette.tsx, SidebarGadgetRow.tsx, GadgetList.tsx, ConnectionConfigModal.tsx, AdminFormatsPanel.tsx, and pickerNavigation.ts.
Workshop integration tests run in parallel; per-file setup was moved from global-setup.ts into per-suite lifecycle hooks, with corresponding updates to vitest.config.ts and workshop-backend/vite.config.ts. Agent integration tests in packages/integration-tests/ cover agent behaviour across workshop-agent.test.ts, workshop-agent-actions.test.ts, and mock-model.test.ts; a gatekeeper-test fixture in fixtures/gatekeeper-test/ provides an isolated Worker for test harness wiring. The packages/integration-tests/ suite includes mock-model infrastructure — src/mock-model.ts and src/network-interceptor.ts — that intercepts outbound model calls and replays scripted responses, enabling deterministic agent testing without live provider access. otClient.test.ts reproduces the stale chat metadata rebuild race scenario and provides regression coverage for the ordering guarantees in otClient.ts. packages/workshop-evals is the canonical location for regression coverage of agent behaviour changes; engineers modifying agent decision logic or action schemas should add or update an eval scenario there rather than relying solely on unit tests. AgentSession in packages/integration-tests/src/agent-session.ts is a reusable abstraction that wraps gatekeeper-test fixtures for use in eval scenarios.
Sources
README.mdgithub.com/cloudflare/cloudflare-os/commit/c5bacc4github.com/cloudflare/cloudflare-os/commit/9a0f4f4packages/workshop-shared/src/gatekeeper.tspackages/workshop-shared/src/api.tspackages/workshop-backend/src/agent.tsgithub.com/cloudflare/cloudflare-os/commit/78fd806github.com/cloudflare/cloudflare-os/commit/2a778eb