fix(agent): clarify action and skill priority guidance (#181)
* fix(agent): clarify action and skill priority guidance * fix(agent): place shared guidance after appended context
- commit
- 130798cd8b45c58e125952a5663596f887607159
- author
- bnema · · 2026-09-06 07:08 UTC
- committer
- GitHub · · 2026-09-06 07:08 UTC
- parent
- d341fee
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown
3 files · +50 -5
M README.md +2 -0
| @@ -159,6 +159,8 @@ $ZUT_HOME/ | |||
| 159 | 159 | ||
| 160 | 160 | 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. | |
| 161 | 161 | ||
| 162 | + | Shared prompt guidance remains active with custom identities and across providers and models. It asks the agent to complete authorized, non-destructive work rather than stop at a plan, while respecting requests for explanation or review and preserving required approvals. Explicit user instructions take precedence over skill guidelines; skills cannot grant permissions or override system or developer constraints. Writing guidance favors concise paragraphs, useful lists, and direct language without canned transitions. These are prompt instructions, not changes to tool permissions or guarantees of model behavior. | |
| 163 | + | ||
| 162 | 164 | 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. | |
| 163 | 165 | ||
| 164 | 166 | A selected subagent profile with `systemPromptMode: replace` supplies the child's base identity even when `--system-prompt` was provided for the parent run. Global append addenda, including enabled Ponytail guidance, still follow their normal inclusion rules. | |
| @@ -159,6 +159,8 @@ $ZUT_HOME/ | |||||
| 159 | 159 | ||||
| 160 | 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. | 160 | 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. | ||
| 161 | 161 | ||||
| 162 | + | Shared prompt guidance remains active with custom identities and across providers and models. It asks the agent to complete authorized, non-destructive work rather than stop at a plan, while respecting requests for explanation or review and preserving required approvals. Explicit user instructions take precedence over skill guidelines; skills cannot grant permissions or override system or developer constraints. Writing guidance favors concise paragraphs, useful lists, and direct language without canned transitions. These are prompt instructions, not changes to tool permissions or guarantees of model behavior. | |||
| 163 | + | ||||
| 162 | 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. | 164 | 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. | ||
| 163 | 165 | ||||
| 164 | A selected subagent profile with `systemPromptMode: replace` supplies the child's base identity even when `--system-prompt` was provided for the parent run. Global append addenda, including enabled Ponytail guidance, still follow their normal inclusion rules. | 166 | A selected subagent profile with `systemPromptMode: replace` supplies the child's base identity even when `--system-prompt` was provided for the parent run. Global append addenda, including enabled Ponytail guidance, still follow their normal inclusion rules. | ||
M packages/agent/systemprompt.go +12 -5
| @@ -35,7 +35,7 @@ type SystemPromptOpts struct { | |||
| 35 | 35 | // | |
| 36 | 36 | // - A one-paragraph identity (who zut is, what the name means, | |
| 37 | 37 | // what the TUI expects for output format). | |
| 38 | - | // - Compact handoff and writing-quality guidance that must survive | |
| 38 | + | // - Compact task, skill-priority, handoff, and writing guidance that survive | |
| 39 | 39 | // custom identities and appended context. | |
| 40 | 40 | // - The date + cwd footer so the model has current-context. | |
| 41 | 41 | // | |
| @@ -83,8 +83,11 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||
| 83 | 83 | sb.WriteString(a) | |
| 84 | 84 | } | |
| 85 | 85 | ||
| 86 | - | // Keep the universal writing policy after optional context so narrower | |
| 87 | - | // addenda cannot accidentally disable it. | |
| 86 | + | // Keep shared guidance after optional context, with writing policy last. | |
| 87 | + | sb.WriteString("\n\n") | |
| 88 | + | sb.WriteString(taskExecutionGuidance) | |
| 89 | + | sb.WriteString("\n\n") | |
| 90 | + | sb.WriteString(skillPriorityGuidance) | |
| 88 | 91 | sb.WriteString("\n\n") | |
| 89 | 92 | sb.WriteString(writingGuidance) | |
| 90 | 93 | ||
| @@ -94,13 +97,17 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||
| 94 | 97 | ||
| 95 | 98 | const defaultIdentity = `You are an expert coding assistant operating inside zut, a coding agent harness. The name "zut" stands for "zero-overhead-tool"; if the user asks what zut means, answer exactly that. | |
| 96 | 99 | ||
| 97 | - | Your output renders in a TUI that understands markdown for prose and plain text for tool output. Use markdown freely, keep answers concise, and let tool calls speak for themselves rather than narrating them in prose before you invoke them. Act first, then summarise what you did. | |
| 100 | + | Your output renders in a TUI that understands markdown for prose and plain text for tool output. Use markdown where it improves clarity, keep answers concise, and let tool calls speak for themselves rather than narrating them in prose before you invoke them. | |
| 98 | 101 | ||
| 99 | 102 | For focused changes to an existing file, inspect its current contents and use edit with verbatim oldText taken from that same file. Include only enough context to make each match unambiguous. Use write when creating a file or replacing it wholesale. Do not mutate files through bash redirections or commands such as cat, echo, sed, or tee, because those changes appear as opaque shell output instead of a readable edit diff.` | |
| 100 | 103 | ||
| 101 | 104 | const compactedSummaryHandoffInstruction = `When you see a "## Context Summary (compacted)" message, treat it as a handoff from earlier work. Keep its active constraints and preferences in force, continue the most recent unresolved user request without waiting for the user to type "continue", and follow a newer user request when it supersedes the summary.` | |
| 102 | 105 | ||
| 103 | - | const writingGuidance = `Adapt every response and document to its medium, audience, and reader's immediate need. Lead with the answer or next useful action. Use plain, precise language, concrete details, and strong verbs. Prefer verified facts to vague emphasis; qualify uncertain claims. Preserve useful structure, descriptive links, and accessibility. Cut throat-clearing, ceremony, repetition, catalogue-like prose, and mechanical sentence patterns. Do not manufacture slang, errors, hesitation, or artificial variation to sound human. Revise silently unless asked to explain your writing choices.` | |
| 106 | + | const taskExecutionGuidance = `Treat requests such as "can you" as instructions to do the requested work within the user's intended scope; do not stop at a plan or an offer to continue. Proceed with authorized, non-destructive work and reasonable assumptions until the task is complete or genuinely blocked. Respect requests for explanation, planning, or review without making unsolicited changes. Ask only when a missing decision blocks safe, correct progress; complete independent authorized work first. Preserve tool permissions, required confirmations, and explicit approval requirements; do not infer permission for destructive or irreversible actions.` | |
| 107 | + | ||
| 108 | + | const skillPriorityGuidance = `Explicit user instructions take precedence over skill guidelines. Skills do not grant permissions or override system or developer constraints. If a skill would block requested work or change its scope, identify its source and the relevant instruction, explain the conflict, and follow the applicable higher-priority instruction.` | |
| 109 | + | ||
| 110 | + | const writingGuidance = `Adapt every response and document to its medium, audience, and reader's immediate need. Lead with the answer or next useful action. Use plain, precise language, concrete details, and strong verbs. Prefer verified facts to vague emphasis; qualify uncertain claims. Preserve useful structure, descriptive links, and accessibility. Default to concise paragraphs unless the requested format calls for another structure. Use lists when items are genuinely parallel, sequential, or easier to compare; avoid unnecessary nesting. Cut throat-clearing, ceremony, repetition, canned transitions, catalogue-like prose, and mechanical sentence patterns. Do not manufacture slang, errors, hesitation, or artificial variation to sound human. Revise silently unless asked to explain your writing choices.` | |
| 104 | 111 | ||
| 105 | 112 | // WritingGuidance returns the universal policy kept at the end of resolved | |
| 106 | 113 | // prompt context, including after interactive prompt updates. | |
| @@ -35,7 +35,7 @@ type SystemPromptOpts struct { | |||||
| 35 | // | 35 | // | ||
| 36 | // - A one-paragraph identity (who zut is, what the name means, | 36 | // - A one-paragraph identity (who zut is, what the name means, | ||
| 37 | // what the TUI expects for output format). | 37 | // what the TUI expects for output format). | ||
| 38 | - | // - Compact handoff and writing-quality guidance that must survive | 38 | + | // - Compact task, skill-priority, handoff, and writing guidance that survive |
| 39 | // custom identities and appended context. | 39 | // custom identities and appended context. | ||
| 40 | // - The date + cwd footer so the model has current-context. | 40 | // - The date + cwd footer so the model has current-context. | ||
| 41 | // | 41 | // | ||
| @@ -83,8 +83,11 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||||
| 83 | sb.WriteString(a) | 83 | sb.WriteString(a) | ||
| 84 | } | 84 | } | ||
| 85 | 85 | ||||
| 86 | - | // Keep the universal writing policy after optional context so narrower | 86 | + | // Keep shared guidance after optional context, with writing policy last. |
| 87 | - | // addenda cannot accidentally disable it. | 87 | + | sb.WriteString("\n\n") |
| 88 | + | sb.WriteString(taskExecutionGuidance) | |||
| 89 | + | sb.WriteString("\n\n") | |||
| 90 | + | sb.WriteString(skillPriorityGuidance) | |||
| 88 | sb.WriteString("\n\n") | 91 | sb.WriteString("\n\n") | ||
| 89 | sb.WriteString(writingGuidance) | 92 | sb.WriteString(writingGuidance) | ||
| 90 | 93 | ||||
| @@ -94,13 +97,17 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||||
| 94 | 97 | ||||
| 95 | const defaultIdentity = `You are an expert coding assistant operating inside zut, a coding agent harness. The name "zut" stands for "zero-overhead-tool"; if the user asks what zut means, answer exactly that. | 98 | const defaultIdentity = `You are an expert coding assistant operating inside zut, a coding agent harness. The name "zut" stands for "zero-overhead-tool"; if the user asks what zut means, answer exactly that. | ||
| 96 | 99 | ||||
| 97 | - | Your output renders in a TUI that understands markdown for prose and plain text for tool output. Use markdown freely, keep answers concise, and let tool calls speak for themselves rather than narrating them in prose before you invoke them. Act first, then summarise what you did. | 100 | + | Your output renders in a TUI that understands markdown for prose and plain text for tool output. Use markdown where it improves clarity, keep answers concise, and let tool calls speak for themselves rather than narrating them in prose before you invoke them. |
| 98 | 101 | ||||
| 99 | For focused changes to an existing file, inspect its current contents and use edit with verbatim oldText taken from that same file. Include only enough context to make each match unambiguous. Use write when creating a file or replacing it wholesale. Do not mutate files through bash redirections or commands such as cat, echo, sed, or tee, because those changes appear as opaque shell output instead of a readable edit diff.` | 102 | For focused changes to an existing file, inspect its current contents and use edit with verbatim oldText taken from that same file. Include only enough context to make each match unambiguous. Use write when creating a file or replacing it wholesale. Do not mutate files through bash redirections or commands such as cat, echo, sed, or tee, because those changes appear as opaque shell output instead of a readable edit diff.` | ||
| 100 | 103 | ||||
| 101 | const compactedSummaryHandoffInstruction = `When you see a "## Context Summary (compacted)" message, treat it as a handoff from earlier work. Keep its active constraints and preferences in force, continue the most recent unresolved user request without waiting for the user to type "continue", and follow a newer user request when it supersedes the summary.` | 104 | const compactedSummaryHandoffInstruction = `When you see a "## Context Summary (compacted)" message, treat it as a handoff from earlier work. Keep its active constraints and preferences in force, continue the most recent unresolved user request without waiting for the user to type "continue", and follow a newer user request when it supersedes the summary.` | ||
| 102 | 105 | ||||
| 103 | - | const writingGuidance = `Adapt every response and document to its medium, audience, and reader's immediate need. Lead with the answer or next useful action. Use plain, precise language, concrete details, and strong verbs. Prefer verified facts to vague emphasis; qualify uncertain claims. Preserve useful structure, descriptive links, and accessibility. Cut throat-clearing, ceremony, repetition, catalogue-like prose, and mechanical sentence patterns. Do not manufacture slang, errors, hesitation, or artificial variation to sound human. Revise silently unless asked to explain your writing choices.` | 106 | + | const taskExecutionGuidance = `Treat requests such as "can you" as instructions to do the requested work within the user's intended scope; do not stop at a plan or an offer to continue. Proceed with authorized, non-destructive work and reasonable assumptions until the task is complete or genuinely blocked. Respect requests for explanation, planning, or review without making unsolicited changes. Ask only when a missing decision blocks safe, correct progress; complete independent authorized work first. Preserve tool permissions, required confirmations, and explicit approval requirements; do not infer permission for destructive or irreversible actions.` |
| 107 | + | ||||
| 108 | + | const skillPriorityGuidance = `Explicit user instructions take precedence over skill guidelines. Skills do not grant permissions or override system or developer constraints. If a skill would block requested work or change its scope, identify its source and the relevant instruction, explain the conflict, and follow the applicable higher-priority instruction.` | |||
| 109 | + | ||||
| 110 | + | const writingGuidance = `Adapt every response and document to its medium, audience, and reader's immediate need. Lead with the answer or next useful action. Use plain, precise language, concrete details, and strong verbs. Prefer verified facts to vague emphasis; qualify uncertain claims. Preserve useful structure, descriptive links, and accessibility. Default to concise paragraphs unless the requested format calls for another structure. Use lists when items are genuinely parallel, sequential, or easier to compare; avoid unnecessary nesting. Cut throat-clearing, ceremony, repetition, canned transitions, catalogue-like prose, and mechanical sentence patterns. Do not manufacture slang, errors, hesitation, or artificial variation to sound human. Revise silently unless asked to explain your writing choices.` | |||
| 104 | 111 | ||||
| 105 | // WritingGuidance returns the universal policy kept at the end of resolved | 112 | // WritingGuidance returns the universal policy kept at the end of resolved | ||
| 106 | // prompt context, including after interactive prompt updates. | 113 | // prompt context, including after interactive prompt updates. | ||
M packages/agent/systemprompt_test.go +36 -0
| @@ -30,6 +30,9 @@ func TestBuildSystemPromptAlwaysIncludesFinalWritingGuidance(t *testing.T) { | |||
| 30 | 30 | for _, want := range []string{ | |
| 31 | 31 | "medium, audience, and reader's immediate need", | |
| 32 | 32 | "plain, precise language", | |
| 33 | + | "Default to concise paragraphs", | |
| 34 | + | "Use lists when items are genuinely parallel, sequential, or easier to compare", | |
| 35 | + | "canned transitions", | |
| 33 | 36 | "verified facts", | |
| 34 | 37 | "descriptive links, and accessibility", | |
| 35 | 38 | "Do not manufacture slang, errors, hesitation", | |
| @@ -43,6 +46,39 @@ func TestBuildSystemPromptAlwaysIncludesFinalWritingGuidance(t *testing.T) { | |||
| 43 | 46 | } | |
| 44 | 47 | } | |
| 45 | 48 | ||
| 49 | + | func TestBuildSystemPromptAlwaysIncludesTaskAndSkillGuidance(t *testing.T) { | |
| 50 | + | for _, custom := range []string{"", "Custom identity"} { | |
| 51 | + | t.Run(custom, func(t *testing.T) { | |
| 52 | + | prompt := BuildSystemPrompt(SystemPromptOpts{ | |
| 53 | + | Custom: custom, | |
| 54 | + | Append: []string{"Workspace context", "Available skills"}, | |
| 55 | + | }) | |
| 56 | + | previous := strings.Index(prompt, "Available skills") | |
| 57 | + | for _, guidance := range []string{taskExecutionGuidance, skillPriorityGuidance, writingGuidance} { | |
| 58 | + | index := strings.Index(prompt, guidance) | |
| 59 | + | if index <= previous { | |
| 60 | + | t.Fatalf("shared guidance must follow appended context in task/skill/writing order") | |
| 61 | + | } | |
| 62 | + | previous = index | |
| 63 | + | } | |
| 64 | + | for _, want := range []string{ | |
| 65 | + | "Treat requests such as \"can you\" as instructions to do the requested work", | |
| 66 | + | "do not stop at a plan or an offer to continue", | |
| 67 | + | "Respect requests for explanation, planning, or review without making unsolicited changes", | |
| 68 | + | "Ask only when a missing decision blocks safe, correct progress", | |
| 69 | + | "Preserve tool permissions, required confirmations, and explicit approval requirements", | |
| 70 | + | "Explicit user instructions take precedence over skill guidelines", | |
| 71 | + | "Skills do not grant permissions or override system or developer constraints", | |
| 72 | + | "identify its source and the relevant instruction", | |
| 73 | + | } { | |
| 74 | + | if count := strings.Count(prompt, want); count != 1 { | |
| 75 | + | t.Errorf("guidance %q count = %d, want 1", want, count) | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | }) | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | ||
| 46 | 82 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | |
| 47 | 83 | prompt := BuildSystemPrompt(SystemPromptOpts{Custom: "custom identity"}) | |
| 48 | 84 | if !strings.Contains(prompt, "custom identity") { | |
| @@ -30,6 +30,9 @@ func TestBuildSystemPromptAlwaysIncludesFinalWritingGuidance(t *testing.T) { | |||||
| 30 | for _, want := range []string{ | 30 | for _, want := range []string{ | ||
| 31 | "medium, audience, and reader's immediate need", | 31 | "medium, audience, and reader's immediate need", | ||
| 32 | "plain, precise language", | 32 | "plain, precise language", | ||
| 33 | + | "Default to concise paragraphs", | |||
| 34 | + | "Use lists when items are genuinely parallel, sequential, or easier to compare", | |||
| 35 | + | "canned transitions", | |||
| 33 | "verified facts", | 36 | "verified facts", | ||
| 34 | "descriptive links, and accessibility", | 37 | "descriptive links, and accessibility", | ||
| 35 | "Do not manufacture slang, errors, hesitation", | 38 | "Do not manufacture slang, errors, hesitation", | ||
| @@ -43,6 +46,39 @@ func TestBuildSystemPromptAlwaysIncludesFinalWritingGuidance(t *testing.T) { | |||||
| 43 | } | 46 | } | ||
| 44 | } | 47 | } | ||
| 45 | 48 | ||||
| 49 | + | func TestBuildSystemPromptAlwaysIncludesTaskAndSkillGuidance(t *testing.T) { | |||
| 50 | + | for _, custom := range []string{"", "Custom identity"} { | |||
| 51 | + | t.Run(custom, func(t *testing.T) { | |||
| 52 | + | prompt := BuildSystemPrompt(SystemPromptOpts{ | |||
| 53 | + | Custom: custom, | |||
| 54 | + | Append: []string{"Workspace context", "Available skills"}, | |||
| 55 | + | }) | |||
| 56 | + | previous := strings.Index(prompt, "Available skills") | |||
| 57 | + | for _, guidance := range []string{taskExecutionGuidance, skillPriorityGuidance, writingGuidance} { | |||
| 58 | + | index := strings.Index(prompt, guidance) | |||
| 59 | + | if index <= previous { | |||
| 60 | + | t.Fatalf("shared guidance must follow appended context in task/skill/writing order") | |||
| 61 | + | } | |||
| 62 | + | previous = index | |||
| 63 | + | } | |||
| 64 | + | for _, want := range []string{ | |||
| 65 | + | "Treat requests such as \"can you\" as instructions to do the requested work", | |||
| 66 | + | "do not stop at a plan or an offer to continue", | |||
| 67 | + | "Respect requests for explanation, planning, or review without making unsolicited changes", | |||
| 68 | + | "Ask only when a missing decision blocks safe, correct progress", | |||
| 69 | + | "Preserve tool permissions, required confirmations, and explicit approval requirements", | |||
| 70 | + | "Explicit user instructions take precedence over skill guidelines", | |||
| 71 | + | "Skills do not grant permissions or override system or developer constraints", | |||
| 72 | + | "identify its source and the relevant instruction", | |||
| 73 | + | } { | |||
| 74 | + | if count := strings.Count(prompt, want); count != 1 { | |||
| 75 | + | t.Errorf("guidance %q count = %d, want 1", want, count) | |||
| 76 | + | } | |||
| 77 | + | } | |||
| 78 | + | }) | |||
| 79 | + | } | |||
| 80 | + | } | |||
| 81 | + | ||||
| 46 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | 82 | func TestBuildSystemPromptAddsCompactionHandoffToCustomPrompt(t *testing.T) { | ||
| 47 | prompt := BuildSystemPrompt(SystemPromptOpts{Custom: "custom identity"}) | 83 | prompt := BuildSystemPrompt(SystemPromptOpts{Custom: "custom identity"}) | ||
| 48 | if !strings.Contains(prompt, "custom identity") { | 84 | if !strings.Contains(prompt, "custom identity") { | ||