✋ call-dibs
Call dibs on shared stuff.
A tiny lock ledger so humans and AI agents stop fighting over the same phone, GPU, or test bench.
$ dibs claim phone-a --note "regression run"
claimed phone-a as alice
$ dibs claim phone-a # …from another session
BUSY phone-a: held by alice since 2026-09-02T21:14:03Z (3m ago) — "regression run"
$ dibs release phone-a
released phone-a (was alice)
Why
You have one test phone, one GPU, one hardware bench — and several
terminals (or several coding agents) that can all reach it. dibs is the
smallest thing that makes them take turns.
- One file, no dependencies. A single Python script, stdlib only. No daemon, no server, no database.
- Cross-platform. macOS, Linux, Windows.
- Legible. The ledger is one human-readable JSON file per resource, and every "busy" says who, since when, and why.
- Agent-friendly. Stable exit codes,
--json, a blocking--wait, and arunwrapper that always releases — even when the command fails.
Install
pipx install call-dibs # or: pip install call-dibs · uv tool install call-dibs
Works the same on macOS, Linux, and Windows. Prefer no package manager? It's
one file — dibs.py — drop it on your PATH.
Set up
Tell dibs what you share. Write ~/.dibs/resources.json once, by hand:
{
"phone-a": "Android test phone on the bench",
"gpu-0": "the shared training GPU",
"printer": "label printer, room 2"
}
Only these names can be claimed, so a typo can't silently create a new lock.
dibs never edits this file.
Use
| command | what it does |
|---|---|
dibs claim <r...> [--note ...] [--wait [--timeout N]] |
claim one or more resources. Exit 2 + holder info if busy; --wait waits in line |
dibs release <r...> [--force] |
release |
dibs status [r|group] [--json] |
who has what (a group tag shows its members) |
dibs wait <r...> [--timeout N] |
block until free, without claiming |
dibs run <r> -- <cmd...> |
claim → run → always release |
dibs watch |
live terminal view |
Exit codes: 0 ok · 2 busy · 3 not yours · 4 wait timeout · 1 error.
Who's asking
Every claim records an owner, and only that owner can release it. Name
yourself once per session with DIBS_OWNER:
export DIBS_OWNER=alice # people
DIBS_OWNER=agent:task-123 dibs claim … # agents: inline, so every call agrees
If it's unset, dibs falls back to user@host:pid — fine for one-off use,
wrong for agents whose shell changes every command. --force releases
someone else's lock when they're gone.
Groups
Claim several resources in one call and they form a group. The claim is all-or-nothing — if any resource is busy, nothing is taken — and the group gets a tag so you can drop the whole set at once:
$ dibs claim phone-a gpu-0 --note "regression run"
claimed gpu-0 as alice
claimed phone-a as alice
group g-3fa2c1: gpu-0, phone-a (discard with: dibs release g-3fa2c1)
$ dibs release g-3fa2c1
Name the group yourself with --as NAME.
Waiting in line
claim --wait takes a place in line. When something frees up, the
longest waiter that can use it goes first, and a claim without --wait
can't jump ahead of it — it gets BUSY … free, but next in line is ….
A waiter still blocked on something else doesn't hold up devices it isn't
using yet, so a big multi-resource claim can still lose its turn to
smaller ones.
Your place lasts only while the wait is running: time out, Ctrl-C, or get
killed and you're out of line (a retry starts at the back). dibs status
shows the line under each resource; --json rows gain a waiting list.
For AI agents
skills/dibs/ is a drop-in skill for Claude Code (and reads fine as an
AGENTS.md snippet for anything else). It teaches the protocol: claim before
touching hardware, one call per set, wait in line instead of retrying, release
as soon as you stop driving it (even to wait on a human), never force-break
someone else's lock.
cp -r skills/dibs ~/.claude/skills/
Menu bar (macOS)
See the ledger at a glance: dibs ✋2 ⏳1 in the top bar when two things
are claimed and one claim is waiting, dibs ✓ when everything is free,
and who-has-what in the dropdown. Under SwiftBar each claimed resource
also has Force release…, behind a confirm dialog — for holders that
are gone. Install SwiftBar, then add the plugin:
open "swiftbar://addplugin?src=https://raw.githubusercontent.com/NeoMarcoPolo/call-dibs/main/contrib/dibs.5s.sh"
Start the UI when you want it with open -a SwiftBar; quit it from the
dropdown's Quit item. (SwiftBar only runs when launched — set it as a
login item if you'd rather have it always there.)
How it works
One JSON file per resource under ~/.dibs/ (override with DIBS_DIR). A
claim is an atomic O_CREAT|O_EXCL create, so racing claimers get exactly
one winner. Locks are advisory and are held until released — there is no
expiry. A waiting claim keeps a small ticket in ~/.dibs/queue/ and
refreshes it every poll; a ticket is dropped after 15 s without a refresh
(longer with a slower --poll), so a waiter that died never blocks the
line. The ledger is per-machine by default; point DIBS_DIR at a shared
directory to span machines.
Contributing
python3 -m unittest discover tests. Keep it small.
Releases: bump version in pyproject.toml and dibs.py, then push a
v* tag — CI publishes to PyPI via trusted publishing (maintainer approval
required).
License
Release files for call-dibs 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| call_dibs-0.4.0.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| call_dibs-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.6 kB
Release files / call_dibs-0.4.0.tar.gz
| Download URL | call_dibs-0.4.0.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d20219e5666c1d70869a74334219043a18d07dffdc3d46b016def71ebac70ff7
|
|
BLAKE2b-256 checksum How to use checksums |
9f45cb55021610b0877daf66dac811d94208e59e0760dd71c7deec61b90f43c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / call_dibs-0.4.0-py3-none-any.whl
| Download URL | call_dibs-0.4.0-py3-none-any.whl |
|---|---|
| Size | 13.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3a2efe2720a277d922806f7daca1c701c65260c08a7f908da4791c04463fb7a9
|
|
BLAKE2b-256 checksum How to use checksums |
40242cdb92f3e10dc22e262ebe3ad333ca64981fc1c4fe386bf81fc32027e12c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log