Skip to content

fix(desktop): propagate agent definition edits to instances and remove dead config knobs#1715

Merged
wpfleger96 merged 10 commits into
mainfrom
wpfleger/agent-config-propagation
Jul 10, 2026
Merged

fix(desktop): propagate agent definition edits to instances and remove dead config knobs#1715
wpfleger96 merged 10 commits into
mainfrom
wpfleger/agent-config-propagation

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Editing an agent definition (the former "persona" surface) silently failed to reach linked instances in several ways, and the instance edit dialog exposed two knobs that no longer do anything. Five fixes:

Runtime propagation (the core regression). Since runtime materialization landed (#1618), the spawn-time persona re-snapshot copied system_prompt/model/provider from the definition but not runtime — so once an instance carried a materialized runtime, definition-runtime edits were inert: no drift badge, no auto-restart, and the stale instance value shadowed the definition's on every resolution. The persona snapshot now carries runtime (copied verbatim from the definition, including None), and the four snapshot-apply sites (start_local_agent_with_preflight, backfill_persona_snapshots, restore_managed_agents_on_launch, and the prospective re-snapshot in spawn_config_hash — the latter is what makes the drift badge and auto-restart fire) now share a single apply_persona_snapshot, so a future snapshot field addition cannot miss a site the way this regression did. agent_command_override still wins as the explicit per-instance pin, and the Inherit sentinel now also clears the materialized record.runtime on definition-linked instances (extracted as apply_agent_command_update and unit-tested) so inherit intent takes effect immediately instead of on the next spawn; definition-less records keep their materialized runtime, since it is the only harness source they have left. Backfilled standalone agents (#1667) round-trip as a no-op since their manufactured definitions copy the record's runtime verbatim.

display_name propagation. update_persona republished the relay kind:0 profile under the new name but left linked records' local name/display_name frozen. Renames now propagate in one pass to linked records that still carry the old definition display name; instances individualized via name_pool keep their pool names, and the rename pass reports the affected pubkeys so only those records get a relay profile re-publish.

mcp_command is derived, not editable. The per-record mcp_command was writable from the instance dialog but never read — spawn, the runtime summary, and the spawn-config hash all derive the MCP command from the runtime catalog. The update arm no longer persists it, create derives it from the catalog, and the input is removed from the instance dialog. Stored values are deliberately not honored: stale records exist in real user data and honoring them would change agents' behavior on upgrade. The request fields stay for wire compatibility, doc-marked inert.

turn_timeout_seconds is dead. BUZZ_ACP_TURN_TIMEOUT is deprecated and ignored by the harness, and the desktop deliberately stopped emitting it — the editable field was a no-op. The input and the update/create arms are removed; the record and summary fields stay serde-compatible and doc-marked deprecated. idle_timeout_seconds and max_turn_duration_seconds are untouched (both work).

Pack write-back failures were invisible. write_back_persona_md swallowed every failure with a bare eprintln!, so edits to pack-backed definitions could silently diverge from the source .persona.md. The inner logic is extracted as try_write_back_persona_md (testable without an AppHandle), failures now come back as a non-fatal writeback_warning on the update_persona result (UpdatePersonaResult, flattened so existing callers keep working), and the API layer logs it. Writes are atomic — a sibling temp file renamed over the target — behind an explicit writability probe (a rename only needs directory permission, which would silently defeat a deliberately read-only pack file) and a canonicalized containment check so a pack manifest cannot redirect the write outside the pack root; symlinked pack dirs still resolve correctly. In-app edits still save when the pack file is unwritable. New regression tests cover the legacy team shape — UUID team ids, sprout-era source_dir whose basename is the team key, symlinked pack dirs, and inserting a missing runtime: frontmatter key — confirming path resolution works on current code, so the observability gap was the defect.

Context: #1618 (runtime materialization), #1667 (B5 standalone backfill).

…ances

The unified-agent materialization left definition-runtime edits inert
once an instance carried a materialized runtime: the spawn re-pin
and spawn_config_hash prospective re-snapshot copied model/provider
but not runtime, so the stale instance value shadowed the definition
via ladder step 2 with no drift badge and no auto-restart. The Inherit
sentinel in the update command also left a stale materialized runtime
that contradicted the just-expressed inherit intent until the next spawn.

Adds runtime to PersonaSnapshot, applies it verbatim from the persona
at all four re-snapshot sites (spawn, backfill, launch-restore,
spawn_config_hash), and clears record.runtime when the Inherit sentinel
clears agent_command_override.
The per-instance mcp_command field is write-only: spawn derives the MCP
command exclusively from the runtime catalog (known_acp_runtime), and the
stored record value is never read at spawn time. Edits made in the instance
dialog were silently discarded.

Stop persisting it on update (remove the patching arm) and on create
(always derive from the catalog instead of honoring input.mcp_command).
Keep the wire fields on both request structs for backward compatibility —
existing frontends that still send the field will parse cleanly with no
side effects. Remove the editable input from the instance dialog.
BUZZ_ACP_TURN_TIMEOUT is deprecated and ignored by the harness (marked
hide=true in buzz-acp/config.rs; the desktop stopped emitting it
deliberately). The per-record turn_timeout_seconds field is still stored
and shown in the runtime summary, but editing it has no effect.

Remove the editable input from the instance dialog and the patching arm
from update_managed_agent. Stop sending it on create too, using the
schema default instead. Keep the wire fields on both request structs so
older clients that still send the field parse cleanly without side effects.
Callers that need actual turn-length control should use
idle_timeout_seconds or max_turn_duration_seconds instead.
…cords

When a persona definition's display_name was renamed via `update_persona`,
linked agent instances whose `name` field matched the old display_name were
left stale. The config-drift badge would fire on next startup and only a
delete+respawn cycle would heal the mismatch.

Capture the old display_name before mutation, then propagate the rename to
any linked instance still carrying that name (skip pool-named instances like
"Birch" or "Compass" so individualized names are preserved). Both `record.name`
and `record.display_name` are updated so the relay profile sync that follows
publishes the correct name to the relay.
write_back_persona_md runs on every update_persona for pack-backed
personas and swallowed every failure behind a bare eprintln!, so a failed
write-back silently diverged the saved record from its source .persona.md
with no signal to the user.

Extract try_write_back_persona_md as a testable function that takes
resolved teams rather than an AppHandle, and return failures as
Option<String> from write_back_persona_md. update_persona forwards it as a
non-fatal writeback_warning on UpdatePersonaResult so the frontend can
surface the divergence; in-app edits still save even when the pack file is
unwritable.

Add regression tests for the legacy team shape (UUID team id, source_dir
whose basename is the team_persona_key, symlinked pack dir, persona .md
missing the runtime: key — confirmed working on current code) and for the
read-only pack file case.
@wpfleger96 wpfleger96 requested a review from a team as a code owner July 10, 2026 15:54
@wpfleger96 wpfleger96 requested a review from wesbillman July 10, 2026 15:54
…agation

The snapshot-to-record apply block was copy-pasted at four sites (spawn
re-pin, launch backfill, restore re-snapshot, prospective hash
re-snapshot); a PersonaSnapshot field addition had to touch all four --
exactly how the runtime propagation regression slipped in. All sites now
share apply_persona_snapshot. Rename propagation similarly ran per
record on one-element slices inside a loop that already filtered by
persona; it now runs once over the records slice and reports renamed
pubkeys for the relay profile sync.
A crash mid-write could truncate a pack .persona.md: writes now go
through a sibling temp file + rename, behind an explicit writability
probe (a rename only needs directory permission, which would silently
defeat a deliberately read-only pack file) and a canonicalized
containment check so a manifest cannot redirect the write outside the
pack root. Symlinked pack dirs still pass -- both sides resolve first.

The inherit sentinel's runtime clear is now guarded on a live persona
link: for a definition-less record the materialized runtime is the only
harness source left after the override clear, so a stray empty
agentCommand from a non-dialog caller must not change what the agent
runs. The edit-apply block is extracted as apply_agent_command_update
so the sentinel behavior is directly unit-tested.
idle_timeout_seconds claimed to override the now-deprecated
turn_timeout_seconds; describe its real BUZZ_ACP_IDLE_TIMEOUT semantics
instead, and mark the inert record/summary fields so a reader of the
struct does not need the PR history. Note on record.runtime that None
means inherit, serialization omits the key, and boot materialization
re-mirrors the live definition -- so a hand-edited "runtime": null is
honored and the state converges rather than resurrecting staleness.
The guard tests claimed that re-adding an update arm would fail their
assertions, but they never call update_managed_agent -- the asserted
record was locally deserialized, so the claim was false confidence.
They now assert only the wire shape: deprecated request fields keep
parsing for older frontends.
…ize guard

The dead-knob docs and sentinel helper pushed types.rs and discovery.rs
past their file-size limits. Move the managed-agent create/update
request DTOs into the existing types/requests.rs, and the
agent_command_override decision family (divergent / create-time /
update-time / apply) into discovery/overrides.rs -- both pure
relocations behind re-exports. The discovery.rs guard entry ratchets
802 -> 685 to bank the headroom.
@wpfleger96 wpfleger96 merged commit 1f48860 into main Jul 10, 2026
23 checks passed
@wpfleger96 wpfleger96 deleted the wpfleger/agent-config-propagation branch July 10, 2026 17:21
wpfleger96 added a commit to wpfleger96/ai-agent-rules that referenced this pull request Jul 10, 2026
…#197)

Two related cleanups to the Sietch Tabr pack deployment.

**Persona runtime declarations.** Adds `runtime: buzz-agent` to the
frontmatter of `paul.persona.md` and `duncan.persona.md`. Thufir and
Alia already declare their runtimes (`codex` / `claude`), but Paul and
Duncan resolved to `buzz-agent` only through the Buzz desktop app's
default-runtime fallthrough. Declaring it in the pack makes the intent
explicit and durable across team re-imports and persona materialization.

**Legacy Sprout symlink removal.** #154 removed the pre-rename
`xyz.block.sprout.app` / `xyz.block.sprout.app.dev` bundles from
`BuzzTool.symlinks`, but machines that installed earlier still carry
pack symlinks at those locations — and stale app state (e.g. the Buzz
`teams.json` `source_dir`) keeps resolving through them. `BuzzTool` now
surfaces those two paths via the existing `get_deprecated_symlinks()`
hook, so `install`/`apply`/`uninstall` actively remove them. Removal
only ever unlinks actual symlinks — regular files or directories at
those paths are left untouched.

Related: [block/buzz#1715](block/buzz#1715),
which fixed how runtime and agent-command edits propagate from persona
definitions to agent instances.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant