1package agent
2
3import (
4 "fmt"
5 "strings"
6 "time"
7)
8
9// ToolSummary is a name+one-line description. Kept as part of the
10// public opts type for backwards compatibility with callers that
11// still pass tool summaries in; the default prompt no longer lists
12// them because the provider already advertises tools in the request
13// body's tools[] array, so listing them again in prose is pure
14// duplication.
15type ToolSummary struct {
16 Name string
17 Description string
18}
19
20// SystemPromptOpts configures BuildSystemPrompt.
21type SystemPromptOpts struct {
22 CWD string
23 Tools []ToolSummary
24 Custom string // if set, replaces the built-in identity and docs guidance
25 Append []string // extra text appended at the end
26 Now time.Time
27 ZutDocsDir string
28}
29
30// BuildSystemPrompt constructs the system prompt.
31//
32// Design note: kept intentionally small. Every byte here is part of
33// the cached prefix on every request, so bloat is cumulatively
34// expensive. We ship only:
35//
36// - A one-paragraph identity (who zut is, what the name means,
37// what the TUI expects for output format).
38// - Compact handoff and writing-quality guidance that must survive
39// custom identities and appended context.
40// - The date + cwd footer so the model has current-context.
41//
42// Everything else (tool listing, operating guidelines, "don't run
43// sudo", "prefer edit over write", etc.) is left out because the
44// current-generation frontier models already internalise it, and
45// the tool schemas sent alongside the request carry each tool's
46// own description.
47//
48// Users who want extra biasing can use --system-prompt (replace),
49// --append-system-prompt (additive, repeatable), or drop a
50// SYSTEM.md in $ZUT_HOME that overrides the default identity.
51func BuildSystemPrompt(o SystemPromptOpts) string {
52 if o.Now.IsZero() {
53 o.Now = time.Now()
54 }
55 date := o.Now.Format("2006-01-02")
56 cwd := o.CWD
57 if cwd == "" {
58 cwd = "."
59 }
60
61 var sb strings.Builder
62
63 if o.Custom != "" {
64 sb.WriteString(o.Custom)
65 } else {
66 sb.WriteString(defaultIdentity)
67 }
68
69 sb.WriteString("\n\n")
70 sb.WriteString(compactedSummaryHandoffInstruction)
71
72 if o.Custom == "" && strings.TrimSpace(o.ZutDocsDir) != "" {
73 sb.WriteString("\n\nZut's own docs are installed under ")
74 sb.WriteString(o.ZutDocsDir)
75 sb.WriteString("; use the read tool there when you need details about zut RPC, extensions, skills, or built-in behaviour.")
76 }
77
78 for _, a := range o.Append {
79 if strings.TrimSpace(a) == "" {
80 continue
81 }
82 sb.WriteString("\n\n")
83 sb.WriteString(a)
84 }
85
86 // Keep the universal writing policy after optional context so narrower
87 // addenda cannot accidentally disable it.
88 sb.WriteString("\n\n")
89 sb.WriteString(writingGuidance)
90
91 fmt.Fprintf(&sb, "\n\nCurrent date: %s\nCurrent working directory: %s\n", date, cwd)
92 return sb.String()
93}
94
95const 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
97Your 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.
98
99For 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
101const 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
103const 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.`
104
105// WritingGuidance returns the universal policy kept at the end of resolved
106// prompt context, including after interactive prompt updates.
107func WritingGuidance() string {
108 return writingGuidance
109}