Findings: - Two competing mechanisms: embedded templates vs local-fork edits - Local-fork created ~200 lines of divergent content in mayor/CLAUDE.md - TOML config overrides exist but only handle operational config Recommendation: Extend TOML override system to support [content] sections for template customization, unifying all override mechanisms.
9.3 KiB
Role Template Management Strategy
Research Date: 2026-01-26 Researcher: kerosene (gastown/crew) Status: Analysis complete, recommendation provided
Executive Summary
Gas Town currently has two competing mechanisms for managing role context, leading to divergent content and maintenance complexity:
- Embedded templates (
internal/templates/roles/*.md.tmpl) - source of truth in binary - Local-fork edits - direct modifications to runtime
CLAUDE.mdfiles
Additionally, there's a third mechanism for operational config that works well:
- Role config overrides (
internal/config/roles.go) - TOML-based config override chain
Recommendation: Extend the TOML override pattern to support template content sections, unifying all customization under one mechanism.
Inventory: Current Mechanisms
1. Embedded Templates (internal/templates/roles/*.md.tmpl)
Location: internal/templates/roles/
Files:
mayor.md.tmpl(337 lines)crew.md.tmpl(17,607 bytes)polecat.md.tmpl(17,527 bytes)witness.md.tmpl(11,746 bytes)refinery.md.tmpl(13,525 bytes)deacon.md.tmpl(13,727 bytes)boot.md.tmpl(4,445 bytes)
How it works:
- Templates are embedded into the binary via
//go:embeddirective gt primecommand renders templates with role-specific data (TownRoot, RigName, etc.)- Output is printed to stdout, where Claude picks it up as context
- Uses Go template syntax:
{{ .TownRoot }},{{ .RigName }}, etc.
Code path: templates.New() → tmpl.RenderRole() → stdout
2. Local-Fork Edits (Runtime CLAUDE.md)
Location: Various agent directories (e.g., mayor/CLAUDE.md, <rig>/crew/<name>/CLAUDE.md)
How it works:
gt installcreates minimal bootstrap CLAUDE.md (~15 lines) viacreateMayorCLAUDEmd()- Bootstrap content just says "Run
gt primefor full context" - THEN humans/agents directly edit these files with custom content
- These edits are committed to the town's git repo
Example: Mayor's CLAUDE.md grew from bootstrap to 532 lines
Key local-fork commit:
1cdbc27 docs: Enhance Mayor role template with coordination system knowledge (sc-n2oiz)
This commit added ~500 lines to mayor/CLAUDE.md including:
- Colony Model (why Gas Town uses coordinated specialists)
- Escalation Patterns (Witness vs Mayor responsibilities)
- Decision Flow (when to use polecats vs crew)
- Multi-phase Orchestration
- Monitoring without Micromanaging
- Teaching GUPP patterns
- Communication Patterns
- Speed Asymmetry
None of this content exists in the embedded template - it's purely local-fork.
3. Role Config Overrides (TOML files)
Location:
- Built-in:
internal/config/roles/*.toml(embedded in binary) - Town-level:
<town>/roles/<role>.toml(optional override) - Rig-level:
<rig>/roles/<role>.toml(optional override)
Resolution order (later wins):
- Built-in defaults (embedded)
- Town-level overrides
- Rig-level overrides
What it handles:
# Example: mayor.toml
role = "mayor"
scope = "town"
nudge = "Check mail and hook status, then act accordingly."
prompt_template = "mayor.md.tmpl"
[session]
pattern = "hq-mayor"
work_dir = "{town}"
needs_pre_sync = false
start_command = "exec claude --dangerously-skip-permissions"
[env]
GT_ROLE = "mayor"
GT_SCOPE = "town"
[health]
ping_timeout = "30s"
consecutive_failures = 3
kill_cooldown = "5m"
stuck_threshold = "1h"
What it DOES NOT handle:
- Template content (the actual markdown context)
- The
prompt_templatefield just names which .md.tmpl to use
Implementation: LoadRoleDefinition() in roles.go handles the override chain with mergeRoleDefinition().
Analysis: Trade-offs
Embedded Templates
| Pros | Cons |
|---|---|
| Single source of truth in binary | Requires recompile for changes |
| Consistent across all installations | No per-town customization |
| Supports placeholder substitution | Can't add town-specific sections |
| Version-controlled in gastown repo | Changes don't propagate to existing installs |
Local-Fork Edits
| Pros | Cons |
|---|---|
| Per-installation customization | Diverges from template source |
| No recompile needed | Manual sync to keep up with template changes |
| Town-specific content | Each install is unique snowflake |
| Immediate effect | Template improvements don't propagate |
Role Config Overrides
| Pros | Cons |
|---|---|
| Clean override chain | Only handles operational config |
| Town/rig level customization | Doesn't handle template content |
| Merge semantics (not replace) | - |
| No recompile needed | - |
Problem Statement
The current situation creates three-way divergence:
┌──────────────────────────────────────────┐
│ Embedded Template (mayor.md.tmpl) │
│ 337 lines - "official" content │
└──────────────────────────────────────────┘
│
│ gt prime renders
│ BUT doesn't include
│ local-fork additions
v
┌──────────────────────────────────────────────────────────────────┐
│ Runtime CLAUDE.md (mayor/CLAUDE.md) │
│ 532 lines - has ~200 lines of local-fork content │
│ INCLUDING: Colony Model, Escalation Patterns, etc. │
└──────────────────────────────────────────────────────────────────┘
Issues:
- When
gt primeruns, it outputs the embedded template (337 lines) - The local-fork content (Colony Model, etc.) is in
mayor/CLAUDE.md - Claude Code reads BOTH via
CLAUDE.md+ startup hooks - But the embedded template and local CLAUDE.md overlap/conflict
- Template improvements in new gt versions don't include local-fork content
- Local-fork improvements aren't shared with other installations
Recommendation: Unified Override System
Extend the existing TOML override mechanism to support template content sections.
Proposed Design
# <town>/roles/mayor.toml (town-level override)
# Existing operational overrides work as-is
[health]
stuck_threshold = "2h" # Town needs longer threshold
# NEW: Template content sections
[content]
# Append sections after the embedded template
append = """
## The Colony Model: Why Gas Town Works
Gas Town rejects the "super-ant" model... [rest of content]
"""
# OR reference a file
append_file = "mayor-additions.md"
# OR override specific sections by ID
[content.sections.escalation]
replace = """
## Escalation Patterns: What to Handle vs Delegate
...[custom content]...
"""
Why This Works
- Single source of truth: Embedded templates remain canonical
- Clean override semantics: Town/rig can append or replace sections
- Existing infrastructure: Uses the same TOML loading + merge pattern
- No recompile: Content overrides are runtime files
- Shareable: Town-level overrides can be committed to town repo
- Migrateable: Existing local-fork content can move to
[content]sections
Implementation Path
-
Phase 1: Add
[content]support to role config- Parse
append,append_file,replace_sectionsfields - Apply after template rendering in
outputPrimeContext()
- Parse
-
Phase 2: Migrate local-fork content
- Extract custom sections from
mayor/CLAUDE.md - Move to
<town>/roles/mayor.toml[content]section - Reduce
mayor/CLAUDE.mdback to bootstrap pointer
- Extract custom sections from
-
Phase 3: Document the pattern
- How to add town-specific guidance
- How to share improvements back to embedded templates
Alternative Considered: Pure Template Approach
Idea: Move all content into embedded templates, remove local CLAUDE.md entirely.
Rejected because:
- Can't support per-town customization (e.g., different escalation policies)
- Requires recompile for any content change
- Forces all installations to be identical
- Doesn't leverage existing override infrastructure
Files Involved
For implementation, these files would need modification:
| File | Change |
|---|---|
internal/config/roles.go |
Add [content] parsing to RoleDefinition |
internal/cmd/prime_output.go |
Apply content overrides after template render |
internal/templates/templates.go |
Potentially add section markers for replace |
internal/cmd/install.go |
Update bootstrap to not create full CLAUDE.md |
Summary
| Approach | Verdict |
|---|---|
| Embedded templates only | Insufficient - no customization |
| Local-fork edits | Current state - creates divergence |
| TOML content overrides | Recommended - unifies all customization |
The TOML content override approach leverages existing infrastructure, provides clean semantics, and allows both standardization (embedded templates) and customization (override sections).