Skip to content

perf(agent): execution profiles with one-shot posture escalation (PR10b+PR10c)#740

Merged
Vasanthdev2004 merged 7 commits into
mainfrom
feat/exec-profiles-pr10bc
Jul 18, 2026
Merged

perf(agent): execution profiles with one-shot posture escalation (PR10b+PR10c)#740
Vasanthdev2004 merged 7 commits into
mainfrom
feat/exec-profiles-pr10bc

Conversation

@Vasanthdev2004

@Vasanthdev2004 Vasanthdev2004 commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

PR10b + PR10c of the performance program in one PR: user-visible execution profiles on every surface, built on the escalation machinery that landed inert in #736.

What this adds

  • zero exec --exec-profile fast|balanced|thorough: a loop-posture bundle (turn budget, reasoning effort, self-correction, escalation triggers). It composes with --mode: the mode picks what runs, the profile picks how hard the loop tries. Precedence is explicit flag > mode > profile, enforced by applying the profile last with the same fill-only-if-unset rule modes use. Unknown names are a usage error listing the valid ones; the legacy inert --profile is untouched and the two coexist (test extended).
  • TUI /profile with the same catalog, applying to the next run. Profiles never stack (switching reverts first), and reverts only restore knobs the user has not explicitly touched since.
  • zero-perf-bench turn --exec-profile: forwarded to every task invocation and stamped into the result (schema v5, additive only), printed in the summary next to mode:, so an A/B capture is self-describing. The bench validates and canonicalizes the name before spawning anything; an unknown name previously produced an exit-0 report whose walls were millisecond usage-error exits.
  • Trace runs record the selected profile name (--trace), balanced included, by design.

The catalog

  • balanced is the empty profile. Selecting it changes nothing: options, turn budget, and loop behavior are identical to an unflagged run, asserted by test. Only the opt-in trace/bench artifacts record the name.
  • fast: 30 turns, low effort, all four escalation triggers armed. When trouble hits (a same-tool failure streak, a second uncertain completion, a failing self-correct cycle, or a critical-risk mutation), a one-shot escalation restores the displaced posture: the pre-profile turn budget and the provider-default effort. The risk trigger is deliberately critical, not high; the sandbox floors every shell command at high before any command analysis, so a high threshold would spend the one-shot on the first go test of any real task.
  • thorough: 160 turns, high effort, full self-correct. Already the maximum posture, so no triggers.

Restoring the displaced effort needed a small addition to the #736 surface: PostureEscalation.RestoreDefaultEffort. A profile only ever fills effort that was unset, so the displaced value is the provider default, and a plain empty target already means "leave untouched". The flag is armed only when the profile actually performed the fill; an explicit -r, a mode's effort, an explicit --spec-reasoning-effort on the draft path, or a TUI /effort/Ctrl+T choice made after selecting the profile all keep or regain control (the TUI disarms the restore on the policy clone, the same way /turns under a profile pins the budget by disarming the escalation's turn target).

Tuning status, honestly

The Fast/Thorough numbers are provisional floors from the Phase 0 baseline's read-class evidence and are pinned by test so a retune is a visible diff. The mutating classes produced no successful samples to tune from (see the baseline investigation and #737), so the Fast-vs-Balanced A/B with the pass-rate gate comes with the post-#737 re-capture; the escalation safety net is what makes shipping provisional values safe in the meantime.

Program gates

  • No performance change expected by default: profiles are opt-in, and the unflagged/balanced path is byte-identical (asserted, not claimed).
  • Provider behavior, permissions, sandboxing, secret scrubbing, output ceilings, and one-result-per-tool-call are untouched; escalation only restores values the run already had.
  • Fully disableable: do not pass the flag.

Testing

  • New suites: internal/execprofile (catalog invariants, displaced-values-only escalation), cli (precedence, budget displacement, balanced identity, usage errors, full echo-provider runs asserting the trace stamp), tui (posture apply, pin/disarm, coincidence-safe reverts), perfbench + bench cmd (passthrough, validation, normalization, schema pin).
  • go build ./..., go vet ./..., gofmt clean; agent, cli, execprofile, perfbench, tui, and bench-cmd suites pass locally (the two known environment-dependent failures on this machine, the symlink-privilege serve test and the alt-screen scroll test, fail identically on main).
  • Real-binary smoke of the three help/usage surfaces and both failure modes (unknown name, inline empty value).

Summary by CodeRabbit

  • New Features
    • Added execution profiles: balanced, fast, thorough.
    • Added --exec-profile support to execution and turn-benchmark commands, including stamping the selected profile into benchmark output; bumped turn benchmark schema to record it.
    • Added TUI /profile controls for selecting and switching profiles.
  • Bug Fixes
    • Improved reasoning-effort restoration during posture escalation, with correct precedence and pinned override behavior.
  • Documentation
    • Updated zero exec help and benchmark summaries to reflect exec-profile behavior.
  • Tests
    • Expanded coverage for parsing/validation, precedence, tracing, benchmark propagation, and TUI profile interactions.

- fast escalates on critical-risk mutations, not high: the sandbox marks
  every shell command high before command analysis, so a high trigger
  spent the one-shot on the first go test of any task
- escalation can now restore the provider-default effort a profile
  displaced (PostureEscalation.RestoreDefaultEffort; a plain empty
  target means leave untouched)
- TUI /turns while a profile is active pins the budget: disarms the
  escalation turn target (mirroring exec's explicit --max-turns rule)
  and profile reverts use touched bits instead of value equality, so
  explicit choices survive even when they coincide with profile values
- spec-draft runs get the same escalation policy as the main run
  instead of the displaced budget with no safety net
- bench validates and canonicalizes --exec-profile before spawning:
  an unknown name previously produced an exit-0 report full of
  millisecond usage-error walls
- turn summary prints exec-profile alongside mode; zero exec rejects
  an inline-empty --exec-profile=; balanced trace label documented and
  pinned as deliberate attribution
…t restore

An explicit effort set AFTER profile selection could be cleared by a
mid-run escalation (the effort analog of the /turns pin, introduced by
RestoreDefaultEffort): TUI /effort and Ctrl+T now disarm the restore on
a policy clone, and the spec-draft path only arms it when no explicit
--spec-reasoning-effort governs the draft.
@github-actions

github-actions Bot commented Jul 18, 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: 6778eb5078d9
Changed files (21): cmd/zero-perf-bench/turn.go, cmd/zero-perf-bench/turn_test.go, internal/agent/loop.go, internal/agent/profile_controller_test.go, internal/agent/types.go, internal/cli/app.go, internal/cli/exec.go, internal/cli/exec_parse.go, internal/cli/exec_profile_test.go, internal/cli/exec_spec.go, internal/cli/exec_test.go, internal/execprofile/profile.go, and 9 more

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

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 16 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca844313-4b46-4cb0-827e-c7220366fc9d

📥 Commits

Reviewing files that changed from the base of the PR and between 86c2c03 and 6778eb5.

📒 Files selected for processing (3)
  • internal/tui/command_center.go
  • internal/tui/profile_command_test.go
  • internal/tui/session_controls.go

Walkthrough

Changes

The PR adds balanced, fast, and thorough execution profiles across the CLI and TUI. Profiles tune agent posture, preserve explicit overrides, propagate through spec and benchmark runs, and appear in traces and turn-benchmark results.

Execution profiles

Layer / File(s) Summary
Profile catalog and escalation policy
internal/execprofile/*, internal/agent/*
Defines built-in profiles, lookup and policy projection, plus default reasoning-effort restoration during posture escalation.
CLI profile selection and execution wiring
internal/cli/*
Adds --exec-profile, precedence and budget handling, spec-draft policy propagation, trace labeling, validation, and tests.
Turn benchmark profile propagation
cmd/zero-perf-bench/*, internal/perfbench/*
Forwards execution profiles to zero exec, records them in results and summaries, and updates the benchmark schema to version 5.
TUI profile state and overrides
internal/tui/*
Adds /profile, applies and reverts posture, reconciles model changes, and preserves explicit turn, effort, and self-correction changes.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant zero_exec
  participant execprofile
  participant agent_Run
  participant trace_Recorder
  User->>zero_exec: Select --exec-profile
  zero_exec->>execprofile: Lookup profile and build policy
  execprofile-->>zero_exec: Profile settings and escalation policy
  zero_exec->>agent_Run: Run with profile options
  zero_exec->>trace_Recorder: Record selected profile name
Loading

Possibly related PRs

  • Gitlawb/zero#736: Modifies the posture-escalation machinery used for default-effort restoration.
  • Gitlawb/zero#700: Modifies the execution tracing integration used to record profile labels.
  • Gitlawb/zero#66: Modifies TUI model-switch and reasoning-effort handling used by profile reconciliation.

Suggested reviewers: anandh8x, gnanam1990

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and accurately reflects the main change: adding execution profiles with posture escalation behavior.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/exec-profiles-pr10bc

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: 4

🤖 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/cli/exec_parse.go`:
- Around line 150-162: Update the exec option validation flow after
`--exec-profile` application so profiles that enable self-correction cannot
remain active with `--use-spec`; reject this combination consistently with
explicit `--self-correct`. Use the existing profile application and validation
symbols in `internal/cli/exec_parse.go`, preserving current behavior for
compatible profiles rather than silently dropping self-correction.

In `@internal/perfbench/turn_bench.go`:
- Line 224: Update RunTurnBench to normalize and validate cfg.ExecProfile once
via execprofile.Lookup at the boundary, then use the returned canonical profile
name for both RunContext.ExecProfile and TurnBenchResult. Ensure equivalent
inputs such as whitespace- or case-variant “fast” produce the same execution
behavior and report label.

In `@internal/tui/profile_command_test.go`:
- Around line 172-175: Update the test around handleProfileCommand("fast") so an
unexpected reasoningEffort value fails the test instead of calling t.Skipf.
Preserve the assertion that the catalog supplies the expected low effort,
ensuring the effort-restore disarming path remains covered.

In `@internal/tui/session_controls.go`:
- Around line 394-399: Update the restoration branch in the session control
logic around execProfileDisplacedMaxTurns so restoring a displaced value of zero
also clears the profile-applied max-turns environment variable; retain
SetMaxTurnsEnv for positive values and explicitly unset or reset the environment
when the displaced value is zero.
🪄 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: e92ba83e-f97c-4bb9-b9cc-ca175e1dae58

📥 Commits

Reviewing files that changed from the base of the PR and between 015452c and d6688ab.

📒 Files selected for processing (20)
  • cmd/zero-perf-bench/turn.go
  • cmd/zero-perf-bench/turn_test.go
  • internal/agent/loop.go
  • internal/agent/profile_controller_test.go
  • internal/agent/types.go
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/cli/exec_parse.go
  • internal/cli/exec_profile_test.go
  • internal/cli/exec_spec.go
  • internal/cli/exec_test.go
  • internal/execprofile/profile.go
  • internal/execprofile/profile_test.go
  • internal/perfbench/taskbench.go
  • internal/perfbench/turn_bench.go
  • internal/perfbench/turn_bench_test.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/profile_command_test.go
  • internal/tui/session_controls.go

Comment thread internal/cli/exec_parse.go
Comment thread internal/perfbench/turn_bench.go Outdated
Comment thread internal/tui/profile_command_test.go
Comment thread internal/tui/session_controls.go
- --use-spec projects away a profile's self-correct knob instead of
  silently arming an option the draft runner ignores (the explicit
  --self-correct --use-spec combination is already a parse-time error)
- RunTurnBench canonicalizes and validates ExecProfile at the library
  boundary, so a direct caller's casing/whitespace cannot fork report
  labels and an unknown name fails before anything runs
- reverting a profile whose displaced budget was zero clears
  ZERO_MAX_TURNS (SetMaxTurnsEnv ignores zero, so sub-agents would have
  kept the removed profile's budget)
- the TUI effort-disarm test fails instead of skipping when the catalog
  stops filling low effort
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator Author

All four addressed in 1c56a40:

  • --use-spec + a self-correcting profile: went with the documented spec-safe projection rather than rejection. The profile's self-correct knob is skipped under --use-spec (the draft runner wires no corrector and the explicit flag combination is already a parse-time error), while the budget and effort knobs still apply to the draft. Covered by TestApplyExecProfileSpecProjectsAwaySelfCorrect.
  • RunTurnBench now canonicalizes and validates ExecProfile at the boundary via the catalog lookup: " FAST " runs and stamps as "fast", and an unknown name errors before any task runs. Tests updated for both.
  • Reverting a profile whose displaced budget was zero now clears ZERO_MAX_TURNS explicitly (SetMaxTurnsEnv ignores zero). TestProfileCommandRevertFromZeroClearsMaxTurnsEnv pins it, including the env value mid-profile.
  • The effort-disarm test fails instead of skipping when the catalog stops filling low effort.

gnanam1990
gnanam1990 previously approved these changes Jul 18, 2026

@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.

APPROVE (code) — high quality, thoroughly verified. One pre-merge requirement: rebase off the stacked chain (it genuinely cannot merge before #736).

Reviewed across 7 dimensions with an independent verifier re-checking each finding against the code. 0 logic blockers, 0 logic majors.

Verified correct:

  • Precedence (flag > mode > profile): applyExecProfile runs after applyExecMode with fill-only-if-unset; an explicit -r, a mode's effort, or --spec-reasoning-effort is never overwritten. ✅
  • Balanced is byte-identical: Balanced = Profile{Name:"balanced"} → nil policy, fills nothing; asserted by reflect.DeepEqual in TestExecProfileBalancedLeavesOptionsUntouched. ✅
  • RestoreDefaultEffort: armed only when the profile actually filled effort (reasoningEffort=="" && profile.ReasoningEffort!=""); honored only when the target effort is empty (loop.go:203-207); disarmed on every user path (/effort, Ctrl+T, --spec-reasoning-effort) via policy-clone. #736 one-shot invariant intact. ✅
  • TUI /profile: never stacks (reverts before applying), revert is coincidence-safe (restores a knob only if untouched AND still equal to the applied value), /turns pins the budget by disarming the escalation turn target — all clone-by-value. ✅
  • perfbench: schema v4→v5 additive-only, name validated/canonicalized before spawning, forwarded+stamped+printed next to mode:, omitempty when unset; baseline.json diff empty. ✅
  • Risk trigger = critical not high — sound rationale (sandbox floors shell at high). ✅

Findings:

1. [Major — process] Stacked on 5 unmerged PRs. upstream/main..pr740 = 8 commits; only the top 3 are real PR10b/c — the lower 5 are #737/#736/#730/#734/#732. Merging as-is drags their diffs into main and conflicts when they land on their own. Genuine hard dependency: loop.go/types.go here consume RestoreDefaultEffort/RestoreCompletionGate from #736, so this cannot merge before #736. Enforce merge order (#730#732#734#736#737), then rebase; review the isolated git diff 015452c..pr740 (~20 files).

2. [Nit] applyProfileTurnBudget(… explicitMaxTurns int …) also receives a mode-filled budget, so the name misleads (behavior is correct — mode must block the profile). Rename to pinnedMaxTurns or add a comment.

3. [Nit] ZERO_MAX_TURNS not cleared on revert when the displaced budget was 0 (session_controls.go:395-398 + SetMaxTurnsEnv no-op for n≤0). Effectively unreachable (TUI budget defaults to 80, only 0 on a failed config resolve), but unconditionally restoring/clearing on revert would close it.

Ship-quality once rebased. Merge is kevin's call per the program gate, after the base chain lands.

@anandh8x anandh8x 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.

Reviewed latest head 1c56a40. The follow-up correctly fixes the benchmark boundary normalization and validation, zero-budget environment restoration, and the skipped effort regression; the spec-safe projection is coherent for a draft-only run. One TUI correctness blocker remains: active profile effort is applied only when /profile is selected, but both model-switch paths can change the model afterward without reconciling that profile state. For example, select fast on gpt-4.1 where low is unsupported, then switch through the provider/model picker to claude-sonnet-4.5: execProfileName remains fast, but reasoningEffort stays auto, so the next run is labeled fast without the catalog's low effort. The reverse cross-provider switch can retain a profile-applied effort unsupported by the destination. Reconcile an untouched active profile after both handleModelCommand and switchProviderModel: remove the old profile-applied effort, apply its configured level when the destination supports it, and update RestoreDefaultEffort consistently, while preserving explicitly touched effort choices. Please add regressions for unsupported-to-supported and supported-to-unsupported switches. Focused profile race tests, vet, formatting, diff hygiene, and govulncheck pass on this head.

@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

🤖 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/profile_command_test.go`:
- Around line 167-185: Update TestProfileCommandRevertFromZeroClearsMaxTurnsEnv
to use os.LookupEnv for config.MaxTurnsEnv after reverting the profile, and fail
if the key is still present regardless of its value. Preserve the existing
expectation that the environment entry is removed.
🪄 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: 0b7b6728-2214-4400-bf60-427720962b14

📥 Commits

Reviewing files that changed from the base of the PR and between d6688ab and 1c56a40.

📒 Files selected for processing (6)
  • internal/cli/exec.go
  • internal/cli/exec_profile_test.go
  • internal/perfbench/turn_bench.go
  • internal/perfbench/turn_bench_test.go
  • internal/tui/profile_command_test.go
  • internal/tui/session_controls.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/perfbench/turn_bench_test.go
  • internal/perfbench/turn_bench.go
  • internal/cli/exec_profile_test.go
  • internal/cli/exec.go
  • internal/tui/session_controls.go

Comment thread internal/tui/profile_command_test.go
The fill is per-model, not a session preference: fast selected on a
model without low fills nothing, and switching to a model that supports
it should behave like selecting the profile there; the reverse switch
must not keep sending a level the destination does not support. Both
switch paths (the /model command and the cross-provider picker) now
re-derive the fill, leaving explicitly touched efforts alone and
keeping the escalation's effort restore in step. Also: the revert-env
test asserts key removal via LookupEnv, and applyProfileTurnBudget's
pinned-budget parameter is named and documented as covering mode
presets, not just the flag.
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator Author

All addressed in b621b5b.

@anandh8x, good catch on the model-switch hole. Both switch paths (the /model command and the cross-provider picker) now call a shared reconcile step after the model changes: an untouched active profile re-derives its effort fill for the destination, so unsupported-to-supported fills the profile's level exactly as selecting it there would, supported-to-unsupported drops back to the model default instead of carrying a level the destination cannot take, and the escalation's effort restore arms/disarms in step. Explicitly touched efforts are never reconciled. Regressions added for all three: TestProfileEffortReconciledOnModelSwitch covers both directions end to end through switchProviderModel (real catalog model with low support on one side, custom no-catalog model on the other), and TestProfileEffortTouchedSurvivesModelSwitch pins the touched case.

@gnanam1990, on the stacked-chain finding: I think your local upstream/main is behind. The branch is cut from 015452c, which is current main and already contains the whole chain merged (#730 dbd9443, #732/#734, #736 af875df, #737 015452c itself). GitHub's compare against main shows only this PR's 20 files, and there is nothing to rebase onto; a fresh fetch should show origin/main..this-branch as just the PR commits. The #736 dependency you flagged is real and satisfied, since #736 merged yesterday. Your env-clear nit landed one commit before your review (1c56a40), and the pinned-budget parameter is renamed with the mode case documented.

CodeRabbit's LookupEnv tightening on the revert test is in as well.

@Vasanthdev2004
Vasanthdev2004 requested a review from anandh8x July 18, 2026 18:50

@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

🤖 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/command_center.go`:
- Around line 474-477: Preserve explicitly touched reasoning effort during
direct model switches: in internal/tui/command_center.go lines 474-477, update
the handleModelCommand reconciliation flow so unsupported destinations drop only
profile-filled effort, not a user-set effort, and let
reconcileProfileAfterModelSwitch apply the profile rules. Add the equivalent
explicit-effort scenario through handleModelCommand in
internal/tui/profile_command_test.go lines 76-93, verifying the touched effort
remains unchanged when switching to an unsupported model.
🪄 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: 4b1120dd-6593-42a1-9478-248512686d61

📥 Commits

Reviewing files that changed from the base of the PR and between 1c56a40 and b621b5b.

📒 Files selected for processing (4)
  • internal/cli/exec.go
  • internal/tui/command_center.go
  • internal/tui/profile_command_test.go
  • internal/tui/session_controls.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/tui/session_controls.go
  • internal/cli/exec.go

Comment thread internal/tui/command_center.go Outdated
…erases it

The /model path's pre-existing unsupported-effort drop ran before the
profile reconciliation and left contradictory state behind: a dropped
touched override kept blocking the profile from governing, and a
dropped profile fill left a stale applied value with the effort restore
still armed. The drop itself is load-bearing (the TUI run path forwards
the effort to the provider unfiltered), so instead of preserving a
known-unsupported level, the voided choice now resets the profile's
effort bookkeeping and the reconcile re-derives from scratch. The whole
sequence lives in one ring-parameterized helper driven by the switch
target's authoritative ring, unit-tested against synthetic rings for
both the void-then-refill arc and the supported-override survival.
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator Author

Fixed in 86c2c03, with one deliberate divergence from the literal suggestion.

Preserving a touched effort through the drop is not safe here: the TUI run path forwards m.reasoningEffort to the provider unfiltered (model.go, options.ReasoningEffort = string(m.reasoningEffort)), so a KNOWN-unsupported level surviving the switch goes straight into requests. That drop predates profiles and applies to a plain explicit effort too; a profile-touched effort should not be stickier than a plain one.

What was genuinely wrong, and is now fixed: the drop ran before the reconciliation and left contradictory profile state behind. A dropped touched override kept blocking the profile from governing, and a dropped profile fill left a stale applied value with the effort restore still armed. Now the whole sequence lives in one helper driven by the switch target's authoritative ring: drop a known-unsupported preference, void the profile's effort bookkeeping when the drop erased its fill or an override of it, then re-derive the fill for the destination. So /profile fast, /effort high, then /model to a ringless model lands on auto with clean state, and a later switch to a low-supporting model fills the profile's low again with the restore re-armed.

The difference from switchProviderModel is intentional and now documented at that call site: cross-provider targets are often custom models the catalog cannot vouch for either way, so an explicit preference is carried there (pre-existing behavior) while the profile's own fill stays conservative.

Tests: the void-then-refill arc and the supported-override survival are covered against synthetic rings (TestProfileEffortVoidedByModelSwitchDrop, TestProfileEffortTouchedSurvivesSupportedModelSwitch), alongside the existing end-to-end switch tests.

anandh8x
anandh8x previously approved these changes Jul 18, 2026

@anandh8x anandh8x 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.

Re-reviewed latest head 86c2c03. The original model-switch blocker and the follow-on touched-effort inconsistency are resolved. Both switch paths now re-derive profile-filled effort for the destination; known-unsupported explicit effort is dropped without leaving stale touched/applied/restore state, supported destinations refill the profile, and unknown custom-provider models conservatively retain explicit user intent. The escalation restore flag stays synchronized. Focused TUI/profile race tests, vet, and diff hygiene pass. Approving.

gnanam1990
gnanam1990 previously approved these changes Jul 18, 2026

@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.

Re-approve — all my prior findings are resolved and the base is now clean.

1. Stacking (my prior Major) — RESOLVED. The base chain (#730/#732/#734/#736/#737) merged; upstream/main..pr740 is now just the 6 PR10b/c commits and the diff is the real ~21 files (+1605/-28), no longer inflated. The #736 dependency is satisfied on main.

2. Nit (param naming) — FIXED. applyProfileTurnBudget(explicitMaxTurns …)pinnedMaxTurns, with a comment noting it covers a mode-preset fill too.

3. Nit (ZERO_MAX_TURNS on revert) — FIXED. revertExecProfile now os.Unsetenv(MaxTurnsEnv) when the displaced budget was 0, so spawned sub-agents can't inherit a removed profile's budget; covered by TestProfileCommandRevertFromZeroClearsMaxTurnsEnv.

New since my last pass — model-switch effort reconciliation, verified correct: reconcileEffortForModelSwitch/reconcileProfileAfterModelSwitch re-derive an active profile's per-model effort fill across switches — supported→unsupported clears the fill and disarms RestoreDefaultEffort; the reverse refills and re-arms; an explicitly touched effort is never reconciled; policy edits are clone-by-value so an in-flight run isn't mutated. Symmetric and correct, with 4 new directional tests. The spec-safe profile.SelfCorrect && !options.useSpec projection is a sensible edge fix.

Ran go test ./internal/tui ./internal/execprofile ./internal/cli ./internal/agent -run 'Profile|ExecProfile|Posture|Escalat|ModelSwitch' on the PR head — all green.

Ship-quality. Note CodeRabbit still shows CHANGES_REQUESTED from earlier commits — worth confirming those threads are addressed/dismissed before merge. Merge is kevin's per the gate.

@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

🤖 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/command_center.go`:
- Around line 467-472: Update the model-switch reconciliation around
reconcileEffortForModelSwitch to pass whether target.reasoningEfforts is
authoritative, so explicit effort is dropped only when supported levels are
known to be unavailable; preserve explicit effort when the ring is nil because
support metadata is unknown. In internal/tui/profile_command_test.go lines
77-124, add separate coverage for authoritative-empty and unknown rings,
asserting reset behavior only for the former.
🪄 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: 3ac8db5d-bbb4-4112-9509-79c428820f4d

📥 Commits

Reviewing files that changed from the base of the PR and between b621b5b and 86c2c03.

📒 Files selected for processing (3)
  • internal/tui/command_center.go
  • internal/tui/profile_command_test.go
  • internal/tui/session_controls.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/session_controls.go

Comment thread internal/tui/command_center.go Outdated
…itative

A nil ring meant both a catalog model with no effort controls and a
live-discovered custom model the catalog cannot vouch for. The drop now
requires an authoritative ring (a resolved catalog entry, whose ring may
legitimately be empty); an unknown ring preserves an explicit preference,
matching the cross-provider picker's behavior, while the profile's own
fill still applies only where support is known.
@Vasanthdev2004
Vasanthdev2004 dismissed stale reviews from gnanam1990 and anandh8x via 6778eb5 July 18, 2026 19:13
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator Author

Point taken, fixed in 6778eb5. The drop now requires an authoritative ring: reconcileEffortForModelSwitch takes ringKnown (target.entry != nil), so a catalog model with a legitimately empty ring still drops a known-unsupported preference, while a live-discovered/custom target with no catalog entry preserves an explicit choice, matching the cross-provider picker. The profile's own fill is unchanged in both regimes: it only ever applies where support is known. Both regimes are tested separately (TestProfileEffortVoidedByModelSwitchDrop for authoritative-empty, TestProfileEffortUnknownRingPreservesExplicitChoice for unknown, including the untouched-fill retreat).

For anyone skimming this thread: the other four comments in this round are re-posts of findings already resolved in 1c56a40 and b621b5b, and each carries its own addressed marker.

@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.

Re-approve (head 6778eb5). One new commit since my last pass — fix(tui): only drop an explicit effort when the target ring is authoritative — reviewed and correct.

It fixes an over-eager drop: previously a nil effort ring was treated as unsupported and cleared an explicit effort, but nil conflated a catalog model with no effort controls (authoritatively empty → drop is right) with a live-discovered/custom model the catalog can't vouch for (unknown → dropping a valid preference is wrong). The new ringKnown flag gates the drop on an authoritative ring (target.entry != nil); an unknown ring preserves the explicit preference, matching the cross-provider picker, while the profile's own fill stays conservative (applies only where support is known).

Verified:

  • Callers consistent: handleModelCommand passes target.entry != nil; the cross-provider switchProviderModel uses only the conservative reconcileProfileAfterModelSwitch retreat (never the drop). ✅
  • Tests updated coherently — (nil, true) still drops (authoritative-empty ring), new TestProfileEffortUnknownRingPreservesExplicitChoice covers (nil, false) for both an explicit choice (survives) and an untouched profile fill (retreats, restore disarmed). ✅
  • Ran go test ./internal/tui ./internal/execprofile ./internal/cli -run 'Profile|ExecProfile|ModelSwitch|Effort' on the head — all green.

All prior findings remain resolved (stacking, param naming, ZERO_MAX_TURNS revert). Ship-quality; merge is kevin's per the gate.

@Vasanthdev2004
Vasanthdev2004 merged commit 378d538 into main Jul 18, 2026
13 of 14 checks passed
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.

3 participants