detached ebbb70f

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 UnverifiedB5690EEEBB952194 The 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
432432
433433 ### Named subagent profiles
434434
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.
436436
437437 ### `/settings`
438438
@@ -432,7 +432,7 @@ Worktree isolation captures a patch and changed-file list without applying it. S
432 432
433 ### Named subagent profiles433 ### 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.
1111
1212 ## Profiles
1313
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.
1716
1817 ```markdown
1918 ---
@@ -11,9 +11,8 @@ journal. Starting a child never launches another zut executable.
11 11
12 ## Profiles12 ## Profiles
13 13
14-Profiles are Markdown files discovered from `~/.agents/agents/*.md`, paths in14+Profiles are Markdown files discovered from `~/.agents/agents/*.md` and paths
15-`ZUT_AGENT_PROFILES`, and the `~/.pi/agent/agents/*.md` compatibility discovery15+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 ```markdown17 ```markdown
19 ---18 ---
M packages/agent/build.go +1 -1
@@ -418,7 +418,7 @@ func findSubagentProfile(cwd, name string) (*subagents.Profile, error) {
418418 }
419419 profile := subagents.Find(profiles, name)
420420 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)
422422 }
423423 return profile, nil
424424 }
@@ -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, nil423 return profile, nil
424 }424 }
M packages/agent/subagents/profiles.go +3 -7
@@ -3,10 +3,9 @@
33 // a YAML-like metadata block followed by the agent's system prompt.
44 //
55 // 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.
109 package subagents
1110
1211 import (
@@ -233,9 +232,6 @@ func searchDirs(cwd, userHome string) []location {
233232 }
234233 if userHome != "" {
235234 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)")
239235 }
240236 return out
241237 }
@@ -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 shared5 // Discovery prefers explicitly configured directories, then the shared
6-// ~/.agents/agents directory, and finally the optional ~/.pi/agent/agents6+// ~/.agents/agents directory. Profiles are metadata plus instructions; the
7-// compatibility directory. Profiles are metadata plus instructions; the main7+// 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 full8+// full profile itself.
9-// profile itself.
10 package subagents9 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 out236 return out
241 }237 }
M packages/agent/subagents/profiles_test.go +8 -8
@@ -8,7 +8,8 @@ import (
88 "testing"
99 )
1010
11-func TestDiscoverLoadsGlobalAgentsProfilesAndPiFallback(t *testing.T) {
11+func TestDiscoverLoadsGlobalAgentsProfilesAndIgnoresPiProfiles(t *testing.T) {
12+ t.Setenv("ZUT_AGENT_PROFILES", "")
1213 home := t.TempDir()
1314 agentsDir := filepath.Join(home, ".agents", "agents")
1415 piDir := filepath.Join(home, ".pi", "agent", "agents")
@@ -45,11 +46,11 @@ Implement the requested change.
4546 if len(errs) != 0 {
4647 t.Fatalf("Discover errors = %v", errs)
4748 }
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)
5051 }
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)
5354 }
5455 reviewer := Find(profiles, "reviewer")
5556 if reviewer == nil {
@@ -70,9 +71,8 @@ Implement the requested change.
7071 if reviewer.FastMode == nil || *reviewer.FastMode {
7172 t.Fatalf("fast mode = %#v, want false", reviewer.FastMode)
7273 }
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)
7676 }
7777 }
7878
@@ -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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close