Conversation
…riants
A custom harness definition file can now declare a `variants` block. Buzz
expands that one definition into one catalog entry per detected profile
directory, so a runtime picker gains entries like
"Hermes Agent [Generalist] (generalist)" with no runtime-specific code
anywhere in the desktop shell.
- `variants` takes `dir`, `marker`, `idTemplate`, `labelTemplate`,
`labelFrom`, `args` and `env`, expands `~/`, and is capped by
`MAX_HARNESS_VARIANTS`.
- `labelFrom` reads one dotted key out of a file in the profile directory
(e.g. `profile.yaml` -> `ui_meta.hermes-bots.title`) to fill `{meta}`.
- A definition without `variants` returns itself as one entry, unchanged; a
definition with `variants` returns itself (the user-editable template)
plus one generated entry per detected profile.
- Generated entries carry `generated` + `generated_from`, render read-only
in the harness gallery, and name the definition the user should edit
instead of offering an edit affordance the next scan would overwrite.
- `modelSelection: "harness"` flows to `AcpRuntimeCatalogEntry`, and the
shared agent-config renderer omits the model field with the reason
`ownedByHarnessSelection`, so the harness owns model choice.
- `custom_harness_entry(...)` becomes the single constructor for catalog
entries, shared by the discovery command layer and the tests.
Tests: 12 unit tests in
`managed_agents/custom_harnesses/variants/tests.rs`, all driven through the
production seam (`load_custom_harnesses` / `save_custom_harness_to_dir`);
new browser spec
`desktop/tests/e2e/harness-profile-variants-screenshots.spec.ts`, registered
in the Playwright `smoke` project, asserts the read-only generated rows and
the omitted model picker and captures the two proof frames.
Signed-off-by: Majied LaFleur <bigmandad@users.noreply.github.com>
🔐 Codex Security Review
|
|
Both frames come from the pushed commit 1. Settings > Agent runtimes: generated entries are read-onlyThe definition row Hermes Agent (profiles) keeps its actions menu (Edit and Delete are still in it). The two expanded profiles render as Hermes Agent [Developer] (default) and Hermes Agent [Generalist] (generalist), each with no actions menu at all, and each carrying the note:
The spec asserts the generated rows have no actions-menu trigger while the definition row's menu still offers Edit and Delete, so a regression that re-adds an edit affordance to a derived row fails it rather than passing quietly. 2. Add agent: a harness-owned model omits the model pickerWith a profile entry selected the dialog shows Agent name, Description, Agent instructions, Agent harness, Advanced. There is no model picker, no provider field and no AI-mode switch, and the picker returns when you switch to Buzz Agent, so the omission is caused by the definition's Where these are hosted. |


Implements #7781.
What this adds
A custom harness definition can now declare a
variantsblock, and Buzz expands that one definition into one catalog entry per detected profile directory. Before this, a runtime with one command but several isolated homes (profiles, projects, per-agent config dirs) needed one hand-written definition file per home, kept in sync by hand: add or rename a profile and the runtime dropdown is wrong until someone regenerates those files.{ "id": "hermes-profiles", "label": "Hermes Agent", "command": "hermes-acp", "modelSelection": "harness", "variants": { "dir": "~/.hermes/profiles", "marker": "profile.yaml", "idTemplate": "{id}-{slug}", "labelTemplate": "{label} [{meta}] ({name})", "labelFrom": { "file": "profile.yaml", "key": "ui_meta.hermes-bots.title" }, "env": { "HERMES_HOME": "{dir}" } } }That definition yields the template entry plus one generated entry per profile directory holding
profile.yaml, each with its ownHERMES_HOME, and each labelled from that profile's own persona title, for exampleHermes Agent [Generalist] (generalist).dirandmarker: which directories count. A directory without the marker is skipped, sodircan point at a shared parent.{id},{label},{name}(directory name),{slug}(id-safe directory name),{meta}fromlabelFrom,{root}(the scanned dir),{dir}(this profile's dir), usable inidTemplate,labelTemplate,args, andenv.labelFrom: one dotted key read out of a file inside the profile directory, with a fallback to a plain label when that file or key is missing, so an unreadable profile degrades instead of vanishing.max: optional; expansion is capped atMAX_HARNESS_VARIANTSregardless.variantsreturns exactly itself as one entry, so existing definitions are unaffected.Two things that go with it
Generated entries are read-only. They carry
generatedplusgenerated_from, render with no edit affordance, and name the definition the user should edit instead (screenshot 1). A derived entry that can be edited is a derived entry that gets silently overwritten by the next scan, which is the failure mode this design is avoiding.modelSelection: "harness". Optional, on any definition. When set, the shared agent-config renderer omits the model field and reports the named reasonownedByHarnessSelection, instead of offering a picker whose value the harness owns and ignores (screenshot 2). This is on the definition rather than keyed to a runtime id, so any harness that owns model choice can use it.Implementation notes
desktop/src-tauri/src/managed_agents/custom_harnesses/variants.rs(new, 414 lines): expansion,{placeholder}rendering,labelFromreading, validation.custom_harnesses/variants/tests.rs(new, 328 lines): 12 unit tests, all driven throughload_custom_harnessesafter a realsave_custom_harness_to_dirround-trip, so removing the expansion call in the loader, thevariantsvalidation, or thelabelFromread fails them.labelFromfile path that escapes the profile directory, an empty file or key, andenvthat the spawn path would reject anyway. Three of the twelve tests cover exactly that.custom_harness_entry(...)is now the single constructor for catalog entries, shared by the command layer and the tests, instead of two places assembling them and drifting.commands/agent_discovery.rsloses 32 lines to it.Manual test
<app_data_dir>/custom_harnesses/hermes-profiles.json(the same directory the Settings harness gallery writes to), adjusted to a profile root that exists on your machine.Hermes Agent [Generalist] (generalist)with no actions menu and the note naming the definition to edit.Verification
All of the following ran at the pushed commit
4752836374a1fb6328562a54e3874c7659ec6481(22 files, +1453/-84).The two spec frames are regenerated on every run and are byte-identical across runs (sha256
00f84233288c879fd59c4b4a78fced89a7deabfff5f4594729d02d5a6984c69cand6ce5151a439391349f50ada72ebc4153d2569d0d1b9ed910f6a572c5be1c0660), so the images below come from this exact commit.Two things I am not claiming as green, since neither is:
biome check .reports 2 warnings and 5 infos, and none of the seven is in a file this commit touches:src/features/home/ui/inboxReopenNavigation.test.mjs(3,useTemplate),src/features/sidebar/lib/channelMutesStorage.test.mjs(useLiteralKeys),src/features/sidebar/lib/channelStarsStorage.test.mjs(useLiteralKeys),src/features/messages/ui/useAgentAddressLockPicker.ts(useOptionalChain),src/shared/styles/globals/terminal.css:276(noImportantStyles). Exit code is 0, so this matches the state ofmainas far as I can tell, but it is not zero.src/shared/lib/useDocumentVisible.test.mjs:254,focused polling pauses on blur and resumes after activation yields. It is intermittent on this tree (3 consecutive runs of that single file: fail, pass, pass) and this commit touches no file in that test's import graph, so I read it as a pre-existing flake rather than something introduced here. Both facts are stated so a maintainer can disagree with the read.Toolchain substitutions, since this changes how much my numbers mean
bin/hermitexits with/pkg/hermit@stable/hermit: No such file or directory), sojust ciandjust desktop-screenshotwere replaced by the underlying commands run directly:npm run build:e2eplus Playwright for the frames, andcargofrom rustup 1.95.0 at~/.cargo/bin(OPUS_LIB_DIR=desktop/src-tauri/target/opus-prebuilt,CMAKE_GENERATOR=Ninja) for the Rust tests. These are not pinned-toolchain runs.chromium_headless_shell-1223, about 112 MB from the Playwright CDN), since the revision cached here was older, and apython3shim for the static server thatplaywright.config.tsstarts withpython3 -m http.server. Recipe written up in a local guide if useful.cargo clippylocally is red for pre-existing reasons: 53 errors across 26 files, with an empty intersection with this diff's file set, measured earlier in this work on the same content. I did not re-run clippy at this commit, so treat CI's clippy as the authority here rather than anything I say about it.Screenshots
The two frames are in the screenshot comment below, pinned to the same commit SHA as the checks above. They are hosted on a branch of my fork rather than through
scripts/post-screenshots.sh, because that script pushesagent-screenshots/<user>toorigin(block/buzz) and computes its raw URLs fromblock/buzz; as a contributor without write access I cannot push there, so I mirrored the script's file naming (pr-<n>--<name>.png) and SHA-pinned URL shape onto my fork. No relay media URLs are used, andscripts/check-pr-image-urls.shpasses on the same markdown.Duplicate check
Searched open and closed PRs and issues for "harness profile", "custom harness variants", "harness definition profiles", "definitionVariants", "generated_from harness", "profile directory catalog", "variants block harness": no existing PR or issue for this primitive. Closest adjacent reports, all different from this change: #5813 (Edit Agent dialog: Harness field not editable), #5155 (custom ACP harnesses meeting
managed-agents.jsonand the model dropdown), #7466 (starter profiles optional and removable).Deferred, not in this PR
labelFromread failures in the UI; today an unreadable profile degrades to the plain label and is logged as a warning.RUNTIME_LOGOS, so two profiles of one runtime cannot look different in the picker.Attribution
Implemented by an AI agent (Hermes) running on the author's machine, at the author's direction; the author is the accountable author of this PR. AI-assisted submissions are explicitly welcome per CONTRIBUTING, and I am saying so rather than leaving it to be inferred.