feat(agents): harness-agnostic effort write path and spawn bridge - #4625
Conversation
2d8f97a to
e9e8f6a
Compare
c93e1c4 to
febf701
Compare
80df98f to
7c4c183
Compare
c60c87c to
5882e44
Compare
kalvinnchau
left a comment
There was a problem hiding this comment.
[P2] Pin-to-inherit can retain stale effort when the current runtime has no effort key — desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx:500
handleInheritHarnessChange decides whether to clear effort from the pre-toggle prospectiveRuntime. For an agent pinned to Claude (no thinkingEnvVar) whose linked persona uses Goose, selecting Inherit skips the clear. On the next render the prospective runtime becomes Goose, but the record's existing BUZZ_AGENT_THINKING_EFFORT remains; resolveInheritedRuntimeSubmission only excludes effort keys from the persona layer, so that record value is saved and the spawn bridge aliases it into Goose effort. This violates the stated pin↔inherit clear contract and can silently resurrect an effort value from an unrelated prior runtime. Resolve the runtime for nextInherit inside the handler (or clear whenever either the before or after runtime owns effort), and pin the Claude-pin → Goose-persona transition with an integration-level state/submit test.
[P2] Consumed legacy effort is also emitted as an advanced field — desktop/src-tauri/src/managed_agents/config_bridge/reader.rs:125
For a Goose record containing only a valid legacy BUZZ_AGENT_THINKING_EFFORT, build_thinking_field consumes it and exposes canonical thinking_effort, but normalized_env_keys contains only GOOSE_THINKING_EFFORT. The loop at line 140 therefore also emits the legacy key in surface.advanced. The config panel presents one persisted setting twice, with conflicting semantics, and an edit can appear to target a generic env var while the normalized field is already interpreting it. Invalid or unconsumed legacy values should remain advanced, so hide the legacy key only when record-tier alias resolution actually consumed it; add a surface test for valid-consumed versus invalid-visible legacy values.
Validation at exact PR tip 5882e4494656b0ce3deeffb2c1d1c1dea25e47f3:
just desktop-test: 4,774 passed, 0 failedjust desktop-tauri-test: passed (workspace; one pre-existing ignored native performance test)git diff --check a8e5c89e2..HEAD: clean
c4a6e99 to
513a75c
Compare
3bbf0e8 to
a9eff9e
Compare
af3d4d5 to
a1778cd
Compare
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Changes requested
-
[P2] Preserve custom-runtime effort env in the restart snapshot —
desktop/src-tauri/src/managed_agents/spawn_snapshot.rs:181The launch projection deliberately uses an empty suppress set for an unknown/custom runtime so wrapper-specific variables such as
GOOSE_THINKING_EFFORTremain ordinary pass-through env.SpawnConfigSnapshot::from_inputs, however, unconditionally removes every globally known effort key. For an unknown command,effective_effortonly readsBUZZ_ACP_EFFORT_LEVEL, so an exact-caseGOOSE_THINKING_EFFORTdisappears from bothsnapshot.envandsnapshot.effort_leveleven though the child receives it. Changing that variable therefore produces no restart diff and can leave a running custom-wrapper agent on the old effort despite auto-restart-on-config-change. Scope snapshot cleanup to the runtime projection: known runtimes can collapse their suppress set, while unknown runtimes must retain non-sentinel effort-looking keys as ordinary env. Please pin the custom-wrapper case with a snapshot regression test.
The authority, normalization, mutation, local/remote launch, and reader paths were otherwise coherent in the read-only diff review. Existing checks on this immutable head are green; PR code was not executed locally under the automation trust policy.
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Changes requested at exact head 239cbdd30cefee7a3a28d02271fc21de5226967f.
P2 — Mixed-case ACP sentinel changes for a custom runtime still disappear from restart snapshots
The new custom-runtime snapshot scoping fixes non-sentinel keys such as GOOSE_THINKING_EFFORT, but it still loses a mixed-case ACP sentinel on Windows.
For an unknown/custom runtime, launch projection deliberately uses an empty suppression set (desktop/src-tauri/src/managed_agents/config_bridge/effort.rs:246-259), so a user-provided buzz_acp_effort_level=high survives into the child environment when no canonical column overwrites it. Windows treats that key as BUZZ_ACP_EFFORT_LEVEL. Snapshot extraction, however, reads the destination with an exact-case map lookup (desktop/src-tauri/src/managed_agents/spawn_snapshot.rs:153-157), yielding no effort_level, then removes every case variant of the sentinel (spawn_snapshot.rs:194-203; config_bridge/effort.rs:208-226). Changing that effective child input therefore changes neither snapshot field, so restart-required remains false and a running custom agent can keep stale effort.
Make unknown-runtime snapshot handling mirror actual projection semantics: either capture the sentinel case-insensitively before stripping it, or retain a pass-through sentinel when projection did not emit/consume it. Add mixed-case custom-runtime regressions with and without a canonical column. The new test at spawn_snapshot/tests_ext.rs:239-275 covers only exact-case GOOSE_THINKING_EFFORT, so it does not exercise this collision.
The prior non-sentinel custom-runtime blocker is otherwise closed. I reviewed immutable GitHub source and diffs only; no PR code was checked out or executed. Hosted checks were green at review time.
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes's GitHub account.
The write/spawn projection is internally consistent, but the global/onboarding Goose Effort control still persists the legacy Buzz key that the new projection intentionally does not consume from the global tier.
deriveAgentConfigFieldModelsetscurrentPersistence.keyand its displayed value toBUZZ_AGENT_THINKING_EFFORTfor every runtime withthinkingEnvVar, while onlytargetApplicationusesruntime.thinkingEnvVar(desktop/src/features/agents/lib/agentConfigCore.ts:206-220).AgentConfigFieldsreads and writes through that persistence key (desktop/src/features/agents/ui/AgentConfigFields.tsx:244-257,638-640,876-885). Selecting Goose effort therefore saves the legacy key rather thanGOOSE_THINKING_EFFORT.- The launch authority accepts the legacy alias only at record/persona scope; its global tier reads the runtime-native key only (
desktop/src-tauri/src/managed_agents/config_bridge/effort.rs:362-383). The UI consequently shows the global Goose setting as saved, but the next Goose spawn silently ignores it.
Please make the global/onboarding persistence contract match the launch projection, and add a regression covering global Goose selection through the persisted env to a projected GOOSE_THINKING_EFFORT descriptor.
Drop fireEvent and useRef from the deferred import block; neither is used in the test suite and biome flagged them as unused variables. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Merge origin/main into duncan/effort-write-genericize. Conflict resolutions: - agents_deploy.rs: keep HEAD's effort-strip-free strip_key (the projection already reduced descriptor.env to one canonical effort key via resolve_effective_harness_descriptor; stripping it from launch.env would hide the projected value from the remote deploy) plus add ACP session policy strip from main. - runtime.rs: keep HEAD's apply_spawn_effort_env seam, add apply_demo_config_home from main. - spawn_snapshot.rs: keep HEAD's effective_effort(descriptor) call, add session_policy field from main. - spawn_snapshot/tests_ext.rs: retain HEAD's custom-runtime snapshot tests, add main's session-policy badge tests. - effort_tests.rs: compact apply_spawn_effort_env call arguments to reduce line count below the 1000-line file-size ratchet (was 1043, now 993). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…tchet effort_tests.rs was 1039 lines at the merge head — the merge from origin/main added 12 session-policy tests that re-inflated the file after the earlier trim. No coverage was removed; the command-boundary strip and production-sequence tests are now in effort_cmd_tests.rs declared via #[path] as mod cmd_tests inside the tests module. Both files are under 1000 lines. - effort_tests.rs: 701 lines (was 1039) - effort_cmd_tests.rs: 234 lines (new file) - All 37 effort tests pass - just file-size-check passes Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Changes requested
Reviewed head 7c0278288b24586d3e5a41857e8b7446c7770817 against base bd73490418266f267d9bb3bdf13e64582adc8e80. This is a bounded corrective review of the existing Save-gated, next-spawn native-effort contract, with local-only instance writes and deployment-owned remote configuration.
P2: Preserve model cleanup while preserving native effort
The new unconditional return at AgentConfigFields.tsx:480–496 stops the feedback loop but also skips clearing the previous provider's model.
Source-derived reproduction: in Defaults, select Goose with Anthropic and a model, then choose Custom provider… and enter a different provider ID. The actual Custom selection handler retains config.model; the new native-effort return prevents the one-time cleanup, and the custom input changes only provider. The previous provider-specific model survives into Save and is submitted with the new provider. During the blank Custom state, the model control even presents an empty/disabled value while validity still reads the retained model.
Smallest repair: return only when no eligible field needs clearing. Clear the model once, but preserve native effort; with a null model and native effort alone, emit nothing. Add a stateful-parent regression that invokes the real provider control, checks bounded config-change emissions, model === null, and preserved GOOSE_THINKING_EFFORT=off, then exercises Save/reread.
The current Custom-switch test replaces props directly, never setting userEditedProviderRef, and does not feed changes through React state. The separate Save test has a nonempty provider throughout. Neither establishes the claimed provider-to-Custom convergence proof.
P2: Remove or test-gate the obsolete production strip helper
After runtime moved to apply_spawn_effort_env, effort.rs:257–269 retains strip_effort_keys_from_command as non-test code, but its remaining callers are the old helper tests. The exact-head Desktop Core job fails deterministically in desktop-tauri-clippy:
error: function `strip_effort_keys_from_command` is never used
--> src/managed_agents/config_bridge/effort.rs:257:15
= note: `-D dead-code` implied by `-D warnings`
error: could not compile `buzz-desktop` (lib) due to 1 previous error
Remove the obsolete helper and migrate its assertions to the live boundary, or make it test-only if it still has a useful test role. Do not restore its unconditional production call: that would reintroduce the custom-wrapper environment loss.
Closed findings and validation limits
The prior render loop itself, inherited custom-wrapper environment loss, and Windows /usr/bin/env failure are addressed in source. The exact-head Windows Rust job is successful. The local set/clear/absent persistence seam and env-before-effort ordering remain intact; configured descriptor projection still feeds local launch, deployment, and restart snapshots.
All three independent review lanes returned and were integrated. Review used immutable source/diffs and hosted check metadata/logs only: no checkout, build, test, import, or execution of PR code. Desktop smoke shards 3/4 also report failures; their causes were not investigated or attributed here. Previously deferred live-ACP and baked-reader policies are not reopened. Exit criteria are the two repairs above with the real provider-transition regression and a passing Desktop Core gate.
…l-clear fix Launch outer seam: wrap apply_spawn_effort_env in apply_effort_to_spawn_command returning an EffortApplied sentinel. The sentinel binding in spawn_agent_child is a compile-time requirement — deleting the call is an error. Deleting apply_spawn_effort_env inside the wrapper turns the production-sequence tests RED. EnvVarGuard OsString: prior value stored as OsString (var_os) so non-Unicode values are restored exactly on Drop rather than silently lost. Crate-wide ENV_MUTEX in managed_agents/mod.rs: mirrors PATH_MUTEX. Effort tests use lock_env_mutex() instead of a file-local mutex, preventing races with other env-mutating test files compiled into the same binary. Dead-code clippy: mark strip_effort_keys_from_command #[cfg(test)] since it is only called from test code. Fix the import path in effort_cmd_tests.rs (super::super:: to reach effort.rs past the tests module boundary). Model cleanup (Carl P2): the orphan-model cleanup effect's isHarnessNativeEffort early-return was skipping the model clear on provider->Custom transitions. Refined to: return early only when model is already null; clear model once while preserving the native-effort key. Adds a mounted regression in effortAutoClear.test.mjs. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…enericize * origin/main: fix(desktop): preserve keyring identity during recovery (#7203) feat(mobile): prepare `buzz-push-gateway` for deployment (#7158) ci: relax file-size ceilings by surface (#6485) fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187) chore(ci): lower Codex security review effort (#7179) fix(dev-mcp): extend shell timeout cap to 20 minutes and align outer budgets (#7185) fix(dev): keep the canonical profile when launching from desktop/ (#7143) feat(buzz-auth): add production NIP-FI federated assertion runtime (#7109) Hide download action on voice notes (#7182) Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
…y tests
Launch seam (runtime.rs): add #[must_use] to EffortApplied, rename call
site from `let _effort` to `let effort`, add `spawn_with_effort_proof` that
consumes the token. Deleting the apply_effort_to_spawn_command call from
spawn_agent_child is now a compile error (effort undefined at spawn site).
Verified: cargo check --lib with line deleted → error[E0425] cannot find
value `effort`.
Update seam (agent_models_update.rs): add #[must_use] + RecordFieldsApplied
token already in place. stamp_record_updated_at consumes it. Deleting the
apply_record_field_updates call from update_managed_agent is a compile error
(applied undefined at stamp site). Verified: same error[E0425] pattern.
update_managed_agent_writes_effort_via_production_command test: drives the
full production sequence (load → apply_record_field_updates →
stamp_record_updated_at → save → load-from-disk) via mock AppHandle +
tempdir HOME/XDG override. Asserts effort_level persists to disk. #[cfg(not
(target_os = "windows"))] to avoid Windows HOME path differences.
Comment corrections: removed false claim ("deleting the call turns that test
RED" was incorrect since the test calls apply_record_field_updates directly
not via update_managed_agent) and replaced excuse comment in effort.rs with
accurate compile-error description.
Frontend (agentDefaultsEditor.test.mjs): two mounted parent journey tests.
- AgentDefaultsEditor: mount with QueryClientProvider + seeded Goose
runtime, get_global_agent_config stub returning GOOSE_THINKING_EFFORT=off,
assert effort custom trigger shows data-value="off" and text "Off".
- DefaultConfigStep: same config with provider populated, assert same
trigger invariant through the onboarding surface.
Both tests go RED when isHarnessNativeEffort branch is removed from
AgentConfigFields (trigger shows "Select" instead of "Off"). QueryClient
cancelQueries+clear in afterEach prevents gcTime timer stall.
Mutation proofs run and confirmed:
- Deleting let effort = apply_effort_to_spawn_command(...) → E0425
- Deleting let applied = apply_record_field_updates(...) → E0425
- Removing isHarnessNativeEffort branch in AgentConfigFields → both
agentDefaultsEditor tests fail with "got: \"Select\""
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
now_iso is used in agent_models_update.rs (stamp_record_updated_at) but not in agent_models.rs. Remove the dead import so clippy -D warnings does not fail. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The two comments claimed that update_managed_agent_writes_effort_via_production_command would go RED on a token-pattern revert (removing apply_record_field_updates from update_managed_agent). That claim is false: the test calls apply_record_field_updates directly in its own body, so deleting the production invocation does not affect it. The actual outer-seam proof for the production call is the compile error: apply_record_field_updates returns a #[must_use] RecordFieldsApplied token consumed by stamp_record_updated_at, so removing the call leaves applied undefined at the stamp site. Update both comment sites to describe exactly what each proof covers: - agent_models_update.rs inline comment: sole proof is the compile error - agent_models_update_tests.rs section header: compile error is the outer-seam gate; integration test proves disk-persistence of the function itself (called directly), not the production invocation - Integration test comment block: reframed as disk-persistence contract with clear scope boundary (does not gate production invocation) Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
- AGENTS.md item 14: update from deleted persistAgentEffortLevel direct-write contract to the shipped Save-gated update_managed_agent effortLevel path. Rewrite is consistent with EffortPickerField's own doc comment. - runtime.rs EffortApplied: add private field (()) so no crate-local code can construct the token outside apply_effort_to_spawn_command. Same shape as RecordFieldsApplied(()). - mod.rs: merge PATH_MUTEX and ENV_MUTEX into a single PROCESS_ENV_MUTEX static. Both lock_path_mutex() and lock_env_mutex() delegate to it so there is exactly one process-env lock domain in the crate; PATH and all env-key mutations are mutually exclusive. - agent_models_update_tests.rs: replace manual HOME/XDG_DATA_HOME restore with RAII EnvVarGuard (OsString, panic-safe Drop); update comment to name lock_path_mutex as the single-domain gate. Rename test from update_managed_agent_writes_effort_via_production_command to record_field_updates_persist_effort_to_disk — the test drives apply_record_field_updates directly and does not independently gate the production update_managed_agent invocation. Update outer-seam comment to reflect the rename. - agentDefaultsEditor.test.mjs: extend both tests from mount+assert to full Save/Next journey tests through the real parent trees. AgentDefaultsEditor: mount → assert 0 writes + Off shown → dirty via Advanced env-var editor → assert still 0 writes → click real Save defaults button → assert 1 write → unmount/remount fresh → assert 0 additional writes + Off still shown. DefaultConfigStep: same contract via real Next button (commit() fires because isDirty=true in draft). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
… journey tests
Thufir p3 IMPORTANT 1: the save→reread linkage was fabricated — neither test
operated the effort control, neither captured the submitted payload, and the
fresh-remount fixture hand-wrote GOOSE_THINKING_EFFORT: "off" back regardless
of what was actually saved.
Changes:
- Both tests now start with GOOSE_THINKING_EFFORT: "low" and select "off"
via the real Popover trigger + option click (AgentDropdownSelect exposes
options as <button data-testid="{testId}-option-{value}">, so fireEvent.click
works without pointer-event emulation).
- The set_global_agent_config stub captures the submitted payload and stores
the canonical response from it; the fresh parent's get_global_agent_config
returns that stored object, not a hand-written fixture.
- Each test now asserts raw GOOSE_THINKING_EFFORT: "off" in the captured
payload. Mutation check confirmed: dropping the key from the payload turns
the assertion RED (undefined !== "off").
- DefaultConfigStep test removes isDirty: true — the real-control effort
selection calls onConfigChange → updateDraft → isDirtyRef=true, so commit()
fires on Next without pre-seeding.
- Wording fix in managed_agents/mod.rs: narrows the mutex comment to its
actual scope (callers of lock_path_mutex/lock_env_mutex only; independent
locks in app_state_tests/agent_config_tests/reader_tests are NOT covered).
- AGENTS.md item 14: removes the stale "does not thread new props through the
dialog / keep effort state inside section component" sentence (code stores
effortLevel in AgentInstanceEditDialog state and passes value/onChange props).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Approved at 4caa6117322ac0f1caf20d7e8976a93b1cbf1bf1. No findings in the final delta. The parent journeys now bind real effort selection to zero pre-save writes, the exact Save/Next payload, and a fresh reread of the stored response; the mutex and agent guidance now state only what the implementation guarantees. Independent targeted run: 7/7 passed. Scores: minimalness 9/10, elegance 9/10, correctness 9/10. Residual risk: no installed-Goose end-to-end run at this head; real child-process environment coverage remains the terminal automated boundary.
* origin/main: feat(agents): harness-agnostic effort write path and spawn bridge (#4625) chore(db): drop Phase-A NIP-FI relay-side authority ledger (#7221) fix(acp): replace real user name in base prompt mention example (#7250) ci: split CI into reusable workflows (#7168) fix(desktop): retain automatic mentions only in threads (#7144) feat: add databricks fable 5.1 model capabilities (#7213) docs(nip-fi): rewrite NIP-FI as stateless OSS Buzz spec v2 (#7214) feat(relay): add detailed readiness metrics (#7149) feat(desktop): add Pi agent preset (#7208) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> # Conflicts: # desktop/src-tauri/src/commands/agent_models_update.rs # desktop/src-tauri/src/commands/agents_deploy.rs # desktop/src-tauri/src/managed_agents/types/requests.rs # desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx # desktop/src/shared/api/types.ts
…agent-edit * origin/main: feat(desktop): add persistent Bestie experience (#7223) fix(desktop): harden profile batch and thread-reply fetches against relay slowness (#7188) docs(nip-fi): adopt deny-until-TTL and extend enforcement to HTTP ingress (#7254) fix(composer): align wrapped inline chip fragments (#7242) Add operation-aware database pool acquisition metrics (#7195) fix(desktop): keep explicit agent profiles bound to their exact key (#7131) fix(desktop): discover authenticated owned relay agents (#7122) feat(agents): harness-agnostic effort write path and spawn bridge (#4625) chore(db): drop Phase-A NIP-FI relay-side authority ledger (#7221) fix(acp): replace real user name in base prompt mention example (#7250) ci: split CI into reusable workflows (#7168) fix(desktop): retain automatic mentions only in threads (#7144) feat: add databricks fable 5.1 model capabilities (#7213) docs(nip-fi): rewrite NIP-FI as stateless OSS Buzz spec v2 (#7214) feat(relay): add detailed readiness metrics (#7149) feat(desktop): add Pi agent preset (#7208) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> # Conflicts: # desktop/src/features/agents/ui/AgentInstanceEditDialog.tsx # desktop/src/features/agents/ui/agentInstanceEditPinning.test.mjs
EffortPickerField gained required disabled/value/onChange props in #4625. Wire them through the merged dialog using the same Save-gated pattern as the now-deleted AgentInstanceEditDialog: effortLevel state + effortTouched ref in AgentEditMergedDialog, passed as effortValue/onEffortChange to the instance section, which forwards to EffortPickerField. In useAgentEditMergedSubmit, add effortLevel/effortTouched to AgentEditSubmitState and include effortLevel in the locked update_managed_agent call when touched. When no other I-fields changed (agentInput would be null), synthesise a minimal input from inst.pubkey so the effort write is still atomic. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…runtime-switch reset IMPORTANT-1: Add effortLevel comparison to observedStateMatchesAgentInput (agentSaveCoordinator.ts) with tri-state semantics — absent submission skips, null clears, string compares against observed column. ManagedAgent now exposes effortLevel from ManagedAgentSummary (Rust types.rs + runtime.rs) so the comparator can detect a backend-rejected effort write rather than closing as success. Regressions: 4 family-14 coordinator tests; mutation (remove compare) turns the 2 rejected-edit tests RED while the 2 settled tests stay GREEN. IMPORTANT-2: Replace raw effortLevel include in useAgentEditMergedSubmit with resolveEffortSubmission — suppresses effort on pin→inherit (agentCommand:"") and on unchanged selections, matching the deleted AgentInstanceEditDialog's PR #4625 semantics exactly. originalEffortLevel threaded from configSurfaceQuery into AgentEditSubmitState. IMPORTANT-3: Add setEffortLevel and effortTouched to RuntimeHandlersInput; call setEffortLevel(null) and effortTouched.current = false in handleRuntimeDropdownChange (useAgentEditRuntimeHandlers.ts) so runtime switches do not carry stale vocabulary values into the next Save. Port the 6 load-bearing mounted production-seam tests from the deleted agentInstanceEditCancelSafety.test.mjs to agentEditMergedCancelSafety.test.mjs against AgentEditMergedDialog: selection+Cancel zero writes, ordinary Save carries effortLevel, rejected Save stays open, pin→inherit suppresses effort, access+effort atomic. Runtime-switch reset covered by hook-level test in agentEditMergedRuntimeReset.test.mjs. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Summary
Genericizes the agent effort write-side so Goose participates in the same canonical effort contract as buzz-agent. A spawn bridge translates the canonical key to whatever the target harness expects at launch time. Read/write/spawn paths all derive their vocabulary from runtime metadata rather than a hardcoded buzz-agent list.
What changed
Rust — config bridge + spawn path
apply_spawn_effort_envineffort.rs: production command-boundary seam — writes baked env, runs the effort projection, strips per-runtime suppress set, and emits exactly one projected key.apply_effort_to_spawn_commandinruntime.rs: thin wrapper returning a#[must_use] EffortApplied(())token (private field — unforgeable outside the function).spawn_agent_childcalls it aslet effort = apply_effort_to_spawn_command(...)and passesefforttospawn_with_effort_proof. Deleting the call is a compile error:effortis undefined at thespawn_with_effort_proofsite. Deletingapply_spawn_effort_envinside the wrapper turns the production-sequence tests RED.apply_record_field_updatesinagent_models_update.rs: returnsResult<RecordFieldsApplied, String>(#[must_use]token).update_managed_agentcalls it aslet applied = apply_record_field_updates(...)?then passesappliedtostamp_record_updated_at. Deleting the call is a compile error:appliedis undefined at thestamp_record_updated_atsite.apply_effort_launch_to_commandskips the suppress loop whenpreserve_passthrough && value.is_none(), preserving ambient ACP sentinels.EnvVarGuard: prior value stored asOsString(var_os) so non-Unicode values are restored exactly on Drop. A singlePROCESS_ENV_MUTEXinmanaged_agents/mod.rsis shared bylock_path_mutex()andlock_env_mutex()— any two tests calling either helper are mutually exclusive with each other. Tests in other modules (app_state_tests,agent_config_tests,reader_tests) maintain their own independent locks and are not in this domain.strip_effort_keys_from_commandmarked#[cfg(test)]; import path ineffort_cmd_tests.rsfixed.TypeScript — renderer + model cleanup
AgentConfigFieldsorphan-model cleanup effect: theisHarnessNativeEffortearly-return was skipping the model clear on provider→Custom transitions. Refined to: return early only when model is already null; clear model once while preserving the harness-native effort key (Carl P2).onConfigChangeloop.EffortSelectField/humanizeEffortLabel: runtime-native option labels title-cased (off→Off) with raw canonical values preserved for round-trip fidelity.AgentConfigFields: drives effort renderer fromselectedRuntime.effortCanonicalValues(harness-native path) or the model/provider catalog (buzz-agent/provider path), selected byisHarnessNativeEffort.Docs
desktop/src/features/agents/AGENTS.mditem 14: updated from deletedpersistAgentEffortLeveldirect-write contract to the shipped Save-gatedupdate_managed_agent.effortLevelpath. Consistent withEffortPickerField's own doc comment.Tests
agent_models_update_tests.rs: seam tests viaapply_record_field_updates— non-local rejects, local set/clear, ordering invariant, ACP-sentinel sweep.record_field_updates_persist_effort_to_disk(renamed from the prior false-claim name) drives load→apply→stamp→save→load via a mock AppHandle + tempdir, assertingeffort_levelpersists to disk. Manual HOME/XDG restore replaced with RAIIEnvVarGuard(panic-safe,OsString-exact).effort_cmd_tests.rs/effort_tests.rs: production-sequence seam tests viaapply_effort_to_spawn_command. Spawns/usr/bin/envto verify child's real env.EnvVarGuardfor panic-safe restore. Windows twin usingcmd /c set.effortAutoClear.test.mjs: five mounted stateful journeys viaAgentConfigFieldswithuseCustomSelect=true. Covers: custom trigger shows "Off" at mount; provider-empty mount is a stable fixed point; provider→Custom switch converges; stale Anthropic model cleared on Custom switch with Goose effort preserved (Carl P2 regression); Settings-style Save/reread preserves effort.agentDefaultsEditor.test.mjs: two full Save/Next journey tests through the real production parent trees. Both start withGOOSE_THINKING_EFFORT: "low"and operate the real Popover-based effort control (click trigger → click "off" option) before Save/Next, asserting zero writes after selection. Theset_global_agent_configstub captures the submitted payload; each test asserts rawGOOSE_THINKING_EFFORT: "off"in the captured config. The stub stores its canonical response from the actual payload; the fresh remount'sget_global_agent_configreturns that stored object (not a hand-written fixture), then asserts "Off" shown. TheDefaultConfigSteptest starts withisDirty: false— the real-control effort selection callsonConfigChange → updateDraft → isDirtyRef=true, making thecommit()on Next load-bearing.Mutation evidence
let effort = apply_effort_to_spawn_command(...)call fromspawn_agent_child→ compile error:error[E0425]: cannot find valueeffort`` atspawn_with_effort_proofsite.let applied = apply_record_field_updates(...)?fromupdate_managed_agent→ compile error:error[E0425]: cannot find valueapplied`` atstamp_record_updated_atsite.apply_spawn_effort_envfrom insideapply_effort_to_spawn_commandwrapper →production_sequence_goose_inherited_collision_resolved_in_childRED.isHarnessNativeEffort &&guard in cleanupuseEffectto bareif (isHarnessNativeEffort) return→ stale model not cleared → Carl P2 regression test RED.isHarnessNativeEffort ||from the nothing-to-clear condition → provider-empty mount emitsonConfigChange→ loop test RED.isHarnessNativeEffortbranch inAgentConfigFields.tsx:634-636→ bothagentDefaultsEditor.test.mjsmount assertions fail: trigger shows "Select" instead of initial effort label.preserve_passthroughguard inapply_effort_launch_to_command→production_sequence_custom_inherited_acp_sentinel_survivesRED.GOOSE_THINKING_EFFORTfrom theset_global_agent_configstub payload → payload assertion inagentDefaultsEditor.test.mjsfails (undefined !== "off") → RED (verified).DefaultConfigSteptest (soisDirtystays false) →commit()is a no-op → write-count assertion after Next fails (0 instead of 1) → RED.