feat: use hq- prefix for Mayor and Deacon session names

Town-level services (Mayor, Deacon) now use hq- prefix instead of gt-:
- hq-mayor (was gt-mayor)
- hq-deacon (was gt-deacon)

This distinguishes town-level sessions from rig-level sessions which
continue to use gt- prefix (gt-gastown-witness, gt-gastown-crew-max, etc).

Changes:
- session.MayorSessionName() returns "hq-mayor"
- session.DeaconSessionName() returns "hq-deacon"
- ParseSessionName() handles both hq- and gt- prefixes
- categorizeSession() handles both prefixes
- categorizeSessions() accepts both prefixes
- Updated all tests and documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gastown/crew/jack
2026-01-05 00:42:10 -08:00
committed by Steve Yegge
parent a459cd9fd6
commit 6b8c897e37
16 changed files with 92 additions and 74 deletions
+16 -11
View File
@@ -28,8 +28,8 @@ type AgentIdentity struct {
// ParseSessionName parses a tmux session name into an AgentIdentity.
//
// Session name formats:
// - gt-mayor → Role: mayor (one per machine)
// - gt-deacon → Role: deacon (one per machine)
// - hq-mayor → Role: mayor (town-level, one per machine)
// - hq-deacon → Role: deacon (town-level, one per machine)
// - gt-<rig>-witness → Role: witness, Rig: <rig>
// - gt-<rig>-refinery → Role: refinery, Rig: <rig>
// - gt-<rig>-crew-<name> → Role: crew, Rig: <rig>, Name: <name>
@@ -39,8 +39,21 @@ type AgentIdentity struct {
// is assumed to be the polecat name. This works for simple rig names but may
// be ambiguous for rig names containing hyphens.
func ParseSessionName(session string) (*AgentIdentity, error) {
// Check for town-level roles (hq- prefix)
if strings.HasPrefix(session, HQPrefix) {
suffix := strings.TrimPrefix(session, HQPrefix)
if suffix == "mayor" {
return &AgentIdentity{Role: RoleMayor}, nil
}
if suffix == "deacon" {
return &AgentIdentity{Role: RoleDeacon}, nil
}
return nil, fmt.Errorf("invalid session name %q: unknown hq- role", session)
}
// Rig-level roles use gt- prefix
if !strings.HasPrefix(session, Prefix) {
return nil, fmt.Errorf("invalid session name %q: missing %q prefix", session, Prefix)
return nil, fmt.Errorf("invalid session name %q: missing %q or %q prefix", session, HQPrefix, Prefix)
}
suffix := strings.TrimPrefix(session, Prefix)
@@ -48,14 +61,6 @@ func ParseSessionName(session string) (*AgentIdentity, error) {
return nil, fmt.Errorf("invalid session name %q: empty after prefix", session)
}
// Check for simple town-level roles (no rig qualifier)
if suffix == "mayor" {
return &AgentIdentity{Role: RoleMayor}, nil
}
if suffix == "deacon" {
return &AgentIdentity{Role: RoleDeacon}, nil
}
// Parse into parts for rig-level roles
parts := strings.Split(suffix, "-")
if len(parts) < 2 {
+7 -7
View File
@@ -13,15 +13,15 @@ func TestParseSessionName(t *testing.T) {
wantName string
wantErr bool
}{
// Town-level roles (simple gt-mayor, gt-deacon)
// Town-level roles (hq-mayor, hq-deacon)
{
name: "mayor",
session: "gt-mayor",
session: "hq-mayor",
wantRole: RoleMayor,
},
{
name: "deacon",
session: "gt-deacon",
session: "hq-deacon",
wantRole: RoleDeacon,
},
@@ -142,12 +142,12 @@ func TestAgentIdentity_SessionName(t *testing.T) {
{
name: "mayor",
identity: AgentIdentity{Role: RoleMayor},
want: "gt-mayor",
want: "hq-mayor",
},
{
name: "deacon",
identity: AgentIdentity{Role: RoleDeacon},
want: "gt-deacon",
want: "hq-deacon",
},
{
name: "witness",
@@ -230,8 +230,8 @@ func TestAgentIdentity_Address(t *testing.T) {
func TestParseSessionName_RoundTrip(t *testing.T) {
// Test that parsing then reconstructing gives the same result
sessions := []string{
"gt-mayor",
"gt-deacon",
"hq-mayor",
"hq-deacon",
"gt-gastown-witness",
"gt-foo-bar-refinery",
"gt-gastown-crew-max",
+6 -3
View File
@@ -8,19 +8,22 @@ import (
"strings"
)
// Prefix is the common prefix for all Gas Town tmux session names.
// Prefix is the common prefix for rig-level Gas Town tmux sessions.
const Prefix = "gt-"
// HQPrefix is the prefix for town-level services (Mayor, Deacon).
const HQPrefix = "hq-"
// MayorSessionName returns the session name for the Mayor agent.
// One mayor per machine - multi-town requires containers/VMs for isolation.
func MayorSessionName() string {
return Prefix + "mayor"
return HQPrefix + "mayor"
}
// DeaconSessionName returns the session name for the Deacon agent.
// One deacon per machine - multi-town requires containers/VMs for isolation.
func DeaconSessionName() string {
return Prefix + "deacon"
return HQPrefix + "deacon"
}
// WitnessSessionName returns the session name for a rig's Witness agent.
+4 -4
View File
@@ -8,8 +8,8 @@ import (
)
func TestMayorSessionName(t *testing.T) {
// Mayor session name is now fixed (one per machine)
want := "gt-mayor"
// Mayor session name is now fixed (one per machine), uses HQ prefix
want := "hq-mayor"
got := MayorSessionName()
if got != want {
t.Errorf("MayorSessionName() = %q, want %q", got, want)
@@ -17,8 +17,8 @@ func TestMayorSessionName(t *testing.T) {
}
func TestDeaconSessionName(t *testing.T) {
// Deacon session name is now fixed (one per machine)
want := "gt-deacon"
// Deacon session name is now fixed (one per machine), uses HQ prefix
want := "hq-deacon"
got := DeaconSessionName()
if got != want {
t.Errorf("DeaconSessionName() = %q, want %q", got, want)
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// TownSession represents a town-level tmux session.
type TownSession struct {
Name string // Display name (e.g., "Mayor")
SessionID string // Tmux session ID (e.g., "gt-mayor")
SessionID string // Tmux session ID (e.g., "hq-mayor")
}
// TownSessions returns the list of town-level sessions in shutdown order.