9.4 KiB
Agent Configuration — Contributor Rules
Scope: desktop/src/features/agents/ (config surfaces, shared config renderer,
and the agent config core). Read this before changing how harness / provider /
model / effort configuration is modeled, rendered, persisted, or applied.
Plan of record: Buzz/Harness-Provider-Model.md in Morgan's Obsidian vault
(PR sequence, decisions log). PRs: #2140 (rename), #2148 (flag reduction),
#2156 (honest model states), #2158 (Agent Config Core).
The one rule
Harness capability facts have exactly one source: the Rust runtime catalog.
KnownAcpRuntime (desktop/src-tauri/src/managed_agents/discovery/runtime_metadata.rs)
declares each harness's model/provider/effort env keys and capabilities. Spawn
applies them; AcpRuntimeCatalogEntry exposes them over IPC; and
lib/agentConfigCore.ts projects them into field descriptors. The frontend
never maintains a rival copy of this table. Setup guidance follows the same
rule: requires_external_cli is derived from KnownAcpRuntime and projected
to the UI rather than inferred from a runtime ID in a component.
If you need a new capability fact (a new env key, a native option, a "supports
X" flag): add it to KnownAcpRuntime first, expose it on
AcpRuntimeCatalogEntry, then project it through the core. Do not shortcut
with a TypeScript lookup table or an id comparison in a component.
Rules
- No hardcoded harness-ID checks in render code.
runtime.id === "claude"belongs inderiveAgentConfigFieldModel(once, with a named reason), never in a component. Components ask the field model what exists (hasRenderableAgentConfigField,getRenderableEffortField). - Effort reads/writes go through the descriptor. Use the effort
descriptor's
currentPersistencekey — never a rawBUZZ_AGENT_THINKING_EFFORTliteral in UI code.currentPersistenceis where the value lives today;targetApplicationis how the harness should receive it. They intentionally differ until PR 2.7 migrates Goose/Claude — do not "fix" one to match the other without doing the migration work. - Field absence has a named reason, not a boolean. Codex effort is
ownedByModelId; Claude effort isdeferredUntilNativeOptionsAvailable. New absences get new named reasons inAgentConfigOmission/render— never ashowXprop. - The clearing policy is the named types.
onContextChange: "resetDependentValues"(user changed harness/provider → dependent values reset everywhere) vsonCatalogMismatch: "explainOnly" | "onboardingCleanup"(an async catalog miss never silently erases saved state outside onboarding's named cleanup). Do not add mutation booleans likeclearInvalidModel; extend the policy types. - "Metadata unknown" ≠ "harness lacks the capability". Passing
runtime: undefinedto the core means fields won't render. Surfaces must gate on the runtime catalog query settling (loading/error states) rather than letting fields silently vanish — seeAgentDefaultsEditor/DefaultConfigStepfor the pattern. - One canonical behavior, disclosure presets for visibility. Behavior
flags were deliberately killed in #2148 (
CANONICAL_CONFIG_BEHAVIORS). Surface differences are expressed via thedisclosurepreset, not new boolean props. Exception:onboarding-essentialhides happy-path helper copy (provider/model descriptions) but a non-null model-discovery status always bypasses the preset and renders the status line — enforced viashouldShowModelStatusMessage()(AgentConfigFields.tsx). Additionally, a successful discovery response that yields no usable options (supportsSwitching:falseor empty model list) synthesizes a warning status viasynthesizeEmptyDiscoveryStatus()and is intentionally not cached so that closing → reopening the dialog re-runs discovery after the user installs or signs into the CLI (isCacheableDiscoveryResponse()). - Onboarding setup detects readiness; it does not select defaults. The
setup page derives visible and ready harnesses from the runtime catalog and
only offers install or sign-in actions. The following defaults page is the
sole onboarding surface that chooses and persists
preferred_runtime, and its Finish gate consumes the shared renderer'sonValidityChangesignal — a harness selection alone does not complete onboarding when the harness requires provider/model/credential config (e.g. buzz-agent with no provider). Baked build env and runtime-file config satisfy the gate.onboarding-agent-defaults.spec.tsis the acceptance gate for anything touching this flow or the shared renderer. - Omit the Model control only after a confirmed successful empty
discovery on an optional-model harness. When the field model marks model
as
acpNative(Claude Code / Codex),shouldRenderModelControlhides the picker while discovery is in flight and after IPC resolves with no usable options (modelDiscoverySuccessfulEmpty/isSuccessfulEmptyDiscovery). A thrown or unavailable discovery keeps the control so #2246 failure UI can render, and must not heal/clear persisted model or effort. Full disclosure still shows the control when Custom model is available. Required-model harnesses always keep the field. Gate:defaults hides model when optional harness has empty discovery(and the failed-discovery counterpart) inonboarding-agent-defaults.spec.ts. - The defaults modal is progressively disclosed. An unset global config
starts on the Buzz Agent-first deployment fallback and carries that visible
harness into the next saved edit. The
progressive-defaultsdisclosure preset therefore begins at Provider for Buzz Agent, then reveals Model, Effort, and Advanced only after a provider is configured. Harnesses whose runtime metadata has no provider field skip that gate. Reveals animate their height through Motion and become immediate when reduced motion is requested. Once the Advanced toggle is visible, its expanded state is exclusively user-controlled: provider, harness, and required-env changes must never open it automatically in defaults, create, or edit flows. In Create mode, the defaults summary follows preferred-harness changes saved while the dialog is open, and its configured state includes required credentials as well as provider/model values. If no available harness can resolve, Create starts in Customize and lets unavailable catalog entries be selected only to expose their setup guidance; submission remains blocked. Advanced-only required credentials mark the collapsed Advanced toggle without opening it in Global Defaults and Edit, and block incomplete saves. Runtime-file credentials satisfy Global Defaults just as they do Create and Edit. In Edit, selecting Custom command keeps its required command field beside the harness picker rather than hiding it in Advanced. - Catalog visibility is community-scoped relay state, never a global
definition field.
AgentDefinition.sharedis only the active relay+owner projection returned to the UI. Durable heads and pending publications live in the scoped retention database, and explicit share toggles await relay acceptance before the UI claims that an agent was published or removed. A queued update must stay visibly queued, and the catalog itself must render only relay-confirmed publications — never an optimistic local persona. - Model IDs are runtime values; model labels are presentation. Preserve
the exact discovered or configured ID when selecting and persisting a
model. Any user-facing Databricks gateway model name must go through
lib/formatAgentModelLabel.tsso itsdatabricks-endpoint prefix is removed consistently without changing the value sent to the runtime.
The tests that enforce this
lib/agentConfigCore.test.mjs— field model per harness × scope, clearing policy. Update when the capability model changes.ui/agentConfigFieldsContract.test.mjs— canonical behaviors + disclosure presets +shouldShowModelStatusMessagestatus-bypass +shouldRenderModelControl(successful-empty omit vs failure keep). If this fails, you probably reintroduced a per-surface flag or conflated empty with failed discovery.ui/usePersonaModelDiscovery.test.mjs—synthesizeEmptyDiscoveryStatus,isCacheableDiscoveryResponse,deriveModelDiscoveryPending,isSuccessfulEmptyDiscovery. If the "reopen to retry" copy becomes inert again, these tests will catch it.desktop/tests/e2e/onboarding-agent-defaults.spec.ts— onboarding behavior acceptance coverage for readiness, failure states, defaults, navigation, successful-empty vs failed optional-model discovery, and persistence races.- Rust:
runtime_metadata_env_varstests pin spawn-time key application. - Rust: persona sharing/retention tests pin relay+owner scoping, durable enqueue errors, relay rejection/unavailability, and accepted publication.
Keep this file true
If you change how agent configuration is modeled, rendered, persisted, applied, or cleared — update this file in the same PR. A rule that no longer matches the code is worse than no rule; a new pattern that isn't written down here will be broken by the next agent that never learns it existed. Reviewers: treat a config-behavior diff without a matching AGENTS.md diff (or an explicit "no rules changed" note) as incomplete.