Add Agent Config Core: harness capability model behind agent config surfaces#2158
Conversation
wesbillman
left a comment
There was a problem hiding this comment.
No blocking behavioral or security findings on 1831b71255f7fde38ae21e64e29d85655497f22d. This is a good seam: static backend runtime metadata becomes the capability source, the IPC exposes only env-variable names (not values/secrets), and frontend use is limited to field derivation/object-key selection. The explicit current-vs-target persistence split also avoids silently changing Goose/Claude behavior in this structural PR. Main risk is future drift in the temporary harness-ID special cases, which the focused core tests and follow-up plan make visible. CI is green. Approval withheld because this head is 11 commits behind current main and overlaps tauri.ts/e2eBridge.ts; rebase and fresh green CI are required before the approval gate.
…vider * origin/main: (111 commits) fix: skip membership lookup for open relays (#2107) fix(desktop): make invite QR downloadable (#2168) Archive managed agents when deleted (#2135) perf(relay): cache Git pack hydration (#2169) chore(deps): update rust crate rustls to v0.23.42 (#2151) chore(deps): update dependency @tanstack/react-virtual to v3.14.6 (#2153) Add Agent Config Core: harness capability model behind agent config surfaces (#2158) chore(deps): update all non-major dependencies (#2152) chore(deps): update rust crate getrandom to v0.4.3 (#2150) fix(relay): bound and observe Git read operations (#2167) fix(desktop): derive default clone URL for relay-hosted repos (#2166) fix(desktop): mask composer rounded corners (#2165) feat(desktop): add PR merge conflict recovery (#2164) fix(desktop): mask scrolled content behind channel and thread composers (#2163) feat(desktop): add inline PR diff comments (#2162) feat(desktop): add commit-scoped PR review decisions (#2161) fix(desktop): batch project work item queries (#2160) feat(desktop): make missing project checkouts actionable (#2159) Fix harness default model states in onboarding (#2156) relay: gate push enqueue on live leases; batch matcher pipeline (T1b/T1a-repair/T2b) (#2145) ...
…rics * origin/main: (96 commits) fix(desktop): prefer live agent mentions (#2149) fix(desktop): close focused threads on Escape (#2154) fix: skip membership lookup for open relays (#2107) fix(desktop): make invite QR downloadable (#2168) Archive managed agents when deleted (#2135) perf(relay): cache Git pack hydration (#2169) chore(deps): update rust crate rustls to v0.23.42 (#2151) chore(deps): update dependency @tanstack/react-virtual to v3.14.6 (#2153) Add Agent Config Core: harness capability model behind agent config surfaces (#2158) chore(deps): update all non-major dependencies (#2152) chore(deps): update rust crate getrandom to v0.4.3 (#2150) fix(relay): bound and observe Git read operations (#2167) fix(desktop): derive default clone URL for relay-hosted repos (#2166) fix(desktop): mask composer rounded corners (#2165) feat(desktop): add PR merge conflict recovery (#2164) fix(desktop): mask scrolled content behind channel and thread composers (#2163) feat(desktop): add inline PR diff comments (#2162) feat(desktop): add commit-scoped PR review decisions (#2161) fix(desktop): batch project work item queries (#2160) feat(desktop): make missing project checkouts actionable (#2159) ...
Context
PR 2.6 of the agent-config plan (plan of record: Harness-Provider-Model). Follows #2148 (flag reduction) and #2156 (honest model resting states + safe harness switching), both merged.
The problem this PR exists to solve: every config surface hand-assumes all harnesses are shaped like Buzz Agent/Goose (Provider → Model → generic Effort). That assumption is how we shipped false controls — a Codex model id like
gpt-5.5[low]next to a separate generic Effort field, and Claude/Goose effort saved throughBUZZ_AGENT_THINKING_EFFORT, a key those harnesses never read. #2156 patched the symptoms (hid the false controls); this PR builds the seam that makes the truth structural, before the dialog-adoption PRs (5a/5b) spread the old assumptions to more surfaces.What we learned (and verified) while building this
KnownAcpRuntimein the Tauri backend declares each harness's real model/provider/effort env keys and uses them at spawn — but none of that crossed the IPC boundary, so the frontend hardcoded its own guesses. That gap is the root cause of the false-control bugs.aaif-goose/goosesource):GOOSE_THINKING_EFFORTis read from env (overriding config.yaml), valid levels are model/provider-specific inside Goose core, and effort is also an ACP session preference. Today's UI writes Buzz Agent's key for Goose — a silent no-op.id: "effort", categorythought_level) that our pre-spawn probe currently filters out (it only keepscategory == "model"options).Decisions encoded in this PR
AcpRuntimeCatalogEntrynow carriesmodel_env_var/provider_env_var/thinking_env_varfrom the sameKnownAcpRuntimetable spawn uses. The frontend core is a projection of the backend's answer, not a rival copy. (We explicitly rejected duplicating the table in TypeScript with a "keep in sync" comment — two copies of the truth drifting is how we got here.)currentPersistence(honest: still the legacyBUZZ_AGENT_THINKING_EFFORTkey for Buzz Agent and Goose) andtargetApplication(the harness's real key/option). This makes the migration state readable from the types — a PR 2.7 author cannot be misled into writing Goose keys that the read path doesn't see.ownedByModelId; Claude effort exists but renders asdeferredUntilNativeOptionsAvailable(its native option isn't plumbed through discovery yet). When 2.7 plumbs it, one enum case changes and every surface picks it up.onContextChange: "resetDependentValues"(harness/provider change clears dependent values everywhere) vsonCatalogMismatch: "explainOnly" | "onboardingCleanup"(an async catalog miss never silently erases state outside onboarding's named cleanup). The core resolves which applies from scope; render code never picks.undefinedand having fields silently vanish. (Caught in review: previously the Settings provider/effort controls could flash in late or disappear permanently if discovery failed.)What this PR does
AcpRuntimeCatalogEntry(one construction site, populated fromKnownAcpRuntime). ExtractsKnownAcpRuntime+ docs intodiscovery/runtime_metadata.rs— a responsibility split that also keepsdiscovery.rsunder the size guard without deleting anyone's comments.features/agents/lib/agentConfigCore.ts— pure, no React.deriveAgentConfigFieldModel(config, runtime, scope)→ field descriptors (kind,optionSource,currentPersistence,targetApplication,render,value) + named omissions + the clearing policy. Unit-tested per harness × scope (5 tests).AgentConfigFieldsderives provider/effort field presence from the core (deleting theshowProviderFieldprop the plan had scheduled for PR 3 — both callers computed it identically), reads/writes/clears effort through the descriptor'scurrentPersistencekey, and derives the onboarding-cleanup gate from the policy instead of a disclosure-string comparison.nullis preserved, so specs can still model a harness without a capability.Behavior is unchanged: all four harnesses render exactly the fields they did after #2156, onboarding pixels untouched (
onboarding-agent-defaults.spec.ts35/35 green as the acceptance gate),just cigreen.What this PR defers (deliberately)
GOOSE_THINKING_EFFORTand source its per-model levels from Goose core/ACP; widen the pre-spawn probe to keep Claude'sthought_leveloption and apply it at launch. The core'scurrentPersistence/targetApplicationsplit is the written spec for that PR.runtimeSupportsLlmProviderSelectiononto the catalog metadata (they agree today; tracked for 2.7/3).Testing
just ci(fmt, clippy, desktop lint, unit tests, builds)agentConfigCore.test.mjs: field model per harness (Buzz Agent, Goose, Claude, Codex) and clearing policy per scopeonboarding-agent-defaults.spec.ts35/35 (onboarding behavior pin)test_global_conn_cap/test_conn_counter_no_leak(buzz-pair-relay) fail identically on a cleanmaincheckout — pre-existing, unrelated to this branch.