feat: Add rig-level custom agent support (#12)

* feat: Add rig-level custom agent support

Implement rig-level custom agent configuration support to enable per-rig
agent definitions in <rig>/settings/config.json, following the same pattern as
town-level agents in settings/config.json.

Changes:
- Added RigSettings.Agents field to internal/config/types.go
- Added DefaultRigAgentRegistryPath() and LoadRigAgentRegistry() functions to internal/config/agents.go
- Updated ResolveAgentConfigWithOverride() to accept and pass rigSettings parameter
- Updated GetRuntimeCommandWithAgentOverride() to use rigSettings when available
- Updated GetRuntimeCommandWithPromptAndAgentOverride() to use rigSettings
- Updated all Build*WithOverride functions to pass rigSettings

This fixes the issue where rig-level agent settings were loaded but
ignored by lookupAgentConfig, enabling per-rig custom agents for
polecats and crew members.

* test: Add rig-level custom agent tests

Added comprehensive unit tests for rig agent registry functions:
- TestDefaultRigAgentRegistryPath: verifies path construction
- TestLoadRigAgentRegistry: verifies file loading and JSON parsing
- TestLookupAgentConfigWithRigSettings: verifies agent lookup priority (rig > town > builtin)

Added placeholder integration test for future CI/CD setup.

* initial commit

* fix: resolve compilation errors in rig-level custom agent support

- Add missing RigAgentRegistryPath function (alias for DefaultRigAgentRegistryPath)
- Restore ResolveAgentConfigWithOverride function that was incorrectly removed
- Fix ResolveAgentConfig to return single value (not triple)
- Add initRegistryLocked() call to LoadRigAgentRegistry to prevent nil panic
- Fix DefaultRigAgentRegistryPath to use rigPath directly (not parent dir)
- Fix test file syntax errors (remove EOF artifacts)
- Fix test parameter order for lookupAgentConfig calls
- Fix test expectations to match correct custom agent override behavior

* test: implement rig-level custom agent integration test

- Add stub agent script that simulates AI agent with Q&A capability
- Test ResolveAgentConfig correctly picks up rig-level agents
- Test BuildPolecatStartupCommand includes custom agent command
- Test ResolveAgentConfigWithOverride respects rig agents
- Test rig agents override town agents with same name
- Add tmux integration test that spawns session and verifies output
- Stub agent echoes 'STUB_AGENT_STARTED' and handles ping/pong Q&A
- All tests pass including real tmux session verification

* docs: add OpenCode custom agent example to reference

- Show settings/agents.json format for advanced configs
- Include OpenCode example with session resume flags
- Document OPENCODE_PERMISSION env var for autonomous mode

* fix: improve rig-level agent support with docs and test fixes

- Add rig-level agent documentation to reference.md
- Document agent resolution order (rig → town → built-in)
- Deduplicate LoadAgentRegistry/LoadRigAgentRegistry into shared helper
- Fix test isolation in TestLoadRigAgentRegistry
- Fix nil pointer dereference in test assertions (use t.Fatal not t.Error)
This commit is contained in:
Subhrajit Makur
2026-01-08 01:23:29 +05:30
committed by Steve Yegge
parent 2de2d6b7e4
commit 00a59dec44
7 changed files with 746 additions and 20 deletions

View File

@@ -824,6 +824,9 @@ func ResolveAgentConfig(townRoot, rigPath string) *RuntimeConfig {
// Load custom agent registry if it exists
_ = LoadAgentRegistry(DefaultAgentRegistryPath(townRoot))
// Load rig-level custom agent registry if it exists (for per-rig custom agents)
_ = LoadRigAgentRegistry(RigAgentRegistryPath(rigPath))
// Determine which agent name to use
agentName := ""
if rigSettings != nil && rigSettings.Agent != "" {
@@ -834,8 +837,7 @@ func ResolveAgentConfig(townRoot, rigPath string) *RuntimeConfig {
agentName = "claude" // ultimate fallback
}
// Look up the agent configuration
return lookupAgentConfig(agentName, townSettings)
return lookupAgentConfig(agentName, townSettings, rigSettings)
}
// ResolveAgentConfigWithOverride resolves the agent configuration for a rig, with an optional override.
@@ -864,6 +866,9 @@ func ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride string) (*R
// Load custom agent registry if it exists
_ = LoadAgentRegistry(DefaultAgentRegistryPath(townRoot))
// Load rig-level custom agent registry if it exists (for per-rig custom agents)
_ = LoadRigAgentRegistry(RigAgentRegistryPath(rigPath))
// Determine which agent name to use
agentName := ""
if agentOverride != "" {
@@ -876,13 +881,21 @@ func ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride string) (*R
agentName = "claude" // ultimate fallback
}
// If an override is requested, validate it exists.
// If an override is requested, validate it exists
if agentOverride != "" {
// Check rig-level custom agents first
if rigSettings != nil && rigSettings.Agents != nil {
if custom, ok := rigSettings.Agents[agentName]; ok && custom != nil {
return fillRuntimeDefaults(custom), agentName, nil
}
}
// Then check town-level custom agents
if townSettings.Agents != nil {
if custom, ok := townSettings.Agents[agentName]; ok && custom != nil {
return fillRuntimeDefaults(custom), agentName, nil
}
}
// Then check built-in presets
if preset := GetAgentPresetByName(agentName); preset != nil {
return RuntimeConfigFromPreset(AgentPreset(agentName)), agentName, nil
}
@@ -890,13 +903,20 @@ func ResolveAgentConfigWithOverride(townRoot, rigPath, agentOverride string) (*R
}
// Normal lookup path (no override)
return lookupAgentConfig(agentName, townSettings), agentName, nil
return lookupAgentConfig(agentName, townSettings, rigSettings), agentName, nil
}
// lookupAgentConfig looks up an agent by name.
// First checks town's custom agents, then built-in presets from agents.go.
func lookupAgentConfig(name string, townSettings *TownSettings) *RuntimeConfig {
// First check town's custom agents
// Checks rig-level custom agents first, then town's custom agents, then built-in presets from agents.go.
func lookupAgentConfig(name string, townSettings *TownSettings, rigSettings *RigSettings) *RuntimeConfig {
// First check rig's custom agents (NEW - fix for rig-level agent support)
if rigSettings != nil && rigSettings.Agents != nil {
if custom, ok := rigSettings.Agents[name]; ok && custom != nil {
return fillRuntimeDefaults(custom)
}
}
// Then check town's custom agents (existing)
if townSettings != nil && townSettings.Agents != nil {
if custom, ok := townSettings.Agents[name]; ok && custom != nil {
return fillRuntimeDefaults(custom)