Operations Guide
This guide covers day-2 operations of a running Open ACE deployment: health probes and monitoring endpoints, the scheduler worker, data collection, TLS certificates, data retention and compliance, and logs and alerts. For initial installation see DEPLOYMENT.md; for cluster deployment see KUBERNETES.md; for reverse proxy see NGINX.md.
Health and Monitoring Endpoints
All endpoints below are served by the web application on port 19888 (defined in app/__init__.py).
| Endpoint | Purpose | Returns | Failure behavior |
|---|---|---|---|
/livez | Liveness probe | {"status": "alive", "timestamp": ...} | Always 200 while the process can respond. Does NOT check dependencies, to avoid restart storms. |
/readyz | Readiness probe | {"status": "ready" | "not_ready", "checks": {...}, "action": ...} | 503 if any critical check fails. |
/metrics | Prometheus metrics | Prometheus text format (via prometheus_flask_exporter, group_by_endpoint, latency buckets 0.01–5 s) | 503 JSON fallback if prometheus_flask_exporter is not installed. |
/security-status | Security baseline | Baseline check results plus security_mode (mode / source / explicit), pilot_metadata, and migration_status | 503 when the baseline reports unhealthy or security mode resolution errors. |
/health | Deprecated | Delegates to /readyz, then adds deprecated: true, a migration message, and version (git commit) | Same as /readyz. Use /livez + /readyz instead. |
/readyz runs nine checks and reports each under checks:
security_mode— must be explicitly set (OPENACE_SECURITY_MODE); pilot metadata in production mode produces a warningdatabase— probe connection with 2 s timeout (dedicated connection, not from the pool)schema_version— Alembic head check viaSchemaCompatibilityService(PostgreSQL production only; SQLite reportsskipped; an active emergency bypass forces 503)config_dir,workspace_dir— exist and are writable/readableencryption_keys— encryption key registry initializedinit_status— application initialization completedssh_sync— no SSH sync failure warning file present (see Logs and Alerts)frontend_build— management UI build artifacts present (fails readiness only in production mode)
When not ready, the response includes an action hint such as running alembic upgrade head.
Which endpoint a Kubernetes probe should use
k8s/deployment.yaml wires the web pod as follows (port http = 19888):
livenessProbe:
httpGet:
path: /livez
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 10
failureThreshold: 3
# Startup probe for slow initialization (Issue #2186)
startupProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 0
periodSeconds: 5
timeoutSeconds: 10
failureThreshold: 60 # 300s max startup time
- Liveness →
/livez(process alive only; dependency failures must not trigger pod restarts) - Readiness →
/readyz(dependency failures remove the pod from Service endpoints) - Startup →
/readyzwithfailureThreshold: 60(up to 300 s for first boot and migrations)
The web pod also carries Prometheus scrape annotations (prometheus.io/scrape: "true", port 19888, path /metrics); with multiple gunicorn workers the deployment sets PROMETHEUS_MULTIPROC_DIR for multi-process metric aggregation. The Docker Compose healthcheck for the open-ace service polls http://localhost:19888/readyz (30 s interval, 3 retries, 30 s start period).
Scheduler Service
Background work (data fetch, quota enforcement, autonomous workflows, alert compensation, scheduler health monitor, SSO cleanup) is decoupled from web workers (Issue #2187). The split is controlled by SCHEDULER_MODE:
SCHEDULER_MODE=scheduler— the process starts all background schedulers with database leader election (thescheduler_leaderstable guarantees a single active leader)SCHEDULER_MODE=webor unset — no schedulers are started (web worker)- Development
python3 server.pyruns web and schedulers in one process
Docker Compose
The scheduler is an opt-in Compose profile (docker-compose.yml, service scheduler):
scheduler:
image: ${IMAGE_NAME:-openace/open-ace:latest}
container_name: open-ace-scheduler
restart: unless-stopped
profiles:
- scheduler
environment:
- FLASK_ENV=production
- PYTHONUNBUFFERED=1
- SCHEDULER_MODE=scheduler
- SECRET_KEY=${SECRET_KEY:-}
- OPENACE_ENCRYPTION_KEY=${OPENACE_ENCRYPTION_KEY:-}
- DATABASE_URL=postgresql://${DB_USER:-ace}:${DB_PASSWORD:-dev-password-change-in-production}@postgres:5432/${DB_NAME:-ace}
# Issue #3095: Pass DB_PASSWORD as standalone env var for security baseline check.
- DB_PASSWORD=${DB_PASSWORD:-}
- SCHEDULER_HEARTBEAT_INTERVAL=${SCHEDULER_HEARTBEAT_INTERVAL:-10}
- SCHEDULER_HEARTBEAT_TIMEOUT=${SCHEDULER_HEARTBEAT_TIMEOUT:-60}
- SCHEDULER_LOCK_TIMEOUT=${SCHEDULER_LOCK_TIMEOUT:-1800}
- SCHEDULER_METRICS_PORT=9090
# #3237: must match the app service exactly.
- OPENACE_AGENT_STATE_ROOT=/var/lib/openace/agent-state
Enable it with docker compose --profile scheduler up -d. It shares the config-data, workspace-data, and agent-state volumes with the app service — the shared agent-state volume is deliberate (#3237), because purge routes run in the web container while the scheduler writes the transcripts. Its health check polls http://localhost:9090/metrics.
Heartbeat and election parameters (read in app/services/leader_election.py):
| Variable | Default | Meaning |
|---|---|---|
SCHEDULER_HEARTBEAT_INTERVAL | 10 (seconds) | How often the leader refreshes its heartbeat row |
SCHEDULER_HEARTBEAT_TIMEOUT | 60 (seconds) | Heartbeat age after which leadership is considered lost |
SCHEDULER_LOCK_TIMEOUT | 1800 (seconds) | Distributed lock expiration |
SCHEDULER_METRICS_PORT | 9090 | Port of the scheduler's own metrics HTTP server |
The scheduler worker (python -m app.scheduler_worker) starts its own metrics server on port 9090 with three endpoints: /livez (alive), /health (alive plus is_leader from leader election), and /metrics (Prometheus). At startup it validates explicit security mode, schema version (REQUIRE_HEAD), scheduler tables, and data-fetch configuration drift (FETCH_USE_SUDO wrapper/sudoers, Issues #2543/#3145 — with critical drift it marks fetch degraded and skips collection jobs rather than running doomed tasks).
Kubernetes
k8s/scheduler-deployment.yaml runs open-ace-scheduler as the counterpart: 2 replicas (main + backup, only one active leader), command: ["python", "-m", "app.scheduler_worker"], the same heartbeat env values (10/60/1800/9090), probes against /livez and /health on port metrics (9090), a PodDisruptionBudget with minAvailable: 1, a PriorityClass (open-ace-scheduler, value 1000000) to prevent eviction, pod anti-affinity, and the shared open-ace-data PVC for /workspace, agent state, and config — OPENACE_AGENT_STATE_ROOT must match the web deployment exactly.
On bare metal, reference units are scripts/openace.service (web, no schedulers) and scripts/openace-scheduler.service (SCHEDULER_MODE=scheduler, runs all background services via leader election).
Data Collection (cron)
Usage data is collected from CLI tool data directories by per-tool scripts in scripts/:
| Script | Collects |
|---|---|
scripts/fetch_claude.py | Claude Code sessions |
scripts/fetch_codex.py | Codex CLI sessions |
scripts/fetch_qwen.py | Qwen Code sessions |
scripts/fetch_openclaw.py | OpenClaw sessions |
scripts/fetch_zcode.py | ZCode CLI databases |
Two collection paths exist:
- In-app scheduler (default in container deployments): the data fetch scheduler runs the fetch scripts as subprocesses every 300 s by default (minimum 60 s). Configure with
DATA_FETCH_INTERVAL/DATA_FETCH_ENABLEDenvironment variables or thedata_fetchsection ofconfig.json(environment variables win). In multi-user containers the scripts run through/usr/local/bin/openace-fetch-wrapperwith sudoers rules (FETCH_USE_SUDO=true, generate rules withscripts/generate-sudoers.sh); in the default single-user containerFETCH_USE_SUDO=falseand the venv Python is used directly. - Host cron (bare-metal deployments): the classic crontab from the deployment guide:
30 0 * * * cd /path/to/open-ace && python3 scripts/fetch_claude.py >> logs/cron.log 2>&1
35 0 * * * cd /path/to/open-ace && python3 scripts/fetch_qwen.py >> logs/cron.log 2>&1
40 0 * * * cd /path/to/open-ace && python3 scripts/fetch_openclaw.py >> logs/cron.log 2>&1
Add fetch_codex.py / fetch_zcode.py lines the same way if those tools are in use. Manual runs support --days N to backfill, e.g. python3 scripts/fetch_claude.py --days 7.
Central server plus remote collectors: scripts/upload-to-central/
For distributed environments, remote machines run the incremental sync daemon instead of cron. Deployment is one command (from scripts/upload-to-central/README.md):
./deploy-remote.sh \
--host user@remote-host \
--server http://CENTRAL_SERVER:19888 \
--auth-key YOUR_AUTH_KEY \
--hostname remote-hostname
For the local machine use deploy.sh with the same --server / --auth-key / --hostname options. Useful options: --interval (sync interval in seconds, default 300), --user, --install-dir (default ~/upload-to-central), --uninstall.
Operational notes:
- The daemon installs a systemd unit
upload-to-central; checksudo systemctl status upload-to-centralandsudo journalctl -u upload-to-central -f - Manual sync:
./upload.sh; forced full re-upload:./upload.sh --full - Sync state lives in
~/.open-ace/sync_state.json(per-hostlast_sync_time,last_upload,total_uploaded); incremental sync only uploads new messages - The daemon POSTs to
/api/upload/batchon the central server with theX-Auth-Keyheader — the key must matchserver.upload_auth_keyin the central server's~/.open-ace/config.json - Remote deployment requires SSH access; installing the systemd service requires root
TLS Certificates
- Compose / bare metal: TLS terminates at the nginx reverse proxy. NGINX.md shows the full configuration: listen on 443 with
ssl_certificate /path/to/ssl/fullchain.pemandssl_certificate_key /path/to/ssl/privkey.pem, TLSv1.2/1.3 only, and an HTTP→HTTPS redirect on port 80. The repository does not ship a certbot configuration; obtain certificates with certbot (webroot or standalone) for your domain and point nginx at the Let's Encrypt paths. Renewal is automated by certbot's own timer — make sure the renewal hook reloads nginx so the new certificate is picked up. - Kubernetes: the Ingress in
k8s/service.yamlis pre-configured for cert-manager with theletsencrypt-prodClusterIssuer (annotationcert-manager.io/cluster-issuer: "letsencrypt-prod", TLS secretopen-ace-tls,ssl-redirect: "true"). cert-manager must be installed before applying the manifests:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
After that, issuing and renewing the certificate is handled entirely by cert-manager; there is no manual renewal step.
Data Retention and Compliance
k8s/configmap.yamldeclaresAUDIT_LOG_RETENTION_DAYS: "90"andDATA_RETENTION_DAYS: "365"(lines 40–41). These values document the intended retention horizon and match the built-in defaults (tenant settings default to 90/365 days; the response-time cleaner defaults to 90 days raw / 365 days aggregated). Note: no background job in the current code base reads these two environment variables directly — runtime retention is driven by the mechanisms below.- Retention rules (
app/modules/compliance/retention.py,DataRetentionManager), per data type:
| Data type | Default | Action |
|---|---|---|
audit_logs | 90 days | delete |
quota_alerts | 30 days | delete |
sessions | 7 days | delete |
sso_sessions | 1 day | delete |
usage_data | 365 days | archive |
messages | 90 days | anonymize |
user_activity | 365 days | archive |
- Admin API (blueprint
/api/compliance, admin only):GET/PUT /api/compliance/retention/rules,POST /api/compliance/retention/cleanup(supports?dry_run=true), andGET /api/compliance/retention/history|storage|status. There is no built-in scheduler that fires retention automatically — run or schedule cleanup through the API; the compliance status check flags instances whose last cleanup is older than a week. Every run is recorded in theretention_historytable. - Schema groundwork (Issue #2188 Phase 1): migration
20260803_002_create_retention_tablescreatedretention_policies,retention_executions,legal_holds,retention_evidence,archive_files, andrecycle_bin. These persistent policy/execution tables (including legal holds and the recycle bin) are schema preparation; treat legal holds and recycle bin as not yet wired to runtime automation and verify your version's coverage before relying on them operationally. - Audit log sync:
scripts/openace-ssh-syncprunes its own audit log (/var/log/openace/ssh-sync.log) after 30 days.
Logs and Alerts
- Log level:
LOG_LEVEL: "INFO"is set ink8s/configmap.yaml. The application factory currently fixes the level in code (logging.basicConfig(level=logging.INFO)inapp/__init__.py; the scheduler worker also logs at INFO), so treat INFO as the effective level. - Container logs:
docker compose logs -f open-ace/docker compose logs -f scheduler. All Compose services use thejson-filedriver withmax-size: 10m,max-file: 3rotation. The application also writes to/app/logsinside the container, bind-mounted to./logson the host in Compose (anemptyDirin Kubernetes). - Alert notification history: the
alerts_historytable records every outgoing notification (alert type, severity, message, channels, recipients,statussent/failed,sent_at). - Alert failure queue: when alert creation fails, the alert compensation worker (part of the scheduler service) retries from a queue. Inspect with
GET /api/alerts/failure-queueand force a processing pass withPOST /api/alerts/failure-queue/retry(admin). Scheduler status endpointsGET /api/schedulers/data-fetchandGET /api/schedulers/quota-enforcementreport the corresponding background jobs. The full Alerts API is documented in API.md. - SSH sync failures affect readiness (Issue #2328): when secure SSH sync fails, the entrypoint writes
/var/log/openace/ssh-sync-failure.warning(human-readable, with remediation hints) and.../ssh-sync-failure.json(structured). While the warning file exists, thessh_synccheck in/readyzfails and the pod reports not ready. Remove the file after fixing the cause; details are inssh-sync.login the same directory.
Related Documents
- DEPLOYMENT.md — installation, Docker Compose, data persistence
- KUBERNETES.md — cluster manifests, scaling, TLS with cert-manager
- NGINX.md — HTTPS reverse proxy, WebSocket/timeouts, 413 fixes
- TROUBLESHOOTING.md — cross-component troubleshooting index
- DATABASE_BACKUP.md — backup and recovery