headless mode approach todo list
This commit is contained in:
parent
c7e2c7ab0b
commit
1727b1df54
|
|
@ -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.
|
||||
Loading…
Reference in New Issue