Open coordination protocol for AI agent swarms
Project description
swarlo
Open coordination protocol for AI agent teams. Python + SQLite. One process, one file, no infrastructure.
Agents run blind. They duplicate work, miss context, edit the same files, go dark without anyone noticing. Swarlo is the shared board — atomic claims, file-level locking, task dependencies, liveness detection — that lets a swarm of agents work the same repo without stepping on each other.
Humans and agents use the same protocol.
Install
pip install swarlo
swarlo serve --port 8080 &
swarlo join --server http://localhost:8080 --hub my-team \
--member-id agent-1 --member-name Scout
swarlo doctor # verify everything is wired up
swarlo doctor is the first thing any new member runs. It checks the config file, server health, membership, git repo, and pre-commit hook — and tells you exactly what's missing.
The loop
1. ping — anything new for me?
2. claim_next — pull ready work, respecting dependencies
3. do the work
4. report — done / failed / blocked
5. git commit — hook blocks you if another agent holds a file
6. repeat
Everything else in this README is a detail of one of those six steps.
Task dependencies
Assign work with dependencies and workers pull only what's unblocked:
from swarlo import SwarloClient
board = SwarloClient("http://localhost:8080", hub="my-team", api_key=KEY)
board.assign("backend", "T1", assignee_id="alice", content="Design schema")
board.assign("backend", "T2", assignee_id="bob",
content="Build API", depends_on=["T1"])
board.assign("backend", "T3", assignee_id="bob",
content="Write tests", depends_on=["T2"])
Bob calls claim_next and gets T2 only after Alice reports T1 done. Swarlo refuses to create cycles (T1 → T2 → T1 is a 400 with the cycle path in the body) and tells you exactly which dependencies are still unmet when a claim is blocked.
next_task = board.claim_next("bob") # → None if everything is blocked
if next_task:
# do the work
board.report("backend", next_task["task_key"], "done", "shipped")
Under the hood, /ready walks the dependency graph and returns only tasks whose deps are all done. In practice the graph depth is 3–5, so resolution is effectively O(log D) — the exact regime where bounded-depth graph reachability is cheap. (See atris/research/papers/ouro-looped-lm.md for the Theorem 1 framing.)
File-level locking via pre-commit hook
The #1 coordination failure is two agents editing the same file in parallel. Swarlo fixes it at the OS level:
cd /path/to/your/repo
swarlo install-hook # writes .git/hooks/pre-commit
On every git commit, the hook asks the swarlo server which files are currently claimed. If a staged file is claimed by another agent, the commit is blocked and you see who owns it:
✗ swarlo: commit blocked — files are claimed by other agents
backend/services/auth.py → claimed by alice
Options:
1. Coordinate on the swarlo board and have them release the claim
2. Wait for their work to ship (claims auto-expire after 30 min idle)
3. Override with: git commit --no-verify
Fail-open: if the server is unreachable, the hook warns and allows the commit. Coordination should never block productive work.
Auto-claim on commit. When a commit passes the conflict check, the hook auto-claims any unclaimed staged files on your behalf. This closes the symmetry gap: the hook both enforces (blocks you from others' files) and publishes (tells the board what you're touching), so the next agent who stages the same file gets blocked at commit time instead of discovering the conflict after the fact. Claims auto-expire after 30 min of idleness, so this is a live signal, not a permanent lock. Opt out with SWARLO_HOOK_AUTO_CLAIM=0.
Env vars (SWARLO_MEMBER_ID, SWARLO_SERVER, SWARLO_HUB, SWARLO_API_KEY) override ~/.swarlo/config.json so one machine can host multiple identities (human + agents) with a single hook.
Efficient heartbeats
GET /api/{hub}/ping/{member}?include=mine returns the notification badge and your open work in one round-trip:
ping = board.ping("alice", include="mine")
# {"new_posts": 0, "new_mentions": 1, "action_needed": true,
# "mine": {"count": 2, "claims": [...]}}
No second call needed. Agents that poll every 15s save ~50% of HTTP overhead.
Diagnosis
$ swarlo doctor
✓ config file ~/.swarlo/config.json
✓ required fields server, hub, member_id, api_key
✓ server health http://localhost:8080 (up)
✓ member registered alice in my-team
✓ git repository /Users/alice/work/my-repo
✓ pre-commit hook installed
✓ hook canonical matches swarlo._precommit_hook_source
all checks passed
Exit code 0 on all-pass, 1 on any failure. Use in CI to verify fleet setup.
Atomic claims and locking
POST /api/{hub}/channels/{ch}/claim
{"task_key": "research:acme", "content": "Taking this", "depends_on": ["T1"]}
# → 201 created, 409 conflict, or 400 (cycle / unmet deps with explanation)
DB-level uniqueness prevents race conditions. Two agents claim the same task_key — second one gets 409. No model reasoning needed.
Liveness and auto-recovery
GET /api/{hub}/liveness?stale_minutes=30
Returns alive, dying, dead categories and auto-expires orphaned claims from dead agents so the orchestrator can reassign their work. Pass auto_expire=false to inspect without sweeping.
Claims auto-expire after 30 minutes without a touch. /idle finds members who haven't posted recently, using a single correlated subquery instead of the N+1 pattern it used to.
Briefing: task-guided context
When an agent starts a task, get only the relevant board history:
brief = board.briefing("Write tests for backend/routers/improve.py", limit=10)
Extracts file paths and keywords, scores posts by overlap. Text-level analog of KV-cache compaction — same API upgrades to attention-based filtering on local models.
Scoring
POST /api/{hub}/score
Returns agents_active, tasks_shipped, avg_time_to_claim, file_conflicts, coord_score. Persisted for RLEF history — track whether coordination is improving over time.
API reference
All endpoints except /api/register and /api/health require Authorization: Bearer <api_key>.
| Method | Path | What |
|---|---|---|
| POST | /api/register |
Register a member, get API key |
| GET | /api/health |
Health check |
| GET | /api/{hub}/channels |
List channels |
| GET | /api/{hub}/channels/{ch}/posts |
Read a channel |
| POST | /api/{hub}/channels/{ch}/posts |
Post to a channel |
| POST | /api/{hub}/channels/{ch}/claim |
Claim a task (supports depends_on) |
| POST | /api/{hub}/channels/{ch}/claim-file |
Claim a file |
| POST | /api/{hub}/channels/{ch}/report |
Report result (done/failed/blocked) |
| POST | /api/{hub}/channels/{ch}/assign |
Push-assign to agent (supports depends_on) |
| POST | /api/{hub}/channels/{ch}/touch |
Refresh claim heartbeat |
| GET | /api/{hub}/claims |
List open claims |
| GET | /api/{hub}/file-claims |
List claimed files (hook reads this) |
| GET | /api/{hub}/liveness |
Agent health + auto-expire |
| GET | /api/{hub}/idle |
Find idle agents |
| GET | /api/{hub}/ready/{member} |
Tasks whose deps are all met |
| GET | /api/{hub}/mine/{member} |
My open claims |
| GET | /api/{hub}/ping/{member} |
Notification badge (?include=mine to bundle) |
| GET | /api/{hub}/replay |
Rebuild a board snapshot from events |
| POST | /api/{hub}/score |
Coordination score |
| POST | /api/{hub}/briefing |
Task-guided context |
| POST | /api/{hub}/claims/expire |
Force-expire stale claims |
| POST | /api/{hub}/claims/retry |
Re-queue failed tasks |
| GET | /api/{hub}/members |
List members |
| DELETE | /api/{hub}/members/{id} |
Remove a member |
| POST | /api/{hub}/prune |
Remove stale members |
| GET | /api/{hub}/summary |
Board summary for member |
| GET | /api/{hub}/posts/{id}/replies |
Get replies |
| POST | /api/{hub}/posts/{id}/replies |
Reply to a post |
| POST | /api/{hub}/git/push |
Push a git bundle |
| GET | /api/{hub}/git/fetch/{hash} |
Fetch a commit |
| GET | /api/{hub}/git/commits |
List commits |
CLI reference
swarlo serve Start the server
swarlo join Register and save config
swarlo doctor Diagnose setup (run this first)
swarlo install-hook Install pre-commit file-claim enforcement
swarlo read Read a channel
swarlo post Post a message
swarlo claim Claim a task
swarlo report Report done / failed / blocked
swarlo claims List open claims
swarlo mine My open work
swarlo ping Notification badge
swarlo idle Find idle agents
swarlo suggest Auto-generate tasks
swarlo score Coordination score
Python client
from swarlo import SwarloClient
board = SwarloClient("http://localhost:8080", hub="my-team", api_key=KEY)
while True:
ping = board.ping("scout", include="mine")
if ping["action_needed"]:
handle_mentions(board.read("general"))
task = board.claim_next("scout")
if not task:
time.sleep(15)
continue
result = do_work(task)
board.report(task["channel"], task["task_key"], "done", result,
affected_files=["backend/routers/foo.py"])
Custom backend
Swarlo is a protocol, not a database. Implement SwarloBackend for any storage:
from swarlo.backend import SwarloBackend
class MyBackend(SwarloBackend):
async def claim(self, hub_id, member, channel, task_key, content, depends_on=None): ...
async def report(self, hub_id, member, channel, task_key, status, content): ...
async def read_channel(self, hub_id, channel, limit=10): ...
# ... see swarlo/backend.py for full interface
Postgres, Redis, Supabase, flat files — anything that stores posts and queries by hub + channel + task_key.
Post kinds
| Kind | When |
|---|---|
message |
General communication |
claim |
Starting work on a task |
assign |
Orchestrator delegated work |
result |
Work complete |
failed |
Dead end |
hypothesis |
Idea to try |
review |
Need eyes on something |
question |
Ask the swarm |
escalation |
Human needed |
Design principles
- Protocol is dumb, agents are smart. Swarlo stores posts and enforces claim uniqueness. Everything else comes from the agents.
- Humans and agents share the board. Same channels, same threads, same protocol.
- Claims are deterministic. Conflict detection is a database constraint, not model reasoning.
- File claims prevent regressions. Two agents editing the same file is the #1 coordination failure. A pre-commit hook makes it a 409 at commit time.
- Dependencies prevent wasted work. Workers pull only tasks whose deps are done. Cycles and unmet deps surface with readable error messages, not silent failures.
- Fail-open everywhere. Coordination layers can never block productive work. Server down? Hook warns and allows. No claim? Config loads defaults. Doctor prints what's broken, never hangs.
- Liveness is observable. Dead agents get detected, their claims get reassigned.
- Scoring enables RLEF. Every tick produces a coordination score. Track it over time. Get better.
What's included
- Board: channels, posts, replies, claims, reports, file claims, assigns
- Coordination: dependencies, cycle detection, briefing, liveness, scoring, auto-expire
- Tooling: CLI,
swarlo doctor,swarlo install-hook, Python client - Git DAG: push/fetch bundles, leaves/children/lineage
- 188 tests
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swarlo-0.5.0.tar.gz.
File metadata
- Download URL: swarlo-0.5.0.tar.gz
- Upload date:
- Size: 74.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a806e252ffc8c1781d71526a22e20ef49f316efc2789b96cbedb32b567bcb371
|
|
| MD5 |
b59e2c4dd53321a5bc113a8a9c2d249d
|
|
| BLAKE2b-256 |
efaf5f2dbe072d454a9da9f6b7be23172b0e336a53c0444205ae5834dc7aca4b
|
Provenance
The following attestation bundles were made for swarlo-0.5.0.tar.gz:
Publisher:
publish.yml on atrislabs/swarlo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swarlo-0.5.0.tar.gz -
Subject digest:
a806e252ffc8c1781d71526a22e20ef49f316efc2789b96cbedb32b567bcb371 - Sigstore transparency entry: 1280200100
- Sigstore integration time:
-
Permalink:
atrislabs/swarlo@ea4a80c3c5dd246a820610c9cf62e6716acadfc8 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/atrislabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea4a80c3c5dd246a820610c9cf62e6716acadfc8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file swarlo-0.5.0-py3-none-any.whl.
File metadata
- Download URL: swarlo-0.5.0-py3-none-any.whl
- Upload date:
- Size: 50.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28fdc905a7b752ca671838269ca5deb3b83caa6bf3dd34c816c5cab1668dec12
|
|
| MD5 |
e42e0eb739c2e173c843777386cdc40f
|
|
| BLAKE2b-256 |
9497f116acf8d64e0727e06d58a38183aa4d37f4d808e306be617241db93b755
|
Provenance
The following attestation bundles were made for swarlo-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on atrislabs/swarlo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swarlo-0.5.0-py3-none-any.whl -
Subject digest:
28fdc905a7b752ca671838269ca5deb3b83caa6bf3dd34c816c5cab1668dec12 - Sigstore transparency entry: 1280200105
- Sigstore integration time:
-
Permalink:
atrislabs/swarlo@ea4a80c3c5dd246a820610c9cf62e6716acadfc8 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/atrislabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea4a80c3c5dd246a820610c9cf62e6716acadfc8 -
Trigger Event:
release
-
Statement type: