- Dockerfile: install acpx@latest alongside @anthropic-ai/claude-code
- compose.yml: bind /mnt/user/appdata/clawdforge/acpx-sessions:/root/.acpx/sessions
- DB: additive sessions + session_events tables in store.py SCHEMA
- clawdforge/acpx_runner.py: AcpxManager + AcpxSession, bounded async pool,
per-invocation subprocess model (acpx CLI itself owns the queue-owner
lifecycle, so each turn = one fresh `acpx prompt -s <uuid>` call)
- server.py: POST/GET/DELETE /sessions + POST /sessions/{id}/turn + GET /sessions
- Per-app isolation: 404 (not 403) on cross-token session access — no
existence leak across tokens
- Lifespan-managed TTL sweeper: every 60s soft-closes idle sessions past
CLAWDFORGE_SESSION_TTL_SECS (1h default), hard-deletes ledger rows past
CLAWDFORGE_SESSION_HARD_TTL_SECS (24h default)
- session_events audit table parallel to existing runs table
(events: create, turn, close, sweep_close, hard_delete)
- /healthz now reports acpx_present + acpx_version + open_sessions count
- tests/test_sessions.py: 16 tests covering create/turn/close/list/isolation/
sweep/pool-full/regression. /run regression test asserts byte-identical
v0.1 response shape.
ACPX research notes (v0.6.1, openclaw/acpx):
- npm package is `acpx`, not `@openclaw/acpx`
- Sessions are scoped by (agentCommand, cwd, name?). We mint our own UUID
as `--name` and give every session a unique cwd subdir, so the scope key
is collision-free across apps.
- session_id source: ours. We pass --name <uuid>, ACPX records it under
~/.acpx/sessions/<encoded-id>.json. We never need to parse ACPX's
acpxRecordId — our UUID is canonical.
- Subprocess lifetime: per-invocation, NOT per-session. The acpx CLI itself
spawns/maintains a per-session "queue owner" process via local IPC; each
`acpx prompt` call we make either elects itself owner or enqueues. The
AcpxSession class is therefore a thin (uuid, cwd, asyncio.Lock) handle,
not a long-lived stdio pipe. The spec's "owns one stdio pipe pair" model
was rewritten to match reality — flagged here.
- Close semantics: soft-close via `acpx sessions close <name>`. The
on-disk record stays (ACPX's `sessions prune` is the hard-delete path,
not invoked from clawdforge). DELETE /sessions/<id> is documented as
idempotent (200 with already_closed=true on second call) so SDKs can
call close() in finally/Drop blocks safely.
- File uploads: ACPX has no file-attach ACP method exposed via the CLI.
We prepend a [Attached files] header listing absolute paths; the agent
uses its Read tool to open them. Same behavior as /run --files in v0.1.
- Permissions: --approve-all on the turn invocation since the container is
unattended and callers are bearer-token-trusted. Future v0.3 may expose
a per-session permission policy.
/run endpoint unchanged — backwards compat verified by
test_run_endpoint_unchanged + test_run_endpoint_unchanged_error_shape.
Spec: memory/spec-clawdforge-v0.2.md
ACPX CLI ref: https://github.com/openclaw/acpx/blob/main/docs/CLI.md
43 lines
1.7 KiB
Text
43 lines
1.7 KiB
Text
# clawdforge — copy to .env on Lucy at /mnt/cache/appdata/secrets/clawdforge.env
|
|
# (chmod 600, root:root)
|
|
|
|
# Bind
|
|
BIND_HOST=0.0.0.0
|
|
BIND_PORT=8800
|
|
|
|
# Bootstrap admin token. Used to mint per-app tokens via /admin/tokens.
|
|
# Once the SQLite db has any token, this var becomes a "root override" and
|
|
# should be rotated or unset.
|
|
ADMIN_BOOTSTRAP_TOKEN=change-me-32-bytes-of-entropy
|
|
|
|
# IP allowlist applied to ALL requests. CIDR list, comma-separated.
|
|
# 172.24.0.0/16 = sulkta bridge (where clawdforge sits with peer apps)
|
|
# 172.17.0.0/16 = docker0 default (some legacy apps still here)
|
|
# 192.168.0.0/24 = LAN clients
|
|
# Loopback always allowed.
|
|
ALLOW_CIDRS=172.24.0.0/16,172.17.0.0/16,192.168.0.0/24
|
|
|
|
# Default claude config (per-request override allowed)
|
|
CLAUDE_BIN=claude
|
|
DEFAULT_MODEL=sonnet
|
|
DEFAULT_TIMEOUT_SECS=120
|
|
|
|
# ACPX (multi-turn /sessions endpoints). Reuses Claude Code auth at /root/.claude.
|
|
ACPX_BIN=acpx
|
|
# Working directory for each session's CWD (acpx scopes by cwd; we give each session its own subdir).
|
|
ACPX_SESSIONS_CWD=/data/acpx-cwds
|
|
# Max simultaneously-open (non-closed) sessions across all apps. New /sessions returns 503 if at cap.
|
|
CLAWDFORGE_MAX_LIVE_SESSIONS=32
|
|
# How long an idle session lives before the sweeper soft-closes it. Counted from last_turn_at (or
|
|
# created_at if no turn ever ran).
|
|
CLAWDFORGE_SESSION_TTL_SECS=3600
|
|
# How long a closed session record stays before hard-delete (ledger row + acpx on-disk metadata).
|
|
CLAWDFORGE_SESSION_HARD_TTL_SECS=86400
|
|
# Sweep cadence in seconds.
|
|
CLAWDFORGE_SWEEP_INTERVAL_SECS=60
|
|
|
|
# Run-staging area inside the container (don't change unless you also change compose mount)
|
|
RUNS_DIR=/data/runs
|
|
|
|
# SQLite db path (don't change unless you also change compose mount)
|
|
DB_PATH=/data/clawdforge.db
|