How Prime Agent Implements Session Leases for Concurrent Writes
Prime Agent prevents concurrent writes to session files using an opt-in filesystem-based lease mechanism that employs atomic directory renames, PID-based liveness checks, and guard locks to ensure only one process holds a lease at a time.
The PrimeIntellect-ai/prime-agent repository coordinates multiple agent processes through a deterministic locking system that protects session file integrity. This implementation uses atomic filesystem operations and process liveness detection to safely manage session leases for concurrent writes. The core logic resides in packages/coding-agent/src/core/session-lease.ts, which provides the acquireSessionLease function and the SessionLease class.
Enabling and Configuring Session Leases
Before any locking occurs, the system must be explicitly activated and configured to handle a specific session file path.
Opt-In Activation
The lease mechanism is disabled by default to avoid overhead in single-process scenarios. To enable it, set the environment variable PRIME_AGENT_INTERNAL_SESSION_LEASES to 1, true, or yes. The acquireSessionLease function checks this variable at session-lease.ts:63-66 and returns null if leasing is not enabled, allowing the caller to proceed without locking.
Canonical Path Resolution
To prevent symlink-based bypasses where different paths refer to the same file, the supplied session path is resolved to a real, absolute path using fs.realpath. This canonicalization ensures that different symlinked references to the same session file resolve to the same lease identity. The resolution logic appears at session-lease.ts:73-84.
Deterministic Lock File Location
Each session maps to a unique lock directory derived from a hash of its canonical path. The system stores leases in <agent-dir>/session-leases/<hash>.lock, guaranteeing consistent lookup across processes. This path calculation occurs at session-lease.ts:68-71.
Lease Acquisition and Contention Handling
Once enabled, acquiring a lease involves a multi-step protocol designed to prevent race conditions and detect crashed owners.
Guard Lock Serialization
Before creating a lease, the process must obtain a guard lock using the proper-lockfile library's directory.guard mechanism. This guard serializes lease creation across competing processes, retrying up to 100 times before aborting with a clear error. The guard acquisition logic is implemented at session-lease.ts:103-149.
Atomic Candidate Promotion
Inside the guard, the process creates a candidate lease directory with a unique name: <lock>.candidate-<pid>-<uuid>. This directory contains an owner.json file storing a random token, the owner PID, a process-start identifier (formatted as proc:…, ps:…, or win:…), an optional session-owner ID, and the session path with timestamp. The candidate is then atomically renamed to the final lease directory. If successful, the process owns the lease. This atomic operation prevents partial writes from leaving corrupt lock states. See session-lease.ts:78-95 for candidate creation and session-lease.ts:95-99 for the rename logic.
Contention Resolution and Stale Lease Detection
If the rename fails because the directory exists (EEXIST or ENOTEMPTY), the system enters contention resolution. It reads the existing lease owner via readLeaseOwner and checks liveness via isLeaseOwnerAlive. Liveness verification combines two checks:
- PID existence: Sending signal
0to the owner PID usingprocess.kill(pid, 0) - Process identity validation: Comparing the stored process-start identifier against the current system's identifier for that PID
This dual check prevents false positives when a PID has been recycled by the operating system. If the owner is alive, the function throws SessionAlreadyActiveError. If dead, the stale lease is reclaimed. The liveness logic appears at session-lease.ts:104-110 and session-lease.ts:92-101, while reclamation occurs at session-lease.ts:151-163.
Lease Release and Cleanup
Proper cleanup ensures that completed sessions do not block future acquisitions.
Safe Lease Release
The SessionLease.release() method removes the lease directory only if the caller's token matches the stored owner.json token. This verification prevents stray processes from deleting leases held by other instances. The release operation wraps itself in the same guard lock used during acquisition to maintain atomicity. The implementation resides at session-lease.ts:45-60.
Implementation Reference
The session lease system spans several key files in the codebase:
| Package | File | Purpose |
|---|---|---|
coding-agent |
[src/core/session-lease.ts](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/session-lease.ts) |
Core implementation of lease acquisition, guard handling, and release. |
coding-agent |
[src/modes/daemon/daemon-mode.ts](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/modes/daemon/daemon-mode.ts) |
Uses acquireSessionLease when starting daemon-hosted sessions. |
coding-agent |
[test/session-lease.test.ts](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/test/session-lease.test.ts) |
Test suite confirming contention handling and stale-lease recovery. |
Practical Usage Examples
The following examples demonstrate how to integrate session leasing into agent workflows.
Acquiring a Lease Before Session Access
import { acquireSessionLease, SESSION_LEASES_ENABLED_ENV } from "./session-lease.js";
const env = { [SESSION_LEASES_ENABLED_ENV]: "1" };
const lease = acquireSessionLease("/path/to/session.jsonl", "/path/to/agentDir", env);
if (!lease) {
throw new Error("Session leasing disabled or session path missing");
}
// ...use the session file safely...
// When done (or on process exit)
lease.release();
Handling Lease Conflicts
import { acquireSessionLease, SessionAlreadyActiveError } from "./session-lease.js";
try {
const lease = acquireSessionLease(sessionPath, agentDir);
// proceed with work
} catch (e) {
if (e instanceof SessionAlreadyActiveError) {
console.error(`Another process already owns the session: ${e.activeSessionId}`);
} else {
throw e;
}
}
Summary
- Session leases are opt-in via the
PRIME_AGENT_INTERNAL_SESSION_LEASESenvironment variable to minimize overhead when not needed. - Atomic directory renames guarantee that lease acquisition is an all-or-nothing operation, preventing partial lock states.
- Dual liveness checks (PID existence plus process-start ID) safely detect crashed processes without falling prey to PID recycling.
- Guard locks from
proper-lockfileserialize lease operations to eliminate race conditions during acquisition and release. - Token-verified release ensures only the legitimate lease owner can clean up the lock directory.
Frequently Asked Questions
How do I enable session leases in Prime Agent?
Set the environment variable PRIME_AGENT_INTERNAL_SESSION_LEASES to 1, true, or yes before starting the agent process. When this variable is absent or set to any other value, acquireSessionLease returns null and the system operates without file locking.
What happens if a process crashes while holding a lease?
The next process attempting to acquire the lease will detect the stale lock by checking if the owner PID is still alive and verifying the process-start identifier matches. If the owner is confirmed dead, the new process automatically reclaims the lease by renaming and deleting the stale directory.
How does Prime Agent prevent PID recycling attacks?
The system stores a process-start identifier (format proc:…, ps:…, or win:…) in the owner.json file alongside the PID. When checking liveness, it verifies both that the PID exists and that its current start identifier matches the stored value, preventing a new process with a recycled PID from impersonating the original lease holder.
Can multiple processes read the session file while one holds the write lease?
The implementation in session-lease.ts specifically protects against concurrent writes. While the lease mechanism does not implement read locks, it ensures that only one process can hold the write lease at a time. Readers should implement their own coordination if consistent read access during writes is required.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →