Skip to content

feat(gastown): Hosted Wasteland service — managed commons with Kilo auth/billing proxy #1810

@jrf0110

Description

@jrf0110

Summary

Offer a hosted Wasteland service on the Kilo platform. Users can create, manage, and participate in Wastelands with a Kilo-native UI and per-wasteland infrastructure that sleeps when not in use.

Revised 2026-03-31 (v4): Simplified architecture. We build our own UI and container image rather than deploying the Wasteland's hosted Go server. No @nangohq/frontend dependency, no cookie bridging, no SPA routing issues. Per-wasteland containers (like towns), not multi-tenant.

Architecture

We build a Kilo-native Wasteland service — our own UI, our own Worker, our own Container image. The Container includes the wl CLI binary for Wasteland protocol operations (browse, claim, done, sync, post). The wl CLI handles the DoltHub fork-push-PR cycle. We do NOT run wl serve --hosted.

Browser (Kilo Next.js app)
  ↓ standard Kilo JWT auth
Cloudflare Worker (wasteland-proxy)
  ├── tRPC or REST API for wasteland management
  ├── Kilo auth (JWT validation, org membership)
  ├── Billing metering
  └── Forwards protocol operations to Container
  ↓
WastelandDO (Durable Object — one per wasteland)
  ├── Wasteland metadata (name, owner, org, visibility)
  ├── Member list + credential storage (encrypted DoltHub tokens)
  ├── Cached wanted board state (polled from DoltHub)
  └── Alarm loop for periodic sync
  ↓
WastelandContainerDO (Container — one per wasteland, sleeps when idle)
  ├── `wl` CLI binary (~50MB)
  ├── Pre-configured: `wl join` during onboarding
  ├── Protocol operations: `wl browse`, `wl claim`, `wl done`, `wl post`, `wl sync`
  ├── Env: DOLTHUB_TOKEN (from WastelandDO credential store)
  └── sleepAfter: 30m (sleeps when wasteland is not in use)

Why NOT wl serve --hosted

The previous versions of this issue proposed running the Wasteland's hosted Go server. We're not doing that because:

  • We build better UI — our Next.js app with Kilo's design system is a better experience than the embedded React SPA
  • No Nango dependency — the hosted mode uses @nangohq/frontend in the browser which we'd need to fork. Instead, we store DoltHub tokens ourselves.
  • Per-wasteland containers — the hosted mode is multi-tenant (one process for all wastelands, never sleeps). We want per-wasteland containers that sleep when idle, matching the Gastown town pattern.
  • Simpler credential model — users provide DoltHub tokens in our settings UI. We store them encrypted in the WastelandDO. No Nango, no OAuth delegation, no session cookies.

The wl CLI is the only Wasteland binary we need. It handles the DoltHub protocol (fork, push, PR, sync) without running a server.


Container Image

Build our own container image with the wl CLI:

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y curl git ca-certificates jq && rm -rf /var/lib/apt/lists/*

# Install Wasteland CLI (remote mode — no Dolt needed)
RUN curl -fsSL https://github.com/gastownhall/wasteland/releases/download/v0.3.0/wasteland_0.3.0_linux_amd64.tar.gz | tar xz -C /usr/local/bin/

# Lightweight control server for receiving commands from the WastelandDO
COPY control-server /app/control-server
CMD ["/app/control-server"]

The control server is a small HTTP server (~200 lines) that receives commands from the WastelandDO and executes wl CLI commands:

POST /wl/browse       → wl browse --json
POST /wl/claim        → wl claim <id>
POST /wl/done         → wl done <id> --evidence <url>
POST /wl/post         → wl post --title ... --description ...
POST /wl/sync         → wl sync
POST /wl/join         → wl join <upstream>
POST /wl/status       → wl status <id>
GET  /health          → check wl + dolt availability

Container Lifecycle

  • Per-wasteland: One WastelandContainerDO per wasteland (same pattern as TownContainerDO)
  • sleepAfter: 30m: Container sleeps when not in use. Wakes on next API call.
  • Cold start: wl CLI needs a configured local clone. On first start (or after eviction): run wl join <upstream> to set up. Cache in the Container's ephemeral disk. Re-join on eviction (~10-30s).
  • State on disk: ~/.config/wasteland/ (wl config) + ~/.local/share/wasteland/<upstream>/ (local Dolt clone). Ephemeral — reconstructed from DoltHub on cold start.

Coding Conventions

Copy cloudflare-gastown/AGENTS.md to cloudflare-wasteland/AGENTS.md as the starting point for coding conventions. The gastown AGENTS.md covers file naming, Durable Object patterns (sub-modules, import * as X), IO boundary validation (Zod), column naming (entity_id), SQL query patterns (type-safe query() helper, table interpolators), and HTTP route conventions (Hono, handler modules). All of these apply equally to the wasteland service.

Credential Storage

No Nango. DoltHub tokens stored encrypted in the WastelandDO:

// WastelandDO SQLite
type WastelandCredential = {
  user_id: string;           // Kilo user ID
  dolthub_token: string;     // Encrypted DoltHub API token
  rig_handle: string;        // User's rig handle on this wasteland
  dolthub_org: string;       // User's DoltHub org (for fork)
  connected_at: string;
};

When executing wl commands in the Container, the WastelandDO:

  1. Resolves the user's encrypted token from its SQLite
  2. Decrypts it
  3. Passes it to the Container as a request header or env var for the specific operation

The Container's control server injects DOLTHUB_TOKEN for each wl CLI invocation.


UI (Kilo-native)

Built in the Kilo Next.js app, not the Wasteland's embedded SPA.

Pages

  • /gastown/wastelands — list user's wastelands (personal)
  • /organizations/:orgId/gastown/wastelands — org wastelands
  • /gastown/wastelands/new — create a new wasteland
  • /gastown/wastelands/:wastelandId — wasteland dashboard:
    • Wanted board — browse, filter, claim items
    • My claims — items claimed by this user/org's rigs
    • Completions — published completions and their validation status
    • Members — rig registry, invite, manage trust levels
    • Settings — name, visibility, DoltHub connection, danger zone

Design

Follow the Gastown town UI design language: dark surfaces, information-dense cards, same typography and color system. The wanted board should feel like the bead list in a town — compact cards with status pills, priority indicators, and type badges.


Authorization Model

Ownership

Same model as Gastown towns:

type WastelandOwnership =
  | { type: 'user'; userId: string }     // Personal wasteland
  | { type: 'org'; orgId: string }        // Org-owned wasteland

Access Resolution

Follow the resolveTownOwnership pattern (router.ts:141-188):

async function resolveWastelandOwnership(env, ctx, wastelandId): Promise<WastelandOwnershipResult> {
  const config = await getWastelandDO(env, wastelandId).getConfig();

  if (config.owner_type === 'user') {
    if (config.owner_user_id !== ctx.userId) {
      if (ctx.isAdmin) return { type: 'admin' };
      throw new TRPCError({ code: 'FORBIDDEN' });
    }
    return { type: 'user', userId: ctx.userId };
  }

  if (config.owner_type === 'org') {
    const membership = getOrgMembership(ctx.orgMemberships, config.organization_id);
    if (!membership || membership.role === 'billing_manager') {
      if (ctx.isAdmin) return { type: 'admin' };
      throw new TRPCError({ code: 'FORBIDDEN' });
    }
    return { type: 'org', orgId: config.organization_id };
  }

  if (ctx.isAdmin) return { type: 'admin' };
  throw new TRPCError({ code: 'NOT_FOUND' });
}

Per-Operation Authorization

Operation Who Notes
Create wasteland Any Kilo user or org admin Personal or org-owned
Delete wasteland Owner or org admin Irreversible
Manage settings Owner or org admin Visibility, etc.
Invite members Owner or org admin
Browse wanted board Any member Public wastelands: any Kilo user
Post wanted items Any member
Claim items Any member
Submit completions The claiming member Enforced by Wasteland protocol
Accept/reject completions Owner or org admin (registered as maintainer) Issues reputation stamps
Connect a Kilo town Any member who owns the town

Organization Support

  • Org-owned wastelands bill to the org
  • Org members (except billing_manager) auto-granted access
  • Org admins registered as trust_level 3 maintainers on the commons
  • Cross-org: Org A's town can participate in Org B's wasteland if the user is a member of both

Wasteland Creation Flow

Creating a new wasteland requires initializing the Dolt database with the v1.2 schema. Two options:

Option A: Template fork (recommended)

Pre-create a template wasteland on DoltHub (kilo-wastelands/wl-template) with the v1.2 schema. Creating a new wasteland = fork this template via the DoltHub REST API. Instant (pointer-based fork).

Option B: CLI initialization

Run wl create in the Container, which uses dolt to init the schema locally and push to DoltHub. Takes 10-30 seconds.

Recommendation: Option A. The fork is instant and doesn't require the Container to be running during creation.


DoltHub Relationship

DoltHub API rate limits are not publicly documented. At scale, we should cultivate a partnership with the DoltHub team for:

  • Higher rate limits for Kilo's service account
  • Potentially a dedicated DoltHub instance or API tier for hosted wastelands
  • Schema migration coordination when the Wasteland protocol evolves
  • Template repo management (kilo-wastelands/wl-template)

Implementation Plan

Phase 1: Infrastructure (Week 1)

  • Create cloudflare-wasteland/ directory in the cloud monorepo
  • Copy cloudflare-gastown/AGENTS.md to cloudflare-wasteland/AGENTS.md (coding conventions)
  • WastelandDO — metadata storage, credential storage, alarm loop
  • WastelandContainerDO — per-wasteland Container class
  • Container Dockerfile with wl CLI + Dolt + lightweight control server
  • Control server: HTTP endpoints that execute wl CLI commands
  • Credential storage: encrypted DoltHub tokens in WastelandDO SQLite
  • wrangler.jsonc configuration (Worker + DO + Container bindings)

Phase 2: Core API + Onboarding (Week 2)

  • tRPC router: createWasteland, listWastelands, deleteWasteland, getWasteland
  • resolveWastelandOwnership with user, org, and admin support
  • Template fork provisioning on DoltHub
  • DoltHub token input + validation in wasteland settings
  • Rig registration: wl join in Container during onboarding
  • Wanted board polling: WastelandDO alarm reads DoltHub REST API, caches results

Phase 3: UI + Operations (Week 3)

  • Wasteland list page (personal + org)
  • "New Wasteland" creation wizard
  • Wasteland dashboard: wanted board, claims, completions, members, settings
  • Claim, post, and done operations via control server → wl CLI
  • Invite flow for members
  • Billing metering

Phase 4: Town Integration + Polish (Week 4)

  • Fast-path: Kilo town connecting to Kilo-hosted wasteland (no manual token entry)
  • Mayor tools from Integrating with the Wasteland — Dolt sync + DoltHub onboarding #1040 (gt_wasteland_browse, etc.) work against hosted wastelands
  • End-to-end test: create wasteland → connect town → claim → complete → publish
  • Container eviction handling (re-join on cold start)
  • Health monitoring
  • Documentation


Key Architectural Decision: Remote Mode + Hybrid Reads

No Dolt in the Container

The wl CLI has a remote mode that uses the DoltHub REST API for all operations. No local Dolt clone, no Dolt binary needed. Cold start = boot Container (~3s) + wl join if needed (~2s) = ~5 seconds. No disk state beyond a tiny config file.

Hybrid: Reads from DO, Writes from Container

  • Read operations (browse, status, poll): Direct DoltHub SQL API calls from the WastelandDO. No Container needed. Instant, no cold start.
  • Write operations (claim, done, post): Via Container wl CLI. The CLI handles branch naming, PR creation, idempotency, and protocol validation.

Users can browse the wanted board instantly without waking the Container. The Container only starts when they act.


Operational Details

Control Server (~450 lines)

8 endpoints x ~40 lines + health check + token injection + serial queue + logging. TypeScript/Bun or Go.

Mutation Verification

wl claim/wl done may silently no-op (SQL WHERE matches 0 rows). The control server must re-query DoltHub after mutations to verify state change.

Container Config

instance_type: standard-2 (lighter than Gastown). max_instances: 200. sleepAfter: 30m.

Heartbeat

60s interval. Reports wl version, last operation timestamp. WastelandDO uses for crash detection.

Version Pinning

Pin wl to specific release in Dockerfile. Health check reports version. Monitor upstream releases. Rebuild on new release.

Error Handling

DoltHub down: retry with backoff, 503 to UI. Claim race: re-query, return "already claimed." Fork drift: wl sync, conflicts at PR merge time. Container eviction mid-op: retry on next tick.

Wasteland Deletion

Soft-delete in DO. Stop Container. DoltHub fork deletion not automatable (manual/orphaned). Member data retained 30 days.

Rate Limiting

Per-user: claim 10/min, done 10/min, post 5/min, browse 60/min.

Observability

wasteland_events Analytics Engine dataset. 100% Workers Observability sampling. Structured JSON logs. Health endpoint in control server.

Acceptance Criteria

Phase 1-2 (MVP)

  • WastelandDO with metadata + credential storage
  • WastelandContainerDO with per-wasteland Container lifecycle
  • Container image with wl CLI + Dolt + control server
  • sleepAfter: 30m — Container sleeps when wasteland is idle
  • Encrypted DoltHub token storage in WastelandDO
  • Template fork provisioning for new wastelands
  • Rig registration via wl join in Container
  • Wanted board polling via DoltHub REST API
  • resolveWastelandOwnership supporting user, org, and admin
  • Org members auto-granted access
  • Org admins registered as trust_level 3 maintainers

Phase 3-4 (Production)

  • Kilo-native UI: wasteland list, dashboard, wanted board, settings
  • Claim, post, done operations end-to-end
  • Town→hosted-wasteland fast-path integration
  • Billing metering
  • Container eviction recovery (re-join on cold start)
  • Health monitoring
  • End-to-end: create → connect town → claim → complete → publish

References

  • Integrating with the Wasteland — Dolt sync + DoltHub onboarding #1040 — Wasteland integration (town-side: connecting to and participating in wastelands)
  • Wasteland CLI: wasteland/cmd/wl/ — browse, claim, done, post, sync, join, status commands
  • Wasteland schema: wasteland/schema/commons.sql (v1.2, 9 tables)
  • DoltHub REST API: wasteland/internal/backend/remote.go — the API our WastelandDO alarm uses for polling
  • Gastown TownContainerDO pattern: cloudflare-gastown/wrangler.jsonc — per-resource Container lifecycle
  • Gastown auth pattern: cloudflare-gastown/src/trpc/router.ts:141-188resolveTownOwnership

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Post-launchenhancementNew feature or requestgt:coreReconciler, state machine, bead lifecycle, convoy flow

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions