Imported from OriginTrail/buzz-dkg-beta (
desktop/src/features/agents/AGENTS.md). Install upstream withnpx skills add OriginTrail/buzz-dkg-beta --skill agents. Copyright stays with the author.
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
preferred_runtime. Its complete draft lives in machine-onboarding session state, so Back performs no write and restores even incomplete edits when the user returns. Skip abandons that draft and advances with zero config writes. Next is the only persistence boundary: it consumes the shared renderer'sonValidityChangesignal, disables editing while awaitingset_global_agent_config, advances only on success, and leaves the draft in place with a retryable inline error on failure. A harness selection alone does not enable Next 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. Drafts intentionally do not survive an app restart.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. - Shared agent access names the consequence where it is selected. The
shared respond-to field shows a persistent warning whenever
anyoneorallowlistis selected — both hand the host's access to someone other than the owner, so both disclose it and only the audience phrase differs. This covers persona-backed create and edit surfaces. Keep that disclosure in the shared field instead of adding surface-specific flags. It renders directly below the selector foranyonebut after the people picker forallowlist, so it never sits between the user and the selection they came to make. The copy leads with the audience ("Anyone can use this agent to access…") so it reads as a warning rather than an explanation, and stays one sentence — don't split the mechanism into a second sentence. Both the machine and the stakes it names come fromlib/agentAccessWarning.ts, keyed on an optionalrunLocation: instance surfaces resolve it fromManagedAgent.backendviarunLocationForBackend, and the create flow fromWhereToRunDraft.runOnviarunLocationForRunOn.AgentDialogis the one place that resolves it for dialog surfaces and publishes it throughui/AgentRunLocationContext.tsx; the field reads that context and lets an explicitrunLocationprop win. Do not thread the value as a prop throughAgentDefinitionDialog/AgentInstanceEditDialog— both are already over the 1000-line ceiling, and neither uses the value itself. Surfaces rendered outsideAgentDialog(e.g.EditRespondToDialog) pass the prop directly. Local names "your computer, including files, accounts, and connected tools"; remote names "the server it runs on, including any accounts and tools available there" — deliberately not the owner's files, which aren't theirs to describe on a host they don't own. An unknown location falls back to the local wording — never hedge with "computer or server". A remote host requires an installedbuzz-backend-*provider, and without oneWhereToRunSectionnever renders, so "server" would name a concept the owner has never been shown; when it is remote they picked that host from the selector themselves. Never synthesize a run location a surface doesn't have. Don't exposerespond-to,allowlist, Nostr, or harness jargon in primary UI copy.
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.ui/respondToFieldContract.test.mjs— plain-language mode labels, the persistent warning contract for shared agent access, and its two render positions (after the people picker forallowlist).lib/agentAccessWarning.test.mjs— every mode × run-location copy variant plus both resolvers, including unknown-reads-as-local and blank-runOn-is-not-a-provider.desktop/tests/e2e/onboarding-agent-defaults.spec.ts— onboarding behavior acceptance coverage for readiness, failure states, defaults, session-draft restoration, zero-write Skip, Next save failure/retry, navigation, and successful-empty vs failed optional-model discovery.- 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.