fix(subagents): stop discovering Pi profiles (#167)
* fix(subagents): stop discovering Pi profiles * fix(subagents): clarify profile discovery sources
- commit
- ebbb70fc535a6ef78cad5f2c525be638cbd9cc7a
- author
- bnema · · 2026-09-03 08:26 UTC
- committer
- GitHub · · 2026-09-03 08:26 UTC
- parent
- f592943
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
5 files · +15 -20
M README.md +1 -1
| @@ -432,7 +432,7 @@ Worktree isolation captures a patch and changed-file list without applying it. S | |||
| 432 | 432 | ||
| 433 | 433 | ### Named subagent profiles | |
| 434 | 434 | ||
| 435 | - | zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, profile metadata is omitted. An interactive primary with proactive delegation enabled continues locally, while an explicit headless `--orchestrate` run reports that its required delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples. | |
| 435 | + | zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default and from additional paths configured through `ZUT_AGENT_PROFILES`. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, profile metadata is omitted. An interactive primary with proactive delegation enabled continues locally, while an explicit headless `--orchestrate` run reports that its required delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples. | |
| 436 | 436 | ||
| 437 | 437 | ### `/settings` | |
| 438 | 438 | ||
| @@ -432,7 +432,7 @@ Worktree isolation captures a patch and changed-file list without applying it. S | |||||
| 432 | 432 | ||||
| 433 | ### Named subagent profiles | 433 | ### Named subagent profiles | ||
| 434 | 434 | ||||
| 435 | - | zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, profile metadata is omitted. An interactive primary with proactive delegation enabled continues locally, while an explicit headless `--orchestrate` run reports that its required delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples. | 435 | + | zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default and from additional paths configured through `ZUT_AGENT_PROFILES`. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, profile metadata is omitted. An interactive primary with proactive delegation enabled continues locally, while an explicit headless `--orchestrate` run reports that its required delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples. |
| 436 | 436 | ||||
| 437 | ### `/settings` | 437 | ### `/settings` | ||
| 438 | 438 | ||||
M docs/subagents.md +2 -3
| @@ -11,9 +11,8 @@ journal. Starting a child never launches another zut executable. | |||
| 11 | 11 | ||
| 12 | 12 | ## Profiles | |
| 13 | 13 | ||
| 14 | - | Profiles are Markdown files discovered from `~/.agents/agents/*.md`, paths in | |
| 15 | - | `ZUT_AGENT_PROFILES`, and the `~/.pi/agent/agents/*.md` compatibility discovery | |
| 16 | - | location. They are inputs only; zut does not execute them. | |
| 14 | + | Profiles are Markdown files discovered from `~/.agents/agents/*.md` and paths | |
| 15 | + | in `ZUT_AGENT_PROFILES`. They are inputs only; zut does not execute them. | |
| 17 | 16 | ||
| 18 | 17 | ```markdown | |
| 19 | 18 | --- | |
| @@ -11,9 +11,8 @@ journal. Starting a child never launches another zut executable. | |||||
| 11 | 11 | ||||
| 12 | ## Profiles | 12 | ## Profiles | ||
| 13 | 13 | ||||
| 14 | - | Profiles are Markdown files discovered from `~/.agents/agents/*.md`, paths in | 14 | + | Profiles are Markdown files discovered from `~/.agents/agents/*.md` and paths |
| 15 | - | `ZUT_AGENT_PROFILES`, and the `~/.pi/agent/agents/*.md` compatibility discovery | 15 | + | in `ZUT_AGENT_PROFILES`. They are inputs only; zut does not execute them. |
| 16 | - | location. They are inputs only; zut does not execute them. | |||
| 17 | 16 | ||||
| 18 | ```markdown | 17 | ```markdown | ||
| 19 | --- | 18 | --- | ||
M packages/agent/build.go +1 -1
| @@ -418,7 +418,7 @@ func findSubagentProfile(cwd, name string) (*subagents.Profile, error) { | |||
| 418 | 418 | } | |
| 419 | 419 | profile := subagents.Find(profiles, name) | |
| 420 | 420 | if profile == nil { | |
| 421 | - | return nil, fmt.Errorf("subagent profile %q not found (looked in ~/.agents/agents and compatibility locations)", name) | |
| 421 | + | return nil, fmt.Errorf("subagent profile %q not found (looked in ~/.agents/agents and configured ZUT_AGENT_PROFILES paths)", name) | |
| 422 | 422 | } | |
| 423 | 423 | return profile, nil | |
| 424 | 424 | } | |
| @@ -418,7 +418,7 @@ func findSubagentProfile(cwd, name string) (*subagents.Profile, error) { | |||||
| 418 | } | 418 | } | ||
| 419 | profile := subagents.Find(profiles, name) | 419 | profile := subagents.Find(profiles, name) | ||
| 420 | if profile == nil { | 420 | if profile == nil { | ||
| 421 | - | return nil, fmt.Errorf("subagent profile %q not found (looked in ~/.agents/agents and compatibility locations)", name) | 421 | + | return nil, fmt.Errorf("subagent profile %q not found (looked in ~/.agents/agents and configured ZUT_AGENT_PROFILES paths)", name) |
| 422 | } | 422 | } | ||
| 423 | return profile, nil | 423 | return profile, nil | ||
| 424 | } | 424 | } | ||
M packages/agent/subagents/profiles.go +3 -7
| @@ -3,10 +3,9 @@ | |||
| 3 | 3 | // a YAML-like metadata block followed by the agent's system prompt. | |
| 4 | 4 | // | |
| 5 | 5 | // Discovery prefers explicitly configured directories, then the shared | |
| 6 | - | // ~/.agents/agents directory, and finally the optional ~/.pi/agent/agents | |
| 7 | - | // compatibility directory. Profiles are metadata plus instructions; the main | |
| 8 | - | // agent sees only the compact manifest, while a selected child loads the full | |
| 9 | - | // profile itself. | |
| 6 | + | // ~/.agents/agents directory. Profiles are metadata plus instructions; the | |
| 7 | + | // main agent sees only the compact manifest, while a selected child loads the | |
| 8 | + | // full profile itself. | |
| 10 | 9 | package subagents | |
| 11 | 10 | ||
| 12 | 11 | import ( | |
| @@ -233,9 +232,6 @@ func searchDirs(cwd, userHome string) []location { | |||
| 233 | 232 | } | |
| 234 | 233 | if userHome != "" { | |
| 235 | 234 | add(filepath.Join(userHome, ".agents", "agents"), "global (agents)") | |
| 236 | - | // This is an additional compatibility location. The shared .agents | |
| 237 | - | // path above remains zut's documented default. | |
| 238 | - | add(filepath.Join(userHome, ".pi", "agent", "agents"), "global (pi)") | |
| 239 | 235 | } | |
| 240 | 236 | return out | |
| 241 | 237 | } | |
| @@ -3,10 +3,9 @@ | |||||
| 3 | // a YAML-like metadata block followed by the agent's system prompt. | 3 | // a YAML-like metadata block followed by the agent's system prompt. | ||
| 4 | // | 4 | // | ||
| 5 | // Discovery prefers explicitly configured directories, then the shared | 5 | // Discovery prefers explicitly configured directories, then the shared | ||
| 6 | - | // ~/.agents/agents directory, and finally the optional ~/.pi/agent/agents | 6 | + | // ~/.agents/agents directory. Profiles are metadata plus instructions; the |
| 7 | - | // compatibility directory. Profiles are metadata plus instructions; the main | 7 | + | // main agent sees only the compact manifest, while a selected child loads the |
| 8 | - | // agent sees only the compact manifest, while a selected child loads the full | 8 | + | // full profile itself. |
| 9 | - | // profile itself. | |||
| 10 | package subagents | 9 | package subagents | ||
| 11 | 10 | ||||
| 12 | import ( | 11 | import ( | ||
| @@ -233,9 +232,6 @@ func searchDirs(cwd, userHome string) []location { | |||||
| 233 | } | 232 | } | ||
| 234 | if userHome != "" { | 233 | if userHome != "" { | ||
| 235 | add(filepath.Join(userHome, ".agents", "agents"), "global (agents)") | 234 | add(filepath.Join(userHome, ".agents", "agents"), "global (agents)") | ||
| 236 | - | // This is an additional compatibility location. The shared .agents | |||
| 237 | - | // path above remains zut's documented default. | |||
| 238 | - | add(filepath.Join(userHome, ".pi", "agent", "agents"), "global (pi)") | |||
| 239 | } | 235 | } | ||
| 240 | return out | 236 | return out | ||
| 241 | } | 237 | } | ||
M packages/agent/subagents/profiles_test.go +8 -8
| @@ -8,7 +8,8 @@ import ( | |||
| 8 | 8 | "testing" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | func TestDiscoverLoadsGlobalAgentsProfilesAndPiFallback(t *testing.T) { | |
| 11 | + | func TestDiscoverLoadsGlobalAgentsProfilesAndIgnoresPiProfiles(t *testing.T) { | |
| 12 | + | t.Setenv("ZUT_AGENT_PROFILES", "") | |
| 12 | 13 | home := t.TempDir() | |
| 13 | 14 | agentsDir := filepath.Join(home, ".agents", "agents") | |
| 14 | 15 | piDir := filepath.Join(home, ".pi", "agent", "agents") | |
| @@ -45,11 +46,11 @@ Implement the requested change. | |||
| 45 | 46 | if len(errs) != 0 { | |
| 46 | 47 | t.Fatalf("Discover errors = %v", errs) | |
| 47 | 48 | } | |
| 48 | - | if len(profiles) != 2 { | |
| 49 | - | t.Fatalf("profiles = %d, want 2: %#v", len(profiles), profiles) | |
| 49 | + | if len(profiles) != 1 { | |
| 50 | + | t.Fatalf("profiles = %d, want 1: %#v", len(profiles), profiles) | |
| 50 | 51 | } | |
| 51 | - | if profiles[0].Name != "implementer" || profiles[1].Name != "reviewer" { | |
| 52 | - | t.Fatalf("profiles not sorted by name: %#v", profiles) | |
| 52 | + | if profiles[0].Name != "reviewer" { | |
| 53 | + | t.Fatalf("profile = %q, want reviewer", profiles[0].Name) | |
| 53 | 54 | } | |
| 54 | 55 | reviewer := Find(profiles, "reviewer") | |
| 55 | 56 | if reviewer == nil { | |
| @@ -70,9 +71,8 @@ Implement the requested change. | |||
| 70 | 71 | if reviewer.FastMode == nil || *reviewer.FastMode { | |
| 71 | 72 | t.Fatalf("fast mode = %#v, want false", reviewer.FastMode) | |
| 72 | 73 | } | |
| 73 | - | implementer := Find(profiles, "implementer") | |
| 74 | - | if implementer == nil || implementer.FastMode != nil { | |
| 75 | - | t.Fatalf("unset fast mode = %#v, want nil", implementer) | |
| 74 | + | if implementer := Find(profiles, "implementer"); implementer != nil { | |
| 75 | + | t.Fatalf("Pi profile was discovered: %#v", implementer) | |
| 76 | 76 | } | |
| 77 | 77 | } | |
| 78 | 78 | ||
| @@ -8,7 +8,8 @@ import ( | |||||
| 8 | "testing" | 8 | "testing" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | func TestDiscoverLoadsGlobalAgentsProfilesAndPiFallback(t *testing.T) { | 11 | + | func TestDiscoverLoadsGlobalAgentsProfilesAndIgnoresPiProfiles(t *testing.T) { |
| 12 | + | t.Setenv("ZUT_AGENT_PROFILES", "") | |||
| 12 | home := t.TempDir() | 13 | home := t.TempDir() | ||
| 13 | agentsDir := filepath.Join(home, ".agents", "agents") | 14 | agentsDir := filepath.Join(home, ".agents", "agents") | ||
| 14 | piDir := filepath.Join(home, ".pi", "agent", "agents") | 15 | piDir := filepath.Join(home, ".pi", "agent", "agents") | ||
| @@ -45,11 +46,11 @@ Implement the requested change. | |||||
| 45 | if len(errs) != 0 { | 46 | if len(errs) != 0 { | ||
| 46 | t.Fatalf("Discover errors = %v", errs) | 47 | t.Fatalf("Discover errors = %v", errs) | ||
| 47 | } | 48 | } | ||
| 48 | - | if len(profiles) != 2 { | 49 | + | if len(profiles) != 1 { |
| 49 | - | t.Fatalf("profiles = %d, want 2: %#v", len(profiles), profiles) | 50 | + | t.Fatalf("profiles = %d, want 1: %#v", len(profiles), profiles) |
| 50 | } | 51 | } | ||
| 51 | - | if profiles[0].Name != "implementer" || profiles[1].Name != "reviewer" { | 52 | + | if profiles[0].Name != "reviewer" { |
| 52 | - | t.Fatalf("profiles not sorted by name: %#v", profiles) | 53 | + | t.Fatalf("profile = %q, want reviewer", profiles[0].Name) |
| 53 | } | 54 | } | ||
| 54 | reviewer := Find(profiles, "reviewer") | 55 | reviewer := Find(profiles, "reviewer") | ||
| 55 | if reviewer == nil { | 56 | if reviewer == nil { | ||
| @@ -70,9 +71,8 @@ Implement the requested change. | |||||
| 70 | if reviewer.FastMode == nil || *reviewer.FastMode { | 71 | if reviewer.FastMode == nil || *reviewer.FastMode { | ||
| 71 | t.Fatalf("fast mode = %#v, want false", reviewer.FastMode) | 72 | t.Fatalf("fast mode = %#v, want false", reviewer.FastMode) | ||
| 72 | } | 73 | } | ||
| 73 | - | implementer := Find(profiles, "implementer") | 74 | + | if implementer := Find(profiles, "implementer"); implementer != nil { |
| 74 | - | if implementer == nil || implementer.FastMode != nil { | 75 | + | t.Fatalf("Pi profile was discovered: %#v", implementer) |
| 75 | - | t.Fatalf("unset fast mode = %#v, want nil", implementer) | |||
| 76 | } | 76 | } | ||
| 77 | } | 77 | } | ||
| 78 | 78 | ||||