diff --git a/HEADLESS_MODE_APPROACH.md b/HEADLESS_MODE_APPROACH.md new file mode 100644 index 0000000..8369c48 --- /dev/null +++ b/HEADLESS_MODE_APPROACH.md @@ -0,0 +1,530 @@ +# OpenHands Headless Mode Approach + +**Date:** 2025-11-30 +**Approach:** Headless Mode (Recommended) +**Priority:** HIGH - Replaces CLI and API approaches + +--- + +## Why Headless Mode? + +✅ **Perfect for Automation:** +- No interactive prompts +- No TTY requirements +- Direct command-line execution +- File-based task loading +- CI/CD pipeline ready + +✅ **No Technical Blockers:** +- Bypasses CLI interactive confirmation +- Avoids API runtime connectivity issues +- Clean Docker-based execution +- Proper error handling + +✅ **Production Ready:** +- Built for batch processing +- Environment variable configuration +- Repository integration support +- Budget and iteration controls + +--- + +## Architecture + +``` +Gitea Push → n8n Webhook → SSH Command → OpenHands Headless (Docker) → Result + ↓ + Verification & Response +``` + +### Flow: +1. **n8n receives Gitea webhook** +2. **SSH node executes Docker command** +3. **OpenHands runs headless in container** +4. **Task completes automatically** +5. **Verification node checks results** +6. **Response sent to Gitea (optional)** + +--- + +## Implementation Steps + +### Step 1: Test Headless Mode (30 min) + +**Test 1: Direct Docker Execution** +```bash +docker run --rm \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:0.62-nikolaik \ + -e LLM_MODEL="openai/MiniMax-M2" \ + -e LLM_API_KEY="${MINIMAX_API_KEY}" \ + -e LOG_ALL_EVENTS=true \ + -e SANDBOX_USER_ID=1000 \ + -e SANDBOX_VOLUMES="/home/bam:/workspace:rw" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /home/bam/.openhands:/.openhands \ + --name openhands-test \ + docker.openhands.dev/openhands/openhands:0.62 \ + python -m openhands.core.main -t "Create a file named headless-test.txt with content: Testing headless mode" +``` + +**Expected:** File created successfully without prompts + +### Step 2: Create n8n Workflow (45 min) + +**Workflow Node Structure:** + +```json +{ + "nodes": [ + { + "name": "Webhook Trigger", + "type": "n8n-nodes-base.webhook", + "parameters": { + "path": "openhands-headless", + "httpMethod": "POST" + } + }, + { + "name": "Execute Headless Mode", + "type": "n8n-nodes-base.ssh", + "parameters": { + "command": "cd /home/bam && /home/bam/run-headless.sh \"{{ $json.repository.full_name }}: {{ $json.commits[0].message }}\"" + } + }, + { + "name": "Verify Results", + "type": "n8n-nodes-base.ssh", + "parameters": { + "command": "ls -la /home/bam/*.txt 2>/dev/null | tail -10" + } + } + ] +} +``` + +### Step 3: Create Wrapper Script (15 min) + +**File:** `/home/bam/run-headless.sh` +```bash +#!/bin/bash +# OpenHands Headless Mode Wrapper + +TASK="$1" +CONTAINER_NAME="openhands-$(date +%s)" + +docker run --rm \ + --name "$CONTAINER_NAME" \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:0.62-nikolaik \ + -e LLM_MODEL="openai/MiniMax-M2" \ + -e LLM_API_KEY="${MINIMAX_API_KEY}" \ + -e LOG_ALL_EVENTS=true \ + -e SANDBOX_USER_ID=1000 \ + -e SANDBOX_VOLUMES="/home/bam:/workspace:rw" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /home/bam/.openhands:/.openhands \ + --add-host host.docker.internal:host-gateway \ + docker.openhands.dev/openhands/openhands:0.62 \ + python -m openhands.core.main -t "$TASK" + +echo "Task completed: $TASK" +``` + +--- + +## Command Reference + +### Basic Task Execution +```bash +# Inline task +python -m openhands.core.main -t "Create a hello world script" + +# Task from file +echo "Review this codebase" > task.txt +python -m openhands.core.main -f task.txt + +# With repository +python -m openhands.core.main -t "Fix linting issues" --selected-repo "owner/repo" +``` + +### Docker Execution +```bash +docker run --rm \ + -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:0.62-nikolaik \ + -e LLM_API_KEY="${MINIMAX_API_KEY}" \ + -e LLM_MODEL="openai/MiniMax-M2" \ + -e SANDBOX_USER_ID=$(id -u) \ + -e SANDBOX_VOLUMES="/path/to/workspace:/workspace:rw" \ + -e LOG_ALL_EVENTS=true \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ~/.openhands:/.openhands \ + --name openhands-exec \ + docker.openhands.dev/openhands/openhands:0.62 \ + python -m openhands.core.main -t "Your task here" +``` + +### Environment Variables +```bash +export LLM_MODEL="openai/MiniMax-M2" +export LLM_API_KEY="your-minimax-key" +export SANDBOX_USER_ID=1000 +export SANDBOX_VOLUMES="/home/bam:/workspace:rw" +export LOG_ALL_EVENTS=true +export GITHUB_TOKEN="your-github-token" # For repo operations +``` + +### Advanced Options +```bash +# Set working directory +python -m openhands.core.main -t "Task" -d "/workspace" + +# Limit iterations +python -m openhands.core.main -t "Task" -i 50 + +# Set budget limit (USD) +python -m openhands.core.main -t "Task" -b 10.0 + +# Load from file +python -m openhands.core.main -f task.txt + +# Repository operation +python -m openhands.core.main -t "Analyze and suggest improvements" --selected-repo "owner/repo-name" +``` + +--- + +## n8n Workflow Configuration + +### Webhook Trigger +```json +{ + "name": "Webhook Trigger", + "parameters": { + "path": "openhands-headless", + "httpMethod": "POST", + "responseMode": "responseNode" + } +} +``` + +### SSH Execute Node +```json +{ + "name": "Execute OpenHands Headless", + "type": "n8n-nodes-base.ssh", + "parameters": { + "command": "cd /home/bam && bash run-headless.sh \"Repository: {{ $json.repository.full_name }}, Commit: {{ $json.commits[0].message }}\"", + "sessionId": "headless-session" + }, + "credentials": { + "sshPassword": { + "id": "ai-dev-localhost", + "name": "ai-dev-localhost" + } + } +} +``` + +### Verification Node +```json +{ + "name": "Verify Files Created", + "type": "n8n-nodes-base.ssh", + "parameters": { + "command": "ls -la /home/bam/*.txt 2>/dev/null | tail -15 && echo \"=== Checking for recent files ===\" && find /home/bam -name \"*.txt\" -newermt '5 minutes ago' 2>/dev/null", + "sessionId": "headless-session" + } +} +``` + +### Response Node +```json +{ + "name": "Send Response", + "type": "n8n-nodes-base.respondToWebhook", + "parameters": { + "respondWith": "json", + "responseBody": { + "status": "success", + "message": "OpenHands headless task completed", + "timestamp": "{{ $now }}", + "task": "{{ $json.task }}" + } + } +} +``` + +--- + +## Testing Checklist + +### Phase 1: Direct Testing (Day 1) +- [ ] Test Docker headless execution manually +- [ ] Verify file creation without prompts +- [ ] Test task with repository cloning +- [ ] Test error handling +- [ ] Measure execution time + +### Phase 2: Wrapper Script (Day 1) +- [ ] Create `/home/bam/run-headless.sh` +- [ ] Test wrapper script execution +- [ ] Test concurrent executions +- [ ] Test cleanup after completion +- [ ] Add logging + +### Phase 3: n8n Integration (Day 2) +- [ ] Import workflow to n8n +- [ ] Configure SSH credentials +- [ ] Test webhook manually +- [ ] Check execution logs +- [ ] Verify file creation + +### Phase 4: Gitea Integration (Day 2) +- [ ] Configure Gitea webhook +- [ ] Test with repository push +- [ ] Verify end-to-end flow +- [ ] Test error scenarios +- [ ] Document setup + +--- + +## Advantages Over Previous Approaches + +### vs CLI Approach +| Aspect | CLI | Headless | +|--------|-----|----------| +| TTY Required | ❌ Yes | ✅ No | +| Interactive Prompts | ❌ Yes | ✅ None | +| Automation | ❌ Difficult | ✅ Easy | +| n8n Compatibility | ❌ Poor | ✅ Excellent | +| Reliability | ❌ Unstable | ✅ Stable | + +### vs API Approach +| Aspect | API | Headless | +|--------|-----|----------| +| Runtime Startup | ❌ Fails | ✅ Works | +| Network Issues | ❌ Complex | ✅ None | +| Status Monitoring | ✅ Good | ⚠️ Via logs | +| Error Handling | ✅ Good | ⚠️ Via exit code | +| Setup Complexity | ❌ High | ✅ Simple | + +--- + +## Gitea Webhook Configuration + +### Webhook Settings +``` +URL: https://n8n.oky.sh/webhook/openhands-headless +Method: POST +Content-Type: application/json +Secret: [generate secure random string] +Events: Push events +Active: ✓ +``` + +### Payload Example +```json +{ + "repository": { + "full_name": "owner/repo-name", + "name": "repo-name", + "clone_url": "https://git.oky.sh/owner/repo-name.git" + }, + "commits": [ + { + "message": "Add new feature", + "id": "abc123", + "url": "https://git.oky.sh/owner/repo-name/commit/abc123" + } + ], + "ref": "refs/heads/main" +} +``` + +### Task Generation +**In n8n workflow:** +```javascript +// Extract repository and commit info +const repo = $json.repository.full_name; +const commitMsg = $json.commits[0].message; +const commitSha = $json.commits[0].id; + +// Generate task for OpenHands +const task = `Build and test repository ${repo} after commit: ${commitMsg} (${commitSha.substring(0,7)})`; + +// Pass to execution node +return { task }; +``` + +--- + +## Error Handling + +### n8n Workflow Error Paths +```json +[ + { + "condition": "Execution failed", + "action": "Send error notification", + "node": "Error Handler" + }, + { + "condition": "File not created", + "action": "Retry task", + "node": "Retry Logic" + }, + { + "condition": "Timeout", + "action": "Kill container and retry", + "node": "Timeout Handler" + } +] +``` + +### Container Cleanup +```bash +# Ensure cleanup after execution +trap "docker rm -f openhands-exec 2>/dev/null || true" EXIT + +# Or use --rm flag (recommended) +docker run --rm --name openhands-exec ... +``` + +### Retry Logic +```bash +MAX_RETRIES=3 +RETRY_COUNT=0 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if docker run --rm ...; then + echo "Success!" + break + else + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo "Retry $RETRY_COUNT/$MAX_RETRIES" + sleep 5 + fi +done +``` + +--- + +## Performance Considerations + +### Container Startup Time +- Initial pull: ~30 seconds +- Subsequent runs: ~5 seconds +- Task execution: Varies (30s - 5min) + +### Resource Usage +- CPU: 1-2 cores during execution +- Memory: 1-2GB +- Disk: Minimal (ephemeral) + +### Optimization +```bash +# Pre-pull image to reduce startup time +docker pull docker.openhands.dev/openhands/openhands:0.62 + +# Use specific runtime image +export SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:0.62-nikolaik + +# Reuse container with persistent volume +# (Trade-off: faster startup vs resource usage) +``` + +--- + +## Security Notes + +### Container Isolation +- Runs in Docker container +- Limited filesystem access +- Network sandboxed +- User permissions respected (SANDBOX_USER_ID) + +### Credential Management +- API keys via environment variables +- GitHub tokens for repository access +- Rotate tokens regularly +- Use n8n credential store + +### File Access +- Limited to SANDBOX_VOLUMES +- Default: `/home/bam:/workspace:rw` +- Can restrict to specific directories + +--- + +## Files to Create + +### 1. Wrapper Script +**File:** `/home/bam/run-headless.sh` +- Docker execution wrapper +- Environment setup +- Error handling +- Logging + +### 2. n8n Workflow +**File:** `/home/bam/claude/mvp-factory/openhands-headless-workflow.json` +- Webhook trigger +- SSH execution node +- Verification node +- Response node + +### 3. Test Script +**File:** `/home/bam/test-headless.sh` +- Direct Docker test +- Wrapper script test +- Verification script +- Performance measurement + +--- + +## Success Criteria + +- [ ] Headless mode executes without prompts +- [ ] Files are created successfully +- [ ] n8n workflow imports and runs +- [ ] Webhook triggers workflow +- [ ] Gitea push initiates task +- [ ] Results are verified +- [ ] Error handling works +- [ ] Documentation complete + +--- + +## Timeline + +| Day | Task | Duration | +|-----|------|----------| +| 1 | Test headless mode | 30 min | +| 1 | Create wrapper script | 15 min | +| 1 | Create n8n workflow | 45 min | +| 1 | Test end-to-end | 60 min | +| 2 | Configure Gitea webhook | 30 min | +| 2 | Production testing | 120 min | +| 2 | Documentation | 30 min | + +**Total: 5-6 hours** + +--- + +## References + +- [OpenHands Headless Mode Docs](https://docs.openhands.dev/openhands/usage/run-openhands/headless-mode) +- Docker Hub: [openhands/openhands](https://hub.docker.com/r/docker.openhands.dev/openhands/openhands/tags) +- Current API: http://localhost:3000 (for reference) + +--- + +## Conclusion + +Headless mode is the **optimal solution** for OpenHands integration with n8n because it: + +1. **Eliminates all technical blockers** from CLI and API approaches +2. **Provides true automation** without interactive prompts +3. **Integrates seamlessly** with n8n SSH nodes +4. **Supports production use** with proper error handling +5. **Enables CI/CD integration** for automated workflows + +**Recommendation:** Proceed immediately with headless mode implementation. \ No newline at end of file