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