Extract the pure cores of list_agent_templates (assemble_agent_templates)
and save_agent_as_template (upsert_template_persona) — behavior unchanged —
and pin them with unit tests: listing filters inactive and builtin-shadow
records and sorts saved templates case-insensitively after built-ins;
saving with an existing name updates the record in place (identity,
created_at, and stored env_vars preserved) instead of duplicating; a fresh
insert never carries credentials; and inactive or team-sourced records with
the same name are never hijacked. Also pin that an inbound persona echo
cannot resurrect a locally deactivated record — is_active is not a
projected field, so a stale relay echo leaves the local flag untouched.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Clicking "Blank agent" or "Use template" froze the entire webview until
runtime discovery finished. The details dialog opens immediately, but on
mount it fires three Tauri commands that were synchronous — and Tauri 2
runs sync commands on the main thread, so the UI blocked while login
shells resolved each agent binary.
- desktop/src-tauri/src/commands/agent_discovery.rs: make
discover_acp_providers and discover_managed_agent_prereqs async and
move their shell-spawning work into tokio::task::spawn_blocking so
the main thread never blocks; both now return Result to surface a
panicked task instead of unwrapping
- desktop/src-tauri/src/commands/agent_providers.rs: same treatment
for discover_backend_providers, which scans every PATH directory on
disk for buzz-backend-* binaries
- desktop/src/features/agents/ui/CreateAgentDialog.tsx: show a spinner
in the submit button while discovery is pending, so the disabled
button reads as "loading" rather than broken
No frontend API changes needed — invokeTauri handles the Result return
transparently. Also fixes the same freeze anywhere these queries fire
(Edit Agent, Add Channel Bot, Settings > Doctor).
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Agents are now only ever minted explicitly through the Create Agent
wizard. Personas surface exclusively inside the New Agent catalog as
"saved templates" instead of rendering as cards on the Agents grid.
Remove the flatten/materialization migration:
- Delete managed_agents/flatten.rs and flatten_tests.rs — the boot-time
migration minted a fresh keypair for every active persona with no
linked agent, but keyed dedup on the persona_id link it erased itself,
so relay persona echoes from other builds (dev vs release, d-tag
drift) re-minted duplicate agents on every launch on both sides
- Remove the flatten_personas_into_agents boot hookup in lib.rs
- Remove materialize_pack_agents_locked from team pack install/sync in
commands/teams.rs — installing a pack now lands its personas in the
catalog without minting agents
Catalog = built-ins + saved templates:
- list_agent_templates merges static built-ins with active persona
records (store lock held, spawn_blocking), skipping records that
shadow a built-in id and sorting saved entries by display name
- AgentTemplate gains provider and source ("builtin" | "saved") on both
the Rust struct and the TS type; templateAgentDraft carries provider
through to the create form prefill
- CreateAgentStartDialog renders saved templates under a "Saved"
sidebar group; detail pane labels the type accordingly
"Save as template" on minted agents:
- New save_agent_as_template Tauri command maps an agent's pinned
config (name, instructions, avatar, runtime, model, provider — never
env_vars) onto a persona record; re-saving the same name updates the
existing template instead of duplicating; retained as kind:30175
pending relay sync so templates reach other devices
- AgentActionsMenu gains a "Save as template" entry wired through
UnifiedAgentsSection/AgentsView with success toast and
agent-templates/personas query invalidation
Tests:
- e2eBridge mocks the merged template list and save_agent_as_template
- agents.spec.ts covers the new menu entry and the save-as-template →
Saved group → detail pane flow
- Stale flatten references in persona/teams test comments cleaned up
873 Tauri unit tests, 69 Playwright tests, and the full just ci gate
pass.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Backend half of removing personas from the UI. User-created agents no
longer have a backing persona record; persona records are quarantined to
team packs, which genuinely need them for directory install/sync and
writeback.
- types.rs, team_events.rs: add agent_pubkeys to TeamRecord, the
create/update team inputs, and the kind:30176 payload. The field is
additive — events published by older clients parse with empty agent
membership (round-trip and tolerance tests included)
- commands/teams.rs: normalize and validate agent pubkeys on team
create/update; team export resolves agent members into the interchange
member shape alongside persona members; pack install/sync materialize
pack personas as stopped agents so they appear in the grid immediately
- commands/personas/mod.rs: inbound kind:30176 reconcile applies
agent_pubkeys from remote events
- managed_agents/flatten.rs (new): idempotent boot migration —
materialize a stopped agent for every active persona without one,
rewrite non-pack team persona members to agent pubkeys, pin persona
config onto linked agents and clear persona_id, then deactivate the
flattened non-pack personas (records stay on disk/relay for rollback).
flatten_tests.rs covers materialization, team rewrite, pack
quarantine, and re-run idempotency
- managed_agents/personas.rs: stop seeding built-in personas as records;
the same data is now a static AgentTemplate list, and merge_personas
only demotes stale is_builtin flags
- commands/agent_templates.rs (new): list_agent_templates and
export_agent_to_json — agent-record export to the .persona.json
interchange format (env vars excluded, matching persona export)
- commands/agents.rs: create_managed_agent accepts an explicit provider
so templates and duplicates can pin the LLM provider at create time
- lib.rs: run the flatten migration post-identity, before event sync, so
rewritten teams/personas are what get reconciled and published;
register the new template commands
- team_repair.rs, personas/writeback.rs, inbound_tests.rs,
personas/tests.rs: fixtures and merge-semantics tests updated for the
new fields and the removal of built-in seeding; the shared test UUID
constant avoids a digit run the secret scanner reads as a card number
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
These predate the agents-first work but fail desktop-tauri-clippy
(-D warnings) and therefore block `just ci`, so they land first as a
standalone mechanical cleanup.
- agent_models.rs: drop the vestigial preserve_first_separator parameter
from title_case_model_suffix — both branches were identical
(if_same_then_else)
- config_bridge/claude.rs: build RuntimeFileConfig via struct-literal
init in the test helper (field_reassign_with_default)
- config_bridge/reader.rs: annotate build_model_field with
too_many_arguments and resolve_with_override with type_complexity —
pre-existing shapes, allowed rather than refactored to keep this
change mechanical
- runtime.rs: use slice::contains instead of iter().any
(manual_contains)
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>