Skip to content

feat(gastown): Rig-level settings — per-rig overrides for town-level configuration #2012

@jrf0110

Description

@jrf0110

Summary

Add a rig settings page that mirrors key town-level settings but applies per-rig. Rig config is more specific than town config — when a rig setting is defined, it overrides the town default for that rig. When it's not defined, the town default applies.

This follows the standard specificity cascade: rig config > town config > system defaults.

Motivation

Different repos have different needs:

  • A production service repo should always go through refinery review (review_mode: 'always'), while a prototype repo can skip review (review_mode: 'never')
  • A large monorepo needs a more capable model for polecats, while a small utility repo can use a cheaper one
  • One repo needs git push --no-verify (heavy pre-push hooks), another doesn't
  • One repo uses pnpm, another uses npm — custom polecat instructions differ per repo

Currently all rigs in a town share the same settings. Users who want different behavior per repo have to create separate towns.

Design

Config Resolution

function resolveConfig<K extends keyof RigConfig>(
  townConfig: TownConfig,
  rigConfig: RigConfig | null,
  key: K
): RigConfig[K] | TownConfig[K] {
  return rigConfig?.[key] ?? townConfig[key];
}

At dispatch time, every configurable value is resolved through this cascade. The reconciler and dispatch code use resolveConfig() instead of reading from townConfig directly.

Rig Config Schema

// All fields optional — undefined means "inherit from town"
type RigConfig = {
  // Models (overrides townConfig.default_model / townConfig.role_models)
  default_model?: string;
  role_models?: {
    polecat?: string;
    refinery?: string;
  };
  // Note: mayor is town-level only (mayor is per-town, not per-rig)

  // Review mode (overrides townConfig.review_mode)
  review_mode?: 'always' | 'never' | 'pr_only';

  // Custom instructions (overrides townConfig.custom_instructions)
  custom_instructions?: {
    polecat?: string;
    refinery?: string;
  };
  // Note: mayor instructions are town-level only

  // Git behavior
  git_push_flags?: string;  // e.g. "--no-verify"
  default_branch?: string;  // override auto-detected default branch

  // PR / review behavior
  auto_resolve_pr_feedback?: boolean;
  auto_merge_delay_minutes?: number | null;

  // Agent limits
  max_concurrent_polecats?: number;  // default: unlimited
  max_dispatch_attempts?: number;    // override town-level max

  // Convoy behavior
  default_convoy_merge_mode?: 'review-then-land' | 'review-and-merge';
};

Storage

Rig config is stored in the TownDO's SQLite alongside rig metadata. Options:

Option A: JSON column on the rigs table

ALTER TABLE rigs ADD COLUMN config TEXT DEFAULT '{}';

The config column stores the RigConfig JSON. Parsed and validated with Zod at read time.

Option B: Separate rig_config table

CREATE TABLE rig_config (
  rig_id TEXT PRIMARY KEY REFERENCES rigs(rig_id),
  config TEXT NOT NULL DEFAULT '{}',
  updated_at TEXT NOT NULL
);

Recommendation: Option A — simpler, rig config is always read alongside rig metadata anyway.

UI: Rig Settings Page

/gastown/:townId/rigs/:rigId/settings (personal) or /organizations/:orgId/gastown/:townId/rigs/:rigId/settings (org)

The settings page mirrors the town settings structure but every field has an "Inherit from town" toggle (or a "Use default" option in dropdowns). When inheriting, the town's value is shown grayed out as a reference.

Rig Settings: acme-api

  Models
    Default Model    [Inherit from town: kilo/balanced  ▾]
    Polecat Model    [anthropic/claude-opus-4.6        ▾]  ← overridden
    Refinery Model   [Inherit from town: kilo/frontier  ▾]

  Review Mode
    ● Inherit from town (Refinery reviews all changes)
    ○ Skip review — merge directly
    ○ PR only — no automated review

  Custom Instructions
    Polecat  [Inherit from town ▾]
    ┌─────────────────────────────────────────┐
    │ (town default shown grayed out)          │
    └─────────────────────────────────────────┘
    [Override for this rig]

    Refinery [Override ▾]
    ┌─────────────────────────────────────────┐
    │ Fix linting errors during review. Do    │
    │ not request rework for style issues.    │
    └─────────────────────────────────────────┘

  Git
    Push flags       [--no-verify                      ]
    Default branch   [Inherit from town (auto-detect)  ]

  PR Feedback
    Auto-resolve     [Inherit from town (disabled)     ]
    Auto-merge delay [Inherit from town (disabled)     ]

  Agent Limits
    Max concurrent polecats  [Inherit from town (unlimited)]
    Max dispatch attempts    [Inherit from town (5)        ]

  Convoy Defaults
    Merge mode       [Inherit from town (review-then-land)]

Dispatch Path Changes

Every place that reads town config for dispatch decisions needs to use the cascade:

Current Change
townConfig.default_model resolveConfig(townConfig, rigConfig, 'default_model')
townConfig.role_models?.polecat rigConfig?.role_models?.polecat ?? townConfig.role_models?.polecat ?? townConfig.default_model
townConfig.review_mode resolveConfig(townConfig, rigConfig, 'review_mode')
townConfig.custom_instructions?.polecat rigConfig?.custom_instructions?.polecat ?? townConfig.custom_instructions?.polecat

The key call sites are:

  • buildKiloConfigContent() in container/src/agent-runner.ts — model resolution
  • applyEvent('agent_done') in reconciler.ts — review mode check
  • applyAction('dispatch_agent') in actions.ts — model + custom instructions injection
  • reconcileReviewQueue Rule 5 — review mode check before popping MR beads

Navigation

Add a "Settings" link/button to the rig detail view (where agents and beads for that rig are shown). The rig settings page uses the same scrollspy sidebar pattern as town settings (#1417).

What Stays Town-Level Only

Setting Why
Mayor model / instructions Mayor is per-town, not per-rig
Billing / spending cap Billing is per-town (or per-org)
Wasteland connections Federation is per-town
Slack integration Slack channel maps to a town
GitHub OAuth token Auth is per-town (user identity)

Acceptance Criteria

  • RigConfig schema defined (all fields optional)
  • Config stored as JSON column on the rigs table
  • resolveConfig() utility function for cascading resolution
  • Rig settings page at /gastown/:townId/rigs/:rigId/settings
  • Every field shows "Inherit from town" with the town value as reference
  • Override toggle per field — set a rig-specific value or inherit
  • Model, review mode, custom instructions, git flags, PR feedback, and agent limits configurable per rig
  • Dispatch path uses cascaded config (not raw townConfig)
  • Town-level settings remain the default when rig config is not set
  • Mayor settings stay town-level only
  • Works for both personal and org-scoped towns

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Post-launchenhancementNew feature or requestgt:coreReconciler, state machine, bead lifecycle, convoy flowgt:uiDashboard, settings, terminal, drawers

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions