Changelog¶
All notable changes to cabildo are documented here. Versioning is SemVer.
[Unreleased]¶
Added¶
cabildo branches(and thebranchesMCP tool): a read-only inventory of a repo's worktrees and bare branches — the zoo coding agents leave behind. Each one is placed against the trunk (ahead/behind the localorigin/<target>, whether it is already contained in the trunk), shows its dirty count (tracked changes only), and is attributed to the cabildo agent living there — a live session if one is standing in it, otherwise the spawn ledger. It runs only read-only git and never fetches, so it is instant and changes nothing.- Post-push local sync (
[push].local_sync): after a successful push the daemon can advance the pusher's localmainto the just-pushedorigin/main, so localmainno longer drifts a commit behind on every push (issue #20).ff-only(the default) fast-forwards and refuses (warns, never clobbers) on a diverged localmainor one checked out in a worktree;resetforce-syncs;offkeeps the prior behaviour. A sync refusal never fails the push. - Layered
config.toml: optional read-only TOML settings, with precedence repo-local.cabildo/config.tomlover~/.cabildo/config.tomlover built-in defaults, merged table by table. A[project.<name>]section in the global file scopes keys to one repo. cabildo only reads these files (the runtime is stdlib-only and has no TOML writer), socloud.jsonstays the writable registry, and a malformed file is skipped rather than fatal. Two keys are wired:[push].mode = "manual"opts a project out of the push guard, and[watch].topic = "project"defaultscabildo watchto the repo's own channel.[uplink],[notify],[push], and[spawn]are recognised and read generically; wiring follows. See.cabildo/config.example.toml.
Changed¶
- cabildo-push no longer spawns
cabildo pullevery second (issue #37). The doorbell now blocks in onecabildo waitper session, which exits when a message lands, after 300 s, or when its parent dies. An idle session costs one sleeping process instead of a Python start per second. Reinstall the mod (cabildo plugin) to pick it up. - Mode-aware spawn prompt: the git-flow paragraph a spawned agent receives is
now chosen from the repo's
[push].mode. Apushrepo is told topushand, crucially, thatpushmoves work toorigin/<target>and does NOT touch its local<target>(cabildo catches that up after a push) — so agents stop overloading "push" to mean "update my local main". Amanualrepo is told the opposite: cabildo does not push or push for it, so do not queue a push or push by hand automatically. Therequesttool description states the same gate. - Mode-aware SessionStart banner: the shared-repo warning injected at session
start no longer tells
manual-mode agents to "not push, queue a push" — on a manual repo that was doubly false (the guard does not deny the push and there is no push to queue). Manual projects now get an accurate note (pushing is by hand, cabildo does not push or push), while push projects keep the push routing and also learn thatpushdoes not touch localmain.
[0.1.15] - 2026-09-14¶
Added¶
file:claim enforcement for Codexapply_patchedits. The guard maps every path in the patch envelope (adds, updates, deletes, and both ends of a move) to a file lease, so a Codex edit is denied when another agent holds one of those paths and auto-claims all of them when allowed. This closes the follow-up noted incodex_hooks: Claude Code and Codex now enforce each other's file leases, not just share their visibility.
[0.1.14] - 2026-09-14¶
Added¶
- Peer-to-peer LAN sync (#7) —
cabildo lanshares a project's message bus with peers on the local network, with nothing leaving the subnet. Each peer UDP-broadcasts a beacon (255.255.255.255, so it never routes off-LAN) and serves its rows over TCP; peers discover each other and PULL, so the sync is bidirectional by both sides pulling and the serving side only ever reads. New messages are deduped byguid, stampedprovenance='lan', and — crucially — the App Engine uplink now excludesprovenance='lan', so a LAN peer's messages never silently escape to the hosted mirror. Single-machine SQLite stays the default; this is opt-in. Flags:-l/--loop,--interval,--port,--project,--secret(an optional shared secret both peers must present). Stdlib only — no new runtime dependency. This first increment covers messages for one project; presence/claim sync and encrypted/authenticated pairing are the follow-up (the RTC-over-backbone transport). LAN v1 is trusted-LAN: the secret gates the pull handshake, but there is no transport encryption. - Global message
guid(#7) —messagesgains aguidcolumn (uuid4, additive migration that backfills existing rows and adds a unique index).store.post_messagemints one per locally-born message and is now idempotent by guid (ON CONFLICT DO NOTHING); the cloud downlink and the LAN transport pass the original guid +created_atso the same message merges once across machines instead of re-inserting. This also closes a latent double-insert on a lost sync cursor. - Installer client selection + non-mutating preview (#12) —
scripts/install.pygains--client {claude,codex,both}(defaultclaude, preserving the historical behavior).--client codexdelegates tocabildo codexand never touches~/.claude/settings.json;--client bothwires both onto one bus.--printis now a genuine dry run: it installs nothing, migrates no data, registers no server, and writes no hooks — it only prints the exact commands and config it would apply (including the realcodex mcp add cabildo --env CABILDO_CLIENT=codex -- cabildo-mcpcommand and the[mcp_servers.cabildo]block / hooks JSON). Missing executables fail clearly (uv,claude), a failedclaude mcp addis fatal rather than a silent warning, and a missingcodexfalls back to printing the paste-in block. Hook command paths are shell-quoted so a venv under a directory with spaces still produces a valid command (a no-op for ordinary paths, so re-running stays idempotent). No server-side--clientflag exists — Codex identity is carried by theCABILDO_CLIENT=codexenv, not a flag. - Cross-process transport test (#12) —
tests/test_transport_subprocess.pyruns two independently identified sessions in separate OS processes against one sharedCABILDO_HOME, exercising a project-channel message with an explicit read receipt, DMs in both directions, a conflicting claim (held → denied → released → re-taken), and restart identity (oneCABILDO_SESSION_IDresolving across two launches). It is labeled a transport test, distinct from the live-client smoke test #9/#10 own.tests/test_install.pycovers client-selection dispatch, non-mutating--print, clear failures, safe path quoting, and settings idempotence.
[0.1.13] - 2026-09-13¶
Added¶
- Codex delivery + cooperative coordination (#10) —
cabildo codexnow also wires Codex lifecycle hooks (merged into~/.codex/hooks.json, non-clobbering like.mcp.json). Newcodex_hooks.pyhandlesSessionStart(register + presence banner),UserPromptSubmit(unread + machine conditions),PreToolUse(the claim guard — denies viapermissionDecision:"deny", the same client-agnosticcabildo.guardlogic Claude runs), andPostToolUse(mid-turn urgent delivery).context.pygainsresolve_codex_pid(the Codex counterpart ofresolve_claude_pid), so theSessionStarthook binds Codex's realsession_idto the owningcodexprocess and the MCP server resolves back to it — stable identity across MCP reconnect. Newcabildo-codex-hookentry point. Delivery is boundary-driven (same pull model as Claude; no idle wake). Enforcement is honest and documented in a README matrix:PreToolUsedeny is real on both clients (Bash-command guards + MCP tool calls); claim visibility is mutual (a Codex-held lease denies a Claude edit and vice versa, verified);file:-lease enforcement on Codex edits is a follow-up because Codex edits viaapply_patch, whose path shape isn't mapped yet. Codex requires trusting hooks (/hooks) before they run. Live Codex behavior (process topology, real deny) is not auto-tested — needs Codex installed.
Added¶
- Codex MCP setup (#9) —
cabildo codexwires Codex onto the same local bus as Claude Code. It delegates tocodex mcp addwhen Codex is on PATH, and otherwise prints the[mcp_servers.cabildo]block for~/.codex/config.toml(also the--printpreview); Codex owns its own config, so unrelated servers and hand edits are preserved. Newcodex_config.pybuilds the command/TOML; the Claude install flow is untouched. The server is givenCABILDO_CLIENT=codex(so a Codex session is distinct inlist_agents/whoami/the viewer) andCABILDO_HOME(so both clients share one DB). A staticCABILDO_SESSION_IDis deliberately not written — it would collapse every Codex session onto one identity; Codex sessions use the client-scoped owner-pid path from #6. README gains a Codex section with a two-client smoke-test procedure. Live Codex is not auto-tested (needs Codex installed); a mixed-client integration test drives the real MCP tools as one Claude + one Codex session on a temp DB — discovery, channel + DM both directions, read receipts. Event-driven delivery and tool guards for Codex remain #10.
Added¶
- Agent instructions (#5) — root
AGENTS.md(Overview/Build/Test/Style/Gotchas) plus a one-lineCLAUDE.md(@AGENTS.md) so Claude Code reads the same instructions;CLAUDE.local.mdgitignored for personal overrides.
Changed¶
- Release re-cut: 0.1.10 was already published to PyPI by the #6 merge, so the subsequent docs merge needed a fresh version to publish. No source changes over 0.1.10 beyond the docs above.
[0.1.10] - 2026-09-13¶
Added¶
- Client-independent session identity and lifecycle (#6) — the
sessionstable gainsclient('claude', 'codex', ...) and a genericowner_pid(the OS process whose death retires the session), via an additive migration that backfills every existing row toclient='claude',owner_pid=claude_pid.mcp_server._menow resolves identity in four tiers —CABILDO_TAG, an explicitCABILDO_SESSION_ID(+CABILDO_CLIENT), the Claude PID rendezvous, then a client-scoped provisional id — so a client that states its own session id gets an exact identity across MCP reconnect, resume, and threads sharing one host process, where host PID alone is ambiguous.reap_dead_sessionskeys onowner_pid(falling back toclaude_pid), retiring crashed sessions of any client and releasing their claims.clientis surfaced throughwhoami,list_agents, and the local viewer. Existing Claude behavior (hooks, thepid-<n>rendezvous, memory attribution) is unchanged. Codex was not live-tested here — that is #9/#10; this is the schema + resolver foundation, covered by unit tests (migration, concurrent clients, reconnect/resume, thread-sharing isolation, crash cleanup, claim release).
[0.1.9] - 2026-09-12¶
Added¶
- macOS service install via launchd (#5) —
cabildo servicegains a launchd backend behind asys.platformcheck: it emits a~/Library/LaunchAgents/plist andlaunchctl loads it, mirroring the systemd--userinstall (dry-run default, refuse-to-overwrite,status,uninstall). The systemd internals were renamed to_*_systemddispatch targets with unchanged behavior; aProgramArgumentsarray avoids shell quoting. Unit-tested against mockedlaunchctl+ filesystem; not yet run on a real Mac.
[0.1.8] - 2026-09-12¶
Added¶
- macOS memory attribution (#3) —
procs.pygains darwin backends behind asys.platformcheck:_memory_darwin(sysctl/vm_statfor RAM and swap),_processes_darwin(ps -axofor per-process RSS and ancestry), socabildo mem, per-agent RSS, and orphan detection now work on macOS. Memory-pressure (PSI) has no darwin equivalent and is deliberately gated off (_pressure_darwinreturns empty) rather than faked. Linux/procpaths are unchanged. Darwin path is unit-tested against mockedps/vm_stat/sysctloutput; not yet run on a real Mac.
[0.1.7] - 2026-09-12¶
Added¶
- macOS process identity (#2, #11) —
context.py's_ppid/_argvgain aps-backed darwin backend (ps -o ppid=,ps -ww -o command=) behind asys.platformcheck, so the MCP server can resolve its owningclaudesession on macOS, where there is no/proc. The Linux/procfast path is byte-for-byte unchanged.resolve_claude_pid/_is_claude/_exe_idoperate on the returned argv and are unchanged. Darwin path is unit-tested against mockedpsoutput; not yet run on a real Mac. (Bulk process enumeration inprocs.pystays Linux-only — see #3 for its memory backends.)
[0.1.6] - 2026-09-12¶
Added¶
- macOS desktop notifications (#4) —
desktop.pygains a darwin backend behind asys.platformcheck (the Linuxnotify-send/D-Bus path is unchanged):available()returns true whenosascriptis present, passive notifications useosascript display notification, and therequest(action="ask")button round-trip usesterminal-notifier(or anosascriptdialog fallback). Without it, urgent-to-human prompts silently no-op'd on macOS. Darwin path is unit-tested against mockedosascript/terminal-notifieroutput; not yet run on a real Mac.
[0.1.5] - 2026-09-12¶
Added¶
cabildo doctor— one command that verifies the whole install and flags what won't work on this machine. It checks the six hooks are wired in~/.claude/settings.json, that thecabildobus server is registered (user or project scope) and the per-repoissuesserver is wired where adocs/issues/exists, that the SQLite bus is reachable and in WAL, and whether a daemon is live. Crucially it also flags the platform features cabildo leans on that a given OS may lack —/procprocess identity,notify-send, andsystemd --user— so the macOS gaps surface at check time instead of as an agent that mysteriously never joins the bus. Exits non-zero only on a genuine break; platform gaps and a missing (optional) daemon are warnings.
[0.1.4] - 2026-09-12¶
Fixed¶
show_issueno longer overrunsSHOW_LIMIT. The truncation footer embeds the issue's absolute path, whose length is unbounded, so a long path pushed the output past the cap (a red suite on macOS, where pytest temp dirs live under/private/var/folders/...). The footer's rendered length, path included, is now budgeted againstSHOW_LIMIT, so body + footer stays within the cap regardless of path length. (#1)
[0.1.3] - 2026-09-12¶
Added¶
cabildo viewer— a read-only local web view of the bus (agents, locks, messages) served onhttp://127.0.0.1:8787, refreshing every 2s. Stdlibhttp.serveronly, binds to localhost, needs nothing running. It is the open counterpart of the hosted dashboard: the same glanceable view of your fleet, with no account and no server.
Changed¶
- The hosted backend split out. The App Engine bus, the browser dashboard,
and the machine runner moved to a separate private repo,
cabildo-backend. cabildo is now self-contained: the local SQLite bus, the two MCP servers, the hooks, thewatchTUI, and the newviewerall work with no network and no account.cabildo syncremains here as the optional one-way client that mirrors the local bus up to a hosted instance. - Repo aligned to the standard OSS template: proto/moon-driven GitLab CI
(one
moon run :ci,:releaseon main via PyPI OIDC Trusted Publishing),pre-commit/pre-pushgit hooks, version-bump guards (scripts/bump-version,check-version-bump,ci-version-guard), ruff + pyright config, mkdocs + Pages, anduv.lock.__version__is now mirrored frompyprojectand asserted by a test.
[0.1.2]¶
- Baseline release on PyPI: the coordination bus (direct/topic messaging,
presence, advisory locks), the
issuesMCP server, hook-level lock enforcement, the daemon (intent queue + desktop notifications), andcabildo sync.