# PRD: Loom — Multi-Agent Project Orchestration System
**Version:** 1.1  
**Status:** Architecture Final — Ready for Phase 1 Implementation  
**Author:** Jason + Claude  
**Last Updated:** 2026-02-18

---

## 1. Overview

### 1.1 Vision

Loom is a production-grade project orchestration system for large, parallel software
projects executed by multiple AI agents. It weaves together parallel threads of work
into a single coherent outcome. 



Loom provides:
- A **persistent, dependency-aware task graph** backed by PostgreSQL (durable) and
  Redis (live cache + event bus)
- A **reliable inter-agent message bus** using Redis pub/sub, streams, and queues
- A **decomposition layer** that turns high-level goals into parallelizable work graphs
- An **MCP server** exposing all of the above as Claude Code-native tools
- A **skill library** of reusable prompt templates for common agent tasks

Activatable in any project directory with a single command. Runs locally via Docker
Compose or on GCP (Cloud SQL + Memorystore + Cloud Run) with only env var changes.
Installed globally via `pipx install loom-agents`.

### 1.2 The Problem

Large AI-executed projects fail because of five consistent failure modes:

1. **Context window limits** — agents lose state mid-task on long-running work
2. **No shared memory** — agents duplicate work or produce conflicting outputs
3. **No coordination protocol** — agents don't know what's done, in-flight, or blocked
4. **No reliable feedback loop** — orchestrators miss failures and completions
5. **Poor decomposition** — breaking a high-level goal into correct, parallelizable
   units is itself a hard problem that requires structured tooling

Loom solves all five.

### 1.3 Design Philosophy

- **PostgreSQL as the source of truth.** Every task write goes to Postgres first.
  Redis is a cache and message bus, never the authoritative store. On restart,
  Redis is rebuilt from Postgres.
- **Redis for speed and coordination.** Live task state, atomic claiming, pub/sub
  events, message queues, and the ready queue all live in Redis during operation.
- **Three services, one compose file.** Postgres + Redis + Loom MCP server.
  `loom up` starts all three. The same Docker image runs locally and on GCP.
- **Python all the way down.** No binary dependencies. PEP8, type hints, reusable
  modules, thin wrappers at every integration boundary.
- **Agents talk to Loom, not to each other.** All coordination goes through the
  Loom MCP server. Agents are interchangeable and stateless.
- **pipx distribution.** `pipx install loom-agents` => `loom init` => `loom up`.
  Globally accessible, isolated, no venv management.

---

## 2. Architecture Decisions (Final)

| Decision | Choice | Rationale |
|---|---|---|
| Durable storage | **PostgreSQL** | Production-standard, Cloud SQL on GCP, ACID guarantees |
| Live cache + messaging | **Redis** | Fast reads, atomic ops, pub/sub, streams, queues |
| Agent interface | **MCP server (FastMCP)** | Native Claude Code integration |
| Primary environments | **Local Docker, GCP Cloud Run** | Compose mirrors Cloud Run topology exactly |
| Implementation language | **Python (PEP8, type hints)** | Jason's primary language; clean reusable modules |
| Distribution | **pipx + PyPI (loom-agents)** | Isolated, globally accessible, no venv friction |

---

## 3. System Architecture

### 3.1 Services Overview

```
+--------------------------------------------------------------------+
|                           Loom System                              |
|                                                                    |
|  Loom MCP Server     Orchestrator Agent(s)    Worker Agents (N)   |
|  (FastMCP)      <--- (goal-driven)            (task-driven)       |
|  stateless,                                                        |
|  horizontally                                                      |
|  scalable                                                          |
|        |                   |                        |              |
|        +-------------------+------------------------+              |
|                            |                                       |
|              +-------------+-------------+                         |
|              |                           |                         |
|              v                           v                         |
|        Redis                       PostgreSQL                      |
|        Live task cache             Authoritative task store        |
|        Ready priority queue        Full event history              |
|        Pub/sub events              Project registry                |
|        Message queues              Skill execution logs            |
|        Escalation queue            Escalation records              |
+--------------------------------------------------------------------+
```

### 3.2 Write Path (Every Task Mutation)

```
Agent calls loom_done(task_id, output)
  |
  v
MCP tool handler validates input
  |
  v
graph/store.py writes to PostgreSQL (ACID commit)
  |
  v
graph/cache.py syncs updated task to Redis hash
  |
  v
bus/events.py appends to Redis Stream + publishes to pub/sub channel
  |
  v
graph/deps.py checks if any blocked tasks are now unblocked
  -> adds newly unblocked tasks to Redis ready queue
  |
  v
Returns updated Task to agent
```

Postgres is always written first. If the Postgres write fails, nothing else happens.
If Redis sync fails after a successful Postgres write, the cache is stale but not
lost — it will be corrected on next read or on MCP server restart.

### 3.3 Read Path

```
Agent calls loom_ready()
  |
  v
graph/cache.py reads from Redis ready queue (sorted set, fast)
  -> on cache miss or cold start: reads from Postgres, rebuilds cache
  |
  v
Returns list[Task] to agent
```

### 3.4 Conflict-Free Task Claiming

Claiming uses PostgreSQL SELECT FOR UPDATE SKIP LOCKED — purpose-built for
concurrent job queues. If two workers try to claim the same task simultaneously,
one gets the lock and the other skips to the next available task. No deadlocks,
no retries needed at the application level.

```python
async def claim_task(task_id: str, agent_id: str) -> Task | None:
    """
    Atomically claim a task. Returns Task if successful, None if already claimed.
    Uses Postgres SELECT FOR UPDATE SKIP LOCKED.
    Syncs to Redis and publishes event after successful claim.
    """
    async with db.transaction():
        task = await db.fetchrow(
            """
            SELECT * FROM tasks
            WHERE id = $1 AND status = 'pending'
            FOR UPDATE SKIP LOCKED
            """,
            task_id,
        )
        if task is None:
            return None

        updated = await db.fetchrow(
            """
            UPDATE tasks
            SET status = 'claimed', assignee = $1,
                claimed_at = NOW(), updated_at = NOW()
            WHERE id = $2
            RETURNING *
            """,
            agent_id, task_id,
        )

    await cache.sync_task(updated)
    await events.publish(EventType.TASK_CLAIMED, updated)
    return Task.from_record(updated)
```

### 3.5 Redis Rebuild on Startup

When the MCP server starts, it rebuilds Redis state from Postgres to ensure
consistency. Safe to run while other instances are running (idempotent).

```python
async def rebuild_redis_cache():
    tasks = await db.fetch(
        "SELECT * FROM tasks WHERE status NOT IN ('done', 'failed')"
    )
    for task in tasks:
        await cache.sync_task(Task.from_record(task))

    await deps.rebuild_ready_queue()
    await queue.restore_escalations()
```

### 3.6 Deployment Topology

**Local (Docker Compose)**
```bash
pipx install loom-agents
loom init           # creates .loom/, generates docker-compose.loom.yml
loom up             # starts Postgres + Redis + MCP server
loom decompose "Build CMS"
```

**Docker Compose (generated by loom init)**
```yaml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: loom
      POSTGRES_USER: loom
      POSTGRES_PASSWORD: loom_local
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes

  loom-mcp:
    image: ghcr.io/your-org/loom-agents:latest
    environment:
      LOOM_DATABASE_URL: postgresql://loom:loom_local@postgres:5432/loom
      LOOM_REDIS_URL: redis://redis:6379
      LOOM_PROJECT_DIR: /project
      LOOM_MCP_PORT: 8765
    ports:
      - "8765:8765"
    volumes:
      - .:/project
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

volumes:
  postgres-data:
  redis-data:
```

**GCP Cloud Run**
```
Cloud Run Service:    loom-mcp (stateless, min-instances=1, same Docker image)
Cloud SQL:            PostgreSQL 16 (private IP, same VPC)
Cloud Memorystore:    Redis 7 (private IP, same VPC)
Secrets Manager:      LOOM_DATABASE_URL, LOOM_REDIS_URL, LOOM_SKILLS_API_KEY
Cloud Build:          CI/CD pipeline -> Artifact Registry -> deploy
```

Only env vars differ between local and GCP. No code changes required.

---

## 4. PostgreSQL Schema

### 4.1 Tables

```sql
-- Projects: one per loom init
CREATE TABLE projects (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name        TEXT NOT NULL,
    description TEXT,
    status      TEXT NOT NULL DEFAULT 'active',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Tasks: core entity
CREATE TABLE tasks (
    id          TEXT PRIMARY KEY,                -- 'loom-a1b2c3d4'
    project_id  UUID NOT NULL REFERENCES projects(id),
    title       TEXT NOT NULL,
    status      TEXT NOT NULL DEFAULT 'pending', -- pending|claimed|done|failed|blocked|epic
    priority    TEXT NOT NULL DEFAULT 'p1',      -- p0|p1|p2
    assignee    TEXT,
    parent_id   TEXT REFERENCES tasks(id),
    context     JSONB NOT NULL DEFAULT '{}',
    output      JSONB NOT NULL DEFAULT '{}',
    done_when   TEXT,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    claimed_at  TIMESTAMPTZ,
    done_at     TIMESTAMPTZ
);

-- Task dependencies: edges in the DAG
CREATE TABLE task_deps (
    task_id     TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    depends_on  TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
    PRIMARY KEY (task_id, depends_on)
);

-- Events: immutable audit log of every state change
CREATE TABLE events (
    id          BIGSERIAL PRIMARY KEY,
    project_id  UUID NOT NULL REFERENCES projects(id),
    event_type  TEXT NOT NULL,
    task_id     TEXT REFERENCES tasks(id),
    agent_id    TEXT,
    payload     JSONB NOT NULL DEFAULT '{}',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Escalations: queue of blocked/failed tasks needing orchestrator attention
CREATE TABLE escalations (
    id          BIGSERIAL PRIMARY KEY,
    project_id  UUID NOT NULL REFERENCES projects(id),
    task_id     TEXT NOT NULL REFERENCES tasks(id),
    message     TEXT NOT NULL,
    resolved    BOOLEAN NOT NULL DEFAULT FALSE,
    resolved_at TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Skill execution log
CREATE TABLE skill_runs (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id  UUID NOT NULL REFERENCES projects(id),
    skill_name  TEXT NOT NULL,
    inputs      JSONB NOT NULL DEFAULT '{}',
    output      JSONB NOT NULL DEFAULT '{}',
    model       TEXT,
    tokens_used INT,
    duration_ms INT,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

### 4.2 Indexes

```sql
CREATE INDEX idx_tasks_project_status  ON tasks(project_id, status);
CREATE INDEX idx_tasks_status          ON tasks(status);
CREATE INDEX idx_tasks_assignee        ON tasks(assignee) WHERE assignee IS NOT NULL;
CREATE INDEX idx_task_deps_depends_on  ON task_deps(depends_on);
CREATE INDEX idx_events_project        ON events(project_id, created_at DESC);
CREATE INDEX idx_events_task           ON events(task_id, created_at DESC);
CREATE INDEX idx_escalations_unresolved ON escalations(project_id, resolved, created_at)
    WHERE resolved = FALSE;
```

### 4.3 Redis Key Schema

All keys namespaced by project ID: `loom:{project_id}:...`

```
loom:{project_id}:task:{task_id}        Hash — full task fields (live cache)

loom:{project_id}:tasks:pending         Set of pending task IDs
loom:{project_id}:tasks:claimed         Set of claimed task IDs
loom:{project_id}:tasks:done            Set of done task IDs
loom:{project_id}:tasks:failed          Set of failed task IDs
loom:{project_id}:tasks:blocked         Set of blocked task IDs

loom:{project_id}:tasks:ready           Sorted set: task_id -> priority score
                                        p0=300, p1=200, p2=100

loom:{project_id}:events                Redis Stream — ordered, persistent, replayable

loom:{project_id}:tasks:updates         Pub/sub: any task state change
loom:{project_id}:escalations           Pub/sub: new escalation raised
loom:{project_id}:messages:{agent_id}   Pub/sub: direct messages to agent
loom:{project_id}:broadcast             Pub/sub: messages to all agents
```

---

## 5. MCP Tool Interface

### 5.1 Complete Tool Definitions

```python
async def loom_ready(
    priority: str | None = None,
    limit: int = 10,
) -> list[Task]:
    """
    Return tasks with no open blockers, sorted by priority (p0 first).
    Reads from Redis ready queue. Falls back to Postgres on cache miss.
    """

async def loom_claim(task_id: str, agent_id: str) -> Task:
    """
    Atomically claim a task via Postgres SELECT FOR UPDATE SKIP LOCKED.
    Raises LoomError if task is not in pending status.
    Syncs Redis and publishes event on success.
    """

async def loom_done(task_id: str, output: dict) -> Task:
    """
    Mark task complete. Stores output JSONB on task record.
    Runs dependency resolution — adds newly unblocked tasks to ready queue.
    Publishes task.done event.
    """

async def loom_fail(task_id: str, reason: str) -> Task:
    """
    Mark task failed. Creates escalation record in Postgres.
    Publishes to escalation channel. Does NOT auto-retry.
    """

async def loom_escalate(task_id: str, message: str) -> None:
    """
    Flag task as blocked. Creates escalation record.
    Orchestrator is responsible for deciding next action.
    """

async def loom_create(
    title: str,
    context: dict,
    depends_on: list[str] | None = None,
    priority: str = "p1",
    parent_id: str | None = None,
    done_when: str | None = None,
) -> Task:
    """
    Create a new task. Generates conflict-free ID (loom-{8hex}).
    Inserts task + dependency edges in a single Postgres transaction.
    Adds to ready queue immediately if no unresolved dependencies.
    """

async def loom_status(task_id: str | None = None) -> Task | ProjectStatus:
    """
    With task_id: full task detail including dependency list.
    Without task_id: ProjectStatus with counts by status, blocked list,
    open escalations, and ready queue depth.
    """

async def loom_message(
    to: str,
    message: str,
    thread_id: str | None = None,
) -> None:
    """
    Send direct message to an agent ID or 'orchestrator'.
    Publishes to loom:{project_id}:messages:{to} channel.
    Supports threaded replies via thread_id.
    """

async def loom_decompose(
    goal: str,
    spec_path: str | None = None,
    confirm: bool = True,
) -> list[Task]:
    """
    Run decomposition skill on goal string or spec file path.
    If confirm=True (default): shows proposed graph, waits for human
    approval before writing to Postgres.
    Returns list of created Tasks.
    """

async def loom_graph(format: str = "json") -> dict | str:
    """
    Return full task dependency graph for current project.
    format: 'json' | 'mermaid' | 'summary'
    """

async def loom_update(
    task_id: str,
    title: str | None = None,
    context: dict | None = None,
    priority: str | None = None,
    depends_on: list[str] | None = None,
    done_when: str | None = None,
) -> Task:
    """
    Update mutable task fields. Cannot change status directly —
    use loom_done / loom_fail / loom_escalate for status transitions.
    """
```

### 5.2 Event Types

```
task.created        New task added to graph
task.claimed        Task claimed by an agent
task.done           Task marked complete with output
task.failed         Task marked failed, escalation created
task.blocked        Task flagged as blocked, escalation created
task.updated        Mutable task fields changed
task.unblocked      Task became ready after a dependency completed
project.decomposed  Decomposition skill completed, tasks created
project.complete    All tasks in project are done
message.sent        Direct agent message sent
escalation.raised   New escalation created
escalation.resolved Escalation resolved by orchestrator
```

---

## 6. Python Module Design

```
loom/
+-- __init__.py
+-- cli.py                  # Click CLI entry point
+-- config.py               # Config: global -> project -> env vars (typed dataclass)
+-- ids.py                  # Task ID generation: loom-{8hex}, conflict-free
+-- exceptions.py           # LoomError hierarchy
|
+-- graph/
|   +-- __init__.py
|   +-- task.py             # Task dataclass, TaskStatus enum, Priority enum
|   +-- store.py            # All Postgres CRUD for tasks (asyncpg) — single module
|   +-- cache.py            # Redis sync layer: reads Redis, falls back to store.py
|   +-- deps.py             # Dependency resolution + ready queue management
|   +-- project.py          # Project CRUD (create, get, list, update status)
|
+-- bus/
|   +-- __init__.py
|   +-- channels.py         # All Redis key/channel name constants — never hardcode
|   +-- events.py           # EventType enum, publish to stream + pub/sub
|   +-- publisher.py        # Redis publish helpers
|   +-- subscriber.py       # Redis subscribe + stream read helpers
|   +-- queue.py            # Escalation queue: LPUSH/BRPOP wrappers
|
+-- db/
|   +-- __init__.py
|   +-- connection.py       # asyncpg pool setup + teardown
|   +-- migrations/
|       +-- 001_initial.sql # Full schema: projects, tasks, task_deps, events, etc.
|
+-- mcp/
|   +-- __init__.py
|   +-- server.py           # FastMCP server: lifespan, tool registration, startup
|   +-- tools.py            # Tool implementations: thin, delegate to graph/ + bus/
|
+-- skills/
|   +-- __init__.py
|   +-- loader.py           # Load .md skill files: ~/.loom/skills -> .loom/skills
|   +-- runner.py           # Execute skill: build prompt, call Anthropic API, parse
|   +-- decomposer.py       # decompose_project multi-pass orchestration logic
|
+-- workflows/
    +-- __init__.py
    +-- loader.py           # Load .yaml workflow definitions
    +-- runner.py           # Execute workflow: step-by-step with state tracking
```

### 6.1 Module Contracts

**graph/store.py** — the only module that writes to Postgres for task data.
All modules needing persistent data go through store.py.

**graph/cache.py** — the only module that reads from Redis for task data.
Falls back to store.py on cache miss, then syncs Redis. Callers never decide
whether to use Redis or Postgres — cache.py makes that decision transparently.

**bus/channels.py** — all Redis key and channel name patterns as typed constants.
No string literals like "loom:tasks:ready" appear anywhere else in the codebase.

**mcp/tools.py** — tool functions must be 15 lines or fewer each. Any business
logic longer than that belongs in graph/ or bus/. Tools are thin coordinators only.

**db/migrations/** — numbered SQL files applied in order on loom up. Never modify
an existing migration file. Always add a new numbered file for schema changes.

---

## 7. Skills System

Skills are reusable markdown prompt templates. Not code. Stored as .md files with
YAML frontmatter. Overrideable per project by placing files in .loom/skills/.

### 7.1 Skill File Format

```markdown
---
name: decompose_project
version: 1.0
description: Break a high-level goal into a dependency-aware task graph
inputs:
  goal: str
  context: str | None
  depth: int   # max decomposition levels, default 3
outputs:
  tasks: list[TaskDefinition]
model: claude-sonnet-4-6
temperature: 0.2
---

## System

You are an expert software architect and project decomposer...

## Process

### Pass 1: Epics
Identify 3-7 major, independently-deliverable components...

### Pass 2: Tasks
For each epic, produce 2-6 concrete atomic tasks (1-4 hours each)...

### Pass 3: Dependencies
For each task, which others must complete before it can start...

### Pass 4: Context
Write a self-contained context blob per task with everything a worker
agent needs — no references to "the conversation" or "as discussed"...

### Pass 5: Review
Check for: dependency cycles, orphaned tasks, missing context, vague titles...

## Output Format

Respond ONLY with valid JSON:
{
  "tasks": [
    {
      "title": "string",
      "priority": "p0|p1|p2",
      "parent_epic": "string or null",
      "depends_on_titles": ["string"],
      "context": {},
      "done_when": "string"
    }
  ]
}
```

### 7.2 Built-In Skills (V1)

| Skill | Input | Output | Invoked By |
|---|---|---|---|
| decompose_project | goal + context | task graph | loom decompose |
| write_task_context | task title + project spec | context blob | decomposer |
| define_done | task description | acceptance criteria | decomposer |
| review_output | task + output + done criteria | pass/fail + notes | orchestrator |
| debug_failure | task + failure reason | retry plan | orchestrator |
| estimate_complexity | task list | XS/S/M/L/XL tags | decomposer |
| identify_parallelism | task graph | parallel groups | decomposer |
| summarize_graph | project graph | status narrative | loom status |
| generate_test_plan | code task description | test cases | worker agents |
| write_spec | requirements | technical spec | orchestrator |

### 7.3 Built-In Workflows (V1)

| Workflow | Steps |
|---|---|
| ship_feature | write_spec -> decompose -> confirm -> await -> review |
| debug_and_fix | diagnose -> create_fix_task -> implement -> verify |
| audit_codebase | decompose_audit -> parallel_analysis -> summarize |
| deploy_to_prod | test -> build -> stage -> verify -> release |

---

## 8. CLI Reference

```bash
# Installation
pipx install loom-agents
pipx upgrade loom-agents

# Project setup
loom init                       # creates .loom/, generates docker-compose.loom.yml + AGENTS.md
loom up                         # starts Postgres + Redis + MCP server
loom down                       # stops all services

# Visibility
loom status                     # project overview: counts, blocked list, ready queue depth
loom status <task-id>           # full task detail with dependency list
loom graph                      # dependency graph as text summary
loom graph --format mermaid     # Mermaid diagram (paste into any markdown)
loom graph --format json        # raw JSON

# Decomposition
loom decompose "goal string"    # run decomposition skill, confirm before writing
loom decompose --from spec.md   # decompose from spec file

# Task operations
loom create "Task title"        # interactively create a task
loom claim <task-id>            # claim a task manually
loom done <task-id>             # mark done (prompts for output notes)
loom fail <task-id>             # mark failed (prompts for reason)

# Skills
loom skill list                 # list available skills (global + project overrides)
loom skill run <name>           # run a skill interactively

# Config
loom config show                # show merged config (global + project + env)
loom config set <key> <value>   # set value in .loom/config.yaml
```

---

## 9. Configuration

### 9.1 .loom/config.yaml

```yaml
loom:
  project_name: my-project
  project_id: uuid              # set by loom init, do not edit manually

database:
  url: postgresql://loom:loom_local@localhost:5432/loom
  # Overridden by LOOM_DATABASE_URL

redis:
  url: redis://localhost:6379
  # Overridden by LOOM_REDIS_URL

mcp:
  port: 8765
  host: 0.0.0.0

skills:
  model: claude-sonnet-4-6

logging:
  level: INFO
  file: .loom/logs/loom.log
```

### 9.2 Environment Variables

```
LOOM_DATABASE_URL     PostgreSQL connection URL
LOOM_REDIS_URL        Redis connection URL
LOOM_MCP_PORT         MCP server port (default: 8765)
LOOM_SKILLS_MODEL     Claude model for skill execution
LOOM_SKILLS_API_KEY   Anthropic API key
LOOM_LOG_LEVEL        DEBUG | INFO | WARNING | ERROR
LOOM_PROJECT_DIR      Project root directory (default: cwd)
```

### 9.3 Load Order

Global (~/.loom/config.yaml) -> Project (.loom/config.yaml) -> Environment variables.
Each layer overrides the previous. config.py handles this transparently — callers
always receive a single merged Config object.

---

## 10. Project File Structure

```
my-project/
+-- .loom/
|   +-- config.yaml             # project config (committed to git)
|   +-- skills/                 # project skill overrides (committed to git)
|   +-- workflows/              # project workflow overrides (committed to git)
|   +-- logs/                   # agent activity logs (gitignored)
+-- docker-compose.loom.yml     # generated by loom init (committed to git)
+-- AGENTS.md                   # agent instructions (generated by loom init)

~/.loom/                        # global installation (managed by pipx)
+-- config.yaml                 # global defaults
+-- skills/                     # built-in skill library
+-- workflows/                  # built-in workflow library
```

Task data lives in PostgreSQL, not in the git repository. The .loom/ directory
holds configuration, skill overrides, and logs only.

---

## 11. Open Questions (Remaining)

| # | Question | Notes |
|---|---|---|
| 1 | Human-in-the-loop UI | V1: CLI + confirm prompts. V2: Obsidian daily digest |
| 2 | Agent identity/auth | V1: trust all. Cloud: per-agent env tokens |
| 3 | Worker agent spawning | V1: manual. V2: orchestrator spawns via Claude API |
| 4 | Skill API key handling | Shared project key vs per-agent keys (auditable) |
| 5 | Multi-project support | V1: one DB per project. V2: shared DB, project_id partitioned |

---

## 12. Success Criteria

Loom V1 is successful if:

1. pipx install && loom init && loom up takes under 3 minutes on a fresh machine
2. loom decompose produces a confirmed, sensible task graph for a complex goal
3. 3+ parallel worker agents claim and complete tasks without conflicts
4. A failed task creates an escalation visible to the orchestrator
5. The full stack runs on Mac and Linux via docker-compose up
6. Deploying to GCP requires only environment variable changes, no code changes

---

## 13. Phased Roadmap

### Phase 1 — Foundation (4-6 weeks)
Goal: Working local stack. A Claude Code agent can claim and complete tasks.

- [ ] Package scaffolding: pyproject.toml, pipx entry point, loom CLI shell
- [ ] db/ — asyncpg pool, migration runner, 001_initial.sql
- [ ] graph/task.py — Task dataclass, TaskStatus enum, Priority enum
- [ ] graph/store.py — full Postgres CRUD for tasks + deps
- [ ] graph/cache.py — Redis sync layer with Postgres fallback
- [ ] graph/deps.py — dependency resolution + ready queue management
- [ ] graph/project.py — project CRUD
- [ ] bus/channels.py — all Redis key/channel constants
- [ ] bus/events.py — event publishing to stream + pub/sub
- [ ] bus/publisher.py, subscriber.py, queue.py
- [ ] mcp/server.py + mcp/tools.py — FastMCP with all 11 tools
- [ ] loom init — creates .loom/, generates docker-compose.loom.yml + AGENTS.md
- [ ] loom up / loom down — docker-compose lifecycle management
- [ ] loom status — terminal project overview
- [ ] config.py — three-layer config loading

Deliverable: A Claude Code agent connects to the MCP server, calls loom_ready,
claims a manually-created task, executes it, and marks it done. State persists
across MCP server restarts.

### Phase 2 — Decomposition (3-4 weeks)
Goal: Give an agent a goal, get a populated task graph.

- [ ] skills/loader.py + skills/runner.py
- [ ] skills/decomposer.py — multi-pass decomposition orchestration
- [ ] Built-in skills: decompose_project, write_task_context, define_done
- [ ] loom decompose CLI with human confirmation before Postgres write
- [ ] loom graph --format mermaid
- [ ] Skill: estimate_complexity
- [ ] Skill run logging to skill_runs table

Deliverable: loom decompose "Build a multi-tenant CMS" produces a confirmed,
populated task graph with context blobs per task.

### Phase 3 — Orchestration (3-4 weeks)
Goal: Orchestrator agents run autonomously end-to-end.

- [ ] Orchestrator agent loop as a built-in skill
- [ ] Full escalation round-trip: worker -> Postgres/Redis -> orchestrator -> decision
- [ ] Skills: debug_failure, review_output, summarize_graph
- [ ] Dead letter queue + retry logic with exponential backoff
- [ ] Claim TTL: auto-release tasks from crashed agents
- [ ] workflows/runner.py + ship_feature workflow

Deliverable: A goal given to an orchestrator produces completed, reviewed work
with no human intervention beyond initial decomposition confirmation.

### Phase 4 — Production and Integrations (4-6 weeks)
Goal: Production-ready on GCP with existing tool integrations.

- [ ] Cloud Run Dockerfile + Cloud Build config
- [ ] Cloud SQL connection (private IP, SSL, connection pooler)
- [ ] Cloud Memorystore connection (TLS)
- [ ] Secrets Manager integration
- [ ] loom deploy CLI command
- [ ] Obsidian vault daily digest (hooks into existing commit monitor pattern)
- [ ] Slack escalation notifications via existing integration
- [ ] Remaining workflows: debug_and_fix, audit_codebase, deploy_to_prod
- [ ] Remaining skills: generate_test_plan, write_spec, identify_parallelism
- [ ] PyPI publish automation via GitHub Actions

Deliverable: Loom runs on GCP. Slack gets escalations. Obsidian gets daily
summaries. pipx install loom-agents installs from PyPI.

---

## 14. Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| Postgres connection pool exhaustion under high parallelism | Medium | asyncpg pool sizing in config; per-agent connection limits |
| Redis cache stale after Postgres write succeeds but sync fails | Low | Rebuild on read (cache.py fallback); full rebuild on MCP restart |
| Decomposition skill produces dependency cycles | Medium | Cycle detection in deps.py before any task is committed to Postgres |
| Claim TTL too aggressive, releases tasks mid-execution | Low | TTL configurable per task; agents heartbeat to extend claim |
| Skill API costs on large decompositions | Medium | Output caching by input hash; cheaper model for review passes |
| Cloud SQL cold connection latency | Low | Connection pooling (pgBouncer or asyncpg pool); min connections = 2 |

---

## 15. Integration with Jason's Existing Tools

| Tool | Integration |
|---|---|
| Claude Code | Primary agent interface — connects to Loom MCP server natively |
| Obsidian vault | Phase 4: summarize_graph skill writes daily digest to vault |
| Commit monitor | Phase 4: Loom events feed into existing daily summary system |
| Slack | Phase 4: escalation notifications via existing Slack integration |
| GCP Cloud Run | Phase 4: MCP server deployment target (same Docker image) |
| GCP Cloud SQL | Phase 1+: Postgres in GCP for cloud deployments |
| GCP Memorystore | Phase 1+: Redis in GCP for cloud deployments |
| Git/GitHub | .loom/config.yaml + skills committed; task IDs referenced in commits |

---

## Potential Future Enhancements
* The goal of this is to create a flexible system. It should be able to be used to build projects of varying sizes and complexities without being a part of the final product. However, if it is relevent to the project's structure, it can be integrated in a more permeant way. This permeant integration should be optional and definately a V2. 
* Another potential future enhancement is to use this as the basis for a semi-autonomous agent that can operate on given tasks and report back to the user. This would be a V2. 

*End of PRD v1.1 — All architecture decisions final.*
*Next step: Phase 1 implementation. Start with db/migrations/001_initial.sql*
*and graph/task.py — everything else builds from these two.*
