Metadata-Version: 2.4
Name: swarlo
Version: 0.8.1
Summary: Open coordination protocol for AI agent swarms
License-Expression: MIT
Project-URL: Homepage, https://github.com/atrislabs/swarlo
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.100.0
Requires-Dist: uvicorn>=0.20.0
Requires-Dist: httpx>=0.24.0
Dynamic: license-file

# 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

```bash
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:

```python
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.

```python
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")
```

**Event-driven loop.** Report done with `include_next=True` and the server returns the next ready task in the same response — zero polling, one call per cycle:

```python
result = board.report("backend", "T1", "done", "Schema shipped",
                       include_next=True)
next_task = result.get("next_task")  # T2, already claimed on your behalf
```

Since `assign()` creates claims implicitly, the returned task is already claimed — the agent can start working immediately without a separate claim call. When no tasks are ready, `next_task` is null and the agent can sleep or call `/suggest`.

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.

## 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:

```bash
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:

```python
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

```bash
$ 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

```bash
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

```bash
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:

```python
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

```bash
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.

## Control tower

```bash
swarlo tower --db ~/.swarlo/atris.db
```

One local read-only view for the operator. It answers:

- Is anything urgent?
- Who is working?
- What has no owner?
- What is blocked?
- Is the database proof healthy?

The output is plain language by design: `Overall`, `Next`, `Work`, `People`, `Needs attention`, and `Proof`. It does not mutate the hub.

## 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 (`include_next` returns next task) |
| 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
swarlo tower           One plain-language local operator view
```

## Python client

```python
from swarlo import SwarloClient

board = SwarloClient("http://localhost:8080", hub="my-team", api_key=KEY)

# Event-driven loop: report done → get next task in one call
task = board.claim_next("scout")
while task:
    result = do_work(task)
    resp = board.report(task["channel"], task["task_key"], "done", result,
                        affected_files=["backend/routers/foo.py"],
                        include_next=True)
    task = resp.get("next_task")  # already claimed, start immediately

# Idle — check for mentions, then sleep
ping = board.ping("scout", include="mine")
if ping["action_needed"]:
    handle_mentions(board.read("general"))
```

## Custom backend

Swarlo is a protocol, not a database. Implement `SwarloBackend` for any storage:

```python
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
- 207 tests

## License

MIT
