diff --git a/Makefile b/Makefile index 1a53777d7..a30adcfce 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync deploy clean +.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync deploy clean check-architecture .PHONY: local-up local-down local-clean local-status local-rebuild local-reload-backend local-reload-frontend local-reload-operator local-sync-version .PHONY: local-dev-token .PHONY: local-logs local-logs-backend local-logs-frontend local-logs-operator local-shell local-shell-frontend @@ -14,7 +14,28 @@ # Configuration CONTAINER_ENGINE ?= podman -PLATFORM ?= linux/amd64 + +# Auto-detect host architecture for native builds +# Override with PLATFORM=linux/amd64 or PLATFORM=linux/arm64 if needed +HOST_OS := $(shell uname -s) +HOST_ARCH := $(shell uname -m) + +# Map uname output to Docker platform names +ifeq ($(HOST_ARCH),arm64) + DETECTED_PLATFORM := linux/arm64 +else ifeq ($(HOST_ARCH),aarch64) + DETECTED_PLATFORM := linux/arm64 +else ifeq ($(HOST_ARCH),x86_64) + DETECTED_PLATFORM := linux/amd64 +else ifeq ($(HOST_ARCH),amd64) + DETECTED_PLATFORM := linux/amd64 +else + DETECTED_PLATFORM := linux/amd64 + $(warning Unknown architecture $(HOST_ARCH), defaulting to linux/amd64) +endif + +# Allow manual override via PLATFORM=... +PLATFORM ?= $(DETECTED_PLATFORM) BUILD_FLAGS ?= NAMESPACE ?= ambient-code REGISTRY ?= quay.io/your-org @@ -76,7 +97,7 @@ help: ## Display this help message @echo '$(COLOR_BOLD)Configuration Variables:$(COLOR_RESET)' @echo ' CONTAINER_ENGINE=$(CONTAINER_ENGINE) (docker or podman)' @echo ' NAMESPACE=$(NAMESPACE)' - @echo ' PLATFORM=$(PLATFORM)' + @echo ' PLATFORM=$(PLATFORM) (detected: $(DETECTED_PLATFORM) from $(HOST_OS)/$(HOST_ARCH))' @echo '' @echo '$(COLOR_BOLD)Examples:$(COLOR_RESET)' @echo ' make local-up CONTAINER_ENGINE=docker' @@ -626,15 +647,32 @@ check-kubectl: ## Check if kubectl is installed @command -v kubectl >/dev/null 2>&1 || \ (echo "$(COLOR_RED)✗$(COLOR_RESET) kubectl not found. Install: https://kubernetes.io/docs/tasks/tools/" && exit 1) +check-architecture: ## Validate build architecture matches host + @echo "$(COLOR_BOLD)Architecture Check$(COLOR_RESET)" + @echo " Host: $(HOST_OS) / $(HOST_ARCH)" + @echo " Detected Platform: $(DETECTED_PLATFORM)" + @echo " Active Platform: $(PLATFORM)" + @if [ "$(PLATFORM)" != "$(DETECTED_PLATFORM)" ]; then \ + echo ""; \ + echo "$(COLOR_YELLOW)⚠ Cross-compilation active$(COLOR_RESET)"; \ + echo " Building $(PLATFORM) images on $(DETECTED_PLATFORM) host"; \ + echo " This will be slower (QEMU emulation)"; \ + echo ""; \ + echo " To use native builds:"; \ + echo " make build-all PLATFORM=$(DETECTED_PLATFORM)"; \ + else \ + echo "$(COLOR_GREEN)✓$(COLOR_RESET) Using native architecture"; \ + fi + _build-and-load: ## Internal: Build and load images - @echo " Building backend..." - @$(CONTAINER_ENGINE) build -t $(BACKEND_IMAGE) components/backend $(QUIET_REDIRECT) - @echo " Building frontend..." - @$(CONTAINER_ENGINE) build -t $(FRONTEND_IMAGE) components/frontend $(QUIET_REDIRECT) - @echo " Building operator..." - @$(CONTAINER_ENGINE) build -t $(OPERATOR_IMAGE) components/operator $(QUIET_REDIRECT) - @echo " Building runner..." - @$(CONTAINER_ENGINE) build -t $(RUNNER_IMAGE) -f components/runners/claude-code-runner/Dockerfile components/runners $(QUIET_REDIRECT) + @echo " Building backend ($(PLATFORM))..." + @$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) -t $(BACKEND_IMAGE) components/backend $(QUIET_REDIRECT) + @echo " Building frontend ($(PLATFORM))..." + @$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) -t $(FRONTEND_IMAGE) components/frontend $(QUIET_REDIRECT) + @echo " Building operator ($(PLATFORM))..." + @$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) -t $(OPERATOR_IMAGE) components/operator $(QUIET_REDIRECT) + @echo " Building runner ($(PLATFORM))..." + @$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) -t $(RUNNER_IMAGE) -f components/runners/claude-code-runner/Dockerfile components/runners $(QUIET_REDIRECT) @echo " Tagging images with localhost prefix..." @$(CONTAINER_ENGINE) tag $(BACKEND_IMAGE) localhost/$(BACKEND_IMAGE) 2>/dev/null || true @$(CONTAINER_ENGINE) tag $(FRONTEND_IMAGE) localhost/$(FRONTEND_IMAGE) 2>/dev/null || true diff --git a/components/manifests/minikube/frontend-deployment.yaml b/components/manifests/minikube/frontend-deployment.yaml index b9891ac2a..f93b09577 100644 --- a/components/manifests/minikube/frontend-deployment.yaml +++ b/components/manifests/minikube/frontend-deployment.yaml @@ -41,7 +41,7 @@ spec: - name: GITHUB_APP_SLUG value: "ambient-code" - name: VTEAM_VERSION - value: "v0.0.12-22-g5553056" + value: "v0.0.19-8-g6d9251e" - name: DISABLE_AUTH value: "true" - name: MOCK_USER diff --git a/components/runners/claude-code-runner/README.md b/components/runners/claude-code-runner/README.md new file mode 100644 index 000000000..dc4c88f8e --- /dev/null +++ b/components/runners/claude-code-runner/README.md @@ -0,0 +1,245 @@ +# Claude Code Runner + +The Claude Code Runner is a Python-based component that wraps the Claude Code SDK to provide agentic coding capabilities within the Ambient platform. + +## Architecture + +The runner consists of several key components: + +- **`adapter.py`** - Core adapter that wraps the Claude Code SDK and produces AG-UI protocol events +- **`main.py`** - FastAPI server that handles run requests via SSE (Server-Sent Events) +- **`observability.py`** - Langfuse integration for tracking usage and performance +- **`security_utils.py`** - Security utilities for sanitizing secrets and timeouts +- **`context.py`** - Runner context for session and workspace management + +## System Prompt Configuration + +The Claude Code Runner uses a hybrid system prompt approach that combines: + +1. **Base Claude Code Prompt** - The built-in `claude_code` system prompt from the Claude Agent SDK +2. **Workspace Context** - Custom workspace-specific information appended to the base prompt + +### Implementation + +In `adapter.py` (lines 508-511), the system prompt is configured as an array: + +```python +system_prompt_config = [ + "claude_code", + {"type": "text", "text": 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` + +### Workspace Context + +The workspace context prompt is built by `_build_workspace_context_prompt()` (lines 1500-1575) 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/`) +- **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` + +## Environment Variables + +### Authentication + +- `ANTHROPIC_API_KEY` - Anthropic API key for Claude access +- `CLAUDE_CODE_USE_VERTEX` - Set to `1` to use Vertex AI instead of Anthropic API +- `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) + +### Model Configuration + +- `LLM_MODEL` - Model to use (e.g., `claude-sonnet-4-5`) +- `LLM_MAX_TOKENS` / `MAX_TOKENS` - Maximum tokens per response +- `LLM_TEMPERATURE` / `TEMPERATURE` - Temperature for sampling + +### Session Configuration + +- `AGENTIC_SESSION_NAME` - Session name/ID +- `AGENTIC_SESSION_NAMESPACE` - K8s namespace for the session +- `IS_RESUME` - Set to `true` when resuming a session +- `INITIAL_PROMPT` - Initial user prompt + +### Repository Configuration + +- `REPOS_JSON` - JSON array of repository configurations + ```json + [ + { + "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) + +### Workflow Configuration + +- `ACTIVE_WORKFLOW_GIT_URL` - URL of active workflow repository + +### Observability + +- `LANGFUSE_PUBLIC_KEY` - Langfuse public key +- `LANGFUSE_SECRET_KEY` - Langfuse secret key +- `LANGFUSE_HOST` - Langfuse host URL + +### Backend Integration + +- `BACKEND_API_URL` - URL of the backend API +- `PROJECT_NAME` - Project name +- `BOT_TOKEN` - Authentication token for backend API calls +- `USER_ID` - User ID for observability +- `USER_NAME` - User name for observability + +### MCP Configuration + +- `MCP_CONFIG_FILE` - Path to MCP servers config (default: `/app/claude-runner/.mcp.json`) + +## MCP Servers + +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. + +## Development + +### Running Tests + +```bash +# Install dependencies +uv pip install -e ".[dev]" + +# Run all tests +pytest -v + +# Run with coverage +pytest --cov=. --cov-report=term-missing +``` + +See [tests/README.md](tests/README.md) for detailed testing documentation. + +### Local Development + +```bash +# Install in development mode +uv pip install -e . + +# Run the server +python main.py +``` + +## API + +The 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 + +- `POST /interrupt` - Interrupt the active execution + +- `GET /health` - Health check endpoint + +## AG-UI Protocol Events + +The runner emits AG-UI protocol events via SSE: + +- `RUN_STARTED` - Run has started +- `TEXT_MESSAGE_START` - Message started (user or assistant) +- `TEXT_MESSAGE_CONTENT` - Message content chunk +- `TEXT_MESSAGE_END` - Message completed +- `TOOL_CALL_START` - Tool invocation started +- `TOOL_CALL_ARGS` - Tool arguments +- `TOOL_CALL_END` - Tool invocation completed +- `STEP_STARTED` - Processing step started +- `STEP_FINISHED` - Processing step completed +- `STATE_DELTA` - State update (e.g., result payload) +- `RAW` - Custom events (thinking blocks, system logs, etc.) +- `RUN_FINISHED` - Run completed +- `RUN_ERROR` - Error occurred + +## Workspace Structure + +The runner operates within a workspace at `/workspace/` with the following structure: + +``` +/workspace/ +├── .claude/ # Claude SDK state (conversation history) +├── 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 + └── credentials/ + └── credentials.json +``` + +## Security + +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 + +See `security_utils.py` for implementation details. + +## Recent Changes + +### System Prompt Configuration (2026-01-28) + +Changed the system prompt configuration to use a hybrid approach: + +**Before:** +```python +system_prompt_config = {"type": "text", "text": workspace_prompt} +``` + +**After:** +```python +system_prompt_config = [ + "claude_code", + {"type": "text", "text": workspace_prompt} +] +``` + +**Rationale:** +- Leverages the built-in Claude Code system prompt for standard capabilities +- Appends workspace-specific context for session-aware operation +- Maintains separation between standard instructions and custom context +- Ensures Claude has both general coding capabilities and workspace knowledge + +**Impact:** +- Claude receives comprehensive instructions from both sources +- No breaking changes to existing functionality +- Better alignment with Claude Agent SDK best practices + +**Files Changed:** +- `components/runners/claude-code-runner/adapter.py` (lines 508-511) diff --git a/components/runners/claude-code-runner/adapter.py b/components/runners/claude-code-runner/adapter.py index 61258f5f0..761769db8 100644 --- a/components/runners/claude-code-runner/adapter.py +++ b/components/runners/claude-code-runner/adapter.py @@ -505,7 +505,10 @@ async def restart_session_tool(args: dict) -> dict: artifacts_path="artifacts", ambient_config=ambient_config, ) - system_prompt_config = {"type": "text", "text": workspace_prompt} + system_prompt_config = [ + "claude_code", + {"type": "text", "text": workspace_prompt} + ] # Configure SDK options options = ClaudeAgentOptions( diff --git a/docs/developer/local-development/kind.md b/docs/developer/local-development/kind.md index 195388993..76ab2b7b4 100644 --- a/docs/developer/local-development/kind.md +++ b/docs/developer/local-development/kind.md @@ -44,6 +44,25 @@ podman ps && kind --version && kubectl version --client docker ps && kind --version && kubectl version --client ``` +## Architecture Support + +The platform auto-detects your host architecture and builds native images: + +- **Apple Silicon (M1/M2/M3):** `linux/arm64` +- **Intel/AMD:** `linux/amd64` + +**Verify native builds:** +```bash +make check-architecture # Should show "✓ Using native architecture" +``` + +**Manual override (if needed):** +```bash +make build-all PLATFORM=linux/arm64 # Force specific architecture +``` + +⚠️ **Warning:** Cross-compiling (building non-native architecture) is 4-6x slower and may crash. + ## Commands ### `make kind-up` @@ -206,6 +225,19 @@ lsof -i:8080 # Find what's using the port # Kill it or edit e2e/scripts/setup-kind.sh to use different ports ``` +### Build crashes with segmentation fault + +**Symptom:** `qemu: uncaught target signal 11 (Segmentation fault)` during Next.js build + +**Fix:** +```bash +# Auto-detect and use native architecture +make local-clean +make local-up +``` + +**Diagnosis:** Run `make check-architecture` to verify native builds are enabled. + ### MinIO errors ```bash diff --git a/e2e/scripts/load-images.sh b/e2e/scripts/load-images.sh index 26462bcb4..f9a9c7977 100755 --- a/e2e/scripts/load-images.sh +++ b/e2e/scripts/load-images.sh @@ -33,6 +33,13 @@ if ! kind get clusters 2>/dev/null | grep -q "^ambient-local$"; then exit 1 fi +# Detect expected architecture based on host +case "$(uname -m)" in + arm64|aarch64) EXPECTED_ARCH="arm64" ;; + x86_64|amd64) EXPECTED_ARCH="amd64" ;; + *) EXPECTED_ARCH="amd64" ;; +esac + # Images to load IMAGES=( "vteam_backend:latest" @@ -47,7 +54,19 @@ echo "Loading ${#IMAGES[@]} images into kind cluster..." for IMAGE in "${IMAGES[@]}"; do echo " Loading $IMAGE..." - + + # Verify image exists + if ! $CONTAINER_ENGINE image inspect "$IMAGE" >/dev/null 2>&1; then + echo " ❌ Image not found. Run 'make build-all' first" + exit 1 + fi + + # Warn if architecture mismatch (don't block) + IMAGE_ARCH=$($CONTAINER_ENGINE image inspect "$IMAGE" --format '{{.Architecture}}' 2>/dev/null) + if [ -n "$IMAGE_ARCH" ] && [ "$IMAGE_ARCH" != "$EXPECTED_ARCH" ]; then + echo " ⚠️ Image is $IMAGE_ARCH, host is $EXPECTED_ARCH (may be slow)" + fi + # Save as OCI archive $CONTAINER_ENGINE save --format oci-archive -o "/tmp/${IMAGE//://}.oci.tar" "$IMAGE"