Skip to content

[Gastown] PR 1: Rig Durable Object #208

@jrf0110

Description

@jrf0110

Parent: #204 | Phase 1: Single Rig, Single Polecat

Goal

The Rig DO is the core state machine that holds beads, agents, mail, and the PR review queue for a single rig. This is the authoritative data store — all state lives in DO SQLite.

New Worker

cloud/cloudflare-gastown/
├── src/
│   ├── index.ts              # Hono router, DO exports
│   ├── types.ts              # Shared types
│   ├── rig-do.ts             # Rig Durable Object
│   ├── town-do.ts            # Town Durable Object (stub in Phase 1)
│   ├── agent-identity-do.ts  # Agent Identity Durable Object
│   └── db/
│       └── rig-schema.sql    # SQLite schema for Rig DO
├── wrangler.jsonc
├── package.json
└── tsconfig.json

Rig DO SQLite Schema

CREATE TABLE beads (
  id TEXT PRIMARY KEY,
  type TEXT NOT NULL,           -- 'issue', 'message', 'escalation', 'merge_request'
  status TEXT NOT NULL DEFAULT 'open',
  title TEXT NOT NULL,
  body TEXT,
  assignee_agent_id TEXT,
  convoy_id TEXT,
  molecule_id TEXT,
  priority TEXT DEFAULT 'medium',
  labels TEXT DEFAULT '[]',     -- JSON array
  metadata TEXT DEFAULT '{}',   -- JSON object
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  closed_at TEXT
);

CREATE TABLE agents (
  id TEXT PRIMARY KEY,
  role TEXT NOT NULL,
  name TEXT NOT NULL,
  identity TEXT NOT NULL UNIQUE,
  cloud_agent_session_id TEXT,
  status TEXT NOT NULL DEFAULT 'idle',
  current_hook_bead_id TEXT REFERENCES beads(id),
  last_activity_at TEXT,
  checkpoint TEXT,               -- JSON: crash-recovery data
  created_at TEXT NOT NULL
);

CREATE TABLE mail (
  id TEXT PRIMARY KEY,
  from_agent_id TEXT NOT NULL REFERENCES agents(id),
  to_agent_id TEXT NOT NULL REFERENCES agents(id),
  subject TEXT NOT NULL,
  body TEXT NOT NULL,
  delivered INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL,
  delivered_at TEXT
);
CREATE INDEX idx_mail_undelivered ON mail(to_agent_id) WHERE delivered = 0;

CREATE TABLE review_queue (
  id TEXT PRIMARY KEY,
  agent_id TEXT NOT NULL REFERENCES agents(id),
  bead_id TEXT NOT NULL REFERENCES beads(id),
  branch TEXT NOT NULL,
  pr_url TEXT,
  status TEXT NOT NULL DEFAULT 'pending',  -- 'pending', 'running', 'merged', 'failed'
  summary TEXT,
  created_at TEXT NOT NULL,
  processed_at TEXT
);

CREATE TABLE molecules (
  id TEXT PRIMARY KEY,
  bead_id TEXT NOT NULL REFERENCES beads(id),
  formula TEXT NOT NULL,         -- JSON: step definitions
  current_step INTEGER NOT NULL DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'active',
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

RPC API Surface

class RigDO extends DurableObject<Env> {
  // -- Beads --
  async createBead(input: CreateBeadInput): Promise<Bead>
  async getBead(beadId: string): Promise<Bead | null>
  async listBeads(filter: BeadFilter): Promise<Bead[]>
  async updateBeadStatus(beadId: string, status: string, agentId: string): Promise<Bead>
  async closeBead(beadId: string, agentId: string): Promise<Bead>

  // -- Agents --
  async registerAgent(input: RegisterAgentInput): Promise<Agent>
  async getAgent(agentId: string): Promise<Agent | null>
  async getAgentByIdentity(identity: string): Promise<Agent | null>
  async listAgents(filter?: AgentFilter): Promise<Agent[]>
  async updateAgentSession(agentId: string, sessionId: string | null): Promise<void>
  async updateAgentStatus(agentId: string, status: string): Promise<void>

  // -- Hooks (GUPP) --
  async hookBead(agentId: string, beadId: string): Promise<void>
  async unhookBead(agentId: string): Promise<void>
  async getHookedBead(agentId: string): Promise<Bead | null>

  // -- Mail --
  async sendMail(input: SendMailInput): Promise<void>
  async checkMail(agentId: string): Promise<Mail[]>  // marks as delivered

  // -- Review Queue --
  async submitToReviewQueue(input: ReviewQueueInput): Promise<void>
  async popReviewQueue(): Promise<ReviewQueueEntry | null>
  async completeReview(entryId: string, status: 'merged' | 'failed'): Promise<void>

  // -- Prime (context assembly) --
  async prime(agentId: string): Promise<PrimeContext>

  // -- Checkpoint --
  async writeCheckpoint(agentId: string, data: unknown): Promise<void>
  async readCheckpoint(agentId: string): Promise<unknown | null>

  // -- Done --
  async agentDone(agentId: string, input: AgentDoneInput): Promise<void>

  // -- Health (called by alarms) --
  async witnessPatrol(): Promise<PatrolResult>
}

Wrangler Config

{
  "name": "gastown",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01",
  "durable_objects": {
    "bindings": [
      { "name": "RIG", "class_name": "RigDO" },
      { "name": "TOWN", "class_name": "TownDO" },
      { "name": "AGENT_IDENTITY", "class_name": "AgentIdentityDO" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["RigDO", "TownDO", "AgentIdentityDO"] }
  ]
}

Acceptance Criteria

  • New cloudflare-gastown worker with project scaffolding
  • Rig DO with SQLite schema applied on first access
  • All RPC methods implemented and unit tested
  • Town DO and Agent Identity DO stubs exported
  • Wrangler config with DO bindings and migrations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions