The Claude Code Runner is a Python-based component that wraps the Claude Code SDK to provide agentic coding capabilities within the Ambient platform.
The runner follows the Ambient Runner SDK architecture (ADR-0006) with a layered design:
main.py- FastAPI entry point with AG-UI run/interrupt/health endpointsadapter.py- Platform adapter that configures and creates theClaudeAgentAdapterprompts.py- System prompt builder (workspace context, workflows, MCP instructions)auth.py- SDK authentication (API key, Vertex AI) and runtime credentialsworkspace.py- Workspace validation and path resolutionmcp.py- MCP server configuration and tool allowlistingobservability.py- Langfuse integration for tracking usage and performancecontext.py- Runner context for session and workspace managementsecurity_utils.py- Security utilities for sanitizing secrets and timeouts
repos.py-/repos/add,/repos/remove,/repos/statusworkflow.py-/workflow(runtime workflow switching)feedback.py-/feedback(Langfuse thumbs-up/down)capabilities.py-/capabilities(framework + platform features)mcp_status.py-/mcp/status(MCP server diagnostics)state.py- Shared mutable state for all endpoint routers
tracing.py- Langfuse tracing middleware (wraps AG-UI event stream)developer_events.py- Platform setup lifecycle events (role="developer")
Extracted package with the bridge pattern for framework-agnostic support:
bridge.py-PlatformBridgeABC,PlatformContext,FrameworkCapabilitiesapp.py-add_ambient_endpoints(app, bridge)public APIbridges/claude.py-ClaudeBridge(Claude Agent SDK)bridges/langgraph.py-LangGraphBridge(validates abstraction)
The Claude Code Runner uses a hybrid system prompt approach that combines:
- Base Claude Code Prompt - The built-in
claude_codesystem prompt from the Claude Agent SDK - Workspace Context - Custom workspace-specific information appended to the base prompt
In prompts.py, the system prompt uses SystemPromptPreset format via build_sdk_system_prompt():
system_prompt_config = {
"type": "preset",
"preset": "claude_code",
"append": workspace_prompt,
}This configuration ensures that:
- Claude receives the standard Claude Code instructions and capabilities
- Additional workspace context is provided, including:
- Repository structure and locations
- Active workflow information
- Artifacts and file upload locations
- Git branch and push instructions for auto-push repos
- MCP integration setup instructions
- Workflow-specific instructions from
ambient.json
The workspace context prompt is built by build_sdk_system_prompt() in prompts.py and includes:
- Working Directory: Current workflow or repository location
- Artifacts Path: Where to create output files
- Uploaded Files: Files uploaded by the user
- Repositories: List of repos available in the session
- Working Branch: Feature branch for all repos (e.g.,
ambient/<session-id>) - Git Push Instructions: Auto-push configuration for specific repos
- MCP Integrations: Instructions for setting up Google Drive and Jira access
- Workflow Instructions: Custom system prompt from workflow's
ambient.json
ANTHROPIC_API_KEY- Anthropic API key for Claude accessUSE_VERTEX- Set to1to use Vertex AI instead of direct API keys (unified flag for all runners; legacyCLAUDE_CODE_USE_VERTEXandGEMINI_USE_VERTEXstill accepted)GOOGLE_APPLICATION_CREDENTIALS- Path to GCP service account key (for Vertex AI)ANTHROPIC_VERTEX_PROJECT_ID- GCP project ID (for Vertex AI)CLOUD_ML_REGION- GCP region (for Vertex AI)
LLM_MODEL- Model to use (e.g.,claude-sonnet-4-5)LLM_MAX_TOKENS/MAX_TOKENS- Maximum tokens per responseLLM_TEMPERATURE/TEMPERATURE- Temperature for sampling
AGENTIC_SESSION_NAME- Session name/IDAGENTIC_SESSION_NAMESPACE- K8s namespace for the sessionIS_RESUME- Set totruewhen resuming a sessionINITIAL_PROMPT- Initial user prompt
REPOS_JSON- JSON array of repository configurations[ { "url": "https://github.com/owner/repo", "name": "repo-name", "branch": "ambient/session-id", "autoPush": true } ]MAIN_REPO_NAME- Name of the main repository (CWD)MAIN_REPO_INDEX- Index of main repo (if name not specified)
ACTIVE_WORKFLOW_GIT_URL- URL of active workflow repository
LANGFUSE_PUBLIC_KEY- Langfuse public keyLANGFUSE_SECRET_KEY- Langfuse secret keyLANGFUSE_HOST- Langfuse host URL
BACKEND_API_URL- URL of the backend APIPROJECT_NAME- Project nameBOT_TOKEN- Authentication token for backend API callsUSER_ID- User ID for observabilityUSER_NAME- User name for observability
MCP_CONFIG_FILE- Path to MCP servers config (default:/app/claude-runner/.mcp.json)
USER_GOOGLE_EMAIL- User's Google email for authentication (set by operator)GOOGLE_MCP_CREDENTIALS_DIR- Path to Google OAuth credentials directory (default:/workspace/.google_workspace_mcp/credentials)
The runner supports MCP (Model Context Protocol) servers for extending Claude's capabilities:
- webfetch - Fetch content from URLs
- mcp-atlassian - Jira integration for issue management
- google-workspace - Google Drive integration for file access
- session - Session control tools (restart_session)
MCP servers are configured in .mcp.json and loaded at runtime.
The google-workspace MCP server (workspace-mcp) requires OAuth credentials for Google Drive access:
Credentials Flow:
- User authenticates via OAuth in the frontend (
/integrationspage) - Backend stores credentials in a cluster-level K8s secret
- Operator creates a session-specific secret with the user's credentials
- Secret is mounted read-only at
/app/.google_workspace_mcp/credentials/ - A postStart lifecycle hook copies credentials to writable
/workspace/.google_workspace_mcp/credentials/ - The MCP server uses the writable path for token refresh
Credentials Format (flat JSON structure):
{
"token": "<access_token>",
"refresh_token": "<refresh_token>",
"token_uri": "https://oauth2.googleapis.com/token",
"client_id": "<oauth_client_id>",
"client_secret": "<oauth_client_secret>",
"scopes": ["https://www.googleapis.com/auth/drive.readonly"],
"expiry": "2026-01-30T12:00:00"
}Why writable credentials directory? The workspace-mcp server refreshes OAuth tokens automatically when they expire. This requires writing updated tokens to the credentials file. K8s secrets are mounted read-only, so the postStart hook copies credentials to a writable location.
# Install dependencies
uv pip install -e ".[dev]"
# Run all tests
pytest -v
# Run with coverage
pytest --cov=. --cov-report=term-missingSee tests/README.md for detailed testing documentation.
# Install in development mode
uv pip install -e .
# Run the server
python main.pyThe runner exposes a FastAPI server with the following endpoints:
-
POST /run- Execute a run with Claude Code SDK- Request:
RunAgentInput(thread_id, run_id, messages) - Response: Server-Sent Events (SSE) stream of AG-UI protocol events
- Request:
-
POST /interrupt- Interrupt the active execution -
GET /health- Health check endpoint
The runner emits AG-UI protocol events via SSE:
RUN_STARTED- Run has startedTEXT_MESSAGE_START- Message started (user or assistant)TEXT_MESSAGE_CONTENT- Message content chunkTEXT_MESSAGE_END- Message completedTOOL_CALL_START- Tool invocation startedTOOL_CALL_ARGS- Tool argumentsTOOL_CALL_END- Tool invocation completedSTEP_STARTED- Processing step startedSTEP_FINISHED- Processing step completedSTATE_DELTA- State update (e.g., result payload)RAW- Custom events (thinking blocks, system logs, etc.)RUN_FINISHED- Run completedRUN_ERROR- Error occurred
The runner operates within a workspace at /workspace/ with the following structure:
/workspace/
├── repos/ # Cloned repositories
│ └── {repo-name}/ # Individual repository
├── workflows/ # Workflow repositories
│ └── {workflow-name}/ # Individual workflow
├── artifacts/ # Output files created by Claude
├── file-uploads/ # User-uploaded files
└── .google_workspace_mcp/ # Google OAuth credentials (writable copy)
└── credentials/
└── credentials.json
/app/
└── .claude/ # Claude SDK state (conversation history)
The runner container runs as uid=1001 (non-root). The init container (hydrate.sh) sets up directory permissions:
/workspace/artifacts,/workspace/file-uploads,/workspace/repos- 777 permissions- Required because MCP servers spawn as subprocesses and need write access to their working directory
chown 1001:0is attempted first but may fail on SELinux-restricted hosts
/app/.claude- 777 permissions- Claude SDK requires write access for conversation state
/workspace/.google_workspace_mcp/credentials/- 777 permissions- Created by postStart hook for writable OAuth token storage
The runner implements several security measures:
- Secret Sanitization: API keys and tokens are redacted from logs
- Timeout Protection: Operations have configurable timeouts
- User Context Validation: User IDs and names are sanitized
- Read-only Workflow Directories: Workflows are read-only, outputs go to artifacts
- OAuth Credentials Isolation: Google credentials are stored in session-specific secrets, copied to writable storage only within the container
See security_utils.py for implementation details.
Changed the system prompt configuration to use the SystemPromptPreset format:
Before (incorrect - caused SDK initialization error):
system_prompt_config = [
"claude_code",
{"type": "text", "text": workspace_prompt}
]After (correct SystemPromptPreset format):
system_prompt_config = {
"type": "preset",
"preset": "claude_code",
"append": workspace_prompt,
}Rationale:
ClaudeAgentOptions.system_promptexpectsstr | SystemPromptPreset | None- The list format was invalid and caused
'list' object has no attribute 'get'error SystemPromptPresetusestype="preset",preset="claude_code", and optionalappend- This leverages the built-in Claude Code system prompt with appended workspace context
Impact:
- Fixes SDK initialization failure
- Claude receives comprehensive instructions from both sources
- Better alignment with Claude Agent SDK type definitions
Files Changed:
components/runners/ambient-runner/adapter.py(lines 557-561)