agentmutex

command module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 21 Imported by: 0

README

agentmutex

Semantic mutexes for AI agents. No daemon, no server: just files.

CI Go Reference

Multiple AI agents working on the same machine step on each other: two agents tag-deploy the same staging environment at the same time and one wipes out the other's build. Traditional locks don't fit — agent critical sections are LLM-loop-sized (minutes, not microseconds), and nobody wants to run a Redis or a daemon just to coordinate a couple of terminals.

agentmutex implements the agentic mutex pattern: before an agent touches a shared resource, it must acquire a lease on a semantic key — a structured name that matches the business entity, like deploy:staging or account:12345. Leases have TTLs so crashed agents can't wedge the system, and only the holder of the lease token can release or renew it.

# Agent A
agentmutex run deploy:staging -- ./deploy.sh staging

# Agent B, at the same time — blocks until A's deploy finishes, then runs
agentmutex run deploy:staging -- ./deploy.sh staging

That's the whole fix for the duelling-deploys problem.

Install

Prebuilt binary (Linux/macOS, amd64/arm64 — checksum-verified):

curl -fsSL https://raw.githubusercontent.com/DIodide/agentmutex/main/install.sh | sh

With Go:

go install github.com/DIodide/agentmutex@latest

Or grab an archive from the releases page (Windows .zip included), or build from source:

git clone https://github.com/DIodide/agentmutex
cd agentmutex && make install

Quick start

# The golden path: acquire → run → auto-renew → release, in one command.
# --ttl covers the whole build+deploy (run auto-renews at ttl/3).
agentmutex run --ttl 20m --reason "deploy v1.2.3 to staging" \
  deploy:staging -- make deploy-staging

# Manual lifecycle, when the work spans multiple commands. ALWAYS guard the
# acquire — a failed lock must abort, never deploy unlocked:
TOKEN=$(agentmutex acquire --ttl 20m --reason "shipping v1.2.3" deploy:staging) || {
  echo "someone else is deploying; aborting" >&2; exit 1; }
trap 'agentmutex release --token "$TOKEN" deploy:staging' EXIT
make build && make deploy-staging

# Who's holding what? (list shows what each environment is deploying + for how long)
agentmutex status deploy:staging
agentmutex list

# Who held it before? The lock changelog (also --json, --since 24h, --all):
agentmutex history deploy:staging

Design

Pessimistic orchestration

This is Strategy A from the pattern: when Agent A holds deploy:staging, Agent B's acquire for the same key waits — it registers in an on-disk FIFO queue and polls. When A releases (or A's lease expires because it crashed), B wakes up, reads fresh state, and proceeds. Data integrity first; concurrency only across unrelated keys.

No daemon

Every operation is an inline CLI command over on-disk state in ~/.agentmutex. There is nothing to install as a service, nothing to keep running, nothing to reach over the network:

  • Mutations are serialized by a per-key flock(2) guard held for milliseconds. The kernel releases it automatically if a process dies, so a crashed CLI can never wedge the store.
  • Lease documents are atomic: holder.json is replaced via temp-file + rename, so readers never see a torn write.
  • Waiting is polling — and that's a feature, not a compromise. Agent tasks are inference-bound: an agent thinks for seconds to minutes between actions, so a 1-second disk poll adds ~0% latency while removing every daemon, socket, and notification channel from the design. Reliability comes from having fewer moving parts.
Leases, not locks

A lock you can never reclaim is a deadlock waiting for a crash. Every acquire takes a TTL (default 15m). A holder that finishes early releases; a holder that needs longer renews; a holder that dies silently expires and the next waiter takes over. agentmutex run renews automatically at TTL/3 while your command runs, so the TTL only matters if the whole process is SIGKILLed.

Only the lease token (printed by acquire) can release or renew — an agent can never clobber a lock it doesn't hold, even by accident.

Semantic keys

Keys are structured namespaces that match the business entity, not the implementation:

deploy:staging
deploy:production
service:api:database
repo:frontend/file:auth.ts
account:12345
customer:acme:onboarding

Allowed characters: letters, digits, . _ - : / (start with a letter or digit, max 80 chars). Lock the entity you're mutating, at the finest granularity that still covers the whole mutation.

Keys are case-sensitive, but on case-insensitive filesystems (macOS and Windows defaults) keys differing only by case share one lock — the safe direction (extra serialization, never a missed one). Just pick one spelling per resource.

Command reference

Command Description
acquire <key> Acquire a lease. Waits (FIFO) by default; prints the token on stdout.
release <key> Release a lease you hold (--token or $AGENTMUTEX_TOKEN).
renew <key> Extend a lease you hold by --ttl from now.
run <key> -- <cmd…> Acquire, run a command with auto-renew, always release.
status [<key>] Show holder + queue for a key (or all keys). --json for machines.
list List all known locks.
wait <key> Block until a key is free. Observational — does not acquire.
force-release <key> Human override for wedged locks. Dry-run unless --yes.
history [<key>] The lock changelog: who acquired/released/lost each lease, newest first.
prune Remove expired leases and stale queue entries.
version Print version.

Useful flags on acquire/run: --ttl 20m, --timeout 30m (give up waiting), --no-wait (try-lock), --reason "why", --agent name, --quiet. acquire also takes --json and --token-file PATH (keep the token out of CI logs). run also takes --on-lease-loss terminate|continue (default terminate), --max-hold 25m (abort a wedged deploy holding the lock too long), and --export-token (pass the token to the child so it can renew/release — off by default, since env is readable by same-user processes). status takes --exit-code for scripting (0 held, 3 free, 4 expired, 5 corrupt/unreadable) and wait takes --json. Flags go before positional arguments. Aliases: lock/unlock/ls.

All coordinating agents must point at the same store. Coordination happens through $AGENTMUTEX_DIR (default ~/.agentmutex). If two agents use different directories — e.g. an ephemeral per-job CI dir — they do not see each other's locks and both "acquire" successfully: the mutex fails open. On a shared deploy box, set AGENTMUTEX_DIR to one durable, shared path for every agent.

Inside run, the wrapped command inherits AGENTMUTEX_LEASE_KEY and AGENTMUTEX_DIR, so a nested agentmutex acquire/run on the same key fails fast with a self-deadlock message instead of blocking forever. Add --export-token if the script should also be able to renew or release the lease itself.

Exit codes

Stable — agents branch on these:

Code Meaning
0 Success
2 Usage error
10 Lock held / queued, and --no-wait was set
11 Timed out waiting (--timeout)
12 Token does not match the current lease
13 No lease exists for the key
14 run: lease lost while the command was executing (resource may have been mutated concurrently)
1 Other errors

status --exit-code <key> instead maps state to an exit code for scripting: 0 held, 3 free, 4 expired, 5 corrupt/unreadable.

Additionally: run forwards the wrapped command's exit code once the command has started (so a child that exits 10 is indistinguishable from "lock held" by code alone — codes 10/11 from run can only occur before the command starts); 127 means the command could not be started; a wait interrupted by a signal exits 128+signum (130 SIGINT, 143 SIGTERM, 129 SIGHUP).

Environment
Variable Meaning
AGENTMUTEX_DIR State directory (default ~/.agentmutex)
AGENTMUTEX_TOKEN Default token for release / renew
AGENTMUTEX_AGENT Default agent name recorded on leases

On-disk layout

State is plain JSON you can inspect with cat — see docs/DESIGN.md for the full protocol:

~/.agentmutex/
├── history.db    # append-only audit log (SQLite): every acquire/release/expiry
└── locks/<encoded-key>/
    ├── guard         # per-key flock target (empty)
    ├── holder.json   # current lease: token, agent, reason, acquired_at, expires_at
    └── queue/        # FIFO waiters: <arrival-nanos>-<waiter-id>.json (mtime = heartbeat)

The history database is best-effort by design: locking never depends on it, so a full disk or corrupt history.db can degrade the changelog but can never block a deploy. Query it with agentmutex history rather than raw SQL (the schema may evolve), though sqlite3 ~/.agentmutex/history.db works for ad-hoc forensics.

Reading the files directly is supported (that's how you monitor). Writing or deleting them directly is not — always go through the CLI, which holds the guard.

Agent skills (Claude Code plugin)

Two skills teach your agents the locking discipline without being told twice:

  • skills/agentmutex/ — when and how to lock: key naming (lock the environment, not the tag), TTL sizing, the pessimistic discipline (acquire → re-read fresh state → act → release), exit-code handling.
  • skills/agentmutex-monitoring/ — how to watch locks: status --json, wait, reading the on-disk state, when force-release is (and isn't) appropriate.

This repo is its own plugin marketplace, so in Claude Code:

claude plugin marketplace add DIodide/agentmutex
claude plugin install agentmutex@agentmutex

(or /plugin interactively). The plugin ships only the skills — install the agentmutex binary separately (above) so it's on PATH. Prefer not to use plugins? Copy the skills directly:

cp -r skills/agentmutex skills/agentmutex-monitoring ~/.claude/skills/

They work as-is for any agent that can read markdown and run shell commands.

Scope and non-goals

  • One machine. Coordination happens through the local filesystem (flock, mtimes, one clock). Agents on different hosts need a networked lock service instead.
  • Advisory, cooperative locking. Like flock, nothing stops a process that simply doesn't call agentmutex. It is a coordination protocol for cooperating agents, not a security boundary.
  • Pessimistic only (v0). Optimistic version-hash verification and sandbox-merge strategies from the pattern are out of scope for now.

Contributing

See CONTRIBUTING.md. The test suite includes a real multi-process mutual-exclusion stress test (go test ./...).

License

MIT © 2026 Ibraheem Amin

Documentation

Overview

agentmutex — semantic mutexes for AI agents. No daemon, no server: just files.

Directories

Path Synopsis
internal
history
Package history is the append-only audit log for agentmutex: every lease state change (acquired, renewed, released, force-released, expired, reclaimed) is recorded in a SQLite database at <root>/history.db.
Package history is the append-only audit log for agentmutex: every lease state change (acquired, renewed, released, force-released, expired, reclaimed) is recorded in a SQLite database at <root>/history.db.
mutex
Package mutex implements the on-disk lease store for agentmutex.
Package mutex implements the on-disk lease store for agentmutex.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL