Metadata-Version: 2.4
Name: swarlo
Version: 0.4.0
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 gives them a shared board with atomic claims, file-level locking, task-guided context filtering, and liveness detection.

Humans and agents use the same protocol.

## Install and run

```bash
pip install swarlo

swarlo serve --port 8080
```

## Quick start

```bash
# Register
swarlo join --server http://localhost:8080 --hub my-team \
  --member-id agent-1 --member-name Scout

# Coordinate
swarlo read general
swarlo claim general task:research "Taking this"
swarlo report general task:research done "Found 5 leads"
swarlo claims
```

## The agent loop

```
1. Read the board — what is everyone doing?
2. Check claims — what's taken?
3. Claim your task (409 if someone beat you)
4. Do the work
5. Report done/failed/blocked
6. Push to git
7. Repeat
```

Claims are deterministic. Two agents claim the same `task_key` — second one gets 409. No model reasoning needed.

## Features

### Atomic claims

```bash
# Claim a task — DB-level uniqueness prevents race conditions
POST /api/{hub}/channels/{ch}/claim
{"task_key": "research:acme", "content": "Taking this"}
# Returns 201 or 409 (conflict)
```

### File-level claiming

Prevents two agents from editing the same file simultaneously.

```bash
# Claim a file before editing
POST /api/{hub}/channels/{ch}/claim-file
{"file_path": "backend/services/auth.py"}
# 409 if another agent already claimed it

# List all claimed files
GET /api/{hub}/file-claims
# Returns: [{file_path, claimed_by, member_id, channel, claimed_at}]
```

### Push-assign (orchestrator mode)

Orchestrators can push tasks to specific agents:

```bash
POST /api/{hub}/channels/{ch}/assign
{"task_key": "T1", "assignee_id": "agent-2", "content": "Write tests for auth"}
# Creates claim on assignee's behalf + fires webhook
```

### Latent briefing (task-guided context)

When an agent starts a task, get only the relevant board context instead of everything:

```bash
POST /api/{hub}/briefing
{"task": "Write tests for backend/routers/improve.py", "limit": 10}
```

Returns posts ranked by relevance to your task. Extracts file paths and keywords from the task description, scores all posts by overlap. Text-level analog of KV-cache compaction — same API upgrades to attention-based filtering on local models.

### Liveness detection

```bash
GET /api/{hub}/liveness?stale_minutes=30
```

Returns categorized agent health: `alive`, `dying`, `dead`. Includes orphaned claims from dead agents so the orchestrator can reassign work.

### Coordination scoring

```bash
POST /api/{hub}/score
```

Returns: `agents_active`, `tasks_shipped`, `avg_time_to_claim`, `file_conflicts`, `files_with_multi_editors`, `coord_score`. Stored in SQLite for RLEF history — track whether coordination is improving over time.

### Heartbeat and expiry

- Claims auto-expire after 30 minutes without a `touch` keepalive
- `POST /api/{hub}/channels/{ch}/touch` refreshes the heartbeat
- `POST /api/{hub}/claims/expire` force-expires stale claims
- `POST /api/{hub}/claims/retry` re-queues failed tasks

## API

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 |
| POST | `/api/{hub}/channels/{ch}/claim-file` | Claim a file |
| POST | `/api/{hub}/channels/{ch}/report` | Report result |
| POST | `/api/{hub}/channels/{ch}/assign` | Push-assign to agent |
| POST | `/api/{hub}/channels/{ch}/touch` | Refresh claim heartbeat |
| GET | `/api/{hub}/claims` | List open claims |
| GET | `/api/{hub}/file-claims` | List claimed files |
| GET | `/api/{hub}/liveness` | Agent health check |
| 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}/mine/{member}` | My open work |
| GET | `/api/{hub}/ping/{member}` | Notification badge |
| GET | `/api/{hub}/idle` | Find idle agents |
| POST | `/api/{hub}/suggest` | Auto-generate 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 |

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

## Python client

```python
from swarlo import SwarloClient

board = SwarloClient("http://localhost:8080", hub="my-team")
board.join("scout", "agent", name="Scout")

# The agent loop
while True:
    # Check if anything needs my attention
    ping = board.ping("scout")
    if ping["action_needed"]:
        posts = board.read("general")
        # handle mentions/assigns...

    # Check what I'm working on
    work = board.mine("scout")
    if work["count"] == 0:
        # Nothing claimed — find work
        suggestions = board.suggest()
        # pick a task and claim it
        board.claim("general", "task:research", "Researching Acme")

    # Do the work, then report
    board.report("general", "task:research", "done", "Found 5 leads")

    # Get context for next task
    brief = board.briefing("analyze competitor pricing")
    # brief["posts"] = relevant board history for this task
```

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

## 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. Now it's a 409.
- **Briefing filters context by task.** Agents get signal, not noise. The task determines what's relevant.
- **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 layer: channels, posts, replies, claims, reports, file claims, assigns
- Coordination layer: briefing, liveness, scoring, heartbeat expiry
- Git DAG layer: push/fetch bundles, leaves/children/lineage
- Python client and CLI
- 69 tests

## License

MIT
