ADR-025: Multi-Agent Lock Coherence and Lease Propagation
Status
Section titled “Status”Accepted (2026-09-29) — proposed 2026-04-23, ratified after Phase 5 implemented it.
Context
Section titled “Context”As Vox moves toward multi-agent environments (multiple agents working on the same task or in the same workspace), resource contention (not just file-level) becomes a risk. We need a way to lock generic resources (URIs, DB rows) and propagate these locks across the bulletin board.
Decision
Section titled “Decision”We extend the locks subsystem with a ResourceLockManager:
- Generic Resource IDs: Locks can be held on arbitrary strings (URIs).
- Lease-based Expiration: All locks have a mandatory TTL to prevent deadlocks from crashed agents.
- Bulletin Synchronization: Lock acquisition and release events are broadcast as
AgentMessagevariants to ensure all agents are aware of the coherence state.
Consequences
Section titled “Consequences”- Agents can safely coordinate on non-file resources.
- The system is resilient to agent failures via lease expiration.
- Real-time visualization of resource contention is possible via the bulletin board.
Implementation (Phase 5)
Section titled “Implementation (Phase 5)”- Lock manager:
ResourceLockManager(crates/vox-orchestrator-queue/src/locks/resource.rs) holds leases with an expiry;try_acquire,release, andis_lockedsweep every expired lease lazily (D-06), so a crashed holder cannot wedge a resource. There is no background sweeper. - Resource on the work item (D-05): a hopper intake item and an
AgentTaskcarry an optionalresource_id(crates/vox-orchestrator/src/hopper/types.rs, validated byvalidate_resource_id); it is persisted in the SQLite hopper (schema 94) and accepted byPOST /api/v2/hopper/submit. - Gate at dispatch (D-06):
ResourceGate(crates/vox-orchestrator/src/orchestrator/safety.rs) holds the lock for the task’s lifetime, using the task timeout as the lease.dispatch_item(crates/vox-orchestrator/src/orchestrator/dispatch.rs) parks an item whose resource is held and retries it when aLockReleasedevent arrives. - Release (D-06): completing, failing, or cancelling the holder (directly or through the hopper) releases the lock, and the waiter is dispatched (
crates/vox-orchestrator/tests/resource_lock_lifecycle.rs). - Events instead of new
AgentMessagevariants: acquire, release, and waiting areLockAcquired,LockReleased, andLockWaitingevents (crates/vox-orchestrator/src/events.rs), carryingsession_idandtask_id, and are visible in the activity feed. - Chat surface (D-10, D-11):
ChatExecutionRail.tsxshows a holding or “waiting on” chip, fed by lockStatesFromActivityincrates/vox-gui/ui/src/hooks/useChatExecutionData.ts.
Known limitations
Section titled “Known limitations”- Single-process scope (D-07): the lock manager is in memory; two orchestrator processes do not see each other’s locks.
- A waiter behind a lease that expires without a release event waits for the next release of that resource; a background retry is deferred.
LockWaitingis emitted again on every park retry, so a long wait produces repeated events.- The chat
vox_submit_taskpath does not go through the hopper, so resource-locked work arrives viaPOST /api/v2/hopper/submit. - The lock is enqueued before it is held, and the check-then-acquire is not atomic across concurrent dispatchers.