claimd
A concurrent task manager CLI built in Rust for multi-agent AI collaboration. Agents can add, view, claim, and complete tasks with atomic locking that prevents two agents from picking up the same work.
Features
- Atomic claiming -- when two agents race to claim the same task, exactly one wins; the other gets an immediate error (exit code 2)
- Multi-claim --
claim-multiclaims multiple tasks in a single atomic operation with all-or-nothing semantics - Statuses -- New, InProgress, PrOpen, PrChangesRequested, Done, Incomplete with transition validation
- Project active/inactive -- mark a project inactive to block new claims while keeping all tasks visible and existing in-progress work completable
- JSON mode --
--jsonflag on any command for machine-parseable output; every task includes aproject_activefield - UUID prefix matching -- reference any task by the first 4+ characters of its ID
- Dependencies -- declare dependencies between tasks; a task with pending deps cannot be claimed until all deps are done
- Minimal footprint -- ~764KB release binary, 6 dependencies, no async runtime or database
Installation
Or build manually:
# Binary at ./target/release/claimd
Quick start
Storage
Data is stored in ~/.claimd/projects/<name>/ by default:
tasks.json-- canonical data filetasks.lock-- flock target for atomic operationstasks.json.tmp-- transient write target (atomic rename)
Override the base location with --dir /path or the CLAIMD_DIR environment variable.
Projects
--project <name> (or CLAIMD_PROJECT env var) is required for all task commands. Each project gets its own isolated store directory with no cross-pollution between projects.
# Initialize separate projects
# Tasks are completely isolated
# Each project only sees its own tasks
# Use env var for convenience
Project active/inactive state
Projects have an active flag (default: true). When a project is inactive, all → InProgress transitions are blocked — claim and claim-multi fail immediately with exit code 2. Existing in-progress tasks are unaffected and can still be progressed to pr_open, done, incomplete, etc. This is designed for spinning down a project: block new work starting while letting active agents finish up cleanly.
Every task output (JSON and human-readable) includes the project's active state so agents always know the current mode without a separate lookup.
# Deactivate a project — blocks new claims
# Reactivate when ready
# Check state of a single project
# List all projects with their active state
Global options
These flags can be used with any command:
| Flag | Description |
|---|---|
--json |
Output as JSON instead of human-readable text |
--dir <DIR> |
Path to the task store directory (env: CLAIMD_DIR) |
--project <NAME> |
Project name for task isolation (env: CLAIMD_PROJECT) |
-h, --help |
Print help |
Exit codes
| Code | Meaning |
|---|---|
0 |
Success |
1 |
General error (not found, invalid args, IO) |
2 |
Claim conflict (already claimed, lock held, unresolved dependencies, or project inactive) |
Commands
init
Initialize the task store for a project. Creates the store directory and an empty tasks.json. Idempotent.
add
Add a new task with status New.
| Option | Description |
|---|---|
--desc <DESC> |
Description text |
--priority <N> |
Priority, 0 = highest (default: 5) |
--tag <TAG> |
Tag (repeatable for multiple tags) |
--link <URL> |
Link (URL or reference) |
--source <SOURCE> |
Source (where this task came from, e.g. "jira", "slack") |
--author <AUTHOR> |
Author (who created this task) |
--depends-on <ID> |
Depends on this task UUID/prefix (repeatable) |
Examples:
list
List tasks. By default shows New, InProgress, and Incomplete tasks only.
| Option | Description |
|---|---|
--status <STATUS> |
Filter by status: new, in_progress, pr_open, pr_changes_requested, done, incomplete |
--tag <TAG> |
Filter by tag |
--all |
Show all tasks including Done |
Examples:
show
Show full detail of a single task. For convenience this command works without --project — when no project is given it scans all projects to find the task by ID.
| Argument | Description |
|---|---|
<ID> |
UUID or prefix (minimum 4 characters) |
Example:
claim
Atomically claim a task, transitioning it from New, Incomplete, or PrChangesRequested to InProgress. Uses a non-blocking lock -- if another process holds the lock, fails immediately with exit code 2. Also fails with exit code 2 if the task has unresolved dependencies or if the project is inactive.
When claiming a PrChangesRequested task, the previous claimed_by agent is moved to the previously_claimed_by list, and the new agent takes over.
| Option | Description |
|---|---|
--agent <AGENT> |
Agent identifier string |
Examples:
claim-multi
Atomically claim multiple tasks in a single operation. All-or-nothing: if any task is not claimable, none are claimed.
| Option | Description |
|---|---|
--agent <AGENT> |
Agent identifier string |
Example:
pr-open
Mark a task as having a PR open and record the PR URL. Valid from InProgress or PrChangesRequested.
| Option | Description |
|---|---|
--pr-url <URL> |
GitHub PR URL (required) |
Example:
pr-changes-requested
Mark a task's PR as having changes requested. Valid from PrOpen.
Example:
done
Mark a task as done. Clears the claimed_by field. When a task is marked done, it is automatically moved from the depends_on list to the depends_on_completed list of any task that depended on it. Once all dependencies are resolved, that task becomes claimable.
Example:
# Any task with 6d45 in its depends_on list now has it in depends_on_completed instead
incomplete
Mark a task as incomplete. Clears the claimed_by field. Optionally appends a reason to the description.
| Option | Description |
|---|---|
--reason <REASON> |
Reason for marking incomplete (appended to description) |
Example:
unclaim
Reset a task to New, transitioning from InProgress or Incomplete. Clears the claimed_by field.
Example:
edit
Edit fields on an existing task. Only specified fields are changed.
| Option | Description |
|---|---|
--title <TITLE> |
New title |
--desc <DESC> |
New description |
--priority <N> |
New priority |
--tag <TAG> |
Replace tags (repeatable) |
--link <URL> |
New link |
--source <SOURCE> |
New source |
--author <AUTHOR> |
New author |
--add-dep <ID> |
Add a dependency on a task UUID/prefix (repeatable) |
--remove-dep <ID> |
Remove a dependency on a task UUID/prefix (repeatable) |
Example:
reorder
Move a task to a specific position in the list (0-indexed).
| Option | Description |
|---|---|
--position <N> |
Target position, 0-indexed |
Example:
remove
Delete a task entirely.
Example:
project list
List all known projects, their active state, and store paths.
Example output:
backend active /Users/me/.claimd/projects/backend
frontend INACTIVE /Users/me/.claimd/projects/frontend
project status
Show the active state of a specific project.
Example:
# backend: active
project activate
Activate a project, re-enabling claim and claim-multi.
Example:
# backend: active
project deactivate
Deactivate a project, blocking all → InProgress transitions. Existing in-progress tasks are unaffected.
Example:
# frontend: INACTIVE
Dependencies
Tasks can declare dependencies on other tasks. A task with unresolved dependencies cannot be claimed.
# Create tasks with a dependency chain
# Trying to claim "Write tests" fails (exit code 2)
# Error: Task c0228801 has unresolved dependencies: 6d45a75a, f5b883f1
# Complete dependencies one by one
# Now claim succeeds
Dependencies can also be added/removed after creation via edit:
Concurrency model
All mutations acquire an exclusive file lock (flock) on tasks.lock:
claim/claim-multi: Usetry_lock_exclusive(non-blocking). If the lock is held by another process, the command fails immediately with exit code 2.- Other mutations (
add,done,edit, etc.): Uselock_exclusive(blocking). The command waits until the lock is available. - Read-only (
list,show): Uselock_shared. Multiple concurrent readers are allowed.
Writes use an atomic rename pattern (write to tasks.json.tmp, then rename over tasks.json) so the data file is never left in a partial state.
JSON output
Pass --json to any command for machine-parseable output. Every task object includes a project_active field so agents can inspect the project state without a separate command.
# List as JSON array — each item includes project_active
# [{"id":"...","title":"...","status":"new","project_active":true,...}]
# Single item as pretty-printed JSON object
# Project commands as JSON
# {"projects":[{"name":"backend","active":true},{"name":"frontend","active":false}]}
# {"name":"backend","active":true}
# Errors as JSON to stderr
# {"error":"Task 6d45a75a is already being worked on by 'agent-1'","code":"already_claimed"}
# Project inactive error
# {"error":"Project is inactive — claiming is disabled","code":"project_inactive"}
Data model
Each task has the following fields:
| Field | Type | Description |
|---|---|---|
id |
UUID | Unique identifier |
title |
string | Title text |
description |
string? | Optional description |
status |
enum | new, in_progress, pr_open, pr_changes_requested, done, incomplete |
priority |
u8 | 0 = highest priority |
created_at |
datetime | Creation timestamp (UTC) |
updated_at |
datetime | Last modification timestamp (UTC) |
claimed_by |
string? | Agent identifier that claimed this task |
pr_url |
string? | GitHub PR URL, set when transitioning to pr_open |
previously_claimed_by |
string[] | Agents that previously worked on this task |
link |
string? | URL or reference link |
source |
string? | Where this task came from (e.g. "jira", "slack", "github") |
author |
string? | Who created this task |
tags |
string[] | List of tags |
depends_on |
UUID[] | IDs of tasks that must be completed before this one can be claimed |
depends_on_completed |
UUID[] | IDs of dependencies that have been completed |
project_active |
bool | Whether the owning project currently allows claiming (injected at output time, not stored on the task) |