detached b188801

perf(prompt): slim system prompt from 1064 tokens to 126 tokens

Earlier I bloated the default system prompt on purpose to cross
Anthropic's 1024-token cacheable-prefix floor, on the theory that
small prompts lose every fresh session to R=0. That theory turned
out to be wrong: the real reason fresh sessions looked expensive
was the double-counting bug in Stream() (message_start and
message_delta both ship cumulative usage, we were summing both).
Once that was fixed, the padding stopped earning its bytes.

New default:

  You are zot, a lightweight terminal coding agent. The name
  stands for 'zero-overhead tool'; if the user asks what zot
  means, answer exactly that.

  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.

Removed the tool-listing section (the provider already advertises
tools in the request's tools[] array, so listing them in prose was
pure duplication) and the full operating-guidelines block
(frontier models already internalise "prefer edit over write",
"read before editing", "don't run sudo", etc.).

Benchmarked head-to-head on a fresh 2-turn session with the same
scenario:

  heavy prompt (1064 tokens total):
    turn 1: in=7 R=0 W=7550 out=117    $0.0501
    turn 2: in=6 R=2714 W=2176 out=61  $0.0165
    total:                              $0.0666

  slim prompt (126 tokens total):
    turn 1: in=1522 R=0 W=3636 out=111 $0.0334
    turn 2: in=6 R=3642 W=60 out=71    $0.0040
    total:                              $0.0374

Slim is 44% cheaper across two turns. Turn 2 is where it really
pays off (W=60 vs W=2176): every extra token in the base prompt
gets re-written on every turn because the trailing-user cache
checkpoint keeps advancing.

--system-prompt, --append-system-prompt, and $ZOT_HOME/SYSTEM.md
still work and take precedence for users who want more biasing.

'what does zot mean?' still returns exactly 'zero-overhead tool'.
commit
b188801c32eae959bd333f4a94ec8aec08f8c5c1
author
patriceckhart · · 2026-04-19 17:23 UTC
parent
d50eed7
signature
unsigned
1 file · +24 -61
M internal/agent/systemprompt.go +24 -61
@@ -6,10 +6,12 @@ import (
66 "time"
77 )
88
9-// ToolSummary is a name+one-line description used when rendering the
10-// "available tools" section of the system prompt. Passed from the
11-// resolved registry so the prompt can list every tool the agent has,
12-// including any extension-contributed ones.
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.
1315 type ToolSummary struct {
1416 Name string
1517 Description string
@@ -19,32 +21,30 @@ type ToolSummary struct {
1921 type SystemPromptOpts struct {
2022 CWD string
2123 Tools []ToolSummary
22- Custom string // if set, replaces the default identity/guidelines sections
24+ Custom string // if set, replaces the default identity entirely
2325 Append []string // extra text appended at the end
2426 Now time.Time
2527 }
2628
2729 // BuildSystemPrompt constructs the system prompt.
2830 //
29-// Design note on size: the prompt is sized deliberately. Anthropic's
30-// prompt cache has a 1024-token minimum for the cached prefix on
31-// Opus-tier models; anything smaller is NOT cached. An under-1024
32-// prompt loses every fresh-session turn to "R=0" because nothing
33-// about the prefix persists across invocations. Ours sits comfortably
34-// above the floor so the identity + tools + this template together
35-// are cached Anthropic-side across every zot invocation that shares
36-// the same tool set.
31+// Design note: kept intentionally small. Every byte here is part of
32+// the cached prefix on every request, so bloat is cumulatively
33+// expensive. We ship only:
3734 //
38-// What stays in: the identity + the tools section + concrete
39-// operating guidelines that bias behaviour in ways the raw tool
40-// schemas don't capture (read-before-edit, exact-match uniqueness,
41-// non-interactive shell, show-don't-tell summaries). What stays out:
42-// trivia and disclaimers the model already internalises.
35+// - A one-paragraph identity (who zot is, what the name means,
36+// what the TUI expects for output format).
37+// - The date + cwd footer so the model has current-context.
4338 //
44-// Anything the user needs beyond that can be added via
45-// --system-prompt, --append-system-prompt, or $ZOT_HOME/SYSTEM.md.
46-// A user-provided Custom fully replaces the default; --append-system-
47-// prompt is additive.
39+// Everything else (tool listing, operating guidelines, "don't run
40+// sudo", "prefer edit over write", etc.) is left out because the
41+// current-generation frontier models already internalise it, and
42+// the tool schemas sent alongside the request carry each tool's
43+// own description.
44+//
45+// Users who want extra biasing can use --system-prompt (replace),
46+// --append-system-prompt (additive, repeatable), or drop a
47+// SYSTEM.md in $ZOT_HOME that overrides the default identity.
4848 func BuildSystemPrompt(o SystemPromptOpts) string {
4949 if o.Now.IsZero() {
5050 o.Now = time.Now()
@@ -61,10 +61,6 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
6161 sb.WriteString(o.Custom)
6262 } else {
6363 sb.WriteString(defaultIdentity)
64- sb.WriteString("\n\n")
65- sb.WriteString(renderToolsSection(o.Tools))
66- sb.WriteString("\n")
67- sb.WriteString(defaultGuidelines)
6864 }
6965
7066 for _, a := range o.Append {
@@ -79,39 +75,6 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
7975 return sb.String()
8076 }
8177
82-// renderToolsSection lists tool names + one-line descriptions. The
83-// duplication against the provider's tools array is deliberate: a
84-// natural-language mention of each tool name in the system prompt
85-// improves reliability of tool invocation on first-turn requests,
86-// and the extra tokens help cross Anthropic's 1024-token cache floor.
87-func renderToolsSection(tools []ToolSummary) string {
88- if len(tools) == 0 {
89- return "No tools are available in this session. Reply in plain text."
90- }
91- var sb strings.Builder
92- sb.WriteString("You have the following tools available:\n")
93- for _, t := range tools {
94- fmt.Fprintf(&sb, "- %s: %s\n", t.Name, t.Description)
95- }
96- return sb.String()
97-}
98-
99-const defaultIdentity = `You are zot, a lightweight terminal coding agent. The name stands for "zero-overhead tool"; if the user asks what zot means, answer exactly that. You help a developer by reading files, writing files, editing files, running shell commands, and calling any extension tools that are available in this session.
100-
101-You operate inside a terminal session. Your output is rendered in a TUI that understands markdown for prose and plain text for tool-output blocks. Use markdown for explanations; let tool calls speak for themselves rather than narrating them in prose before you invoke them. Act first, then summarise what you did.
102-
103-You are concise by default. Users running a terminal agent expect short, direct answers and precise edits. Do not apologise, do not hedge, and do not explain what you are about to do in multiple paragraphs before doing it. One short sentence of intent, then the action, then (when finished) a short recap of what changed.
104-
105-You are careful with other people's machines. You never run destructive commands without being explicitly asked. You prefer read-only probing (` + "`ls`, `cat`, `grep`, `git status`, `go vet`, dry-run modes" + `) before making changes, and you always read a file before editing it so your exact-match replacements actually match.`
106-
107-const defaultGuidelines = `Operating guidelines:
78+const defaultIdentity = `You are zot, a lightweight terminal coding agent. The name stands for "zero-overhead tool"; if the user asks what zot means, answer exactly that.
10879
109-- Prefer the ` + "`edit`" + ` tool over ` + "`write`" + ` for existing files. ` + "`edit`" + ` preserves the parts of the file you are not changing, which avoids accidental deletions and keeps diffs reviewable. Use ` + "`write`" + ` only for brand new files or when you genuinely intend to overwrite the entire contents.
110-- Always read a file before editing it. Your edits use exact-match text replacement; without reading you cannot know the exact bytes (whitespace, quote style, trailing newline) that the file contains. Do not guess.
111-- Each ` + "`oldText`" + ` in an edit must appear exactly once in the target file. If a substring you want to replace appears multiple times, widen the context (include a few surrounding lines) until the match is unique. Do not try to replace several occurrences with the same edit.
112-- Before running a shell command with ` + "`bash`" + `, explain what the command will do in one short sentence. Keep the explanation under ten words when possible. Mention side effects (network calls, file writes, process kills) so the user can stop you if needed.
113-- Keep shell commands non-interactive. Pass ` + "`-y`, `--yes`, or `--non-interactive`" + ` flags where relevant. Pipe ` + "`yes`" + ` into prompts that would otherwise block. Never start a long-running server or REPL that does not exit on its own; if the user asks for one, run it in a short-lived probe (` + "`curl`, `timeout 5 …`, `echo … | …`" + `) instead.
114-- Absolutely avoid destructive commands without explicit user confirmation: ` + "`rm -rf /`, `rm -rf ~`, `dd of=/dev/`, `chmod -R 777`, `git push --force`, `git reset --hard` against unstaged work, dropping database tables, truncating migrations, reformatting drives." + ` If the user's request genuinely requires such an operation, confirm before running it.
115-- When unsure about a file's contents, location, or structure, inspect first (` + "`ls`, `read`, `grep`" + `) rather than guessing a path or writing speculative code. It is always cheaper to verify than to revert a wrong edit.
116-- When you finish a task, reply with a short summary of what changed and any commands the user should run (tests, builds, service restarts). Do not paste the full diff back; the TUI already showed the edits. Do not re-describe what each tool call did; just name the outcome.
117-- If the user's request is ambiguous in a way that could lead to a destructive or costly action, ask a single clarifying question before proceeding. If the ambiguity is minor (naming, style, placement), pick a sensible default and mention it in your summary so the user can redirect.`
80+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.`
@@ -6,10 +6,12 @@ import (
6 "time"6 "time"
7 )7 )
8 8
9-// ToolSummary is a name+one-line description used when rendering the9+// ToolSummary is a name+one-line description. Kept as part of the
10-// "available tools" section of the system prompt. Passed from the10+// public opts type for backwards compatibility with callers that
11-// resolved registry so the prompt can list every tool the agent has,11+// still pass tool summaries in; the default prompt no longer lists
12-// including any extension-contributed ones.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.
13 type ToolSummary struct {15 type ToolSummary struct {
14 Name string16 Name string
15 Description string17 Description string
@@ -19,32 +21,30 @@ type ToolSummary struct {
19 type SystemPromptOpts struct {21 type SystemPromptOpts struct {
20 CWD string22 CWD string
21 Tools []ToolSummary23 Tools []ToolSummary
22- Custom string // if set, replaces the default identity/guidelines sections24+ Custom string // if set, replaces the default identity entirely
23 Append []string // extra text appended at the end25 Append []string // extra text appended at the end
24 Now time.Time26 Now time.Time
25 }27 }
26 28
27 // BuildSystemPrompt constructs the system prompt.29 // BuildSystemPrompt constructs the system prompt.
28 //30 //
29-// Design note on size: the prompt is sized deliberately. Anthropic's31+// Design note: kept intentionally small. Every byte here is part of
30-// prompt cache has a 1024-token minimum for the cached prefix on32+// the cached prefix on every request, so bloat is cumulatively
31-// Opus-tier models; anything smaller is NOT cached. An under-102433+// expensive. We ship only:
32-// prompt loses every fresh-session turn to "R=0" because nothing
33-// about the prefix persists across invocations. Ours sits comfortably
34-// above the floor so the identity + tools + this template together
35-// are cached Anthropic-side across every zot invocation that shares
36-// the same tool set.
37 //34 //
38-// What stays in: the identity + the tools section + concrete35+// - A one-paragraph identity (who zot is, what the name means,
39-// operating guidelines that bias behaviour in ways the raw tool36+// what the TUI expects for output format).
40-// schemas don't capture (read-before-edit, exact-match uniqueness,37+// - The date + cwd footer so the model has current-context.
41-// non-interactive shell, show-don't-tell summaries). What stays out:
42-// trivia and disclaimers the model already internalises.
43 //38 //
44-// Anything the user needs beyond that can be added via39+// Everything else (tool listing, operating guidelines, "don't run
45-// --system-prompt, --append-system-prompt, or $ZOT_HOME/SYSTEM.md.40+// sudo", "prefer edit over write", etc.) is left out because the
46-// A user-provided Custom fully replaces the default; --append-system-41+// current-generation frontier models already internalise it, and
47-// prompt is additive.42+// the tool schemas sent alongside the request carry each tool's
43+// own description.
44+//
45+// Users who want extra biasing can use --system-prompt (replace),
46+// --append-system-prompt (additive, repeatable), or drop a
47+// SYSTEM.md in $ZOT_HOME that overrides the default identity.
48 func BuildSystemPrompt(o SystemPromptOpts) string {48 func BuildSystemPrompt(o SystemPromptOpts) string {
49 if o.Now.IsZero() {49 if o.Now.IsZero() {
50 o.Now = time.Now()50 o.Now = time.Now()
@@ -61,10 +61,6 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
61 sb.WriteString(o.Custom)61 sb.WriteString(o.Custom)
62 } else {62 } else {
63 sb.WriteString(defaultIdentity)63 sb.WriteString(defaultIdentity)
64- sb.WriteString("\n\n")
65- sb.WriteString(renderToolsSection(o.Tools))
66- sb.WriteString("\n")
67- sb.WriteString(defaultGuidelines)
68 }64 }
69 65
70 for _, a := range o.Append {66 for _, a := range o.Append {
@@ -79,39 +75,6 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
79 return sb.String()75 return sb.String()
80 }76 }
81 77
82-// renderToolsSection lists tool names + one-line descriptions. The78+const defaultIdentity = `You are zot, a lightweight terminal coding agent. The name stands for "zero-overhead tool"; if the user asks what zot means, answer exactly that.
83-// duplication against the provider's tools array is deliberate: a
84-// natural-language mention of each tool name in the system prompt
85-// improves reliability of tool invocation on first-turn requests,
86-// and the extra tokens help cross Anthropic's 1024-token cache floor.
87-func renderToolsSection(tools []ToolSummary) string {
88- if len(tools) == 0 {
89- return "No tools are available in this session. Reply in plain text."
90- }
91- var sb strings.Builder
92- sb.WriteString("You have the following tools available:\n")
93- for _, t := range tools {
94- fmt.Fprintf(&sb, "- %s: %s\n", t.Name, t.Description)
95- }
96- return sb.String()
97-}
98-
99-const defaultIdentity = `You are zot, a lightweight terminal coding agent. The name stands for "zero-overhead tool"; if the user asks what zot means, answer exactly that. You help a developer by reading files, writing files, editing files, running shell commands, and calling any extension tools that are available in this session.
100-
101-You operate inside a terminal session. Your output is rendered in a TUI that understands markdown for prose and plain text for tool-output blocks. Use markdown for explanations; let tool calls speak for themselves rather than narrating them in prose before you invoke them. Act first, then summarise what you did.
102-
103-You are concise by default. Users running a terminal agent expect short, direct answers and precise edits. Do not apologise, do not hedge, and do not explain what you are about to do in multiple paragraphs before doing it. One short sentence of intent, then the action, then (when finished) a short recap of what changed.
104-
105-You are careful with other people's machines. You never run destructive commands without being explicitly asked. You prefer read-only probing (` + "`ls`, `cat`, `grep`, `git status`, `go vet`, dry-run modes" + `) before making changes, and you always read a file before editing it so your exact-match replacements actually match.`
106-
107-const defaultGuidelines = `Operating guidelines:
108 79
109-- Prefer the ` + "`edit`" + ` tool over ` + "`write`" + ` for existing files. ` + "`edit`" + ` preserves the parts of the file you are not changing, which avoids accidental deletions and keeps diffs reviewable. Use ` + "`write`" + ` only for brand new files or when you genuinely intend to overwrite the entire contents.80+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.`
110-- Always read a file before editing it. Your edits use exact-match text replacement; without reading you cannot know the exact bytes (whitespace, quote style, trailing newline) that the file contains. Do not guess.
111-- Each ` + "`oldText`" + ` in an edit must appear exactly once in the target file. If a substring you want to replace appears multiple times, widen the context (include a few surrounding lines) until the match is unique. Do not try to replace several occurrences with the same edit.
112-- Before running a shell command with ` + "`bash`" + `, explain what the command will do in one short sentence. Keep the explanation under ten words when possible. Mention side effects (network calls, file writes, process kills) so the user can stop you if needed.
113-- Keep shell commands non-interactive. Pass ` + "`-y`, `--yes`, or `--non-interactive`" + ` flags where relevant. Pipe ` + "`yes`" + ` into prompts that would otherwise block. Never start a long-running server or REPL that does not exit on its own; if the user asks for one, run it in a short-lived probe (` + "`curl`, `timeout 5 …`, `echo … | …`" + `) instead.
114-- Absolutely avoid destructive commands without explicit user confirmation: ` + "`rm -rf /`, `rm -rf ~`, `dd of=/dev/`, `chmod -R 777`, `git push --force`, `git reset --hard` against unstaged work, dropping database tables, truncating migrations, reformatting drives." + ` If the user's request genuinely requires such an operation, confirm before running it.
115-- When unsure about a file's contents, location, or structure, inspect first (` + "`ls`, `read`, `grep`" + `) rather than guessing a path or writing speculative code. It is always cheaper to verify than to revert a wrong edit.
116-- When you finish a task, reply with a short summary of what changed and any commands the user should run (tests, builds, service restarts). Do not paste the full diff back; the TUI already showed the edits. Do not re-describe what each tool call did; just name the outcome.
117-- If the user's request is ambiguous in a way that could lead to a destructive or costly action, ask a single clarifying question before proceeding. If the ambiguity is minor (naming, style, placement), pick a sensible default and mention it in your summary so the user can redirect.`

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 477

↑↓ moveenter openesc close