Files
gastown/thoughts/shared/research/role-template-strategy.md
kerosene 9d87f01823 research: analyze role template management strategy
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.
2026-01-26 13:05:15 -08:00

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:

  1. Embedded templates (internal/templates/roles/*.md.tmpl) - source of truth in binary
  2. Local-fork edits - direct modifications to runtime CLAUDE.md files

Additionally, there's a third mechanism for operational config that works well:

  1. 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:embed directive
  • gt prime command 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 install creates minimal bootstrap CLAUDE.md (~15 lines) via createMayorCLAUDEmd()
  • Bootstrap content just says "Run gt prime for 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):

  1. Built-in defaults (embedded)
  2. Town-level overrides
  3. 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_template field 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:

  1. When gt prime runs, it outputs the embedded template (337 lines)
  2. The local-fork content (Colony Model, etc.) is in mayor/CLAUDE.md
  3. Claude Code reads BOTH via CLAUDE.md + startup hooks
  4. But the embedded template and local CLAUDE.md overlap/conflict
  5. Template improvements in new gt versions don't include local-fork content
  6. 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

  1. Single source of truth: Embedded templates remain canonical
  2. Clean override semantics: Town/rig can append or replace sections
  3. Existing infrastructure: Uses the same TOML loading + merge pattern
  4. No recompile: Content overrides are runtime files
  5. Shareable: Town-level overrides can be committed to town repo
  6. Migrateable: Existing local-fork content can move to [content] sections

Implementation Path

  1. Phase 1: Add [content] support to role config

    • Parse append, append_file, replace_sections fields
    • Apply after template rendering in outputPrimeContext()
  2. Phase 2: Migrate local-fork content

    • Extract custom sections from mayor/CLAUDE.md
    • Move to <town>/roles/mayor.toml [content] section
    • Reduce mayor/CLAUDE.md back to bootstrap pointer
  3. 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).