feat(save-mode): add Save Mode cost controls — FE + local dev wiring#2888
Open
ricardo-leiva wants to merge 2 commits into
Open
feat(save-mode): add Save Mode cost controls — FE + local dev wiring#2888ricardo-leiva wants to merge 2 commits into
ricardo-leiva wants to merge 2 commits into
Conversation
Contributor
|
Reviews (1): Last reviewed commit: "feat(save-mode): add Save Mode cost cont..." | Re-trigger Greptile |
2 tasks
ddfe044 to
c92d176
Compare
Introduces Save Mode: a three-tier user setting (off / balanced / max_save) that reduces per-turn LLM cost without changing the workflow. Levers: model downshift (Opus→Sonnet-4-6 at max_save), effort cap (high/medium), terse system reminder (fewer output tokens), and x-posthog-property-* headers that stamp baseline vs effective model/effort on every $ai_generation event. Wiring (task start): resolveSaveMode → systemPromptOverride → taskCreationSaga → ConnectParams → agent.start → buildSystemPrompt Wiring (live session): SessionService.applySaveMode() stores per-task baselines and calls setSessionConfigOptionByCategory for model + thought_level; useApplySaveMode wraps the single service call in the UI (AGENTS.md rule 5). UI: SaveModeToggle appears next to ReasoningLevelSelector in both the task-input bar and the session prompt bar; saveMode setting persisted in settingsStore. Also fixes missing BROWSER_TAB_OPENED / LINK_CLICKED_IN_CHAT analytics event types that were referenced in the browser feature but never added to the schema. Local dev: COST_CONTROLS_ENABLED=true enables the alpha gate without a PostHog flag (see scripts/dev-cost-controls.sh). The gateway wiring (cache TTL upgrade, budget guard) lives in a separate BE PR. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHrRwvm6QmmEM39ez3AjD3
P1 fixes: - useTaskCreation: pass saveModeResult.model/effort to prepareTaskInput — only systemReminder was forwarded before, silently dropping the two biggest cost levers - useRunWorkstreamAction: forward saveModeResult.effort as reasoningLevel to TaskCreationInput — model was updated but effort cap was discarded P2 fixes: - sessionService: evict _saveModeBaselines on teardownSession to prevent slow leak when tasks complete/error with save mode still active - saveMode.test.ts + budget.test.ts: convert to it.each parameterised table shape per AGENTS.md testing conventions; adds balanced/low-effort edge case Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHrRwvm6QmmEM39ez3AjD3
c92d176 to
1447ea3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Heavy AI coding sessions cost real money. A user running Opus at max effort for 10+ turns is spending ~$0.80 per session — and the agent is doing routine file reads and edits where Opus quality is overkill. There's no way to dial this back without leaving the app.
How it works
The toggle lives next to the reasoning-level selector in both the task-input bar and the live session bar. Setting is persisted in
settingsStoreand applied immediately to the active session when changed.Request flow
Changes
New files:
packages/core/src/save-mode/saveMode.ts— pureresolveSaveMode()resolver + testspackages/core/src/save-mode/budget.ts—evaluateBudget()helper + testspackages/ui/src/features/sessions/components/SaveModeToggle.tsx— toggle UI componentpackages/ui/src/features/sessions/hooks/useApplySaveMode.ts— wraps one service call (AGENTS.md rule 5)scripts/dev-cost-controls.sh— local dev helperCore wiring (task start):
shared/task-creation-domain.ts→systemPromptOverridefield inTaskCreationInputcore/task-detail/taskInput.ts→ forwards it fromPrepareTaskInputOptionscore/task-detail/taskCreationSaga.ts→ passes toConnectParamscore/sessions/sessionService.ts→ConnectParams.systemPromptOverride,createNewLocalSession,_saveModeBaselines,applySaveMode()Agent wiring:
agent/adapters/claude/session/options.ts→saveModeHeadersinOptionsagent/adapters/claude/claude-agent.ts→ passessaveModeHeadersthroughagent/adapters/claude/types.ts→NewSessionMeta.saveModeHeadersagent/utils/gateway.ts→LLM_GATEWAY_BASE_URLenv overrideworkspace-server/services/agent/agent.ts→SessionConfig.saveModeHeaders, wired intonewSession/resumeSession_metaworkspace-server/services/agent/schemas.ts→ Zod schema forsaveModeHeadersUI:
ui/features/sessions/components/SessionView.tsx→SaveModeToggleinreasoningSelectorui/features/task-detail/components/TaskInput.tsx→SaveModeToggleinreasoningSelectorui/features/task-detail/hooks/useTaskCreation.ts→ computessaveModeResult, passessystemPromptOverride, stampssave_modeonTASK_CREATEDeventui/features/settings/settingsStore.ts→saveMode: SaveMode+setSaveMode+ persistenceui/features/settings/sections/GeneralSettings.tsx→ save mode in Settings panelui/features/home/hooks/useRunWorkstreamAction.ts→ save mode for workstream tasksAlso fixes: missing
BROWSER_TAB_OPENED/LINK_CLICKED_IN_CHATanalytics event types in@posthog/shared(were used in the browser feature but never registered in the schema).How did you test this?
Automated tests run and passing:
packages/core/src/save-mode/saveMode.test.ts— 5 cases: off pass-through, balanced caps effort, max_save downshifts model, never raises low effort, perTaskFullPower bypasspackages/core/src/save-mode/budget.test.ts— 5 cases: disabled/no cap, ok, warn@70%, engage@85%, blocked@100%pnpm typecheckpasses across all packages (includingapps/webandapps/code)pnpm --filter @posthog/core test run— 1610 tests, all passManual testing by human:
Automatic notifications
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Built with Claude Code (claude-sonnet-4-6) across two sessions. The implementation covers:
resolveSaveModefunction (testable, host-neutral) computing effective model/effort/remindersaveModeHeaders(x-posthog-property-* headers) fed to the LLM Gateway in a companion BE PRSessionService.applySaveMode()(AGENTS.md rule 5), replaced deadSETTING_CHANGEDevent with typedSAVE_MODE_CHANGED, fixedclaude-fabledownshift target toclaude-sonnet-4-6main(notfeat/skills-improvements) to avoid contamination