Skip to content

feat(gastown): Per-role model configuration in town settings — mayor, refinery, polecat #1512

@jrf0110

Description

@jrf0110

Summary

Allow users to configure a different model for each agent role (mayor, refinery, polecat) in town settings. Currently there is a single default_model that applies to all agents. Users should be able to, for example, run the mayor on a cheaper/faster model (Sonnet) while running the refinery on a more capable model (Opus) for thorough code review.

Motivation

Different roles have different requirements:

  • Mayor: Conversational, needs to be fast and responsive. A lighter model is often fine.
  • Refinery: Reviews code quality, correctness, and security. Benefits from the most capable model available.
  • Polecat: Implements features and fixes. Benefits from strong coding models, but cost scales with the number of concurrent polecats.

A single default_model forces a trade-off between cost and quality across all roles. Per-role configuration lets users optimize each independently.

Design

Image Image

Town Config Schema

// townConfig — extend existing config
{
  default_model: string;           // Existing — fallback for any role without a specific model
  role_models?: {                  // New — per-role overrides
    mayor?: string;                // e.g. "anthropic/claude-sonnet-4.6"
    refinery?: string;             // e.g. "anthropic/claude-opus-4.6"  
    polecat?: string;              // e.g. "anthropic/claude-sonnet-4.6"
  };
}

When dispatching an agent, the model is resolved as:

const model = townConfig.role_models?.[agent.role] ?? townConfig.default_model;

This is backward compatible — existing towns without role_models continue using default_model for everything.

Settings UI

In the "Agent Defaults" section of town settings, replace the single model selector with a section showing:

Agent Models

  Default Model     [anthropic/claude-sonnet-4.6  ▾]
  
  Override by role (optional):
  Mayor             [Use default                  ▾]
  Refinery          [anthropic/claude-opus-4.6    ▾]
  Polecat           [Use default                  ▾]

Each role dropdown includes a "Use default" option (which maps to undefined in the config, falling back to default_model). The dropdowns should use the model selector component from #1430 once that ships, or plain text inputs as a stopgap.

Dispatch Changes

buildKiloConfigContent() in container/src/agent-runner.ts currently receives a single model string. Extend StartAgentRequest to include the role-specific model:

interface StartAgentRequest {
  // ... existing fields
  model: string;        // Already resolved per-role by the caller
}

The resolution happens in TownDO before dispatch — scheduling.dispatchAgent or the reconciler's applyAction('dispatch_agent') resolves the model from role_models?.[role] ?? default_model and passes it to the container.

Mayor Model Change

If the mayor's model is changed while it's running, this ties into #1438 (mayor session restart on model change). The same mechanism applies — detect the change in updateTownConfig and restart the mayor session with the new model.

Acceptance Criteria

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Post-launchenhancementNew feature or requestgt:coreReconciler, state machine, bead lifecycle, convoy flowgt:uiDashboard, settings, terminal, drawerskilo-duplicateAuto-generated label by Kilokilo-triagedAuto-generated label by Kilo

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions