Skip to content

polish: cut per-turn overhead, stabilize cache, add /new#478

Merged
kevincodex1 merged 3 commits into
mainfrom
polish/per-turn-overhead-cache-new
Jul 4, 2026
Merged

polish: cut per-turn overhead, stabilize cache, add /new#478
kevincodex1 merged 3 commits into
mainfrom
polish/per-turn-overhead-cache-new

Conversation

@anandh8x

@anandh8x anandh8x commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

What's in this PR

Context economy

  • Compaction: preserveLast 8→6, triggerRatio 0.8→0.7 (both copies in sync).
  • System prompt: killed the "ELABORATE summary" directive; summary now scales to the work. Comment discipline moved into the core prompt for all model families (was Anthropic-only) — Minimax/DeepSeek/Qwen/etc. now get it.
  • Token-budget regression test ratchets the fixed per-turn overhead (base prompt + eager tool schemas) using the runtime's own estimator, so silent prompt/schema growth fails CI.

Response quality

  • defaultResponseStyle balanced→concise. Reversible via /style.

Tool discovery

  • <available_skills> block in the system prompt lists installed skills (name + one-line description) so the model loads the right one on the first call instead of guessing and reading the failure. Budget-capped at 640 bytes with an "...and N more" fallback; renders nothing when no skills are installed (byte-identical to prior prompt).

Session lifecycle

  • /new starts a fresh session in place; previous session stays on disk and is named in the confirmation for /resume. Guards against mid-run reset.
  • /clear now notes that context persists and points to /new.

Performance & caching

  • partitionTools no longer alpha-sorts the whole tools array every turn, which inserted a loaded deferred tool mid-array and shifted the cached prefix on every load. It now builds append-only regions [eager sorted] + [tool_search] + [loaded deferred sorted] so a mid-session load grows the tail instead of busting the eager prefix. Regression test asserts the eager block precedes any loaded tool.
  • /context now shows cache hit rate (flags low rate over >5 turns as a possible unstable prefix) and rolling average turn latency with time-to-first-token. Both reset on /new.

Measured baselines

Auto-mode, no workspace: base prompt 3162 tokens, eager tool schemas 2433 across 12 tools. Budget ceilings set ~10% above.

Verification

  • go build ./... clean
  • go vet on all changed packages clean
  • internal/agent, internal/tui, internal/usage, internal/cli, internal/sessions full test suites green

Summary by CodeRabbit

  • New Features
    • Added an installed-skills section to the system prompt (with budgeted/truncated listing).
    • Enhanced the TUI /context footer with rolling latency, time-to-first-token, and cache-efficiency.
    • Added support for the /new command to start a fresh session.
  • Bug Fixes
    • Improved /clear and /new transcript/session reset behavior, including guidance and safeguards while a run is active.
    • Stabilized tool ordering during deferred tool loading.
    • Updated system prompt instructions and model guidance behavior; tuned default compaction preservation/trigger settings.
  • Tests
    • Added/updated tests covering tool ordering, prompt/token budgets, skills context, latency/TTFT, and cache-efficiency.

Context economy
- Compaction: preserveLast 8->6, triggerRatio 0.8->0.7 (both copies in sync).
- System prompt: kill the "ELABORATE summary" directive; scale summary to
  the work. Comment discipline moved into the core prompt for all model
  families (was Anthropic-only); Minimax/DeepSeek/Qwen/etc. now get it.
- Token-budget regression test ratchets the fixed per-turn overhead (base
  prompt + eager tool schemas) using the runtime's own estimator, so silent
  prompt/schema growth fails CI.

Response quality
- defaultResponseStyle balanced->concise. Reversible via /style.

Tool discovery
- <available_skills> block in the system prompt lists installed skills
  (name + one-line description) so the model loads the right one on the
  first call instead of guessing and reading the failure. Budget-capped at
  640 bytes with an "...and N more" fallback; renders nothing when no
  skills are installed (byte-identical to prior prompt).

Session lifecycle
- /new starts a fresh session in place; previous session stays on disk and
  is named in the confirmation for /resume. Guards against mid-run reset.
- /clear now notes that context persists and points to /new.

Performance & caching
- partitionTools no longer alpha-sorts the whole tools array every turn,
  which inserted a loaded deferred tool mid-array and shifted the cached
  prefix on every load. It now builds append-only regions
  [eager sorted] + [tool_search] + [loaded deferred sorted] so a
  mid-session load grows the tail instead of busting the eager prefix.
  Regression test asserts the eager block precedes any loaded tool.
- /context now shows cache hit rate (flags low rate over >5 turns as a
  possible unstable prefix) and rolling average turn latency with
  time-to-first-token. Both reset on /new.

Measured baselines (auto-mode, no workspace): base prompt 3162 tokens,
eager tool schemas 2433 across 12 tools. Budget ceilings set ~10% above.

Deliberately deferred (need a decision): inverted safety-policy rewrite
(sandbox doesn't catch ~half the B-tier rules), core-tool/web_fetch
deferral (activation-semantics blocker), aggressive prompt slim.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9f8e2775-44a7-4a1d-b736-21d65bc0a1a5

📥 Commits

Reviewing files that changed from the base of the PR and between 661357e and 416d0aa.

📒 Files selected for processing (2)
  • internal/tui/new_session_test.go
  • internal/tui/session.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/tui/new_session_test.go
  • internal/tui/session.go

Walkthrough

This PR retunes compaction defaults, stabilizes deferred tool partitioning, adds skills metadata to the system prompt, removes Anthropic-specific prompt addendum text, and updates the TUI with /new, timing, cache-efficiency, and response-style changes.

Changes

Agent: compaction, tool partitioning, and prompt updates

Layer / File(s) Summary
Compaction tuning constants
internal/agent/compaction.go, internal/sessions/replay.go
Lowers defaultCompactionPreserveLast and compactionTriggerRatio, with replay using the same preserve-last default.
Stable tool partitioning
internal/agent/loop.go, internal/agent/partition_cache_stable_test.go, internal/agent/prompt_budget_test.go
Rebuilds partitionTools into stable eager/search/deferred regions, adds runtimeToolDefinition, and adds cache-stability and prompt-budget tests.
Skills in the system prompt
internal/agent/types.go, internal/agent/system_prompt.go, internal/agent/skills_context_test.go, internal/cli/app.go, internal/cli/exec.go, internal/cli/plugin_activate.go
Adds SkillInfo and Options.Skills, renders a budgeted skills block in the prompt, and wires plugin-derived skills into CLI agent setup.
Prompt wording and model addendum
internal/agent/system_prompt.md, internal/agent/system_prompt_models.go, internal/agent/system_prompt_models_test.go, internal/agent/system_prompt_test.go
Shortens summarize guidance, adds comment-density guidance, and removes the Anthropic-specific addendum path.

TUI: /new command, timing, and cache efficiency

Layer / File(s) Summary
Default response style
internal/tui/model.go, internal/tui/command_polish_test.go, internal/tui/session_controls_test.go
Changes the default response style to concise and updates style expectations.
/new command and session reset
internal/tui/commands.go, internal/tui/session.go, internal/tui/model.go, internal/tui/flush_test.go, internal/tui/model_test.go, internal/tui/new_session_test.go
Adds /new, resets session state and transcript, blocks reset during active runs, and updates /clear behavior and tests.
Turn latency and TTFT tracking
internal/tui/model.go, internal/tui/command_views.go, internal/tui/latency_test.go
Adds rolling latency and TTFT counters, captures first-token timing, and shows average latency in /context.
Cache efficiency reporting
internal/usage/tracker.go, internal/usage/cache_efficiency_test.go, internal/tui/session_controls.go
Adds cache-hit-rate formatting and shows cache-efficiency text in the TUI context view.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as cli.app/exec
  participant PluginActivation
  participant AgentOptions
  participant buildSystemPrompt
  CLI->>PluginActivation: skillInfos(skillsDir)
  PluginActivation-->>CLI: []SkillInfo
  CLI->>AgentOptions: Options.Skills = skillInfos
  AgentOptions->>buildSystemPrompt: buildSystemPrompt(options)
  buildSystemPrompt->>buildSystemPrompt: skillsContext(options.Skills)
Loading
sequenceDiagram
  participant User
  participant Model as tui.model
  participant Session as startNewSession
  User->>Model: submit "/new"
  Model->>Model: check pending / compactInFlight
  alt run in progress
    Model-->>User: system message to cancel first
  else idle
    Model->>Session: startNewSession()
    Session->>Session: clear session state and transcript
    Session-->>Model: reset model state
    Model-->>User: new-session transcript note
  end
Loading

Possibly related PRs

  • Gitlawb/zero#125: Both PRs change compaction defaults and related compaction behavior.
  • Gitlawb/zero#185: The new skillInfos wiring builds on plugin skill-root discovery plumbing.
  • Gitlawb/zero#66: Both PRs touch TUI session controls and response-style behavior.

Suggested reviewers: Vasanthdev2004, gnanam1990

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: lower overhead, cache-stable tool ordering, and the new /new session command.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch polish/per-turn-overhead-cache-new

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/cli/plugin_activate.go (1)

62-81: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Swallowed error from MergedSkills.

merged, _ := plugins.MergedSkills(...) discards the error entirely, while every other failure path in this file (activatePlugins) surfaces diagnostics via writePluginActivationWarning. A broken skill file or unreadable skills dir here fails silently — the <available_skills> block just goes missing with no signal to the user.

♻️ Proposed fix
-func (a pluginActivation) skillInfos(defaultDir string) []agent.SkillInfo {
-	merged, _ := plugins.MergedSkills(defaultDir, a.skillRoots)
+func (a pluginActivation) skillInfos(defaultDir string, stderr io.Writer) []agent.SkillInfo {
+	merged, err := plugins.MergedSkills(defaultDir, a.skillRoots)
+	if err != nil {
+		writePluginActivationWarning(stderr, "failed to load skills: "+err.Error())
+	}
 	if len(merged) == 0 {
 		return nil
 	}

Call site in exec.go would become pluginActivation.skillInfos(deps.skillsDir(), stderr).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/plugin_activate.go` around lines 62 - 81, The skill discovery
path in pluginActivation.skillInfos is swallowing the error from
plugins.MergedSkills, so broken or unreadable skills fail silently. Update
skillInfos to accept stderr and surface any MergedSkills failure through
writePluginActivationWarning, matching activatePlugins’ other diagnostics, while
still returning nil or filtered skills as appropriate; also update the exec.go
call site to pass stderr into skillInfos.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/session.go`:
- Around line 43-86: The startNewSession flow is not clearing staged attachments
and related UI state, so a fresh /new session can inherit old files. Update
model.startNewSession to also reset pendingImages, pendingImageLabels,
pendingDocuments, queuedMessage, and any stale file/plan display state alongside
the existing session and usage resets. Use the startNewSession method and the
attachment-related fields on model as the place to make the cleanup consistent
for every new session.

---

Nitpick comments:
In `@internal/cli/plugin_activate.go`:
- Around line 62-81: The skill discovery path in pluginActivation.skillInfos is
swallowing the error from plugins.MergedSkills, so broken or unreadable skills
fail silently. Update skillInfos to accept stderr and surface any MergedSkills
failure through writePluginActivationWarning, matching activatePlugins’ other
diagnostics, while still returning nil or filtered skills as appropriate; also
update the exec.go call site to pass stderr into skillInfos.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57b4ef45-be59-4765-9e05-c9f0c78d35d3

📥 Commits

Reviewing files that changed from the base of the PR and between 949ee43 and 09a9e9e.

📒 Files selected for processing (28)
  • internal/agent/compaction.go
  • internal/agent/loop.go
  • internal/agent/partition_cache_stable_test.go
  • internal/agent/prompt_budget_test.go
  • internal/agent/skills_context_test.go
  • internal/agent/system_prompt.go
  • internal/agent/system_prompt.md
  • internal/agent/system_prompt_models.go
  • internal/agent/system_prompt_models_test.go
  • internal/agent/system_prompt_test.go
  • internal/agent/types.go
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/cli/plugin_activate.go
  • internal/sessions/replay.go
  • internal/tui/command_polish_test.go
  • internal/tui/command_views.go
  • internal/tui/commands.go
  • internal/tui/flush_test.go
  • internal/tui/latency_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/new_session_test.go
  • internal/tui/session.go
  • internal/tui/session_controls.go
  • internal/tui/session_controls_test.go
  • internal/usage/cache_efficiency_test.go
  • internal/usage/tracker.go

Comment thread internal/tui/session.go
Resolve the model struct conflict by keeping both sides: main's keyBindings
field (reconfigurable shortcuts, #417) and this branch's turn-latency / TTFT
counters. All suites pass on the merged tree.
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 416d0aaa5695
Changed files (28): internal/agent/compaction.go, internal/agent/loop.go, internal/agent/partition_cache_stable_test.go, internal/agent/prompt_budget_test.go, internal/agent/skills_context_test.go, internal/agent/system_prompt.go, internal/agent/system_prompt.md, internal/agent/system_prompt_models.go, internal/agent/system_prompt_models_test.go, internal/agent/system_prompt_test.go, internal/agent/types.go, internal/cli/app.go, and 16 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

CodeRabbit: startNewSession reset conversation/usage/latency state but not
the staged input (pendingImages, pendingImageLabels, pendingDocuments,
queuedMessage), which is only consumed at prompt-submit. Without clearing it,
the fresh session's first prompt silently inherited the previous session's
images/documents/queued text. Test now stages all four and asserts they clear.

@Vasanthdev2004 Vasanthdev2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — a strong, coherent context-economy + observability pass. Two opinionated defaults are yours to consciously accept.

Worth merging: yes, clearly. This isn't churn — it's exactly the kind of substantive improvement Zero wants: it trims the fixed per-turn overhead, lands a real prompt-cache stability fix, adds the observability to see both, and cleans up session lifecycle. Thoroughly tested (7 new test files + updates), fully commented with rationale, and green on all three platforms including the Windows smoke that's been flaky elsewhere. Thanks @anandh8x.

I ground-truthed the load-bearing pieces rather than taking them on faith:

partitionTools cache-stability rewrite (the heart of it) — correct. The old code alpha-sorted the whole tools array every turn, so a newly-loaded deferred tool got inserted mid-array and shifted every later definition — busting the provider's cached prefix from that point through the system prompt and messages, on every load. The new layout [eager sorted] + [tool_search] + [loaded tail sorted] makes a load grow the tail instead. I verified the active/inactive split preserves the prior behavior: the inactive path returns early byte-identically, and on the active path tool_search is unconditionally exposed exactly as before (it was always skipped in the loop, so the old !exposedNames[...] guard was always true anyway). The comment is honest that tool_search's own discovery text still shrinks as tools load, so the eager block is what stays cached, not the tail — a real, partial win, accurately described.

Cache-rate math — safe. CacheHitRate() divides CachedInputTokens/InputTokens; I checked Normalize (tracker.go:181-183) actually clamps cachedInputTokens to inputTokens per record, so the accumulated summary can't exceed 1.0. FormatCacheEfficiency and the empty-input n/a guard line up with the tests.

Skills block — byte-safe. skillsContext returns "" on no skills and buildSystemPrompt skips the append, so a skill-less run reproduces the prior prompt exactly (test covers it). Budget-capped at 640B with a "…and N more" summary, first skill always listed, nameless entries skipped, descriptions truncated at 100 runes. Wired through both TUI (app.go) and exec (exec.go) via MergedSkills — the same set the skill tool resolves against, so it can't advertise a skill the tool can't load. All helpers exist as used.

/new — data-safe. The prior session is already persisted on disk, so /new only clears in-memory state; it refuses mid-run (m.pending || m.compactInFlight) so it can't strand an in-flight turn, names the previous id for /resume, and correctly clears staged input (images/docs/queued message) so nothing leaks into the fresh session. The /clear→"context still there, use /new" note fixes a genuine, common confusion.

Two conscious defaults — flagging, not blocking

These are product-behavior changes, reversible per-session, but they change Zero's out-of-box feel, so they should be a decision, not a side effect:

  1. defaultResponseStyle balanced → concise + killing the "ELABORATE summary" directive. Together these make Zero terser by default. I think that's the right call for a coding agent, but it's the most user-visible thing in here.
  2. compactionTriggerRatio 0.8 → 0.7 is self-described as speculative tuning (the author says validate against the latency/compaction metrics before trusting it). Earlier compaction = more frequent summarization. Honestly flagged; just merge it knowing it's a guess to be validated, not a measured value. preserveLast 8→6 is synced across both copies (compaction.go + replay.go) — good.

One non-blocking note

It's a wide PR — ~10 distinct concerns across 28 files under one theme. Coherent, but if the speculative 0.7 ratio or the concise default needs backing out, it's entangled with everything else rather than being a clean revert. Not a reason to hold it; just the tradeoff of shipping the pass as one unit.

Net: correct, well-tested, genuinely improves Zero. Approving. The only things to decide rather than verify are the two defaults above.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — a lot of well-scoped, well-tested work in one pass (prompt slimming, skills-in-prompt, /new, the budget guard, and latency/cache-efficiency visibility). Reviewed each slice with a regression lens and built/tested locally: no existing behavior breaks, and the tool exposure/deferral semantics are provably unchanged. A few notes, none blocking.

Cache stabilization — the scaffolding is right, but the win isn't delivered yet. The per-turn tool list is now correctly append-only ([eager | tool_search | loadedTail]), and the eager block is genuinely byte-stable across a deferred-tool load — that part is real and regression-free. But the actual cache-hit win doesn't materialize as shipped: the tools cache breakpoint sits on the last tool, and tool_search's description still changes on a load (it drops the now-loaded tool) right after the stable eager block, so the reusable prefix still breaks downstream. The loop.go comment discloses this as a scoped follow-up, which is the right call — just flagging that the "stabilize cache" headline is scaffolding-complete but payoff-pending. Consider softening the title, or landing the loader-description follow-up alongside this.

Strengthen partition_cache_stable_test.go. It currently asserts tool ordering (readPos/searchPos < alphaPos), but the property this slice actually claims is a byte-identical tools prefix across a load. A two-call test — partitionTools with loaded={} vs loaded={alpha:true} — asserting the eager + tool_search prefix bytes are identical would pin the real invariant (and would surface the tool_search-description instability noted above).

Smaller nits (optional):

  • /new doesn't reset the plan/specialists UI panels, so a pinned plan can linger until the next run. Not a context leak — the agent's in-context history is fully cleared, and /resume behaves the same — just cosmetic; a m.plan = planPanelState{} (+ specialists) would fully honor the "fresh session" promise.
  • compactionTriggerRatio 0.7 is flagged "speculative" in the comment — worth validating against the new avg-turn-latency metric before trusting it. The direction is safe (compacts a bit earlier, never risks overflow).
  • The swallowed error in plugin_activate.go:68 (merged, _ := plugins.MergedSkills(...)) means a broken/unreadable skills dir makes the <available_skills> block silently vanish, while every other path in that file surfaces a warning — worth matching. (The staged-attachments reset is already handled in 416d0aa.)

Nice work overall — the budget guard is hermetic and the /new state reset is thorough. Approving; the cache follow-up and the test strengthening are the two I'd prioritize.

@kevincodex1 kevincodex1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! thank you so much @anandh8x !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants