> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Devin SDK example: Cloudflare Agents chat

> A chat on Cloudflare Workers where each conversation is an AIChatAgent Durable Object driving a Devin cloud session, streamed to useAgentChat.

> [!NOTE]
>
> Source:
> [`cloudflare-agents`](https://github.com/COG-GTM/devin-sdk-examples/tree/main/cloudflare-agents)
> — requires `0.0.1-beta.2` or later.

The [Cloudflare Agents](https://docs.devin.ai/sdk/cloudflare-agents/overview.md) example. Every conversation is a
Cloudflare Agent that drives a Devin cloud session through `@cognition-ai/sdk`; `useAgentChat`
streams Devin's typed events — text, reasoning, tool calls, plans, permission prompts, pull
requests — into the browser, and the chat, its transcript and its Devin session survive refreshes,
hibernation and deploys in Durable Object storage. The console is laid out like
[harness chat](https://docs.devin.ai/sdk/examples/harness-chat.md): chats on the left, the transcript in the middle, and on
the right a **Session** panel (status, activity, cost, a link to the session in the Devin app) and
a **Workspace** panel (Devin's plan, its pull requests, and the files it touched).

Unlike harness chat, nothing runs in a sandbox you own: Devin works in its own cloud VM, and the
Worker only holds the API key and relays events. That is also why this example can continue a
session you started in the Devin app.

![Cloudflare Agents chat: a chat list, a transcript with reasoning, write and bash cards showing a passing npm test run and Devin's reply, a Session panel with the session id and status, and a Workspace panel listing three files with +/− counts](https://mintcdn.com/cognitionai/JKDXpuU-9rfPoVUG/images/sdk/cloudflare-agents.png?fit=max\&auto=format\&n=JKDXpuU-9rfPoVUG\&q=85\&s=75a6ec67b6c2f264b43e5bd849f480a3)

The console: Devin's reasoning, file writes and a test run as typed cards; the Session panel with the cloud session's status; the Workspace panel with the files it touched

## How it fits together

```mermaid theme={null}
flowchart LR
    subgraph browser["Browser"]
        direction TB
        ua["useAgent(Workspace)"]
        uac["useAgentChat(DevinChat facet)"]
    end
    subgraph worker["Worker (routeAgentRequest)"]
        direction TB
        ws["Workspace (Durable Object, per user)<br/>chat list in SQLite, @callable methods"]
        chat["DevinChat facet (per chat)<br/>DevinChatAgent = AIChatAgent"]
        ws --- chat
    end
    ua <-- "WebSocket" --> ws
    uac <-- "WebSocket" --> chat
    chat <-- "ACP over WebSocket" --> devin["Devin cloud session"]
```

* **`Workspace extends Agent`** (`src/server.ts`) is the sidebar: `createChat`, `deleteChat` and
  `recentSessions` are `@callable()` methods, the chat list lives in SQLite and is broadcast as
  Agent state, and `onBeforeSubAgent` only lets clients reach chats this workspace created.
* **`DevinChat extends DevinChatAgent`** is one chat, a facet of the workspace.
  `@cognition-ai/cloudflare-agents` does all of the Devin work — session create and attach, one
  turn per message, stop, permissions as tool approvals, start over, importing a session,
  recovery. The example adds `onChatResponse` (the chat's sidebar entry), `sessionSnapshot` (what
  the cloud says about the session between turns) and `respondToPermission`.
* **`src/console.tsx` and `src/components/`** are the React app: one chronological
  `message.parts.map()` per message, and a card per tool call rendered from the SDK's normalized
  `ToolCallDetail`.

The `DEVIN_API_KEY` secret only exists in the Worker. The browser talks to your Agents, never to
Devin.

## The key pattern

The server is a `DevinChatAgent` plus whatever your app needs around it:

```ts src/server.ts (excerpt) theme={null}
import { DevinChatAgent } from "@cognition-ai/cloudflare-agents";

export class DevinChat extends DevinChatAgent<Env> {
  protected override async onChatResponse() {
    const workspace = await this.parentAgent(Workspace);
    await workspace.recordTurn(this.name, {
      /* title, preview, devinUrl */
    });
  }
}
```

The client is the stock Agents hooks, typed with the package's state and message types:

```tsx src/console.tsx (excerpt) theme={null}
import type { DevinChatMessage, DevinChatState } from "@cognition-ai/cloudflare-agents";

const agent = useAgent<DevinChatState>({
  agent: "Workspace",
  name: userId,
  sub: [{ agent: "DevinChat", name: chat.id }],
});
const { messages, sendMessage, stop, clearHistory, addToolApprovalResponse } = useAgentChat<
  DevinChatState,
  DevinChatMessage
>({ agent, sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses });
// agent.state.sessionUrl links to the session in the Devin app
```

## What maps to what

| Devin | In the app |
| - | - |
| assistant text | text parts, rendered as Markdown |
| thoughts | a collapsible **Reasoning** block, open while it streams; short step labels render as one quiet line |
| tool calls | `dynamic-tool` parts whose `input` is the SDK's `ToolCallDetail`: `bash` shows the command, cwd, exit code and output; `edit`/`write` the files with `+/−` |
| `permission_request` tool calls | Allow / Deny, answered through `DevinChat.respondToPermission` → `session.respondToPermission` |
| `ask_user_question` tool calls | the questions, with their options as chips that prefill the composer |
| `computer_use` tool calls (Devin's browser) | a card with the actions Devin took (`key ctrl+l · type · screenshot`) |
| plans | `data-devin-plan` parts as a checklist, and `agent.state.plan` in the Workspace panel |
| pull requests | `data-devin-pull-request` cards; the Workspace panel lists them with state, review and `+/−` |
| files Devin edited | the Workspace panel lists them with `+/−`, from the `files` of `edit` / `write` tool calls |
| `status`, `activity`, `turn_state`, `lifecycle` | `agent.state` while a turn streams; between turns the Session panel polls the session snapshot so it stays current |

Internal bookkeeping the cloud reports as tool calls is hidden, as in the Devin app. The full
mapping is in [AI SDK UI](https://docs.devin.ai/sdk/ai-sdk/overview.md#what-the-stream-contains).

## Lifecycle

| Action | Local chat | Devin session |
| - | - | - |
| Send while a turn runs | the composer keeps the draft until the turn ends | — |
| Stop | turn ends, streamed parts are kept | cancelled; a command already running finishes in the VM, its late output is dropped |
| New session (`startOver`) | transcript and session state cleared | detached, keeps running in the cloud; the next message starts a new one |
| Delete chat | facet and its storage deleted | detached, keeps running in the cloud |
| Continue a session | new chat rebuilt from the session's replay | attached (`importSession`) |
| Refresh / reconnect | messages and state reload from SQLite | unaffected |
| Durable Object restart mid-turn | streamed parts are kept, the prompt is not resent | keeps working; the next message attaches to it |

Nothing here stops a Devin session remotely; archive or end sessions in the Devin app or with
`devin.api`.

## Run it

```sh theme={null}
cd cloudflare-agents
bun install
echo 'DEVIN_API_KEY=cog_…' > .env      # app.devin.ai → Settings → API keys
bun run start                          # http://localhost:5173
```

`bun run test` runs the Worker in workerd against a fake Devin ACP server; `bun run deploy`
publishes it (`wrangler secret put DEVIN_API_KEY` first).

> [!WARNING]
>
> This is a single-user demo: there is no login, and anyone who can reach the Worker can use your
> Devin key through it. Put [Cloudflare Access](https://developers.cloudflare.com/cloudflare-one/policies/access/)
> in front of it or add your own auth before sharing a URL; the example's README has the three
> changes that take.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.