Parent: #204 | Phase 1
⚠️ This is the single highest-priority remaining issue. Without tools, the Mayor is a chatbot that can't do anything. The entire chat-first product vision depends on the Mayor being able to delegate work via gt_sling. Until this lands, the product doesn't function.
Goal
Give the Mayor agent tools to delegate work across rigs. Without tools, the mayor is just a chatbot. With tools, it becomes the town coordinator described in the Gastown spec.
Tools
| Tool |
Description |
Proxies to |
gt_sling |
Sling a task to a polecat in a specific rig |
RigDO.slingBead(rigId, ...) |
gt_list_rigs |
List all rigs in the town |
GastownUserDO.listRigs(townId) |
gt_list_beads |
List beads in a rig (filterable by status) |
RigDO.listBeads(filter) |
gt_list_agents |
List agents in a rig |
RigDO.listAgents(filter) |
gt_mail_send |
Send mail to an agent in any rig |
RigDO.sendMail(...) |
(gt_convoy_create deferred until convoy system lands in #220)
Implementation
The Mayor runs as a kilo serve session in the container. It needs the Gastown plugin loaded with mayor-specific tools. Two approaches:
Option A: Extend the existing plugin (Recommended)
The Gastown plugin at container/plugin/ already loads for all agents. Add mayor-specific tools that are only registered when GASTOWN_AGENT_ROLE=mayor env var is set. The tools call the Gastown worker API using the Mayor's JWT (which has townId scope, not rigId scope).
Option B: Separate mayor plugin
A second plugin loaded only for the mayor. More isolation but more packaging complexity.
Worker-side: Mayor tool routes
New handler file src/handlers/mayor-tools.handler.ts:
POST /api/mayor/:townId/tools/sling → creates bead in target rig, assigns polecat
GET /api/mayor/:townId/tools/rigs → lists all rigs in the town
GET /api/mayor/:townId/tools/beads → lists beads (cross-rig fan-out)
GET /api/mayor/:townId/tools/agents → lists agents (cross-rig fan-out)
POST /api/mayor/:townId/tools/mail → sends mail to agent in any rig
Auth: Mayor JWT validated by townId match. No rigId constraint (mayor is cross-rig).
Mayor System Prompt
The system prompt must:
- Describe the mayor's role as town coordinator (reference Gastown architecture)
- List available rigs, their repos, and their purposes
- Describe each tool with input/output schemas and when to use it
- Explain the conversational model: respond directly for questions, delegate via
gt_sling for work
- Instruct non-blocking delegation: when slinging work, respond immediately to the user ("I've assigned Toast to work on that") — don't wait for the polecat to finish
- Follow the Propulsion Principle (GUPP)
Dependencies
Acceptance Criteria
Parent: #204 | Phase 1
Goal
Give the Mayor agent tools to delegate work across rigs. Without tools, the mayor is just a chatbot. With tools, it becomes the town coordinator described in the Gastown spec.
Tools
gt_slingRigDO.slingBead(rigId, ...)gt_list_rigsGastownUserDO.listRigs(townId)gt_list_beadsRigDO.listBeads(filter)gt_list_agentsRigDO.listAgents(filter)gt_mail_sendRigDO.sendMail(...)(
gt_convoy_createdeferred until convoy system lands in #220)Implementation
The Mayor runs as a kilo serve session in the container. It needs the Gastown plugin loaded with mayor-specific tools. Two approaches:
Option A: Extend the existing plugin (Recommended)
The Gastown plugin at
container/plugin/already loads for all agents. Add mayor-specific tools that are only registered whenGASTOWN_AGENT_ROLE=mayorenv var is set. The tools call the Gastown worker API using the Mayor's JWT (which has townId scope, not rigId scope).Option B: Separate mayor plugin
A second plugin loaded only for the mayor. More isolation but more packaging complexity.
Worker-side: Mayor tool routes
New handler file
src/handlers/mayor-tools.handler.ts:Auth: Mayor JWT validated by townId match. No rigId constraint (mayor is cross-rig).
Mayor System Prompt
The system prompt must:
gt_slingfor workDependencies
Acceptance Criteria