withvibe
Concepts

AI engines — Agent SDK vs Claude Code CLI

Every prompt in an env is processed by one of two engines. They emit the same chat events to the UI, but they have different ergonomics, capabilities, and operational footprint. This page explains both, when each runs, and how the fallback works.

The two engines

Agent SDK (in-process)

The default. The API process imports @anthropic-ai/claude-agent-sdkdirectly, spawns an agent loop in-process, and proxies tool calls through withvibe's MCP servers (env, workspace, member, docker, agent, human).

  • No extra container; nothing to install per env.
  • Tool calls are typed TypeScript on the API process side.
  • Best when you want predictable, well-instrumented behavior.

Claude Code CLI (per-env runner)

An optional engine that runs the real claude CLI (Claude Code) inside a per-env sidecar container called claude-runner-<envId>. The CLI gets the env's worktree mounted at /workspaceand joins the env's compose network so it can reach the user's services.

  • You get the full Claude Code experience, including its built-in skills and tools.
  • The runner is one container per env; it's lazy and idempotent — first turn boots it.
  • MCP parity is via HTTP — the runner calls back to the API at /api/mcp/<server>.
  • Sessions are durable: the first turn captures a Claude session id; subsequent turns pass --resume.
Same UI, same chat events
Both engines emit the same ChatEvent stream — assistant chunks, tool calls, tool results, thinking. Switching engines is invisible to teammates already in the chat.

How withvibe picks an engine

Engine selection is per env, with an automatic fallback path:

  1. The env declares its preferred engine via chatEngineagent_sdk or claude_code.
  2. On a turn marked claude_code, the chat stream calls a fast preflight: if the runner image isn't present, or the runner can't boot, withvibe auto-falls-back to the Agent SDK for that turn and surfaces a banner so devs can fix the runner state without blocking the conversation.
  3. The runner is brought up in the background; subsequent turns try Claude Code again.

The runner container

The runner image bundles the claude CLI plus the bits needed to bridge into withvibe:

  • Name: claude-runner-<envId> — stable, one per env, collision-free.
  • Mount: env worktree at /workspace.
  • Networks: joins the env's compose network (best-effort) so the agent can curl the user's services. Adds a host.docker.internal entry so it can reach the API.
  • Labels: withvibe.runner=true, withvibe.envId=<id> for housekeeping.
  • Health check: claude --version must succeed, otherwise the runner is removed and the engine falls back.

Build the runner image once and pin its tag:

docker build -t withvibe-claude-runner:latest apps/api/runner
# point the API at it (default already matches):
CLAUDE_RUNNER_IMAGE=withvibe-claude-runner:latest

MCP bridge

Tool calls in either engine end up at the same set of MCP servers (env, workspace, member, docker, agent, human). The Agent SDK calls them in-process; the runner calls them over HTTP at $${CLAUDE_RUNNER_MCP_BASE_URL}/<server>. The bridge token is baked into the runner's context so each MCP call is authenticated and scoped to the env.

Operational notes

  • Per-env memory: a runner adds ~200–400 MB to an env's footprint. Plan capacity if you switch a large team to Claude Code.
  • Lifecycle: runners are reaped when the env is deleted or stopped. They survive API restarts.
  • Network attach: if the user starts compose after the runner boots, withvibe re-attaches the runner to the new compose network on the next turn — no restart needed.

Choosing per env

  • Use Agent SDK when you want maximum reliability with no per-env containers. Good default for most teams.
  • Use Claude Code whenyou want the CLI's skills and built-in tools, or when teammates already drive Claude Code in their daily workflow and want the same experience inside a shared env.