Files
openclaw/docs/refactor/acp.md
Peter Steinberger a9d3980ed1 refactor(acpx): complete lease ownership and remove legacy reaper heuristics (#121016)
* refactor(acpx): harden pending process leases

* fix(acpx): bound retained probe leases
2026-08-09 03:15:39 -07:00

176 lines
8.9 KiB
Markdown

---
summary: "Current ACP session ownership and ACPX process-lease migration status"
read_when:
- Refactoring ACP session lifecycle or ACPX process cleanup
- Debugging ACPX orphan processes, PID reuse, or multi-gateway cleanup safety
- Changing sessions_list visibility for spawned ACP or subagent sessions
- Designing ownership metadata for background tasks, ACP sessions, or process leases
title: "ACP lifecycle refactor"
sidebarTitle: "ACP lifecycle refactor"
---
ACP session ownership is now explicit in session rows, and generated ACPX
wrapper launches are lease-backed. The remaining process-cleanup heuristics are
not the target design: they are temporary coverage for launch paths that the
published `acpx` runtime does not yet expose to OpenClaw.
This page records the current boundary and the work that remains. It is not a
proposal for a second lifecycle controller.
## Invariants
- Lease-based cleanup signals a process only after current live evidence matches
the lease id, Gateway instance id, and expected wrapper path.
- `cancel` stops the active turn without closing a reusable ACP session.
- `close` owns terminal session cleanup.
- Session visibility reads normalized ownership from session rows.
- Core remains independent of ACPX package and adapter details.
## Current ownership model
The ACPX plugin persists a stable Gateway instance id and version 1 process
leases in plugin SQLite state. The lease shape shipped in `v2026.7.2-beta.7` and
remains compatible:
```ts
type AcpxProcessLease = {
leaseId: string;
gatewayInstanceId: string;
sessionKey: string;
wrapperRoot: string;
wrapperPath: string;
rootPid: number;
processGroupId?: number;
commandHash: string;
startedAt: number;
state: "open" | "closing" | "closed" | "lost";
};
```
Before entering an upstream launch that uses an OpenClaw-generated wrapper, the
plugin writes a pending lease with `rootPid: 0`. The wrapper command receives
the lease id and Gateway instance id as arguments:
```sh
--openclaw-acpx-lease-id <lease-id> \
--openclaw-gateway-instance-id <gateway-instance-id>
```
The ACPX session-store save promotes the pending lease with the wrapper PID.
Generated-wrapper availability and doctor probes use the same lease-first
ordering. Delegate fulfillment does not prove a probe wrapper exited: OpenClaw
checks the exact live lease identity and reaps a surviving tree, but retains the
lease even when the wrapper is absent. The wrapper strips lease arguments before
spawning its detached adapter, so wrapper absence cannot prove reparented
descendants are gone. The probe lease stays open until stable descendant identity
can prove the whole tree absent. If delegate entry throws, OpenClaw likewise
cannot distinguish a pre-spawn failure from a post-spawn failure.
Direct agent commands are different. Published `acpx` resolves and spawns those
processes internally and exposes neither a pre-spawn identity hook nor a
post-spawn ownership callback. OpenClaw does not duplicate that launch policy;
direct-agent sessions and direct-agent probes therefore remain outside complete
lease coverage.
## Startup recovery
Startup first lists open leases owned by the current Gateway instance.
- A lease with a recorded PID is reaped only when the live root command matches
its lease id, Gateway instance id, and wrapper root.
- A pending `rootPid: 0` wrapper lease is recovered by enumerating live rows and
requiring exactly one process with the exact wrapper path, lease id, and
Gateway instance id. Foreign or missing matches are not signaled.
- Ambiguous matches, unavailable process evidence, invalid wrapper paths, and
unsupported platforms leave the lease open for a later retry.
- Missing generated-probe wrappers also leave their lease open because detached
descendants do not carry the lease identity.
- Aggregate marker scanning is separate from lease retirement. Its result never
closes or loses a specific lease.
The legacy marker scan remains temporarily for unleased direct-agent processes
and reparented descendants. It keeps its existing narrow trigger: startup saw a
pending lease from an uncertain spawn. Lease-aware wrapper roots are excluded
from that scan and are handled only by exact lease recovery.
## Session visibility
Session rows carry lineage such as `spawnedBy`, `parentSessionKey`, and
`ownerSessionKey`. Visibility checks consume those row fields directly for
`self`, `tree`, `agent`, and `all`; they no longer perform a secondary
`sessions.list({ spawnedBy })` lookup per row. A requester-owned cross-agent ACP
child remains visible whenever the configured visibility includes the
requester's tree, and `all` is not less capable than `tree`.
## Migration status
| Phase | Status | Current result |
| ------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1. Identity and leases | Complete for generated wrappers | Stable Gateway identity, SQLite lease store, lease-first generated session/probe launch, and session-record lease identity are live. Direct upstream spawns are blocked on `acpx` hooks. |
| 2. Lease-first close cleanup | Complete | Close loads the lease first and verifies current process evidence. Shipped legacy session records retain fail-closed PID/command cleanup. |
| 3. Lease-first startup reaping | Partial | PID and `rootPid: 0` wrapper leases use exact live evidence. Process-group coverage, reparented descendants, direct agents, and Windows reaping remain blocked. |
| 4. Session ownership rows | Complete | Writers persist lineage and visibility reads normalized row metadata without secondary list lookups. |
| 5. Remove legacy heuristics | Blocked | Visibility fallbacks are gone. Command-marker reaping must remain until the process-ownership blockers below are closed and proven. |
## Remaining blockers
### Upstream launch ownership
`acpx` owns probe, initial session, reconnect, and control-operation process
creation. OpenClaw needs upstream pre-spawn identity injection plus a post-spawn
callback that reports the root PID or an equivalent launch primitive. Without
that contract, locally wrapping direct launches would duplicate `acpx` command
resolution and platform behavior.
### Stable descendant identity
Generated wrappers create a detached adapter process group on Unix, but the
group id is not reported back into the lease. The reaper currently discovers
descendants from PPID rows, so a reparented grandchild can escape the recorded
tree. Removing its marker fallback requires upstream or wrapper-owned reporting
of a stable process-group identity and reaper support that verifies that identity
against the lease.
### Windows cleanup
The ACPX reaper does not yet enumerate and terminate Windows process trees with
the same live command-line evidence used on Unix. Windows cleanup therefore
fails closed and leaves leases open. Marker removal requires a tested Windows
process-list and tree-termination implementation, not a platform skip.
## Compatibility
The lease schema is unchanged from the latest shipped release, so no doctor
migration is required for this work. Old session records without `leaseId` keep
the legacy fail-closed cleanup path:
- require a live root process
- require wrapper-root ownership for generated wrappers
- require stored/live command agreement for non-wrapper roots
- never signal from stale PID metadata alone
If a legacy record cannot be verified, cleanup leaves it alone.
## Proof expectations
Process lifecycle coverage must include:
- generated probe lease persistence before upstream entry
- completed probe exact-wrapper inspection and conservative lease retention
- PID reuse by an unrelated process or another Gateway instance
- exact `rootPid: 0` own-versus-foreign recovery
- ambiguous, unavailable, and Windows evidence failing closed
- adapter children reaped in child-first order
- a reparented descendant test before claiming marker removal
Session visibility coverage must retain the `self`, `tree`, `agent`, and `all`
matrix across same-agent, cross-agent, a2a-disabled, and requester-owned spawned
rows.
## Completion criteria
The refactor is complete only when upstream launch hooks cover every direct
spawn, leases carry stable process-tree identity across reparenting, Windows has
equivalent verified cleanup, and the marker scan plus its package/path literals
can be deleted without reducing orphan cleanup coverage.