> ## 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 on Cloudflare Agents

> Put a Devin cloud session behind a Cloudflare Agents AIChatAgent with @cognition-ai/cloudflare-agents: one Durable Object per chat, driven by useAgentChat.

`@cognition-ai/cloudflare-agents` puts a Devin cloud session behind a
[Cloudflare Agents](https://developers.cloudflare.com/agents/) `AIChatAgent`, so `useAgentChat`
drives Devin the way it drives a model. Each chat is a Durable Object that owns one Devin session:
the API key stays in the Worker, the transcript persists in the object's SQLite storage, and the
session keeps working in Devin's cloud while the Durable Object hibernates.

It is built on [`@cognition-ai/ai-sdk`](https://docs.devin.ai/sdk/ai-sdk/overview), which does the event-to-UI-message
mapping. The [Cloudflare Agents chat example](https://docs.devin.ai/sdk/examples/cloudflare-agents.md) is a complete app on
top of it.

## Setup

```sh theme={null}
npm install @cognition-ai/cloudflare-agents@beta @cognition-ai/sdk@beta agents @cloudflare/ai-chat ai
wrangler secret put DEVIN_API_KEY
```

> [!NOTE]
>
> The `@cognition-ai` packages are in beta. Every release is a `0.0.1-beta.N` prerelease on the
> `beta` dist-tag (currently `0.0.1-beta.7`), so the `@beta` suffix is required: a bare
> `npm install @cognition-ai/sdk` fails on purpose. Pin the exact version in your lockfile, and keep
> every `@cognition-ai` package you use (`sdk`, `ai-sdk`, `cloudflare-agents`, `harness-devin`) on
> the same version; they release together. Each published version is stable, but the API can
> change between beta bumps; see the [changelog](https://docs.devin.ai/sdk/changelog.md) for what moved.

## Server

The whole agent is one class and a route:

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

export class DevinChat extends DevinChatAgent<Env> {}

export default {
  async fetch(request, env) {
    return (await routeAgentRequest(request, env)) ?? new Response("Not found", { status: 404 });
  },
} satisfies ExportedHandler<Env>;
```

```jsonc wrangler.jsonc theme={null}
{
  "compatibility_flags": ["nodejs_compat"],
  "durable_objects": { "bindings": [{ "class_name": "DevinChat", "name": "DevinChat" }] },
  "migrations": [{ "new_sqlite_classes": ["DevinChat"], "tag": "v1" }],
}
```

`DevinChatAgent` reads `DEVIN_API_KEY`, and optionally `DEVIN_ORG_ID` and `DEVIN_BASE_URL`, from the
Worker's bindings. Override the hooks for anything else, such as per-user keys or scoping new
sessions to repositories:

```ts theme={null}
import { CloudDevin } from "@cognition-ai/sdk";

export class DevinChat extends DevinChatAgent<Env> {
  protected override connectDevin() {
    return CloudDevin.connect({ apiKey: this.env.DEVIN_API_KEY, orgId: this.env.DEVIN_ORG_ID });
  }

  protected override sessionOptions() {
    return { repos: ["acme/app"] };
  }
}
```

## Client

```tsx theme={null}
import type { DevinChatMessage, DevinChatState } from "@cognition-ai/cloudflare-agents";
import { useAgentChat } from "@cloudflare/ai-chat/react";
import { useAgent } from "agents/react";
import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";

const agent = useAgent<DevinChatState>({ agent: "DevinChat", name: chatId });
const chat = useAgentChat<unknown, DevinChatMessage>({
  agent,
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
});
```

`agent.state` holds the session id and URL, status, activity, plan and pull requests, and updates
live while a turn streams. Messages carry text, reasoning, `dynamic-tool` parts whose `input` is
the SDK's [`ToolCallDetail`](https://docs.devin.ai/sdk/guides/streaming-events#tool-calls-the-same-on-both-agents), and
`data-devin-plan` / `data-devin-pull-request` parts; see [AI SDK UI](https://docs.devin.ai/sdk/ai-sdk/overview.md) for
the full mapping.

## What the agent does

* **One turn per message.** The first message creates a Devin session; later ones attach to it,
  including after hibernation, a deploy, or from another client. Messages sent mid-turn queue.
* **Stop.** `chat.stop()` cancels the Devin turn. A command Devin was already running finishes in
  its cloud VM; its late output is dropped rather than folded into the next turn.
* **Permissions.** Devin's permission requests arrive as AI SDK tool approvals. Answer them with
  `chat.addToolApprovalResponse({ id, approved })`, and the same turn resumes. Approving picks
  allow-once; denying picks reject-once.
* **Start over.** `agent.stub.startOver()` detaches from the session and stops the turn, so the next
  message starts a new session. Clear the transcript with `chat.clearHistory()`. The old session
  keeps running in the cloud.
* **Continue an existing session.** `agent.stub.importSession(id)` rebuilds the transcript from the
  session's replay and attaches future turns to it — the way to pick up a session started in the
  Devin app. It throws on a chat that already has a session or messages. `parseSessionId(input)`
  accepts a session id or a Devin web app URL.
* **Recovery.** If the Durable Object restarts mid-turn, what already streamed is kept and the
  prompt is not sent again: Devin already has it.
* **Late events.** Events from a turn the chat moved on from (stopped, started over) never touch
  the new session's state.

Workers can't start processes, so this is cloud sessions only; `LocalDevin` needs Node.js or Bun.

## Building your own agent

For a different agent shape, `connectDevin(env)` opens a connection from the bindings, and
`reduceDevinChatState(state, event)` projects session events onto `DevinChatState`. Combine them
with `toUIMessageStream`, `toUIMessages` and `ToolApprovals` from
[`@cognition-ai/ai-sdk`](https://docs.devin.ai/sdk/ai-sdk/overview).


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