Metadata-Version: 2.4
Name: pac-collab
Version: 0.2.1
Summary: Multi-project sandbox coordinator: SQLite-backed task locks + git worktree isolation for concurrent AI agent sessions
Requires-Python: >=3.11
Requires-Dist: mcp>=1.2
Requires-Dist: platformdirs>=4.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# pac-collab

Multi-project sandbox coordinator for concurrent AI agent sessions.

Multiple independent agents can work on different tasks across **entirely
different repositories** at the same time. `pac-collab` gives each task a
lock (so two agents never grab the same task in the same project) and an
isolated **git worktree** (so their files never touch), all tracked in one
shared SQLite registry.

## How it works

- **Shared state** — a single `coordinator.db` lives in the OS user data
  directory (located via `platformdirs`), so every invocation of the package,
  from any process or repository, sees the same master registry.
  WAL journal mode + 10s busy timeout make it safe under concurrency.
- **The lock** — a partial unique index on `(project_id, task_id)
  WHERE status = 'ACTIVE'`. Acquiring inserts the ACTIVE row inside a
  `BEGIN IMMEDIATE` transaction; two concurrent acquires of the same task
  cannot both succeed. Finished rows stay as history.
- **File isolation** — every task gets its own worktree at
  `~/.pac-collab/sandboxes/{project_id}/task_{task_id}`, on its own branch
  `agent/{feature_name}-{task_id}` cut from `origin/main` (configurable via
  `base_ref` / `--base-ref` for repos whose default branch differs).

## Install

```
pip install pac-collab
```

## MCP server (primary interface for agents)

The package ships an MCP server (stdio) exposing every coordinator operation
as a tool, so agent harnesses call the coordinator natively instead of
shelling out:

```
pac-collab-mcp                     # console script
python -m pac_collab.mcp_server    # PATH-independent equivalent
```

Register it in any MCP client (`.mcp.json` / Claude Code / plugin):

```json
{
  "mcpServers": {
    "pac-collab": {
      "command": "python",
      "args": ["-m", "pac_collab.mcp_server"]
    }
  }
}
```

Tools: `acquire_sandbox(base_repo_path, project_id, task_id, feature_name,
base_ref="origin/main")`, `heartbeat(project_id, task_id)`,
`release_sandbox(project_id, task_id, outcome)`,
`list_sandboxes(project_id=None)`, `prune_stale_sandboxes(timeout_minutes=60)`
— identical semantics to the Python API below. Every tool call opens its own
SQLite connection, so concurrent sessions can each run their own server
instance against the same shared registry.

## Python API

```python
from pac_collab import SandboxCoordinator

with SandboxCoordinator() as coord:
    grant = coord.acquire_sandbox(
        base_repo_path="C:/repos/backend-api",
        project_id="backend-api",
        task_id="T-101",
        feature_name="rate-limiter",
    )
    # {"status": "ALLOCATED", "sandbox_path": "...", "branch_name": "agent/rate-limiter-T-101"}
    # or {"status": "REJECTED", "reason": "Task already active in this project"}

    coord.heartbeat("backend-api", "T-101")          # call periodically while working
    coord.release_sandbox("backend-api", "T-101", "SUCCESS")  # commit + push + destroy worktree

    coord.list_sandboxes()                # everything, all projects
    coord.list_sandboxes("backend-api")   # one project
    coord.prune_stale_sandboxes(60)       # reap ACTIVE rows with heartbeats older than 60 min
```

## CLI

Every command prints JSON; failed operations exit non-zero.

```
pac-collab acquire --base-repo-path C:/repos/backend-api --project-id backend-api --task-id T-101 --feature-name rate-limiter
pac-collab heartbeat --project-id backend-api --task-id T-101
pac-collab release  --project-id backend-api --task-id T-101 --outcome SUCCESS
pac-collab list     [--project-id backend-api]
pac-collab prune    [--timeout-minutes 60]
```

## Semantics worth knowing

- `release --outcome SUCCESS` commits (`agent: completed {task_id}`) and
  pushes **before** destroying the worktree. If the commit or push fails, the
  sandbox is kept ACTIVE and intact — no work is lost, and the error is
  returned so the agent can retry.
- `release --outcome FAILED` discards everything: worktree removed, local
  branch deleted, row marked FAILED.
- `prune` marks stale sessions `STALE_FAILED` and removes their worktrees
  even if the base repo has vanished — the lock is always released.
- Identifiers are slugified (`[^A-Za-z0-9._-]` → `-`) for branch and
  directory names; the lock itself always uses the raw ids.
- Statuses: `ACTIVE` → `SUCCESS` | `FAILED` | `STALE_FAILED`.

## Development / publishing

```
python -m build
twine upload dist/*
```

Publish with the eatopia PyPI token (in `~/.pypirc`, username `__token__`) —
same account as `pac-pincers`.
