Skip to content

[Gastown] PR 6: tRPC Routes — Town & Rig Management #268

@jrf0110

Description

@jrf0110

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

Note: This was previously part of #212, which has been repurposed as the Rig DO Alarm. The tRPC routes are now a separate issue.

Goal

Dashboard API for creating and managing towns and rigs. The sling mutation creates DO state and arms the alarm — the alarm handles dispatching to the container. All reads go through the Gastown worker HTTP API (DO SQLite), no Postgres.

New Router

src/server/routers/gastown.ts

export const gastownRouter = router({
  // -- Towns --
  createTown: protectedProcedure.input(z.object({
    name: z.string().min(1).max(64),
  })).mutation(async ({ ctx, input }) => { /* create town via gastown worker */ }),

  listTowns: protectedProcedure
    .query(async ({ ctx }) => { /* list towns for current user */ }),

  getTown: protectedProcedure.input(z.object({ townId: z.string().uuid() }))
    .query(async ({ ctx, input }) => { /* get town with rigs */ }),

  // -- Rigs --
  createRig: protectedProcedure.input(z.object({
    townId: z.string().uuid(),
    name: z.string().min(1).max(64),
    gitUrl: z.string().url(),
    defaultBranch: z.string().default('main'),
  })).mutation(async ({ ctx, input }) => { /* create rig, initialize Rig DO */ }),

  getRig: protectedProcedure.input(z.object({ rigId: z.string().uuid() }))
    .query(async ({ ctx, input }) => { /* get rig with agents, active beads */ }),

  // -- Beads (read from DO via worker API) --
  listBeads: protectedProcedure.input(z.object({
    rigId: z.string().uuid(),
    status: z.enum(['open', 'in_progress', 'closed', 'cancelled']).optional(),
  })).query(async ({ ctx, input }) => { /* list beads via gastown worker */ }),

  // -- Agents --
  listAgents: protectedProcedure.input(z.object({ rigId: z.string().uuid() }))
    .query(async ({ ctx, input }) => { /* list agents via gastown worker */ }),

  // -- Work Assignment --
  sling: protectedProcedure.input(z.object({
    rigId: z.string().uuid(),
    title: z.string(),
    body: z.string().optional(),
    model: z.string().default('kilo/auto'),
  })).mutation(async ({ ctx, input }) => {
    // 1. Create bead in Rig DO (via internal auth HTTP call)
    // 2. Register or pick an agent (Rig DO allocates name)
    // 3. Hook bead to agent (Rig DO updates state)
    // 4. Arm Rig DO alarm → alarm will dispatch agent to container
    // 5. Return agent info (no stream URL yet — comes from container)
  }),

  // -- Send message to Mayor --
  sendMessage: protectedProcedure.input(z.object({
    townId: z.string().uuid(),
    message: z.string(),
    model: z.string().default('kilo/auto'),
  })).mutation(async ({ ctx, input }) => {
    // 1. Create a message bead assigned to the Mayor agent
    // 2. Arm alarm → dispatches to container
  }),

  // -- Agent Streams --
  getAgentStreamUrl: protectedProcedure.input(z.object({
    agentId: z.string().uuid(),
    townId: z.string().uuid(),
  })).query(async ({ ctx, input }) => {
    // Fetch stream ticket from container via TownContainer.fetch()
    // Return WebSocket URL for dashboard to connect to
  }),
});

Key difference from original #212: The sling mutation no longer creates a cloud-agent-next session. It creates state in the DO and arms the alarm. The alarm handles dispatching to the container. This decouples the API response time from container cold starts.

Dependencies

  • PR 1 (Rig DO)
  • PR 2 (HTTP API Layer)
  • PR 4 (Town Container)
  • PR 5 (Rig DO Alarm)

Acceptance Criteria

  • gastownRouter added to tRPC app router
  • Town CRUD (create, list, get)
  • Rig CRUD (create, get)
  • sling mutation: creates bead → assigns agent → hooks bead → arms alarm
  • sendMessage mutation for Mayor communication
  • Bead and agent list queries via Gastown worker API
  • Agent stream URL endpoint (fetches ticket from container)
  • Authorization checks (user owns the town)

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