GitHunt

OpenClaw Spine (v0)

A file-backed job queue with bearer token authentication for distributed agents.

  • Port: 36725 (default)
  • Health: GET /health
  • Data dir: ./data/{jobs,events,blobs}

Auth

Send Authorization: Bearer <TOKEN>.

Roles:

  • headHEAD_TOKEN (single) or HEAD_TOKENS (CSV for rotation)
  • left_clawLEFT_CLAW_TOKEN or LEFT_CLAW_TOKENS (CSV)
  • right_clawRIGHT_CLAW_TOKEN or RIGHT_CLAW_TOKENS (CSV)

Token Rotation

To rotate tokens without downtime, set the CSV env to include both old and new tokens:

HEAD_TOKENS=old-token,new-token

Both will be accepted. Remove the old one once all clients have switched.

Endpoints

Method Path Who Description
GET /health anyone Health check
GET /skill.md anyone Serve skill markdown (public, no auth)
POST /jobs head Create job
GET /jobs head + claws List jobs (filtered by role/target)
GET /jobs/:id head + claws Get single job
POST /jobs/:id/claim claws Claim a queued job (increments attempts)
POST /jobs/:id/heartbeat claws Extend lease
POST /jobs/:id/complete claws Mark done
POST /jobs/:id/fail claws Fail (requeues by default, or terminal)
POST /jobs/:id/release claws Release back to queued (no attempt increment)
POST /jobs/:id/comment head + claws Add a comment
POST /blobs head + claws Upload blob (multipart)

Ownership

"Owner" refers to the claw that successfully claimed a job via /claim.

Only the current claimant can call /heartbeat, /complete, /fail, and /release on a running job. Head always has admin override on these endpoints.

Job Lifecycle

queued → (claim) → running → (complete) → done
                           → (fail, requeue=true) → queued  (retry)
                           → (fail, requeue=false) → failed
                           → (fail, max attempts) → dead
                           → (release) → queued
                           → (lease expires, reaper) → queued / dead

Attempts & maxAttempts

  • Each /claim increments attempts by 1.
  • If the new attempts count reaches maxAttempts, the job is immediately marked dead instead of running.
  • /fail with requeue: true (default) returns to queued if under limit.
  • /release does NOT increment attempts (it's a voluntary give-back).
  • Default maxAttempts: 5 (set via DEFAULT_MAX_ATTEMPTS env).
  • Override per-job in POST /jobs body: { maxAttempts: 10 }.

Expiry Reaper

A background loop (every REAPER_INTERVAL_MS, default 30s) scans running jobs with expired leases and:

  • Returns them to queued if under maxAttempts
  • Marks them dead if at the limit

Fail Endpoint

POST /jobs/:id/fail

{ "error": "reason string", "requeue": true }
  • requeue defaults to true. If true and under maxAttempts, returns to queued.
  • If false or at maxAttempts, goes to failed or dead.

Release Endpoint

POST /jobs/:id/release

{ "reason": "optional reason" }

Returns a running job to queued without counting as a failure.

Env

Var Default Description
SPINE_PORT 36725 Listen port
SPINE_HOST 127.0.0.1 Listen host
SPINE_DATA_DIR ./data Data directory
LEASE_SECONDS 300 Lease duration per claim/heartbeat
REAPER_INTERVAL_MS 30000 Expiry reaper scan interval
DEFAULT_MAX_ATTEMPTS 5 Default max claim attempts
SPINE_SKILL_MD_PATH ./SKILL.md Path to SKILL.md served at /skill.md
HEAD_TOKEN Single head token
HEAD_TOKENS CSV head tokens (rotation)
LEFT_CLAW_TOKEN Single left claw token
LEFT_CLAW_TOKENS CSV left claw tokens
RIGHT_CLAW_TOKEN Single right claw token
RIGHT_CLAW_TOKENS CSV right claw tokens

Run

Set at least one token (e.g. HEAD_TOKEN=test123) before starting.

cd spine
npm i
npm run start      # production
npm run dev        # with .env file
npm test           # integration tests

Storage

  • All writes use temp-file + rename() for atomicity.
  • Claims use exclusive lock files (<id>.lock) to prevent races.
  • Events append to events/<id>.jsonl.