Local coordination daemon for AI agents — named locks, shared state, events, and a task queue over MCP.
Airlock is a coordination daemon with 22 well-named, verb-prefixed tools (lock_resource, unlock_resource, claim_next_task, etc.). All tools have descriptions (avg ~150 chars, within baseline 34 - 392 range) and explicit JSON Schema input definitions with typed properties and required fields. Schemas are hand-built and visible in internal/mcp/tools.go. However, output schemas are NOT documented, the tool descriptions state what is returned (e.g., 'lock_token', 'wake_token', 'queue_position') but no formal response schema is provided. Error handling is minimal: no recovery guidance, no categorization of retryable vs. fatal errors, and no actionable error messages. Parameter descriptions are present but sometimes terse (e.g., 'Counter name' for increment_counter.name). Deprecated parameter aliases (ttl_minutes) are documented but not removed. No tool annotations (readOnlyHint, destructiveHint, idempotentHint) are present despite clear risk classifications in the tool list.
Claim the next task from the queue (FIFO). Returns task_id, name, input, and a lease_token valid for lease_seconds. If no task is available, blocks up to wait_seconds (0-50, default 25). Lease auto-expires; renew with renew_task_lease or complete with complete_task.
Reset a named event's generation to absent (0).
Mark a task complete and remove it from the queue. Pass the lease_token returned by claim_next_task. Optionally include result (JSON-serializable output) and/or error (failure reason).
Delete a shared note by key. Returns deleted=false if it did not exist.
Enqueue a new task to the queue. Returns task_id. The task will be claimed by the next agent that calls claim_next_task.
Read a shared note by key. Returns found=false if absent or expired.
Output schemas not documented. Tool descriptions mention return fields (lock_token, wake_token, queue_position, etc.) but no formal response schema is provided. LLMs cannot plan downstream tool calls or extract fields reliably without knowing the response structure.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Tools are classified as READ_ONLY, WRITE, or DESTRUCTIVE in the tool list, but these are not exposed in the MCP schema. Agents cannot distinguish safe from risky operations without reading descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2026-07-28+ | v2 |
Atomically add to a named shared counter (creating it if absent) and return the new value.
List all currently registered (non-expired) agents with remaining seconds.
List all currently held locks with their holder agent and remaining seconds.
List all non-expired shared notes.
List all tasks in the queue (pending, claimed, or completed) with their status, holder, and remaining lease time.
Acquire an exclusive lock on a named resource so other agents do not touch it concurrently. Pass wait_seconds (up to 50) to BLOCK until the lock is acquired — this is the coordination-by-default path; with the default wait_seconds=0 a contended lock returns locked=false immediately. Use a descriptive name like 'file:/path/to/file', 'npm-install', or 'agent:some-id'. On success you get a lock_token; pass it to renew_lock/unlock_resource. If blocking times out you get a wake_token + queue_position — call again with that wake_token to keep your place in line.
Atomically acquire several locks at once (all-or-nothing). If any name is held, none are acquired. Returns a token per name on success.
Register/heartbeat the calling agent with a TTL so others can see it is alive. Call periodically to stay registered; when it lapses the agent's locks are auto-released.
Extend a lock you hold before it expires. Prefer passing lock_token; agent_id is accepted for compatibility (renews only if the live holder matches).
Extend a task lease you hold before it expires. Pass the lease_token returned by claim_next_task.
Store a shared key/value note visible to all agents. Optionally set an expiry via ttl_seconds; default is no expiry.
Atomic compare-and-swap on a note: only writes new_value if the current value equals expected_value (an absent/expired note counts as ""). Use for lock-free coordination on shared state.
Fire a named event, bumping its generation counter and waking all waiters. Returns the new generation.
Release a lock you hold. Prefer passing the lock_token returned by lock_resource; agent_id is accepted for compatibility. No-op if not held or not yours.
Gracefully unregister the calling agent, releasing all locks it holds.
Block until a named event's generation advances beyond last_seen_generation, up to wait_seconds (0-50, default 25). Pass back the generation you last saw to avoid missing signals.
Error handling lacks recovery guidance. No tool description explains what to do on failure (e.g., 'If lock acquisition times out, call again with wake_token'). Errors are not categorized as retryable, user-fixable, or fatal. LLMs receive no actionable next steps.
Deprecated parameter aliases (ttl_minutes) remain in schemas. While documented as deprecated, they should be removed or migrated to ttl_seconds only. Keeping both increases confusion and maintenance burden.
List tools (list_locks, list_notes, list_agents, list_tasks) lack pagination parameters (limit, offset, cursor). No indication of result limits or truncation. Large result sets could exhaust context windows.