-
Notifications
You must be signed in to change notification settings - Fork 7
Closed
Description
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
-
gastownRouteradded to tRPC app router - Town CRUD (create, list, get)
- Rig CRUD (create, get)
-
slingmutation: creates bead → assigns agent → hooks bead → arms alarm -
sendMessagemutation 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)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels