Files
buzz/desktop/src/features/agents/AGENTS.md
2026-07-29 19:32:57 +01:00

9.4 KiB
Raw Blame History

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

  1. No hardcoded harness-ID checks in render code. runtime.id === "claude" belongs in deriveAgentConfigFieldModel (once, with a named reason), never in a component. Components ask the field model what exists (hasRenderableAgentConfigField, getRenderableEffortField).
  2. Effort reads/writes go through the descriptor. Use the effort descriptor's currentPersistence key — never a raw BUZZ_AGENT_THINKING_EFFORT literal in UI code. currentPersistence is where the value lives today; targetApplication is 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.
  3. Field absence has a named reason, not a boolean. Codex effort is ownedByModelId; Claude effort is deferredUntilNativeOptionsAvailable. New absences get new named reasons in AgentConfigOmission / render — never a showX prop.
  4. The clearing policy is the named types. onContextChange: "resetDependentValues" (user changed harness/provider → dependent values reset everywhere) vs onCatalogMismatch: "explainOnly" | "onboardingCleanup" (an async catalog miss never silently erases saved state outside onboarding's named cleanup). Do not add mutation booleans like clearInvalidModel; extend the policy types.
  5. "Metadata unknown" ≠ "harness lacks the capability". Passing runtime: undefined to 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 — see AgentDefaultsEditor / DefaultConfigStep for the pattern.
  6. One canonical behavior, disclosure presets for visibility. Behavior flags were deliberately killed in #2148 (CANONICAL_CONFIG_BEHAVIORS). Surface differences are expressed via the disclosure preset, not new boolean props. Exception: onboarding-essential hides happy-path helper copy (provider/model descriptions) but a non-null model-discovery status always bypasses the preset and renders the status line — enforced via shouldShowModelStatusMessage() (AgentConfigFields.tsx). Additionally, a successful discovery response that yields no usable options (supportsSwitching:false or empty model list) synthesizes a warning status via synthesizeEmptyDiscoveryStatus() and is intentionally not cached so that closing → reopening the dialog re-runs discovery after the user installs or signs into the CLI (isCacheableDiscoveryResponse()).
  7. 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's onValidityChange signal — 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.ts is the acceptance gate for anything touching this flow or the shared renderer.
  8. 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), shouldRenderModelControl hides 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) in onboarding-agent-defaults.spec.ts.
  9. 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-defaults disclosure 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.
  10. Catalog visibility is community-scoped relay state, never a global definition field. AgentDefinition.shared is 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.
  11. 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.ts so its databricks- 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 + shouldShowModelStatusMessage status-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_vars tests 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.