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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions