Running OpenACP as a Daemon with REST API: The DevOps Guide
For personal use, running OpenACP in the foreground is fine -- start it when you need it, stop it when you are done. But for teams, servers, and automated workflows, you need OpenACP running as an always-on background service with programmatic access. This guide covers daemon mode, the REST API, auto-start configuration, plugin management, structured logging, and real-world automation patterns.
Daemon Mode: Always-On Agent Access
Daemon mode runs OpenACP as a background process that persists across terminal sessions and system restarts. Instead of keeping a terminal window open, the daemon runs silently, listening for messages from your configured platforms and managing agent sessions automatically.
Starting the Daemon
# Start OpenACP as a daemon
openacp --daemon start
# Output:
# OpenACP daemon started (PID: 42381)
# REST API listening on http://127.0.0.1:21420
# Telegram adapter connected
# Discord adapter connected
# Ready to receive messages
When started in daemon mode, OpenACP forks into the background and writes its PID to ~/.openacp/daemon.pid. Logs are written to ~/.openacp/logs/ instead of stdout.
Managing the Daemon
# Check if the daemon is running
openacp --daemon status
# Output:
# OpenACP daemon: running (PID: 42381)
# Uptime: 3 hours, 14 minutes
# Active sessions: 2
# Total sessions today: 7
# Memory usage: 184 MB
# REST API: http://127.0.0.1:21420
# Stop the daemon gracefully
openacp --daemon stop
# Output:
# Stopping OpenACP daemon (PID: 42381)...
# Terminating 2 active sessions...
# Daemon stopped.
# View daemon logs
openacp --daemon logs
# Follow logs in real-time (like tail -f)
openacp --daemon logs --follow
Daemon Lifecycle
The daemon follows a well-defined lifecycle:
- Start: Process forks, writes PID file, initializes adapters, starts REST API server
- Ready: All adapters connected, accepting messages and API requests
- Running: Managing sessions, handling messages, serving file viewer and API
- Stopping: Receives SIGTERM, gracefully terminates active sessions (30-second grace period)
- Stopped: All sessions terminated, PID file removed, process exits
If the daemon crashes unexpectedly, the PID file remains. The next openacp --daemon start detects the stale PID file, verifies the process is no longer running, cleans up, and starts a fresh instance.
Auto-Start on Boot
For server deployments, you want OpenACP to start automatically when the machine boots. Here are configurations for common init systems:
systemd (Linux)
# /etc/systemd/system/openacp.service
[Unit]
Description=OpenACP - AI Agent Bridge
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
User=openacp
Group=openacp
WorkingDirectory=/home/openacp
ExecStart=/usr/local/bin/openacp --daemon start
ExecStop=/usr/local/bin/openacp --daemon stop
PIDFile=/home/openacp/.openacp/daemon.pid
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/openacp/.openacp /home/openacp/projects
[Install]
WantedBy=multi-user.target
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable openacp
sudo systemctl start openacp
# Check status
sudo systemctl status openacp
# View logs via journald
sudo journalctl -u openacp -f
launchd (macOS)
<!-- ~/Library/LaunchAgents/org.openacp.daemon.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.openacp.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/openacp</string>
<string>--daemon</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/openacp.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/openacp.stderr.log</string>
</dict>
</plist>
# Load and start
launchctl load ~/Library/LaunchAgents/org.openacp.daemon.plist
# Unload
launchctl unload ~/Library/LaunchAgents/org.openacp.daemon.plist
Docker
# Dockerfile
FROM node:22-slim
RUN npm install -g @openacp/cli
# Create non-root user
RUN useradd -m -s /bin/bash openacp
USER openacp
WORKDIR /home/openacp
# Copy configuration
COPY --chown=openacp:openacp config.json .openacp/config.json
EXPOSE 21420
CMD ["openacp"]
# docker-compose.yml
version: '3.8'
services:
openacp:
build: .
restart: unless-stopped
ports:
- "127.0.0.1:21420:21420"
volumes:
- ./config.json:/home/openacp/.openacp/config.json:ro
- ./projects:/home/openacp/projects
environment:
- OPENACP_TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
- OPENACP_DISCORD_TOKEN=${DISCORD_TOKEN}
- NODE_ENV=production
REST API Overview
When running (in either foreground or daemon mode), OpenACP exposes a REST API on port 21420. This API provides programmatic access to session management, message sending, status monitoring, and administrative operations.
The port number 21420 is the default and can be configured:
{
"api": {
"port": 21420,
"host": "127.0.0.1",
"auth": {
"type": "bearer",
"token": "your-secret-api-token"
}
}
}
Authentication
The API uses Bearer token authentication. Every request must include an Authorization header:
curl -H "Authorization: Bearer your-secret-api-token" \
http://127.0.0.1:21420/api/health
If no auth token is configured, the API is accessible without authentication -- but only on localhost. This is acceptable for personal use but should always be secured for team deployments.
API Endpoints Reference
Here is the complete API reference for all available endpoints:
POST /api/sessions/new
Create a new agent session programmatically.
curl -X POST http://127.0.0.1:21420/api/sessions/new \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agent": "claude",
"cwd": "/home/user/projects/my-app",
"userId": "api-user",
"platform": "api"
}'
# Response:
{
"sessionId": "sess_abc123",
"agent": "claude",
"status": "active",
"createdAt": "2026-03-26T10:30:00Z"
}
POST /api/sessions/:id/send
Send a message to an active session.
curl -X POST http://127.0.0.1:21420/api/sessions/sess_abc123/send \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Add error handling to the login endpoint"
}'
# Response:
{
"messageId": "msg_def456",
"status": "sent"
}
POST /api/sessions/:id/cancel
Cancel the current operation in a session.
curl -X POST http://127.0.0.1:21420/api/sessions/sess_abc123/cancel \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
{
"status": "cancelled"
}
GET /api/sessions/:id/status
Get the current status of a session.
curl http://127.0.0.1:21420/api/sessions/sess_abc123/status \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
{
"sessionId": "sess_abc123",
"status": "active",
"agent": "claude",
"platform": "api",
"createdAt": "2026-03-26T10:30:00Z",
"lastActivity": "2026-03-26T10:35:22Z",
"messageCount": 5,
"usage": {
"inputTokens": 12450,
"outputTokens": 3210,
"estimatedCost": 0.42
}
}
GET /api/topics
List all active topics (Telegram forum topics, Discord threads, Slack channels).
curl http://127.0.0.1:21420/api/topics \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
{
"topics": [
{
"id": "topic_123",
"platform": "telegram",
"name": "Feature: Auth Module",
"sessionId": "sess_abc123",
"messageCount": 12,
"lastActivity": "2026-03-26T10:35:22Z"
}
]
}
POST /api/cleanup
Clean up expired sessions and temporary resources.
curl -X POST http://127.0.0.1:21420/api/cleanup \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
{
"cleanedSessions": 3,
"freedMemoryMB": 450,
"removedTempFiles": 12
}
GET /api/health
Health check endpoint for monitoring and load balancers.
curl http://127.0.0.1:21420/api/health \
-H "Authorization: Bearer YOUR_TOKEN"
# Response:
{
"status": "healthy",
"version": "1.4.2",
"uptime": 11640,
"activeSessions": 2,
"adapters": {
"telegram": "connected",
"discord": "connected",
"slack": "disconnected"
},
"memory": {
"heapUsedMB": 142,
"heapTotalMB": 256,
"rssMB": 310
}
}
SSE Streaming: Real-Time Event Feed
The REST API includes a Server-Sent Events (SSE) endpoint for real-time streaming of session events. This is useful for building custom dashboards, monitoring tools, or alternative frontends.
# Connect to the SSE stream
curl -N http://127.0.0.1:21420/api/sessions/sess_abc123/stream \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: text/event-stream"
# Events stream in real-time:
data: {"type":"text","content":"I'll add error handling to the login endpoint.","timestamp":"2026-03-26T10:35:22Z"}
data: {"type":"tool_use","tool":"bash","args":{"command":"cat src/routes/login.ts"},"timestamp":"2026-03-26T10:35:23Z"}
data: {"type":"permission_request","tool":"file_write","path":"src/routes/login.ts","timestamp":"2026-03-26T10:35:25Z"}
data: {"type":"text","content":"I've added try-catch blocks around the authentication logic...","timestamp":"2026-03-26T10:35:30Z"}
data: {"type":"done","timestamp":"2026-03-26T10:35:31Z"}
SSE Event Types
| Event Type | Description | Fields |
|---|---|---|
text |
Agent text response (streamed) | content, timestamp |
tool_use |
Agent using a tool | tool, args, timestamp |
tool_result |
Tool execution result | tool, result, timestamp |
permission_request |
Permission gate triggered | tool, details, timeout |
permission_response |
User approved/denied | tool, decision, timestamp |
error |
Error occurred | message, code, timestamp |
done |
Agent finished responding | timestamp, usage |
Building a Custom Dashboard with SSE
// Browser-based dashboard using EventSource
const eventSource = new EventSource(
'http://127.0.0.1:21420/api/sessions/sess_abc123/stream',
{ headers: { 'Authorization': 'Bearer YOUR_TOKEN' } }
);
eventSource.addEventListener('text', (event) => {
const data = JSON.parse(event.data);
appendToChat(data.content);
});
eventSource.addEventListener('tool_use', (event) => {
const data = JSON.parse(event.data);
showToolExecution(data.tool, data.args);
});
eventSource.addEventListener('permission_request', (event) => {
const data = JSON.parse(event.data);
showPermissionDialog(data.tool, data.details);
});
eventSource.addEventListener('error', () => {
console.log('SSE connection lost, reconnecting...');
});
Plugin System
OpenACP supports plugins that extend its functionality. Plugins are npm packages installed to ~/.openacp/plugins/ and loaded automatically at startup.
Installing Plugins
# Install a plugin from npm
npm install --prefix ~/.openacp/plugins @openacp/plugin-github-issues
# Or install from a local path
npm install --prefix ~/.openacp/plugins ./my-custom-plugin
# Plugins are auto-detected at startup
openacp --daemon restart
Plugin Directory Structure
~/.openacp/
plugins/
node_modules/
@openacp/
plugin-github-issues/
index.js
package.json
my-custom-plugin/
index.js
package.json
Plugins can add new chat commands, new API endpoints, custom message formatters, or integration with external services. The plugin API is documented in the plugins and contributing guide.
Structured Logging with Pino
OpenACP uses Pino for structured JSON logging. Every log entry includes a timestamp, log level, component name, and contextual data. This makes logs easy to parse with tools like jq, Grafana Loki, or the ELK stack.
# Default log output (JSON, one entry per line)
{"level":30,"time":1711440600000,"msg":"Telegram adapter connected","component":"TelegramAdapter","botUsername":"MyACPBot"}
{"level":30,"time":1711440601000,"msg":"Session created","component":"SessionManager","sessionId":"sess_abc123","agent":"claude","userId":"123456789"}
{"level":20,"time":1711440602000,"msg":"Agent tool use","component":"Session","sessionId":"sess_abc123","tool":"bash","args":{"command":"ls -la"}}
{"level":30,"time":1711440603000,"msg":"Permission approved","component":"PermissionGate","sessionId":"sess_abc123","tool":"bash","responseTime":2341}
Log Levels
| Level | Value | Usage |
|---|---|---|
| fatal | 60 | Application crash, cannot continue |
| error | 50 | Errors that need attention |
| warn | 40 | Potential issues, deprecations |
| info | 30 | Normal operations (default) |
| debug | 20 | Detailed operational info |
| trace | 10 | Very detailed debug info |
# Set log level via environment variable
LOG_LEVEL=debug openacp --daemon start
# Pretty-print logs for local development
openacp --daemon logs | npx pino-pretty
# Filter logs with jq
openacp --daemon logs | jq 'select(.component == "PermissionGate")'
# Count sessions per hour
openacp --daemon logs | jq 'select(.msg == "Session created") | .time' \
| xargs -I {} date -r {} +%H | sort | uniq -c
Automation Examples
The REST API enables powerful automation workflows. Here are some practical examples:
Automated Code Review Bot
#!/bin/bash
# Trigger AI code review when a PR is opened
# (called from a GitHub webhook handler)
PR_DIFF="$1"
PR_NUMBER="$2"
# Create a new session
SESSION=$(curl -s -X POST http://127.0.0.1:21420/api/sessions/new \
-H "Authorization: Bearer $OPENACP_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"agent\": \"claude\",
\"cwd\": \"/home/openacp/repos/main-app\"
}" | jq -r '.sessionId')
# Send the review request
curl -s -X POST "http://127.0.0.1:21420/api/sessions/$SESSION/send" \
-H "Authorization: Bearer $OPENACP_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"message\": \"Review this pull request diff and provide feedback on code quality, potential bugs, and improvements:\\n\\n$PR_DIFF\"
}"
# Wait for response and post as PR comment
# (use SSE stream to collect the full response)
REVIEW=$(curl -s -N "http://127.0.0.1:21420/api/sessions/$SESSION/stream" \
-H "Authorization: Bearer $OPENACP_API_TOKEN" \
| timeout 120 jq -r 'select(.type == "text") | .content' \
| tr -d '\n')
gh pr comment "$PR_NUMBER" --body "$REVIEW"
Scheduled Maintenance Script
#!/bin/bash
# Daily maintenance: cleanup, health check, and report
# Health check
HEALTH=$(curl -s http://127.0.0.1:21420/api/health \
-H "Authorization: Bearer $TOKEN")
STATUS=$(echo "$HEALTH" | jq -r '.status')
SESSIONS=$(echo "$HEALTH" | jq -r '.activeSessions')
MEMORY=$(echo "$HEALTH" | jq -r '.memory.rssMB')
# Cleanup expired sessions
CLEANUP=$(curl -s -X POST http://127.0.0.1:21420/api/cleanup \
-H "Authorization: Bearer $TOKEN")
CLEANED=$(echo "$CLEANUP" | jq -r '.cleanedSessions')
# Send summary to Slack
curl -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"OpenACP Daily Report:\\n- Status: $STATUS\\n- Active sessions: $SESSIONS\\n- Memory: ${MEMORY}MB\\n- Cleaned sessions: $CLEANED\"
}"
Integration Test Runner
// Node.js script: use the API to run integration tests via AI agent
import fetch from 'node-fetch';
const API_URL = 'http://127.0.0.1:21420';
const TOKEN = process.env.OPENACP_API_TOKEN;
async function runTestWithAgent(testDescription: string) {
// Create session
const { sessionId } = await fetch(`${API_URL}/api/sessions/new`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
agent: 'claude',
cwd: '/home/openacp/repos/main-app'
})
}).then(r => r.json());
// Send test instruction
await fetch(`${API_URL}/api/sessions/${sessionId}/send`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: `Write and run a test for: ${testDescription}. Use vitest. Auto-approve all file writes.`
})
});
// Collect results via SSE...
console.log(`Test session ${sessionId} started for: ${testDescription}`);
}
// Run multiple test sessions in parallel
await Promise.all([
runTestWithAgent('user authentication flow'),
runTestWithAgent('payment processing webhook'),
runTestWithAgent('file upload and validation')
]);
Production Deployment Checklist
Before deploying OpenACP's daemon mode in a production or team environment, verify these items:
- API token configured: Never run the API without authentication in non-localhost environments
- Firewall rules: Port 21420 should not be publicly accessible unless you have a specific reason
- systemd/launchd configured: Auto-restart on failure ensures uptime
- Log rotation: Configure logrotate or equivalent for
~/.openacp/logs/ - Monitoring: Set up health check polling on
/api/health - Backups: Back up
~/.openacp/config.json(not the logs) - Resource limits: Set appropriate
maxConcurrentSessionsfor your server's capacity - Update plan: Schedule regular updates via
npm update -g @openacp/cli
Wrapping Up
Daemon mode and the REST API transform OpenACP from a personal development tool into a team-scale platform for AI-assisted coding. The daemon ensures always-on availability, the API enables programmatic automation, SSE streaming supports real-time monitoring, and the plugin system allows unlimited extensibility.
Whether you are running OpenACP on a single developer laptop or a shared team server, the daemon and API layer provides the operational foundation you need for reliable, automated AI agent access.
Deploy OpenACP for Your Team
Install OpenACP, start the daemon, and give your whole team AI agent access from chat.
npm install -g @openacp/cli && openacp --daemon start