feat(executors): add isolated Claude Code bridge over Devin ACP (#8914)

Validated in post-merge-train sweep (boards clean on release/v3.8.50 tip)
This commit is contained in:
Lucas Israel
2026-08-06 11:08:37 -03:00
committed by GitHub
parent 813dbb6e03
commit 2a94cbfe14
51 changed files with 5106 additions and 0 deletions

181
docs/DEVIN_CLAUDE_BRIDGE.md Normal file
View File

@@ -0,0 +1,181 @@
# Devin Claude Bridge
`devin-cli-agentic` lets the real Claude Code runtime use OmniRoute's local Anthropic
Messages endpoint while the official Devin CLI supplies model responses over ACP stdio. It
does not modify the existing Anthropic, Claude OAuth, Claude Web, or `devin-cli` providers.
> **Current status: offline and live validated.** The pinned Claude Code `2.1.220` completed
> three isolated scenarios through Devin CLI `3000.2.17` and model
> `swe-1-7-lightning`. The final live run proved client-owned `Read`, `Edit`, and `Bash`
> turns, successful `npm test` results, project command and skill discovery, Devin-only
> routing, and zero Claude egress.
## Architecture
```text
Claude Code 2.1.220 (isolated non-root Linux container)
-> http://omniroute:20128/v1/messages
-> devin-cli-agentic (Claude-format, no-auth provider)
-> devin acp --agent-type summarizer (official ACP stdio, no Devin tools)
-> Devin account in the dedicated devin-auth volume
```
The official CLI's default ACP agent can execute its own tools, so this bridge does not use
it. It starts the fixed `summarizer` ACP agent, whose official CLI mode has no tools, and
frames the serialized Anthropic request as an execution trace. When another Claude-owned
action is needed, the response must contain exactly one client tool envelope. Any ACP
`tool_call` or `tool_call_update` is rejected before a response can be reported as
successful.
The serializer in `open-sse/executors/devin-agentic/serializer.ts` preserves `system`,
`text`, `tool_use`, `tool_result`, `thinking`, `redacted_thinking`, `tool_choice`, and the
tools supplied by Claude Code. Images and unknown blocks fail explicitly. Large tool results
use a visible truncation marker.
The parser accepts one standalone `<tool>{...}</tool>` envelope per model turn. It checks
the name against the request's tool list, validates arguments against that tool's JSON
Schema, rejects mixed narrative/actions, and permits one bounded repair. Claude Code then
executes the resulting Anthropic `tool_use` locally and sends the `tool_result` back through
OmniRoute.
## Isolation and threat model
The host's Claude installation, account, and configuration are out of scope and treated as
forbidden. The Compose services:
- run as UID/GID `10001:10001`, with a read-only root filesystem, dropped capabilities, and
`no-new-privileges`;
- use a private `/home/bridge`, a dedicated Claude config volume, isolated OmniRoute data,
and a separate `devin-auth` volume;
- mount only disposable `.sandbox` workspaces/evidence;
- do not mount the host home, Keychain, SSH, cloud credentials, or Docker socket;
- construct explicit environments and remove Anthropic API/OAuth/routing variables;
- direct Claude Code inference only to `http://omniroute:20128` with a local-only key.
The offline profile uses an internal network. In the live profile, OmniRoute reaches the
official Devin endpoints only through `network-guard`; unrelated destinations are denied.
Claude Code has a separate deny-all egress guard and can reach only the local OmniRoute
service through `NO_PROXY`. Guard audit files are mounted only by their guard process. The
scripts verify file ownership, mode, link count, and every decision before exporting
token-free evidence.
Run the isolation proof independently:
```bash
./scripts/devin-bridge/verify-anthropic-isolation
```
It validates topology, named mounts, non-root/read-only settings, explicit local routing,
absence of sensitive environment variables, absence of the Docker socket, blocked access to
`api.anthropic.com` and `claude.ai`, Devin-only provider selection, and explicit failure when
the ACP backend is unavailable.
## First-time setup and normal use
Build the pinned image:
```bash
./scripts/devin-bridge/build
```
Authenticate only the isolated Devin volume:
```bash
ENABLE_LIVE_DEVIN_TESTS=1 ./scripts/devin-bridge/login-devin
```
The login command uses the official manual-token flow intended for remote/container
environments. The value is entered directly into the CLI prompt; it is not passed as a
process argument, written to Git, or copied from the host.
Launch the isolated Claude Code runtime:
```bash
./scripts/devin-bridge/launch
```
`launch` rechecks isolation, Devin authentication, and model discovery before starting the
containerized Claude Code. It never runs the host's Claude executable. Model aliases can be
set in `.env.devin-bridge`; every configured value must keep the
`devin-cli-agentic/` prefix.
## Validation commands
The reproducible offline path requires no Devin account and has no runtime Internet:
```bash
./scripts/devin-bridge/test-unit
./scripts/devin-bridge/test-contract
./scripts/devin-bridge/test-e2e-mock
./scripts/devin-bridge/verify-anthropic-isolation
```
The authenticated opt-in live path is:
```bash
ENABLE_LIVE_DEVIN_TESTS=1 ./scripts/devin-bridge/test-live-devin
```
The live runner waits between scenarios to avoid opening ACP sessions in a burst and
validates structured Claude stream events instead of trusting textual claims. Its three
scenarios prove:
1. direct project reads and defect analysis;
2. a real `Edit`, a client-owned `Bash` `npm test`, and a terminal result;
3. `/bridge-check` plus `bridge-proof` discovery, project reads, another successful
client-owned `npm test`, and completion without pending work.
The final gate also checks the Devin network audit and requires the Claude egress audit to
remain empty.
## Updating pinned tools
The image pins Node, Claude Code, and Devin CLI in
`docker/devin-bridge/Dockerfile`. To update:
1. change the explicit versions;
2. replace both architecture-specific Devin archive checksums with values for the official
artifact;
3. rebuild and run every offline validation command;
4. confirm the versions inside the image;
5. rerun the authenticated three-scenario live suite.
Do not install either CLI globally on the host or replace checksum verification with an
unverified download.
## Diagnosis and cleanup
- `docker compose -f docker/devin-bridge/compose.yml --profile offline logs omniroute`
shows local routing and sanitized executor errors.
- `.sandbox/evidence/mock-acp.jsonl` records deterministic mock ACP actions.
- `.sandbox/evidence/claude-stream.jsonl` records the real Claude Code offline run.
- `.sandbox/evidence/live-*.jsonl` records the three validated live streams.
- `.sandbox/evidence/egress.jsonl` and `.sandbox/evidence/claude-egress.jsonl` are validated,
token-free copies of the guard audits.
Stop owned containers and networks while preserving login/config volumes:
```bash
./scripts/devin-bridge/clean
```
Remove the complete bridge-owned environment, including named volumes:
```bash
./scripts/devin-bridge/clean --all
```
## Limits
- The bridge relies on the fixed no-tools `summarizer` role because Devin CLI `3000.2.17`
does not expose a neutral no-tools ACP agent. The adapter compensates for summary-shaped
intermediate responses, but one bounded repair can still fail explicitly.
- Live ACP calls can return transient `502`/`504` responses. The harness spaces scenarios;
persistent failure remains fail-closed and never selects another provider.
- ACP context is reconstructed from each Anthropic request; there is no process/session
affinity.
- One tool call is supported per model response; parallel calls are rejected.
- Images are explicitly unsupported. Vision, thinking output, effort controls, and a 1M
context window are not advertised.
- SSE uses valid Anthropic lifecycle events but is emitted after the bounded ACP turn is
collected; ACP chunks are not forwarded incrementally.

View File

@@ -0,0 +1,115 @@
# Devin Claude Bridge Progress
Updated: 2026-07-28
## Baseline
- Fork version: `3.8.49`.
- Starting branch: `release/v3.8.49`.
- Starting commit: `ed7db3ee5f89a144b2d931d8605534522f83de30`.
- Fixed runtime artifacts: Node `26.0.0`, Claude Code `2.1.220`, Devin CLI `3000.2.17`.
- Existing `devin-cli` remains unchanged; the new path is the separate
`devin-cli-agentic` provider.
## Implemented architecture
- Claude Code runs only inside the non-root bridge container with its own empty config
volume and local OmniRoute base URL.
- `devin-cli-agentic` preserves Anthropic messages, tool schemas, `tool_use`, and
`tool_result`, then calls the official Devin CLI over ACP stdio.
- The executor starts `devin acp --agent-type summarizer`. This is the only fixed official
ACP role in the pinned CLI that has no Devin-owned tools.
- The request is framed as an execution trace. Devin can return one strict client tool
envelope; Claude Code executes that tool locally.
- Internal ACP `tool_call` events, unsupported blocks, invalid schemas, narrative actions,
timeouts, cancellation, and process failure all fail closed.
- Provider and network policy prevent combo/auto/Anthropic fallback.
## Offline proof
- Focused serializer, parser, executor, ACP lifecycle, wire-format, environment, and audit
tests pass (39/39).
- The contract suite covers Anthropic JSON/SSE, `tool_use`, `tool_result` continuation,
fragmented ACP frames, stderr, early exit, timeout, cancellation, and fail-closed provider
loss.
- The production bridge image builds with the pinned CLIs.
- Real Claude Code offline E2E loads `CLAUDE.md`, the project skill and slash command, fires
hooks, executes local tools over multiple turns, observes a failed test, repairs the file,
reruns the test, and completes.
- The isolation verifier proves non-root/read-only execution, isolated mounts and config,
blocked Anthropic/Claude access, no host credential mounts, local-only inference, and no
fallback.
Evidence is generated under `.sandbox/evidence` and ignored by Git.
## Regression status
- `typecheck:core`, focused ESLint, Prettier, shell/Node syntax, and the complete documentation
accuracy suite pass.
- The broad `npm run check` is not reported as passed: after its lint phase, the repository
test runner remained alive while an existing `ioredis` client repeatedly retried an
unavailable local Redis endpoint after `quota-redis-store.test.ts`. The bridge-focused
suites, production image build, offline E2E, isolation proof, and live gate do not use that
Redis service and all pass.
## Live Devin proof
Passed with the official in-container login and discovered model
`swe-1-7-lightning`. The terminal live run completed all three scenarios:
1. Claude Code loaded the fixture instructions, issued client-owned `Read` calls, and
returned a correct defect analysis.
2. Claude Code issued a real `Edit` changing subtraction to addition, then a client-owned
`Bash` call running `npm test`; the test reported one pass and zero failures.
3. Claude Code initialization listed `bridge-check` and `bridge-proof`, read the corrected
source and test, executed another client-owned `npm test`, and completed successfully.
The live evidence validator parses stream JSON and requires successful tool results. It does
not accept a textual claim that a tool ran. It also rejects terminal summaries that report a
blocker, incomplete work, or required next steps.
The final live gate reported:
```text
PASS: validated Claude evidence for LIVE_ANALYSIS_COMPLETE
PASS: validated Claude evidence for LIVE_FIX_COMPLETE
PASS: validated Claude evidence for BRIDGE_E2E_COMPLETE
PASS: three live Devin-backed Claude Code scenarios completed
PASS: live model swe-1-7-lightning was discovered and validated by three scenarios
```
The same gate validated the network audit: only the Devin guard path was used, no internal
Devin tool event was accepted, and the Claude egress audit remained empty.
## Investigation conclusion
The initial default-agent hypothesis failed because ACP permission modes do not turn the
default Devin agent into a raw inference backend. Even `ask` mode can emit Devin-owned
`tool_call` events. A discovered `allowed-tools: []` agent configuration was not consumed by
`devin acp` in CLI `3000.2.17`.
The working adaptation uses the official `summarizer` agent because it is structurally
no-tools. Its fixed summarization behavior can produce intermediate prose, so the bridge
frames requests as execution traces, detects future-action narration, performs at most one
strict repair, and otherwise fails. Live validation also exposed transient ACP timeouts;
the harness now spaces independent scenarios rather than weakening routing or retrying into
another provider.
## Safety record
No host Claude executable, configuration, login, OAuth token, Keychain, or Anthropic API was
used. The dedicated Docker volumes remain role-separated. No credential value is written to
the repository or evidence output.
During the early baseline, a focused test without isolated `DATA_DIR` initialized the
repository's normal OmniRoute database at `/Users/lucasisrael/.omniroute/storage.sqlite`.
It was not rolled back or touched again. Every bridge command now pins database and temporary
paths under the worktree's `.sandbox` directory.
## Remaining limits
- The no-tools backend has a summarizer system role rather than a neutral generation role.
- One client tool call per response is supported; parallel tool calls are rejected.
- ACP processes are per-turn and stateless.
- Live Devin availability can still produce explicit `502`/`504` failures.
- Images and unadvertised vision/effort/large-context capabilities remain unsupported.

View File

@@ -381,6 +381,14 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Custom path to the Qwen Code CLI binary. |
| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Custom path to the Devin CLI binary (v3.8.0). Used by the Windsurf/Devin executor. |
| `CLI_DEVIN_AGENTIC_BIN` | `devin` | `open-sse/executors/devin-cli-agentic.ts` | Agentic bridge-only Devin CLI override. The executor accepts only the local ACP stdio upstream. |
| `DEVIN_AGENTIC_HOME` | _(required)_ | `open-sse/executors/devin-cli-agentic.ts` | Absolute isolated home for the agentic Devin subprocess; accepted bridge paths are `/home/bridge` and task-local `.sandbox` paths. |
| `DEVIN_AGENTIC_ACP_TIMEOUT_MS` | `120000` | `open-sse/executors/devin-cli-agentic.ts` | Maximum duration of one Devin ACP turn before the bridge terminates the child and returns an explicit timeout. |
| `DEVIN_BRIDGE_MODEL` | `devin-cli-agentic/swe-1-7` | `docker/devin-bridge/compose.yml` | Main Claude Code model alias for the isolated bridge. The live harness replaces the example with a model returned by the current Devin account. |
| `DEVIN_BRIDGE_SONNET_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used when Claude Code requests its Sonnet default. |
| `DEVIN_BRIDGE_OPUS_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used when Claude Code requests its Opus default. |
| `DEVIN_BRIDGE_HAIKU_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used when Claude Code requests its Haiku default. |
| `DEVIN_BRIDGE_SUBAGENT_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used for Claude Code subagents. |
| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Absolute-path override for the Augment (Auggie) CLI binary used by the local `auggie` provider. Falls back to `CLI_AUGGIE_BIN`, then a PATH lookup. |
| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Alias override for the Augment (Auggie) CLI binary path (checked after `AUGGIE_BIN`). |
| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent home directory where OmniRoute reads/writes the Hermes CLI config. Matches the env var the Hermes PowerShell installer sets on Windows (`%LOCALAPPDATA%\hermes`). |

View File

@@ -0,0 +1,252 @@
# Devin Claude Bridge Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a fail-closed `devin-cli-agentic` provider that serves local Anthropic Messages requests through Devin CLI ACP stdio while preserving Claude Code tool-use semantics.
**Architecture:** Add a separate Claude-format provider and executor instead of changing the existing OpenAI-format `devin-cli` summarizer. Keep parsing, prompt serialization, Anthropic response rendering, and ACP process handling in focused files under `open-sse/executors/devin-agentic/`, then wire them into the existing provider and executor registries.
**Tech Stack:** TypeScript ES modules, Node child process stdio, Anthropic Messages JSON/SSE, JSON-RPC 2.0 ACP, Node test runner.
---
### Task 1: Agentic Bridge Core
**Files:**
- Create: `open-sse/executors/devin-agentic/types.ts`
- Create: `open-sse/executors/devin-agentic/serializer.ts`
- Create: `open-sse/executors/devin-agentic/toolParser.ts`
- Create: `open-sse/executors/devin-agentic/anthropicResponse.ts`
- Test: `tests/unit/executor-devin-cli-agentic-core.test.ts`
- [ ] **Implement and prove serialization, parsing, validation, and Anthropic rendering**
Interfaces:
```ts
export function serializeAnthropicForDevin(body: unknown): DevinPrompt;
export function parseDevinToolRequest(text: string, tools: AnthropicTool[]): ParsedToolRequest | null;
export function buildClaudeTextResponse(args: ClaudeResponseArgs): Record<string, unknown>;
export function buildClaudeToolUseResponse(args: ClaudeToolUseArgs): Record<string, unknown>;
export function buildClaudeSseFrames(message: Record<string, unknown>): string;
```
Invariants:
- Preserve `text`, `tool_use`, `tool_result`, `thinking`, and `redacted_thinking`.
- Reject `image` with a clear error.
- Reject unknown content block types.
- Allow only one tool request per model turn.
- Validate tool arguments against object JSON Schema with `required`, `type`, `properties`, `additionalProperties`, `enum`, `items`, and scalar types.
- Generate deterministic ids from tool name and canonicalized arguments.
Run: `node --import tsx/esm --test tests/unit/executor-devin-cli-agentic-core.test.ts`
Expected: core tests pass after dependencies are installed.
### Task 2: ACP Executor And Provider Wiring
**Files:**
- Create: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `open-sse/executors/index.ts`
- Create: `open-sse/config/providers/registry/devin-cli-agentic/index.ts`
- Modify: `open-sse/config/providers/index.ts`
- Test: `tests/unit/executor-devin-cli-agentic-acp.test.ts`
- [ ] **Implement and prove fail-closed ACP execution**
Behavior:
- `buildUrl()` returns `devin://acp/stdio`.
- `buildHeaders()` returns `{}`.
- `execute()` spawns only `devin acp` by default or the explicit `CLI_DEVIN_AGENTIC_BIN`/`CLI_DEVIN_BIN` override.
- The child environment removes Anthropic and Claude routing credentials before spawn.
- The executor sends `initialize`, `session/new`, and `session/prompt`.
- The executor collects `agent_message_chunk` text and `session/prompt` final result.
- Non-streaming Claude clients receive native Anthropic JSON.
- Streaming Claude clients receive native Anthropic SSE lifecycle frames.
- Spawn failure, ACP error, timeout, and early exit produce non-2xx responses with sanitized messages.
Run: `node --import tsx/esm --test tests/unit/executor-devin-cli-agentic-acp.test.ts`
Expected: ACP mock tests pass after dependencies are installed.
### Task 3: Isolation Scripts And Documentation
**Files:**
- Create: `scripts/devin-bridge/verify-anthropic-isolation`
- Create: `scripts/devin-bridge/test-unit`
- Create: `scripts/devin-bridge/launch`
- Create: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Modify: `.gitignore`
- [ ] **Implement offline guardrails and operator docs**
Behavior:
- `verify-anthropic-isolation` fails if `CLAUDE_CONFIG_DIR` is missing, points outside an isolated path, or if Anthropic routing env vars are present.
- `test-unit` runs the focused unit tests.
- `launch` refuses to start unless `ENABLE_LIVE_DEVIN_TESTS=1` for live Devin or `DEVIN_BRIDGE_OFFLINE=1` for offline mock mode.
- Documentation distinguishes tested offline behavior from live Devin opt-in behavior.
Run: `./scripts/devin-bridge/verify-anthropic-isolation` with explicit isolated env.
Expected: exits 0 with isolated env and non-zero without it.
### Task 4: Verification
**Files:**
- No additional source files.
- [ ] **Run proportional checks and capture real output**
Commands:
```bash
./scripts/devin-bridge/test-unit
npm test
```
Expected in this workspace before installing dependencies: both commands fail with `ERR_MODULE_NOT_FOUND` for `tsx`. Expected after `npm install`: focused tests pass; `npm test` outcome must be reported from real output.
### Task 5: Close Core Security And Protocol Gaps
**Files:**
- Modify: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `open-sse/executors/devin-agentic/*.ts`
- Modify: `tests/unit/executor-devin-cli-agentic-*.test.ts`
- [ ] **Prove environment allowlisting, response-id correlation, strict standalone tool envelopes, unique ids, bounded repair, size limits, cancellation cleanup, sanitized errors, and explicit `devin://acp/stdio` validation**
Run with `HOME`, `DATA_DIR`, and `SQLITE_FILE` under `.sandbox`; expected: all focused tests pass and an outside-path test fails closed.
### Task 6: Build Reproducible Containers And Network Guard
**Files:**
- Create: `docker/devin-bridge/Dockerfile`
- Create: `docker/devin-bridge/compose.yml`
- Create: `docker/devin-bridge/network-guard/*`
- Create: `docker/devin-bridge/mock-devin/*`
- Create: `.env.devin-bridge.example`
- [ ] **Pin Claude Code 2.1.220 and Devin CLI 3000.2.17, create non-root offline/live profiles, separate auth/config volumes, explicit env allowlist, no host credential mounts, and denied-domain telemetry**
Run: `docker compose -f docker/devin-bridge/compose.yml --profile offline config`; expected: no forbidden mounts/env inheritance and only internal runtime networks.
### Task 7: Deliver Isolation And Operator Scripts
**Files:**
- Create/modify: `scripts/devin-bridge/{build,test-unit,test-contract,test-e2e-mock,verify-anthropic-isolation,login-devin,test-live-devin,launch,clean}`
- [ ] **Make every command idempotent, sandbox-scoped, fail-closed, and secret-safe**
Run: `./scripts/devin-bridge/verify-anthropic-isolation`; expected: positive offline proof passes and each deliberately removed guard returns non-zero.
### Task 8: Real Claude Code Offline E2E
**Files:**
- Create: `tests/fixtures/devin-bridge/e2e-workspace/*`
- Create: `tests/e2e/devin-claude-bridge.e2e.*`
- [ ] **Run pinned Claude Code in the offline container through local `/v1/messages` and mock ACP, proving CLAUDE.md, skill, command, hook, Read/Edit/Bash, tests, multi-turn continuation, and no Anthropic traffic**
Run: `./scripts/devin-bridge/test-e2e-mock`; expected: workspace diff and tests prove Claude Code executed tools while mock Devin only requested them.
### Task 9: Regression, Documentation, Live Gate, And Delivery
**Files:**
- Modify: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Create: `docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md`
- [ ] **Run focused suites, typecheck, lint, build, docs checks, offline E2E, and isolation proof with fresh output; then run live only after official in-container Devin login**
If login is unavailable, record live as not tested and expose exactly `./scripts/devin-bridge/login-devin` followed by `./scripts/devin-bridge/test-live-devin`. Commit each reversible unit; do not merge or publish until all offline critical checks are green.
### Task 10: Close The Authenticated Live Runtime
**Files:**
- Modify: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `docker/devin-bridge/compose.yml`
- Create: `docker/devin-bridge/network-guard/policy.mjs`
- Modify: `docker/devin-bridge/network-guard/proxy.mjs`
- Modify: `scripts/devin-bridge/select-live-model.mjs`
- Modify: `scripts/devin-bridge/common`
- Modify: `scripts/devin-bridge/login-devin`
- Modify: `scripts/devin-bridge/test-live-devin`
- Modify: `scripts/devin-bridge/verify-anthropic-isolation`
- Modify: `tests/unit/executor-devin-cli-agentic-acp.test.ts`
- Create: `tests/unit/devin-bridge-live-runtime.test.ts`
- [ ] **Implement and prove the authenticated network, auth, and catalog boundaries with block-level TDD**
Invariants:
- The ACP child receives proxy variables only when `DEVIN_BRIDGE_PROXY_URL` is exactly
`http://network-guard:8080`; arbitrary inherited proxy and credential variables stay absent.
- The guard permits suffixes `.devin.ai` and `.cognition.ai`, exact hosts
`server.codeium.com` and `unleash.codeium.com`, and nothing else.
- Claude services cannot mount `devin-auth`; non-Claude services cannot mount the Claude config.
- A zero exit from `devin auth status` is insufficient when output contains a server-fetch failure.
- `family_uid: swe-1.7-lightning` resolves to catalog id `swe-1-7-lightning`; unknown normalized
values fail instead of becoming model ids.
- Login uses the official manual-token flow so no container loopback callback is required.
Run:
```bash
./scripts/devin-bridge/test-unit
node --import tsx/esm --test tests/unit/devin-bridge-live-runtime.test.ts
./scripts/devin-bridge/verify-anthropic-isolation --static
```
Expected: focused tests and static isolation pass; deliberate untrusted proxy, host, mount, auth
status, and model fixtures fail closed.
- [ ] **Commit the reversible live-runtime repair**
```bash
git add open-sse/executors/devin-cli-agentic.ts docker/devin-bridge \
scripts/devin-bridge tests/unit/devin-bridge-live-runtime.test.ts \
tests/unit/executor-devin-cli-agentic-acp.test.ts
git commit -m "fix: close Devin bridge live runtime gaps"
```
### Task 11: Prove Offline And Live Completion
**Files:**
- Modify: `docker/devin-bridge/run-claude-live-e2e.sh`
- Modify: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Modify: `docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md`
- [ ] **Run the complete deterministic bridge proof before any paid request**
```bash
./scripts/devin-bridge/test-unit
./scripts/devin-bridge/test-contract
./scripts/devin-bridge/test-e2e-mock
./scripts/devin-bridge/verify-anthropic-isolation
npm run typecheck:core
npm run lint
npm run build
npm run check:docs-all
```
Expected: all bridge-specific checks, typecheck, lint, build, and documentation checks pass with
isolated data paths. Any unrelated full-suite infrastructure hang is recorded separately and is
not converted into a pass.
- [ ] **Run exactly the three authorized live scenarios and the no-fallback failure probe**
```bash
ENABLE_LIVE_DEVIN_TESTS=1 ./scripts/devin-bridge/test-live-devin
```
Expected: dynamic discovery selects a returned Devin catalog model; Claude Code reads without
editing, then edits and runs the fixture test, then executes the fixture command. Evidence shows
native tool use by Claude Code, only `devin-cli-agentic` routing, no allowed non-Devin egress,
and an Anthropic-shaped error after the Devin backend is deliberately made unavailable.
- [ ] **Update verified documentation and commit the evidence-backed delivery state**
```bash
git add docker/devin-bridge/run-claude-live-e2e.sh docs/DEVIN_CLAUDE_BRIDGE.md \
docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md
git commit -m "docs: record verified Devin bridge live delivery"
```

View File

@@ -0,0 +1,134 @@
# Devin Claude Bridge Design
## Baseline
- Branch: `release/v3.8.49`
- HEAD: `ed7db3ee5f89a144b2d931d8605534522f83de30`
- Package version: `3.8.49`
- Node: `v26.0.0`
- npm: `11.12.1`
- Pre-existing worktree state: `.tug/` untracked
- Dependency state: `node_modules` is absent; the first focused test run failed before loading tests because `tsx` was not installed.
- Tugline state: `tug` exists, but `tug search` failed with MCP connection closed and `tug doctor` hung; it was interrupted.
- Upstream check: `git ls-remote` failed because GitHub DNS was unavailable. Web search of the public repository showed the existing `devin-cli` summarizer provider, but no evidence of `devin-cli-agentic`.
## Source Anchors
- `/v1/messages`: `src/app/api/v1/messages/route.ts`
- Existing Devin provider: `open-sse/config/providers/registry/devin-cli/index.ts`
- Existing Devin executor: `open-sse/executors/devin-cli.ts`
- Executor registry: `open-sse/executors/index.ts`
- Provider registry: `open-sse/config/providers/index.ts`
- Format detection: `open-sse/services/provider.ts`
- Claude non-streaming response conversion: `open-sse/handlers/responseTranslator.ts`
- Existing Devin ACP unit test: `tests/unit/executor-devin-cli-acp-protocol-8406.test.ts`
## Findings
The existing `devin-cli` provider is intentionally OpenAI-format and summarizer-oriented. Its executor spawns `devin acp --agent-type summarizer`, flattens the message history into a single text prompt, and emits OpenAI SSE text chunks. It does not preserve Anthropic `tool_use` and `tool_result` blocks.
The safest implementation is a new provider id, `devin-cli-agentic`, with a separate executor. This leaves `devin-cli`, Anthropic OAuth, Claude OAuth, Claude Web, and all host Claude configuration code untouched. The new provider is fail-closed: it only resolves to `devin://acp/stdio`, uses the official Devin CLI ACP stdio path, and has no fallback provider.
## Architecture
Claude Code sends Anthropic Messages requests to local OmniRoute. OmniRoute resolves model ids prefixed with `devin-cli-agentic/` to a new Claude-format provider. The new executor translates the complete Anthropic request into an explicit text prompt for Devin ACP, including system text, structured message history, tool schemas, and prior tool results.
Devin remains a model backend. The executor starts the official fixed no-tools summarizer
role with `devin acp --agent-type summarizer` and frames the serialized request as an
execution trace. Devin must request client-owned tool execution by emitting a strict
XML-wrapped JSON block:
```xml
<tool>
{"name":"Read","arguments":{"file_path":"src/index.ts"}}
</tool>
```
The bridge parses exactly one tool request per model turn, validates that the tool name was supplied in the incoming request, validates arguments against a minimal JSON Schema validator, generates a stable `tool_devin_...` id, and returns a native Anthropic `tool_use` block. If no valid tool request is present, the bridge returns text with `stop_reason: "end_turn"`.
## Error And Safety Rules
- Unsupported Anthropic content blocks fail explicitly; images are rejected.
- Unknown tools fail explicitly.
- Invalid tool arguments fail explicitly.
- Invalid tool XML/JSON fails explicitly.
- Narrative claims that a tool was executed are returned as text, not actions.
- ACP spawn, timeout, early exit, and stderr-only failures return explicit Devin errors.
- The executor never reads `~/.claude`, `~/.claude.json`, macOS Keychain paths, or host Claude config.
- Live Devin is outside normal tests and remains opt-in via `ENABLE_LIVE_DEVIN_TESTS=1`.
## Test Strategy
Focused unit tests cover serialization, tool parsing, validation, Anthropic JSON, Anthropic SSE, malformed tool output, unknown tools, invalid arguments, image rejection, timeout, and spawn failure. Environment scripts provide an offline isolation verifier without reading host Claude credentials.
## Mandatory Runtime Isolation
The bridge runs only through `docker/devin-bridge/compose.yml`. The runtime image is non-root, uses a private `/home/bridge`, and mounts only disposable workspaces, evidence, and bridge harness files. Application source is copied into the image. It never mounts the host home, Docker socket, SSH, cloud credentials, or global Claude configuration. The container receives an explicit environment allowlist; the executor also constructs an allowlisted child environment instead of copying `process.env`.
Build-time network access installs Claude Code `2.1.220` and Devin CLI `3000.2.17` with pinned integrity/checksum. Runtime profiles are separate: `offline` uses only an internal Compose network; `live-devin` exposes egress only through a proxy guard whose allowlist contains Devin/Cognition suffixes and whose default is denial. Devin authentication lives only in the named `devin-auth` volume. Claude configuration lives in a different named volume and is initialized empty.
## Fail-Closed Routing
`devin-cli-agentic` accepts only the synthetic `devin://acp/stdio` target and an explicit Devin binary path inside the container. It cannot use provider combos, auto routing, account fallback, fallback URLs, or an HTTP upstream. Model aliases resolve only to models returned by the Devin catalog or explicitly configured Devin model ids. An ACP failure, timeout, cancellation, invalid frame, unavailable model, or stopped sidecar becomes an Anthropic-shaped error response; no secondary provider is attempted.
## Agentic Contract
The serializer preserves request order, `system`, `tool_choice`, exact tool schemas, `text`, `tool_use`, `tool_result`, `thinking`, and `redacted_thinking`. It rejects unsupported blocks and caps large tool results with an explicit truncation marker and original size. The parser accepts exactly one standalone `<tool>` envelope, validates with Zod/JSON Schema infrastructure already present in OmniRoute, rejects unknown tools and mixed narrative/action output, and performs at most one bounded repair prompt. Tool ids combine a per-request nonce with canonical arguments so repeated identical calls remain unique while their association is stable within the turn.
## Required Proof
The offline profile must prove the ACP lifecycle, fragmented frames, stderr, early exit, hang/cancel, Anthropic JSON/SSE order, no fallback, and a real pinned Claude Code run that reads, edits, runs tests, observes `CLAUDE.md`, loads a skill and command, fires a hook, and completes at least one `tool_use -> tool_result -> continuation` loop. The isolation verifier checks env, mounts, UID, config paths, DNS/connection logs, local inference destination, selected provider, and fail-closed behavior. Live Devin is proved only by official in-container login and three isolated agentic scenarios.
## Safety Incident During Baseline
The first focused test was run without `DATA_DIR` isolation and initialized `/Users/lucasisrael/.omniroute/storage.sqlite`; logs reported schema-column additions. No Anthropic data was accessed. The external database will not be touched again or destructively rolled back. Every bridge command and test now must set `HOME`, `DATA_DIR`, `SQLITE_FILE`, and temporary directories inside `.sandbox`, and an automated guard must reject paths outside the task workspace.
## Live Completion Repair
The first authenticated live attempt disproved four assumptions in the initial container
design. The official CLI reports a valid login even when its server-status request fails;
that request uses the exact hosts `server.codeium.com` and `unleash.codeium.com`, which the
guard denied. The OmniRoute executor also built a fresh allowlisted child environment that
omitted the proxy, so `devin acp` could not leave the internal network. Model discovery emits
family identifiers such as `swe-1.7`, while the OmniRoute catalog uses canonical ids such as
`swe-1-7`. Finally, browser login redirects to a loopback listener inside the one-off
container, which is not reachable from the host browser.
The repair keeps the fully containerized architecture and does not weaken the deny-by-default
network. The guard gains an exact-host allowlist for the two Codeium control-plane hosts while
retaining suffix-based access only for Devin and Cognition; telemetry destinations such as
Sentry remain denied. Compose supplies `DEVIN_BRIDGE_PROXY_URL` with the single accepted value
`http://network-guard:8080`, and the executor derives `HTTP_PROXY` and `HTTPS_PROXY` from that
explicit bridge setting instead of inheriting arbitrary host proxy variables. Claude services
mount only the Claude config volume, and only the OmniRoute live service mounts the Devin auth
volume.
Fresh login uses the official `devin auth login --force-manual-token-flow`, which is intended
for remote environments where localhost redirects cannot work. The credential is pasted only
into the interactive CLI terminal and never appears in arguments, logs, evidence, or Git.
Authentication validation requires both the logged-in marker and the absence of a server-fetch
failure. Model discovery accepts the real `family_uid`/`model_uid` fields, maps punctuation to a
catalog id only after an exact normalized match, and prefers the already-proved lightning model
when available.
Tests first prove the trusted proxy boundary, exact host policy, volume separation, strict auth
status gate, and catalog normalization. The live gate then runs three real Claude Code scenarios
through the authenticated in-container Devin CLI and requires local Read/Edit/Bash activity,
passing fixture tests, Devin-only routing, no allowed non-Devin egress, and an explicit error
when the Devin backend is stopped.
## Final Live Result
The default-agent design was rejected after live evidence showed that `ask` mode can still
emit Devin-owned ACP tool calls. The pinned CLI does not apply its top-level agent
configuration to `devin acp`, so an `allowed-tools: []` configuration could not create a
neutral backend. The fixed summarizer role is the only official ACP role in this version that
is structurally no-tools.
The execution-trace adaptation passed the authenticated live gate with
`swe-1-7-lightning`. Three Claude Code processes completed analysis, edit/test, and local
command/skill scenarios. Structured evidence proved that Claude Code issued `Read`, `Edit`,
and `Bash` tool calls; two client-owned `npm test` calls succeeded. The guard audit proved
Devin-only outbound access and zero Claude egress. Intermediate summary-shaped responses and
transient ACP timeouts remain explicit failure modes; the adapter performs one bounded repair
and the harness spaces scenarios to avoid bursty session creation.