Rensei docs
Worker Protocol

File Reservation

Pessimistic file locking.

The file reservation protocol prevents concurrent sessions from writing to the same file simultaneously. Workers reserve files before modifying them and release them when done, giving other agents a chance to coordinate.

Overview

When multiple agents are active in the same repository (e.g. a feature agent and a QA agent running concurrently), file reservation ensures they do not destructively overwrite each other's changes. The mechanism is pessimistic - a second reservation attempt on an already-reserved file returns a conflict, and the requesting worker must wait or choose a different approach.

File reservations are scoped to a session and automatically released when the session reaches a terminal state (completed, failed, stopped). Manual release is only required for mid-session cleanup.

Endpoints

Reserve a file

POST /api/sessions/{sessionId}/files/reserve
Authorization: Bearer <runtimeJwt>
Content-Type: application/json

Request body

{
  "filePath": "src/services/user-service.ts",
  "ttlSeconds": 120
}
FieldTypeRequiredDescription
filePathstringYesRepository-relative path to the file
ttlSecondsnumberNoReservation TTL in seconds (default: 60, max: 300)

Success response (200)

{
  "reservationId": "res_01abc...",
  "filePath": "src/services/user-service.ts",
  "sessionId": "sess_01abc...",
  "expiresAt": "2026-06-02T12:02:00Z"
}

Conflict response (409)

{
  "error": "File already reserved",
  "reservedBy": "sess_02def...",
  "expiresAt": "2026-06-02T12:01:30Z"
}

When you receive a 409, the expiresAt tells you when the existing reservation expires. You can either wait and retry, or check whether the blocking session is still active.

Release a file

POST /api/sessions/{sessionId}/files/release
Authorization: Bearer <runtimeJwt>
Content-Type: application/json
{
  "filePath": "src/services/user-service.ts"
}

Success response (200)

{ "ok": true }

Release all files

Release all file reservations held by a session at once (use at session end):

POST /api/sessions/{sessionId}/files/release-all
Authorization: Bearer <runtimeJwt>

Success response (200)

{
  "releasedCount": 3,
  "releasedPaths": [
    "src/services/user-service.ts",
    "src/models/user.ts",
    "tests/user-service.test.ts"
  ]
}

Check for conflicts

Check whether any of a list of files are currently reserved by another session:

GET /api/sessions/{sessionId}/files/check-conflicts
Authorization: Bearer <runtimeJwt>

Query parameters

ParameterDescription
pathsComma-separated list of file paths to check

Example

curl "https://app.rensei.ai/api/sessions/sess_01abc.../files/check-conflicts?paths=src/services/user-service.ts,src/models/user.ts" \
  -H "Authorization: Bearer <runtimeJwt>"

Response

{
  "conflicts": [
    {
      "filePath": "src/services/user-service.ts",
      "reservedBy": "sess_02def...",
      "expiresAt": "2026-06-02T12:01:30Z"
    }
  ],
  "clear": ["src/models/user.ts"]
}

List reserved files

List all files currently reserved by a specific session:

GET /api/sessions/{sessionId}/reserved-files
Authorization: Bearer <runtimeJwt>

Response

{
  "reservations": [
    {
      "reservationId": "res_01abc...",
      "filePath": "src/services/user-service.ts",
      "reservedAt": "2026-06-02T12:00:00Z",
      "expiresAt": "2026-06-02T12:02:00Z"
    }
  ]
}

Usage pattern

A well-behaved worker follows this pattern for each file it modifies:

Check for conflicts before starting work on a set of files using the batch conflict check endpoint. If conflicts are found, wait until the blocking reservation expires.

Reserve each file before writing. Use a TTL that covers your expected edit window plus a buffer.

Write the files and commit your changes.

Release the files explicitly after committing. This allows other waiting agents to proceed without waiting for TTL expiry.

TypeScript example

async function withFileReservation(
  sessionId: string,
  jwt: string,
  filePaths: string[],
  fn: () => Promise<void>,
): Promise<void> {
  const base = 'https://app.rensei.ai';
  const headers = {
    Authorization: `Bearer ${jwt}`,
    'Content-Type': 'application/json',
  };
  const reservationIds: string[] = [];

  try {
    // Reserve all files
    for (const filePath of filePaths) {
      const res = await fetch(`${base}/api/sessions/${sessionId}/files/reserve`, {
        method: 'POST',
        headers,
        body: JSON.stringify({ filePath, ttlSeconds: 120 }),
      });
      if (res.status === 409) {
        const conflict = await res.json();
        throw new Error(`File locked by ${conflict.reservedBy}: ${filePath}`);
      }
      const { reservationId } = await res.json();
      reservationIds.push(reservationId);
    }

    // Execute the write operation
    await fn();
  } finally {
    // Release all reservations
    await fetch(`${base}/api/sessions/${sessionId}/files/release-all`, {
      method: 'POST',
      headers,
    });
  }
}

On this page