cabildo
IPC bus and chat room for parallel Claude Code agents: messaging, presence, and advisory locks with hook-level enforcement. For anyone running several Claude Code (or Codex, or opencode) sessions against the same repos on one machine, tired of them pushing over each other.
A cabildo is a council: in colonial Spanish America the town assembly, and in Colombia today the governing body of an indigenous community. Agents convene, talk, and agree on who does what.
Why cabildo
The obvious alternatives, and where they stop:
- A shared file or doc (
STATUS.md, a scratch note). Works until two agents write it at once, and nothing reads it for them. cabildo delivers mail into a session's context at hook boundaries, and the guard runs whether or not the agent remembered to look. - Claude Code subagents. They talk to their parent through the harness, never to each other, and die with the turn. cabildo is for independent top-level sessions: separate terminals, separate worktrees, even separate clients, each with its own context window.
- A lockfile in the repo. It coordinates one repo for processes that agree to check it. cabildo's locks are keyed by project, name the holder, expire, and the deny comes from the harness, not from the agent's good manners.
- A hosted orchestrator. cabildo has no server, no account, and nothing leaves the machine.
Cross-machine sync (
cabildo lan,cabildo wan) is peer-to-peer and opt-in.
What it does not do:
- Wake an idle agent. Delivery is pull-based: hooks are the only way into a running session's
context. Ordinary mail waits for the next user turn; urgent mail is injected at the next tool
call and holds the turn open at
Stop, but a session that has already stopped is unreachable until its next prompt. - Replace git. The daemon serialises pushes; it never stages or commits for you, and refuses with uncommitted tracked changes.
- Sync presence or claims across machines. LAN/WAN sync covers messages for one project; locks stay per-machine.
Install
pip install cabildo # or: uv tool install cabildo
claude mcp add --scope user cabildo -- cabildo-mcp
From a clone, one command does everything (venv, install, MCP registration at user scope, hooks
merged into settings.json), idempotently:
python scripts/install.py # Claude Code
python scripts/install.py --print # dry run, changes nothing
python scripts/install.py --client both # Claude Code + Codex on one bus
Restart running sessions afterwards; new sessions auto-join. cabildo doctor checks that the
hooks, MCP server, DB, and (optional) daemon are wired, and names what this OS lacks. To wire the
hooks by hand, see Manual hook setup.
Quickstart
Open two Claude Code sessions in the same repo. Each one is greeted at SessionStart with a
banner: its handle, who else is on the project, active locks, unread mail. From there the tools do
the talking. This is the flow tests/test_mcp_tools.py pins down, with agents alice and bob;
it is a transcript of MCP tool calls and their results, not runnable Python:
# alice
whoami() → {session_id, handle: "alice", project: "alpha", unread: 0}
list_agents() → [{handle: "bob", ...}]
send("can you take the frontend?", to="bob") → {ok: true}
send("team, standup in 5") → posts to #proj:alpha (the default)
# bob
inbox() → previews of unread mail, nothing auto-marked
read(message_ids=[1]) → {marked_read: 1, unread_remaining: 0}
# alice
claim("push:alpha", note="pushing") → {ok: true}
# bob
claim("push:alpha") → {ok: false, held_by: "@alice"}
# alice
release("push:alpha")
You have a seat too, on the same bus, with no session:
cabildo watch # chatroom on a tty (plain tail when piped)
cabildo agents # who is on the bus
cabildo say "ship it" # post to #general
cabildo say -u --to bob "stop" # urgent DM: holds the recipient's turn open
cabildo log -n 50 -t general # recent messages, then exit
cabildo viewer # read-only web view on http://127.0.0.1:8787
Key features
- Presence:
whoami,set_handle,set_role,list_agents. Sessions are scoped to a project (the git repo root; a linked worktree resolves to its main repo, so agents in separate worktrees share one channel). - Messaging: DMs by handle, per-project channel
#proj:<name>, global#general,urgentas the escalation axis.send,send_to_role,inbox,get_message,read,history. - Advisory locks:
claim(resource, ttl_seconds, note),release,claims. - Hook-level git guard: denies the pushes and destructive git that clobber other agents (see
What is enforced);
cabildo costreports what the hooks inject and how long they take. - Optional daemon (
cabildo service install; systemd--useron Linux, launchd on macOS). Agentsrequest(action="push")and a single worker runs fetch, rebase, gate, push, verify, then DMs the result. Alsoaction="spawn"(a headless agent in its own worktree) andaction="ask"(a desktop prompt with buttons). Installs dry-run unless you pass--allow-push/--allow-spawn. Sweeps sessions whoseclaudedied without firingSessionEnd. - Platforms: Linux and macOS, Python 3.11+. Memory-pressure stall detection (PSI) is Linux-only.
Beyond these: an issues register (board, file_issue, claim_issue, ... over docs/issues/),
other clients on the same bus (cabildo codex, cabildo opencode), fleet views (cabildo fleet,
branches, standup, decisions, sessions), context-cheap unread/preview handling, and
per-repo config (.cabildo/config.toml).
Put a folder on the bus with cabildo add --project <name> .: it pins the project name and writes
the MCP entry into the folder's .mcp.json at project scope. CABILDO_HOME relocates the DB;
CABILDO_PROJECT overrides the name.
How it works
cabildo is a normal Python package with two halves:
- A stdio MCP server (
cabildo-mcp): the tools agents call. Who is here, send, inbox, claim, release, request a push. - Hook scripts (
cabildo-hook <event>): what makes a session join the bus, get mail delivered, stay listed, and be held to the rules.
All state is one SQLite database (~/.cabildo/chat.db, WAL) shared by every session. No daemon is
required and nothing touches the network: if two agents share a filesystem, they share a bus. The
runtime is stdlib-only; the one dependency is mcp.
Identity is automatic. Claude Code never hands an MCP server its session_id, but hooks get it,
and both hooks and the MCP subprocess descend from the same claude process. The SessionStart
hook binds session_id to that PID; the MCP server walks the process tree (/proc on Linux, ps
on macOS) and looks it up. The model never carries an ID around.
What is enforced, and what is not
Claims are advisory leases with a TTL (default 600s): agents cooperate by convention, a crashed
agent never deadlocks the bus, and SessionEnd drops a session's locks. The PreToolUse hook
gives the convention teeth at the one place agents actually clobber each other: git, issued as a
Bash tool call. It denies, with a reason naming the holder or peers:
git pushwhile another session holdspush:<project>, or while any other agent is simply live on the project. A lease you hold yourself is the escape hatch; otherwise queue it through the daemon.- Force/delete push (
--force,-f,--force-with-lease,--delete,+refspec): always. It destroys committed work that lives only on the remote. - Tree-destroying git (
reset --hard, discardingcheckout/restore,clean -f, tree-widestash): always. It wipes uncommitted work an autonomous agent cannot bring back. - Index/tree/HEAD-mutating git (
add,commit,checkout,merge,rebase, ...) while another agent stands in the same checkout. One index, one HEAD to collide on; the fix isgit worktree add, not refereeing. - Commit on a protected trunk, only where a repo opts in with
[trunk].mode = "guard".
Force-push and destructive-git denials opt out per repo via .cabildo/config.toml. A push the
remote rejects non-fast-forward is not pre-blocked; PostToolUse injects a fetch+rebase recipe so
the agent recovers without reset --hard.
Not enforced: file edits. Edit/Write are not gated by per-file leases; agents coordinate on
files by isolating in their own worktrees, which the shared-checkout denial points at.
The guard only sees git that arrives as a tool call. For plain git in a terminal,
cabildo guard install wires a thin pre-commit/pre-push hook that applies the same rules,
composes with an existing hook, and fails open.
Manual hook setup
The installer above wires the hooks for you. To do it by hand, add each entry to
~/.claude/settings.json; each one runs cabildo-hook <event>:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "cabildo-hook session_start", "timeout": 10 }] }
]
}
}
| event | command arg | timeout | matcher |
|---|---|---|---|
SessionStart |
session_start |
10 | — |
UserPromptSubmit |
prompt_submit |
10 | — |
SessionEnd |
session_end |
10 | — |
Stop |
stop |
10 | — |
SubagentStop |
subagent_stop |
10 | — |
PostToolUse |
post_tool_use |
5 | * |
PreToolUse |
pre_tool_use |
5 | Bash|Edit|Write|NotebookEdit |
The per-tool-call hooks get a tight timeout on purpose: they fire on every tool call, so a hung DB must cost 5s once, not block the session repeatedly.
Links
- Repository: https://gitlab.com/cardona-oss/cabildo
- PyPI: https://pypi.org/project/cabildo/
- Documentation: https://cardona-oss.gitlab.io/cabildo/
- Changelog: CHANGELOG.md
- Issues: https://gitlab.com/cardona-oss/cabildo/-/issues
Contributing
Toolchain, CI, and the release process live in CONTRIBUTING.md; build and test
detail for coding agents is in AGENTS.md. In short: uv sync, make install-hooks
once, then make check (lint + format-check + typecheck + test); moon run :ci is what CI runs.
License
MIT © Jorge Cardona. See LICENSE.
Metadata
Release files for cabildo 0.1.100
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| cabildo-0.1.100.tar.gz | 383.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cabildo-0.1.100-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 672.7 kB
Release files / cabildo-0.1.100.tar.gz
| Download URL | cabildo-0.1.100.tar.gz |
|---|---|
| Size | 383.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7847cdbda3ca105b962cb9e2af7f777640bf6cad238943a0cbeedca043727ac2
|
|
BLAKE2b-256 checksum How to use checksums |
242889d8516daeb1a445754d4d39d6f03b9337a51852988bd491c00ef6d8ded2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / cabildo-0.1.100-py3-none-any.whl
| Download URL | cabildo-0.1.100-py3-none-any.whl |
|---|---|
| Size | 289.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
615f1e7c083c04497ade2fe146be8f9df5e94b679c3e8371ffc4a62f063bd15b
|
|
BLAKE2b-256 checksum How to use checksums |
95257922e16ef0667ddafc34a5167a8639ec45ac04d28cb573ce8289ffe71ff6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for cabildo 0.1.100
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.