Skip to content

Comments

feat(gastown): Gastown (#204)#544

Open
jrf0110 wants to merge 2 commits intomainfrom
204-gt-prop-d
Open

feat(gastown): Gastown (#204)#544
jrf0110 wants to merge 2 commits intomainfrom
204-gt-prop-d

Conversation

@jrf0110
Copy link
Contributor

@jrf0110 jrf0110 commented Feb 24, 2026

What is Gastown?

Gastown is an agent orchestration system for managing multiple AI coding agents working concurrently across git repositories. It was originally built as a local CLI tool — two Go binaries (gt for orchestration and bd for the Beads work-tracking database) coordinated with tmux on the user's machine.

The core primitive is beads — a universal data model where every object (issues, mail, merge requests, escalations, convoys, agents) is a bead. Agents are organized into roles: a Mayor that coordinates, Polecats that do the work, a Refinery that reviews and merges, and a Witness that monitors health.

What is this PR?

Gastown Cloud — a cloud-native implementation of Gastown as a Kilo platform feature, built on Cloudflare Workers, Durable Objects, and Containers. Instead of tmux on a laptop, agents run in Cloudflare Containers. Instead of databases on disk, state lives in DO SQLite. Users interact through a web dashboard with live agent terminals via xterm.js.

There's still a lot of work to do being tracked in #204


Overview Page Rig Page
Overview Rig
Agent Detail Drawer Bead Detail Drawer
Agent Detail Bead Detail
Merge Queue Beads List
Merge Queue Beads List
Town Settings
Town Settings

Architecture

A diagram for ants
image

  • Cloudflare Worker (Hono) — API gateway with CF Access auth
  • TownDO — central control-plane DO with all rig, agent, bead, and config state in SQLite
  • AgentDO — per-agent event storage with 10GB isolation
  • TownContainerDO — Docker container lifecycle, PTY proxy, git merge infrastructure
  • GastownUserDO — user-to-town ownership registry

Data model

  • Beads-centric schema — all domain objects (issues, messages, molecules, agents, merge requests, convoys, escalations) are beads with type-specific satellite metadata tables
  • Bead dependencies graph for convoy tracking and molecule step ordering
  • Bead events log for audit trail

Agent system

  • Mayor — orchestrating AI that triages, plans, and delegates work via tool calls
  • Polecats — parallel workers dispatched to individual beads, working in isolated git worktrees
  • Refinery — LLM agent that reviews diffs and runs quality gates before merge (falls back to deterministic merge when no gates are configured)
  • Witness patrol — mechanical health checks in the TownDO alarm (stale agent detection, GUPP violation checks, mail delivery)
  • Mail — inter-agent messaging implemented as message-type beads, delivered by the alarm loop to running agents
  • Escalation — severity-routed with configurable thresholds and automatic re-escalation of unacknowledged escalations
  • Convoys — batch work tracking with auto-landing detection (backend complete, not yet wired to Mayor tools)
  • Molecules — multi-step task decomposition as parent-child bead trees with dependency ordering (backend complete, not yet used by agents)

Terminal and UI

  • xterm.js PTY terminals connected to container SDK sessions via WebSocket passthrough (browser → worker → container DO → container)
  • Next.js dashboard with drawer stack navigation for drill-down into beads, agents, and events
  • Persistent mayor terminal bar across all town pages
  • Town-specific sidebar with live rig list
  • All Gastown UI routes gated to admin-only users

Infrastructure

  • Git credential resolution from platform integrations with on-demand token refresh
  • Detached worktree merge strategy to avoid branch lock conflicts during concurrent merges
  • Multi-rig support — multiple rigs per town sharing a single container with isolated git clones

Gastown is an autonomous software engineering platform built on
Cloudflare Workers, Durable Objects, and Containers. This branch
implements the full stack from scratch.

Architecture:
- Cloudflare Worker (Hono) as the API gateway with CF Access auth
- TownDO as the central control-plane Durable Object, consolidating
  all rig, agent, bead, and config state in SQLite
- AgentDO for per-agent event storage with 10GB isolation
- TownContainerDO running Docker containers with the Kilo SDK,
  process manager, PTY proxy, and git merge infrastructure
- GastownUserDO and AgentIdentityDO for user/agent identity management

Data model:
- Beads-centric universal schema: all domain objects (issues, messages,
  molecules, agents, merge requests, convoys, escalations) are beads
  with type-specific satellite metadata tables
- Bead dependencies graph for convoy/molecule relationships
- Bead events log for full audit trail

Agent system:
- Mayor agent as the orchestrating AI that triages, plans, and delegates
- Polecat agents as parallel workers dispatched to individual tasks
- Refinery agent for code review before merge
- Witness agent for periodic health monitoring
- Convoy system for coordinated multi-agent execution
- Molecule system for multi-step task decomposition
- Mail system for inter-agent messaging
- Escalation system with configurable thresholds and auto-bump

Terminal and UI:
- xterm.js PTY terminals connected to container SDK sessions via
  WebSocket passthrough (browser → worker → container)
- Dashboard with drawer stack navigation for deep drill-down into
  beads, agents, and events
- Persistent mayor terminal bar across all town pages
- Bead kanban board, activity feed, agent grid, and observability views
- Town-specific sidebar with live rig list

Infrastructure:
- Git credential resolution from platform integrations with on-demand
  refresh via Next.js API
- Detached worktree merge strategy to avoid branch lock conflicts
- All Gastown UI and tRPC routes gated to admin-only users
- Next.js tRPC router with 22 procedures for the frontend
@kiloconnect
Copy link
Contributor

kiloconnect bot commented Feb 24, 2026

Code Review Summary

Status: 8 Issues Found | Recommendation: Address before merge

Fix these issues in Kilo Cloud

Overview

Severity Count
CRITICAL 2
WARNING 5
SUGGESTION 1
Issue Details (click to expand)

CRITICAL

File Line Issue
cloudflare-gastown/src/gastown.worker.ts 270 Town-level routes (/api/towns/:townId/config, convoys, escalations, container, mayor) have no authorization middleware beyond CF Access — any authenticated user can read/write any town's config including secrets
cloudflare-gastown/src/gastown.worker.ts 372 (existing) WebSocket upgrade requests bypass Cloudflare Access auth

WARNING

File Line Issue
cloudflare-gastown/src/gastown.worker.ts 247 (existing) No authorization check that the authenticated CF Access user owns the userId in the route
cloudflare-gastown/src/middleware/auth.middleware.ts 50 authMiddleware validates rigId matches JWT but not townId — agent JWT for town A could operate on town B's data
cloudflare-gastown/src/middleware/auth.middleware.ts N/A (existing) X-Town-Id header fallback could allow cross-town access
cloudflare-gastown/src/handlers/mayor-tools.handler.ts 155 handleMayorListBeads missing rig_id filter — returns beads from all rigs instead of the requested rig
cloudflare-gastown/src/handlers/mayor-tools.handler.ts 185 handleMayorListAgents missing rig_id filter — returns agents from all rigs instead of the requested rig

SUGGESTION

File Line Issue
cloudflare-gastown/src/middleware/auth.middleware.ts N/A (existing) Avoid TypeScript as cast — use flow-sensitive typing
Other Observations (not in diff)

No additional issues found outside the diff.

Files Reviewed (96 files)
  • cloudflare-gastown/src/gastown.worker.ts - 2 issues (1 new, 1 existing)
  • cloudflare-gastown/src/middleware/auth.middleware.ts - 3 issues (1 new, 2 existing)
  • cloudflare-gastown/src/middleware/cf-access.middleware.ts - 0 issues
  • cloudflare-gastown/src/middleware/mayor-auth.middleware.ts - 0 issues
  • cloudflare-gastown/src/util/jwt.util.ts - 0 issues
  • cloudflare-gastown/src/handlers/towns.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/rig-agents.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/rig-beads.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/town-container.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/mayor-tools.handler.ts - 2 issues
  • cloudflare-gastown/src/handlers/mayor.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/rig-escalations.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/rig-mail.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/town-config.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/town-convoys.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/town-escalations.handler.ts - 0 issues
  • cloudflare-gastown/src/handlers/town-events.handler.ts - 0 issues
  • cloudflare-gastown/src/dos/Town.do.ts - 0 issues
  • cloudflare-gastown/src/dos/TownContainer.do.ts - 0 issues
  • cloudflare-gastown/src/dos/GastownUser.do.ts - 0 issues
  • cloudflare-gastown/src/dos/town/container-dispatch.ts - 0 issues
  • cloudflare-gastown/container/Dockerfile - 0 issues (uses non-root user)
  • cloudflare-gastown/container/src/control-server.ts - 0 issues
  • cloudflare-gastown/container/src/agent-runner.ts - 0 issues
  • cloudflare-gastown/container/src/main.ts - 0 issues
  • Other files (tables, prompts, types, tests, etc.) - reviewed for context

…t container, as casts

- Add CF Access JWT validation to WebSocket upgrade handler (was
  bypassing Hono middleware). Extracted validateCfAccessRequest() from
  the Hono middleware for reuse outside the middleware chain.
- Remove X-Town-Id header fallback from getTownId() — all callers
  already have :townId in the route path, and the header was a
  client-controllable cross-town vector.
- Add non-root "agent" user to both Dockerfiles with proper ownership
  of /workspace, /app, and ~/.config/kilo directories.
- Remove as casts in getEnforcedAgentId() and getTownId(), using
  flow-sensitive typing instead.

// ── Town Configuration ──────────────────────────────────────────────────

app.get('/api/towns/:townId/config', c => handleGetTownConfig(c, c.req.param()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Town-level routes (/api/towns/:townId/config, /api/towns/:townId/convoys, /api/towns/:townId/escalations, /api/towns/:townId/container/*, /api/towns/:townId/mayor/*) have no authorization middleware beyond Cloudflare Access.

Any CF Access-authenticated user can:

  • Read/write any town's configuration (including secrets like kilocode_token, git_auth tokens via PATCH /api/towns/:townId/config)
  • Start/stop agents in any town's container
  • Send messages as the mayor of any town
  • Create convoys and acknowledge escalations in any town

The rig routes are protected by authMiddleware (line 142), but these town-level routes are not. Consider adding an ownership check middleware for /api/towns/:townId/* routes that verifies the authenticated user owns the town.


// Verify the rigId in the JWT matches the route param
const rigId = c.req.param('rigId');
if (rigId && result.payload.rigId !== rigId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: authMiddleware validates rigId matches the JWT but does not validate townId. An agent with a valid JWT for rig R in town A could call /api/towns/<townB>/rigs/<R>/... and the middleware would pass because the rigId matches. The handler then resolves townId from the route param (via getTownId), allowing the agent to operate on town B's data.

Consider also checking:

Suggested change
if (rigId && result.payload.rigId !== rigId) {
// Verify the rigId in the JWT matches the route param
const rigId = c.req.param('rigId');
if (rigId && result.payload.rigId !== rigId) {
return c.json(resError('Token rigId does not match route'), 403);
}
// Verify the townId in the JWT matches the route param
const townId = c.req.param('townId');
if (townId && result.payload.townId !== townId) {
return c.json(resError('Token townId does not match route'), 403);
}

);

const town = getTownDOStub(c.env, params.townId);
const beads = await town.listBeads({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: handleMayorListBeads verifies the rig belongs to the town but then calls town.listBeads() without passing rig_id: params.rigId. This returns beads from ALL rigs in the town, not just the requested rig. The route is /api/mayor/:townId/tools/rigs/:rigId/beads which implies rig-scoped results.

Suggested change
const beads = await town.listBeads({
const beads = await town.listBeads({
status: status?.data,
type: type?.data,
rig_id: params.rigId,
assignee_agent_bead_id:
c.req.query('assignee_agent_bead_id') ?? c.req.query('assignee_agent_id'),
limit: limit?.data,
offset: offset?.data,
});

);

const town = getTownDOStub(c.env, params.townId);
const agents = await town.listAgents({});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: handleMayorListAgents verifies the rig belongs to the town but then calls town.listAgents({}) with an empty filter. The route is /api/mayor/:townId/tools/rigs/:rigId/agents which implies rig-scoped results, but this returns agents from ALL rigs.

Suggested change
const agents = await town.listAgents({});
const agents = await town.listAgents({ rig_id: params.rigId });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant