-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Part of #271 — Gastown Cloud Proposal A (Sandbox-per-Town)
Goal
Mint per-town JWT tokens that route all LLM calls through the user's billing context via the Kilo gateway. Implement automatic token refresh so sandboxes never lose gateway access.
Context
The Kilo gateway (POST /api/gateway/chat/completions) accepts Authorization: Bearer <jwt> and extracts user context for billing, model routing, and abuse detection. The existing getUserFromAuth() function handles this — no gateway changes needed. We just need to mint appropriate tokens and keep them refreshed.
Requirements
Token Minting
mintGastownToken(userId, townId, organizationId?) → JWT string
Payload:
{
"sub": "<user_id>",
"type": "gastown_sandbox",
"town_id": "<town_id>",
"organization_id": "<org_id or null>",
"iat": 1700000000,
"exp": 1700086400
}- Lifetime: 24 hours
- Signing: HS256 with
NEXTAUTH_SECRET(same as existing stream tickets) - Called during
createTown(PR 2) to provision the initial token
Token Refresh
The sandbox refreshes its token before expiry via a cron job (every 12 hours).
Cloud-side endpoint (add to gastown tRPC router):
gastown.refreshToken — mutation
- Input:
{ townId: string } - Auth:
x-internal-api-key(sandbox → cloud) - Validates town exists and is active
- Mints a new 24-hour token
- Returns the new JWT string
Sandbox-side cron:
# Every 12 hours
NEW_TOKEN=$(curl -s "${CLOUD_API_URL}/api/gastown/refresh-token" \
-H "x-internal-api-key: ${INTERNAL_API_KEY}" \
-d '{"townId": "'${TOWN_ID}'"}')
echo "KILO_JWT=${NEW_TOKEN}" > /home/gt/.env.gatewayThe startup script sources this env file. New agent sessions pick up the refreshed token.
Model Configuration Flow
Users configure models via the dashboard. The flow:
- User selects model in dashboard (handled in PR 10)
- Cloud app calls sandbox internal API:
PATCH /configwith model settings - Sandbox writes to
gtconfig files:- Town-level:
~/gt/settings/config.json→"role_agents"map - Rig-level:
~/gt/<rig>/settings/config.json→ per-rig overrides
- Town-level:
- New agent sessions pick up the config on next spawn
No gateway changes needed — the gateway already routes any model the user requests.
Files
cloud/src/lib/gastown/auth.ts(new) —mintGastownToken,verifyGastownToken- Updates to
cloud/src/server/api/routers/gastown.ts—refreshTokenendpoint cloud/infra/gastown-sandbox/refresh-token.sh(new) — cron script
Acceptance Criteria
-
mintGastownTokenproduces a valid JWT with correct payload - Token expires after 24 hours
- Gateway accepts the token and correctly attributes usage to the user
-
refreshTokentRPC mutation validates town ownership and returns new token -
refreshTokenrejects requests for destroyed/stopped towns - Sandbox cron script successfully refreshes token and writes to env file
- New agent sessions pick up the refreshed token (via env file sourcing)
- Organization context is included in token when applicable
Dependencies
- PR 2 (Provisioning API) —
createTowncallsmintGastownTokenfor initial provisioning - PR 3 (Sandbox Internal API) —
PATCH /configendpoint for model configuration