fix(agent): omit docs from custom system prompts
- commit
- 2dc650c30b415570e5e1ca35afde1aa3cbbf335e
- author
- brice · · 2026-08-10 13:52 UTC
- committer
- brice · · 2026-08-11 07:21 UTC
- parent
- da49ca5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +42 -5
M README.md +3 -3
| @@ -145,7 +145,7 @@ $ZUT_HOME/ | |||
| 145 | 145 | └── logs/ # app log files | |
| 146 | 146 | ``` | |
| 147 | 147 | ||
| 148 | - | Drop a `SYSTEM.md` in `$ZUT_HOME` to replace the built-in identity for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity. | |
| 148 | + | Drop a `SYSTEM.md` in `$ZUT_HOME` to replace the built-in identity and docs guidance for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity. | |
| 149 | 149 | ||
| 150 | 150 | Ponytail coding mode is enabled by default, including when an existing `config.json` does not contain a `ponytail_enabled` field. When disabled, its compact guidance is omitted from resolved system prompts; when enabled, the guidance tells the model to apply itself only to engineering work. The setting persists its explicit on/off choice in `$ZUT_HOME/config.json` and can be changed from `/settings`. It is advisory guidance only: it does not change tool permissions, confirmations, jail behavior, or sandboxing. | |
| 151 | 151 | ||
| @@ -186,9 +186,9 @@ Treat questions and discussions as requests for explanation. Do not edit files o | |||
| 186 | 186 | |---|---|---| | |
| 187 | 187 | | `$ZUT_HOME/AGENTS.md` | global, every run | appended to the default prompt | | |
| 188 | 188 | | `./AGENTS.md` (and parent dirs) | project | appended to the default prompt | | |
| 189 | - | | `$ZUT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity; append addenda remain | | |
| 189 | + | | `$ZUT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity and docs guidance; append addenda remain | | |
| 190 | 190 | | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) | | |
| 191 | - | | `--system-prompt <text>` | single run | replaces the built-in identity for one invocation; append addenda remain | | |
| 191 | + | | `--system-prompt <text>` | single run | replaces the built-in identity and docs guidance for one invocation; append addenda remain | | |
| 192 | 192 | ||
| 193 | 193 | > **Note:** zut does not read a `CLAUDE.md` instruction file. The only Claude-compatible thing it picks up is skills under `.claude/skills/`. If you are migrating from Claude Code, move that content into `AGENTS.md` (global or per-project) and zut will use it. | |
| 194 | 194 | ||
| @@ -145,7 +145,7 @@ $ZUT_HOME/ | |||||
| 145 | └── logs/ # app log files | 145 | └── logs/ # app log files | ||
| 146 | ``` | 146 | ``` | ||
| 147 | 147 | ||||
| 148 | - | Drop a `SYSTEM.md` in `$ZUT_HOME` to replace the built-in identity for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity. | 148 | + | Drop a `SYSTEM.md` in `$ZUT_HOME` to replace the built-in identity and docs guidance for every run. Existing append addenda, including `AGENTS.md`, skills, and enabled Ponytail coding mode, remain layered on top. `--system-prompt` wins per invocation unless a selected replace-mode subagent profile supplies the child's identity. Delete the file to revert to the default identity. |
| 149 | 149 | ||||
| 150 | Ponytail coding mode is enabled by default, including when an existing `config.json` does not contain a `ponytail_enabled` field. When disabled, its compact guidance is omitted from resolved system prompts; when enabled, the guidance tells the model to apply itself only to engineering work. The setting persists its explicit on/off choice in `$ZUT_HOME/config.json` and can be changed from `/settings`. It is advisory guidance only: it does not change tool permissions, confirmations, jail behavior, or sandboxing. | 150 | Ponytail coding mode is enabled by default, including when an existing `config.json` does not contain a `ponytail_enabled` field. When disabled, its compact guidance is omitted from resolved system prompts; when enabled, the guidance tells the model to apply itself only to engineering work. The setting persists its explicit on/off choice in `$ZUT_HOME/config.json` and can be changed from `/settings`. It is advisory guidance only: it does not change tool permissions, confirmations, jail behavior, or sandboxing. | ||
| 151 | 151 | ||||
| @@ -186,9 +186,9 @@ Treat questions and discussions as requests for explanation. Do not edit files o | |||||
| 186 | |---|---|---| | 186 | |---|---|---| | ||
| 187 | | `$ZUT_HOME/AGENTS.md` | global, every run | appended to the default prompt | | 187 | | `$ZUT_HOME/AGENTS.md` | global, every run | appended to the default prompt | | ||
| 188 | | `./AGENTS.md` (and parent dirs) | project | appended to the default prompt | | 188 | | `./AGENTS.md` (and parent dirs) | project | appended to the default prompt | | ||
| 189 | - | | `$ZUT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity; append addenda remain | | 189 | + | | `$ZUT_HOME/SYSTEM.md` | global, every run | replaces the built-in identity and docs guidance; append addenda remain | |
| 190 | | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) | | 190 | | `--append-system-prompt <text>` | single run | appends for one invocation (repeatable) | | ||
| 191 | - | | `--system-prompt <text>` | single run | replaces the built-in identity for one invocation; append addenda remain | | 191 | + | | `--system-prompt <text>` | single run | replaces the built-in identity and docs guidance for one invocation; append addenda remain | |
| 192 | 192 | ||||
| 193 | > **Note:** zut does not read a `CLAUDE.md` instruction file. The only Claude-compatible thing it picks up is skills under `.claude/skills/`. If you are migrating from Claude Code, move that content into `AGENTS.md` (global or per-project) and zut will use it. | 193 | > **Note:** zut does not read a `CLAUDE.md` instruction file. The only Claude-compatible thing it picks up is skills under `.claude/skills/`. If you are migrating from Claude Code, move that content into `AGENTS.md` (global or per-project) and zut will use it. | ||
| 194 | 194 | ||||
M packages/agent/systemprompt.go +2 -2
| @@ -21,7 +21,7 @@ type ToolSummary struct { | |||
| 21 | 21 | type SystemPromptOpts struct { | |
| 22 | 22 | CWD string | |
| 23 | 23 | Tools []ToolSummary | |
| 24 | - | Custom string // if set, replaces the default identity entirely | |
| 24 | + | Custom string // if set, replaces the built-in identity and docs guidance | |
| 25 | 25 | Append []string // extra text appended at the end | |
| 26 | 26 | Now time.Time | |
| 27 | 27 | ZutDocsDir string | |
| @@ -67,7 +67,7 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||
| 67 | 67 | sb.WriteString("\n\n") | |
| 68 | 68 | sb.WriteString(compactedSummaryHandoffInstruction) | |
| 69 | 69 | ||
| 70 | - | if strings.TrimSpace(o.ZutDocsDir) != "" { | |
| 70 | + | if o.Custom == "" && strings.TrimSpace(o.ZutDocsDir) != "" { | |
| 71 | 71 | sb.WriteString("\n\nZut's own docs are installed under ") | |
| 72 | 72 | sb.WriteString(o.ZutDocsDir) | |
| 73 | 73 | sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.") | |
| @@ -21,7 +21,7 @@ type ToolSummary struct { | |||||
| 21 | type SystemPromptOpts struct { | 21 | type SystemPromptOpts struct { | ||
| 22 | CWD string | 22 | CWD string | ||
| 23 | Tools []ToolSummary | 23 | Tools []ToolSummary | ||
| 24 | - | Custom string // if set, replaces the default identity entirely | 24 | + | Custom string // if set, replaces the built-in identity and docs guidance |
| 25 | Append []string // extra text appended at the end | 25 | Append []string // extra text appended at the end | ||
| 26 | Now time.Time | 26 | Now time.Time | ||
| 27 | ZutDocsDir string | 27 | ZutDocsDir string | ||
| @@ -67,7 +67,7 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||||
| 67 | sb.WriteString("\n\n") | 67 | sb.WriteString("\n\n") | ||
| 68 | sb.WriteString(compactedSummaryHandoffInstruction) | 68 | sb.WriteString(compactedSummaryHandoffInstruction) | ||
| 69 | 69 | ||||
| 70 | - | if strings.TrimSpace(o.ZutDocsDir) != "" { | 70 | + | if o.Custom == "" && strings.TrimSpace(o.ZutDocsDir) != "" { |
| 71 | sb.WriteString("\n\nZut's own docs are installed under ") | 71 | sb.WriteString("\n\nZut's own docs are installed under ") | ||
| 72 | sb.WriteString(o.ZutDocsDir) | 72 | sb.WriteString(o.ZutDocsDir) | ||
| 73 | sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.") | 73 | sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.") | ||
M packages/agent/systemprompt_test.go +37 -0
| @@ -3,6 +3,7 @@ package agent | |||
| 3 | 3 | import ( | |
| 4 | 4 | "strings" | |
| 5 | 5 | "testing" | |
| 6 | + | "time" | |
| 6 | 7 | ) | |
| 7 | 8 | ||
| 8 | 9 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | |
| @@ -22,3 +23,39 @@ func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | |||
| 22 | 23 | } | |
| 23 | 24 | } | |
| 24 | 25 | } | |
| 26 | + | ||
| 27 | + | func TestBuildSystemPromptCustomOmitsBuiltInDocs(t *testing.T) { | |
| 28 | + | got := BuildSystemPrompt(SystemPromptOpts{ | |
| 29 | + | CWD: "/workspace", | |
| 30 | + | Custom: "Custom instructions", | |
| 31 | + | Append: []string{"Additional context"}, | |
| 32 | + | Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC), | |
| 33 | + | ZutDocsDir: "/zut/docs", | |
| 34 | + | }) | |
| 35 | + | ||
| 36 | + | if strings.Contains(got, "Zut's own docs") || strings.Contains(got, "/zut/docs") { | |
| 37 | + | t.Fatalf("custom prompt includes built-in docs guidance:\n%s", got) | |
| 38 | + | } | |
| 39 | + | for _, want := range []string{ | |
| 40 | + | "Custom instructions", | |
| 41 | + | "Additional context", | |
| 42 | + | "Current date: 2026-08-06", | |
| 43 | + | "Current working directory: /workspace", | |
| 44 | + | } { | |
| 45 | + | if !strings.Contains(got, want) { | |
| 46 | + | t.Errorf("custom prompt missing %q:\n%s", want, got) | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | func TestBuildSystemPromptDefaultIncludesBuiltInDocs(t *testing.T) { | |
| 52 | + | got := BuildSystemPrompt(SystemPromptOpts{ | |
| 53 | + | CWD: "/workspace", | |
| 54 | + | Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC), | |
| 55 | + | ZutDocsDir: "/zut/docs", | |
| 56 | + | }) | |
| 57 | + | ||
| 58 | + | if !strings.Contains(got, "Zut's own docs are installed under /zut/docs") { | |
| 59 | + | t.Fatalf("default prompt missing built-in docs guidance:\n%s", got) | |
| 60 | + | } | |
| 61 | + | } | |
| @@ -3,6 +3,7 @@ package agent | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "strings" | 4 | "strings" | ||
| 5 | "testing" | 5 | "testing" | ||
| 6 | + | "time" | |||
| 6 | ) | 7 | ) | ||
| 7 | 8 | ||||
| 8 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | 9 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | ||
| @@ -22,3 +23,39 @@ func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | |||||
| 22 | } | 23 | } | ||
| 23 | } | 24 | } | ||
| 24 | } | 25 | } | ||
| 26 | + | ||||
| 27 | + | func TestBuildSystemPromptCustomOmitsBuiltInDocs(t *testing.T) { | |||
| 28 | + | got := BuildSystemPrompt(SystemPromptOpts{ | |||
| 29 | + | CWD: "/workspace", | |||
| 30 | + | Custom: "Custom instructions", | |||
| 31 | + | Append: []string{"Additional context"}, | |||
| 32 | + | Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC), | |||
| 33 | + | ZutDocsDir: "/zut/docs", | |||
| 34 | + | }) | |||
| 35 | + | ||||
| 36 | + | if strings.Contains(got, "Zut's own docs") || strings.Contains(got, "/zut/docs") { | |||
| 37 | + | t.Fatalf("custom prompt includes built-in docs guidance:\n%s", got) | |||
| 38 | + | } | |||
| 39 | + | for _, want := range []string{ | |||
| 40 | + | "Custom instructions", | |||
| 41 | + | "Additional context", | |||
| 42 | + | "Current date: 2026-08-06", | |||
| 43 | + | "Current working directory: /workspace", | |||
| 44 | + | } { | |||
| 45 | + | if !strings.Contains(got, want) { | |||
| 46 | + | t.Errorf("custom prompt missing %q:\n%s", want, got) | |||
| 47 | + | } | |||
| 48 | + | } | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | func TestBuildSystemPromptDefaultIncludesBuiltInDocs(t *testing.T) { | |||
| 52 | + | got := BuildSystemPrompt(SystemPromptOpts{ | |||
| 53 | + | CWD: "/workspace", | |||
| 54 | + | Now: time.Date(2026, time.August, 6, 0, 0, 0, 0, time.UTC), | |||
| 55 | + | ZutDocsDir: "/zut/docs", | |||
| 56 | + | }) | |||
| 57 | + | ||||
| 58 | + | if !strings.Contains(got, "Zut's own docs are installed under /zut/docs") { | |||
| 59 | + | t.Fatalf("default prompt missing built-in docs guidance:\n%s", got) | |||
| 60 | + | } | |||
| 61 | + | } | |||