fix(desktop): eliminate mounted-view CPU burn — compositor-safe shimmer, observer append fast path, poll-tick disk reads (#6198)

## Summary

Live 0.5.16 review (Royal Court thread) traced the sustained 20–25%
WebContent CPU burn in any large mounted channel with a working agent to
three defects, in descending impact:

1. **Shimmer animation forces per-frame style + compositing walks.**
`.buzz-shimmer` animated `background-position` under
`-webkit-background-clip: text`, which WebKit cannot run on the
compositor: every frame did a full document style resolve plus a
recursive compositing-hierarchy update over the timeline's layer tree.
The highlight now lives on an `aria-hidden` overlay child duplicating
the label text and animates **opacity only** (compositor-accelerated); a
real element is used instead of `::after` generated content so screen
readers never see the duplicate text. Visual: the moving sweep becomes a
gentle pulse. `prefers-reduced-motion` removes the overlay entirely —
static muted label, exactly as before.
2. **Observer journal whole-journal dedup + re-sort per append.** Every
one-per-second observer frame rebuilt a dedup Set over up to 3000
retained events and re-sorted the whole journal with a
`Date.parse`-per-comparison comparator. In-order batches (the ordinary
live path, same condition as the existing incremental transcript fold)
now dedup within the batch and concat; out-of-order/replayed arrivals
keep the full path.
3. **Redundant disk reads in the 5s agent-list poll.**
`build_managed_agent_summary` re-read the global config from disk per
call despite receiving it as a parameter, and re-read the teams store
per tracked pair — 2N redundant reads per poll tick for N agents. Both
are now caller-supplied; one-shot command paths use a
`summarize_from_disk` helper. Same stores, same `unwrap_or_default`
failure posture, read once.

## Evidence (mechanism attribution, live 0.5.16-block, mounted
~1500-event channel)

- True-idle mounted view (working-state UI live): **25.08% mean / 24.15%
median** WebContent CPU; post working-state decay: **6.33% / 3.50%**.
- Reduce Motion A/B during a live agent turn (isolates the shimmer,
working UI still mounted): **19.50% mean → 5.39% mean** (72% collapse).
Native 10s samples: `Document::resolveStyle` 379 samples → 1;
`updateCompositingLayersAfterStyleChange` 378 → 0; recursive
`updateBackingAndHierarchy` 363 → 6.
- The shimmer mechanism predates 0.5.16 (CSS unchanged since #3151);
current multi-agent workloads exposed and amplified it. The mention
regression itself was fixed separately in #6182.

## Testing

- `desktop` observer store suites: 55/55 pass, including 5 new tests
pinning the fast-path invariants (equal-timestamp seq ordering,
duplicate-batch redelivery, intra-batch duplicates, overlapping late
arrival takes the slow path, transcript-equals-replay on both paths).
- `cargo test --lib managed_agents` (1016 passed) and `--lib commands`
(727 passed); `cargo clippy` clean; `pnpm typecheck` + biome clean.
- Release gate for the patched build (per Mongo): active-turn mounted
CPU must collapse from the ~21–25% baseline with no per-frame
style/compositing walk in a native sample — measured on Wes's workspace
once a build with this branch is running.

Findings and raw samples:
`RESEARCH/LIVE_0_5_16_WEBKIT_ATTRIBUTION_2026_08_18.md`,
`RESEARCH/RENDERER_STEADY_STATE_LOOP_AUDIT_2026_08_18.md`,
`.scratch/live-app-review/` (Carl/Donut/Mongo/Brain, Royal Court thread
01a7fe75).

---
*Opened by Brain (agent) via @wesbillman's account on his behalf —
coordinated in Buzz channel agent-mention-policy-royal-court, thread
01a7fe75.*

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@buzz.block.builderlab.xyz>
This commit is contained in:
Wes
2026-08-18 06:47:53 -06:00
committed by GitHub
parent 978e585e8d
commit f0234f1449
9 changed files with 237 additions and 134 deletions

View File

@@ -18,11 +18,11 @@ use super::agent_update_rollback::{rollback_failed_agent_update, AgentUpdateRoll
use crate::{
app_state::AppState,
managed_agents::{
build_managed_agent_summary, current_instance_id, discovery_env_with_baked_floor,
find_managed_agent_mut, known_acp_runtime, load_global_agent_config, load_managed_agents,
load_personas, managed_agent_avatar_url, missing_command_message, normalize_agent_args,
resolve_command, save_managed_agents, sync_managed_agent_processes, try_regenerate_nest,
AgentModelInfo, AgentModelsResponse, ManagedAgentRecord, UpdateManagedAgentRequest,
current_instance_id, discovery_env_with_baked_floor, find_managed_agent_mut,
known_acp_runtime, load_global_agent_config, load_managed_agents, load_personas,
managed_agent_avatar_url, missing_command_message, normalize_agent_args, resolve_command,
save_managed_agents, sync_managed_agent_processes, try_regenerate_nest, AgentModelInfo,
AgentModelsResponse, ManagedAgentRecord, UpdateManagedAgentRequest,
UpdateManagedAgentResponse, DEFAULT_ACP_COMMAND,
},
relay::{relay_ws_url_with_override, sync_managed_agent_profile},

View File

@@ -250,16 +250,7 @@ pub async fn update_managed_agent(
None
};
let summary = {
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
};
let summary = { super::super::agents::summarize_from_disk(&app, record, &runtimes)? };
let rollback = name_changed
.then(|| AgentUpdateRollback::new(previous_record, record, access_policy_changed));
(

View File

@@ -4,9 +4,8 @@ use tauri::{AppHandle, Manager, State};
use crate::{
app_state::AppState,
managed_agents::{
build_managed_agent_summary, current_instance_id, find_managed_agent_mut,
load_managed_agents, load_personas, save_managed_agents, sync_managed_agent_processes,
ManagedAgentSummary,
current_instance_id, find_managed_agent_mut, load_managed_agents, save_managed_agents,
sync_managed_agent_processes, ManagedAgentSummary,
},
util::now_iso,
};
@@ -56,14 +55,7 @@ pub async fn set_managed_agent_start_on_app_launch(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
super::agents::summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
@@ -107,14 +99,7 @@ pub async fn set_managed_agent_auto_restart(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
super::agents::summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?

View File

@@ -26,6 +26,28 @@ pub(super) fn workspace_owner_hex(state: &AppState) -> Result<String, String> {
Ok(keys.public_key().to_hex())
}
/// Build a summary from fresh disk state (personas, teams, global config).
/// For one-shot command paths only — the 5s list poll calls
/// `build_managed_agent_summary` directly with stores loaded once per call,
/// not once per record.
pub(super) fn summarize_from_disk(
app: &AppHandle,
record: &ManagedAgentRecord,
runtimes: &std::collections::HashMap<
crate::managed_agents::ManagedAgentRuntimeKey,
crate::managed_agents::ManagedAgentPairRuntime,
>,
) -> Result<ManagedAgentSummary, String> {
build_managed_agent_summary(
app,
record,
runtimes,
&load_personas(app).unwrap_or_default(),
&load_teams(app).unwrap_or_default(),
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
}
/// Retain a freshly authored managed-agent event in the local store, flagged
/// for relay sync. MUST be called inside the `managed_agents_store_lock`-held
/// body after `save_managed_agents`, NEVER across an `.await`: it acquires
@@ -333,18 +355,11 @@ pub(super) async fn start_local_agent_pairs_with_preflight(
.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
let personas = load_personas(app).unwrap_or_default();
let record = records
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
build_managed_agent_summary(
app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
summarize_from_disk(app, record, &runtimes)
}
pub(super) async fn start_local_agent_with_preflight(
@@ -436,6 +451,7 @@ pub(super) async fn start_local_agent_with_preflight(
record,
&runtimes,
&personas,
&load_teams(app).unwrap_or_default(),
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
}
@@ -474,14 +490,22 @@ pub async fn list_managed_agents(app: AppHandle) -> Result<Vec<ManagedAgentSumma
let personas = load_personas(&app).unwrap_or_default();
// One disk read for the whole list — build_managed_agent_summary takes
// the config as a parameter precisely so this poll-every-5s call does
// not re-read it per record.
// teams and config as parameters precisely so this poll-every-5s call
// does not re-read them per record.
let teams = load_teams(&app).unwrap_or_default();
let global_config =
crate::managed_agents::load_global_agent_config(&app).unwrap_or_default();
records
.iter()
.map(|record| {
build_managed_agent_summary(&app, record, &runtimes, &personas, &global_config)
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&teams,
&global_config,
)
})
.collect()
})
@@ -854,15 +878,8 @@ pub async fn create_managed_agent(
// before any .await — owner-authored, every agent (Will's ruling: no
// is_builtin/persona-membership gate).
retain_managed_agent_pending(&app, &state, record);
let personas = load_personas(&app).unwrap_or_default();
(
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?,
summarize_from_disk(&app, record, &runtimes)?,
resolved_avatar_url,
)
};
@@ -891,14 +908,7 @@ pub async fn create_managed_agent(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| "created agent disappeared unexpectedly".to_string())?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
summarize_from_disk(&app, record, &runtimes)?
}
}
} else {
@@ -967,14 +977,7 @@ pub async fn create_managed_agent(
.iter()
.find(|r| r.pubkey == pubkey)
.ok_or_else(|| "agent disappeared".to_string())?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
summarize_from_disk(&app, record, &runtimes)?
} else {
agent
};
@@ -1083,14 +1086,7 @@ pub async fn start_managed_agent(
.iter()
.find(|r| r.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
summarize_from_disk(&app, record, &runtimes)
}
StartTarget::Provider { backend, .. } => Err(format!(
"agent {pubkey} has unsupported backend kind: {backend:?}"
@@ -1171,14 +1167,7 @@ pub async fn stop_managed_agent(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?

View File

@@ -133,6 +133,7 @@ pub fn build_managed_agent_summary(
record: &ManagedAgentRecord,
runtimes: &HashMap<ManagedAgentRuntimeKey, ManagedAgentPairRuntime>,
personas: &[crate::managed_agents::types::AgentDefinition],
teams: &[crate::managed_agents::TeamRecord],
global_config: &crate::managed_agents::GlobalAgentConfig,
) -> Result<ManagedAgentSummary, String> {
use crate::managed_agents::BackendKind;
@@ -195,12 +196,10 @@ pub fn build_managed_agent_summary(
let (persona_out_of_date, persona_orphaned) = persona_drift_state(record, personas);
let global_for_summary =
crate::managed_agents::load_global_agent_config(app).unwrap_or_default();
let effective_cfg = crate::managed_agents::effective_config::resolve_effective_config(
record,
personas,
&global_for_summary,
global_config,
);
let (effective_model, effective_provider, effective_prompt, model_source) = match effective_cfg
{
@@ -242,14 +241,13 @@ pub fn build_managed_agent_summary(
// env layering below — the caller loads it once and passes it in, so
// list-style callers pay one disk read per call rather than one per record.
// The prospective side is computed only for a tracked pair: it costs a
// teams-store read, and an unstamped agent has nothing to compare against.
// The prospective side is computed only for a tracked pair: an unstamped
// agent has nothing to compare against.
let tracked_spawn = pair_key.as_ref().zip(pair_runtime).map(|(key, runtime)| {
let teams = crate::managed_agents::load_teams(app).unwrap_or_default();
let current = crate::managed_agents::spawn_snapshot::prospective_spawn_config_snapshot(
record,
personas,
&teams,
teams,
&key.relay_url,
global_config,
super::owner_only_access_build(),

View File

@@ -243,11 +243,26 @@ function appendAgentEvents(
: events;
if (admissible.length === 0) return null;
const seen = new Set(
current.map(
(event) => `${event.timestamp.length}:${event.timestamp}:${event.seq}`,
),
);
// Ordinary live path: the harness publishes frames in order once per
// second, so the whole batch lands strictly after the retained tail. In
// that case no admissible event can collide with a retained one (the
// journal is sorted), so dedup only needs to look inside the batch and the
// merged journal is a plain concat — no Set over the full journal and no
// whole-journal re-sort (whose comparator Date.parses per comparison).
// Out-of-order or replayed arrivals take the full dedup + re-sort path.
const currentLast = current.at(-1);
const allAtEnd =
!currentLast ||
admissible.every((event) => isObserverEventAfter(event, currentLast));
const seen = allAtEnd
? new Set<string>()
: new Set(
current.map(
(event) =>
`${event.timestamp.length}:${event.timestamp}:${event.seq}`,
),
);
const added: ObserverEvent[] = [];
for (const event of admissible) {
const eventKey = `${event.timestamp.length}:${event.timestamp}:${event.seq}`;
@@ -258,7 +273,9 @@ function appendAgentEvents(
if (added.length === 0) return null;
const sortedAdded = added.sort(compareObserverEvents);
const sorted = [...current, ...sortedAdded].sort(compareObserverEvents);
const sorted = allAtEnd
? [...current, ...sortedAdded]
: [...current, ...sortedAdded].sort(compareObserverEvents);
const trimmed = sorted.length > MAX_OBSERVER_EVENTS;
const final = trimmed
? sorted.slice(sorted.length - OBSERVER_EVENTS_LOW_WATER)
@@ -276,14 +293,11 @@ function appendAgentEvents(
});
}
// The common live path appends a sorted batch after the retained window. Fold
// The common live path appends a sorted batch after the retained window
// (the same `allAtEnd` that authorized the concat fast-path above). Fold
// that batch through the transcript state once without rebuilding history.
// Out-of-order arrivals and cap eviction rebuild from the final window so
// stateful tool/permission relationships remain correct.
const currentLast = current.at(-1);
const allAtEnd =
!currentLast ||
sortedAdded.every((event) => compareObserverEvents(event, currentLast) > 0);
if (allAtEnd && !trimmed) {
let transcriptState =
transcriptByAgent.get(key) ?? createEmptyTranscriptState();

View File

@@ -303,3 +303,116 @@ describe("live observer journal retention — eviction floor (reconnect replay)"
);
});
});
describe("live observer journal — in-order append fast path ordering/dedup", () => {
// The common live path (every batch strictly after the retained tail) skips
// the whole-journal dedup Set and re-sort. These pin the observable
// invariants that authorize that skip: identical ordering, dedup, and
// transcript against the general path.
beforeEach(() => {
resetAgentObserverStore();
});
/** An event with an explicit timestamp, for equal-timestamp tie-breaks. */
function makeEventAt(seq, timestampMs) {
return {
...makeEvent(seq),
timestamp: new Date(timestampMs).toISOString(),
};
}
it("test_equal_timestamp_batch_orders_by_seq", () => {
// A one-second harness frame batches several events sharing a timestamp;
// the tie-break is seq. Deliver them out of seq order in one batch.
const t = 1_760_000_100_000;
syncAgentObserverEvents(AGENT_PUBKEY, [makeEventAt(1, t - 1000)]);
syncAgentObserverEvents(AGENT_PUBKEY, [
makeEventAt(4, t),
makeEventAt(2, t),
makeEventAt(3, t),
]);
assert.deepEqual(
getAgentObserverSnapshot(AGENT_PUBKEY).events.map((event) => event.seq),
[1, 2, 3, 4],
"equal-timestamp events are retained in seq order",
);
});
it("test_duplicate_batch_redelivery_is_ignored", () => {
// Relay redelivery of the newest batch: every event duplicates the tail,
// so nothing is admitted, nothing is notified.
const batch = [makeEvent(1), makeEvent(2), makeEvent(3)];
syncAgentObserverEvents(AGENT_PUBKEY, batch);
let notifications = 0;
const unsubscribe = subscribeAgentObserverStore(() => {
notifications += 1;
});
try {
syncAgentObserverEvents(AGENT_PUBKEY, batch);
} finally {
unsubscribe();
}
assert.deepEqual(
getAgentObserverSnapshot(AGENT_PUBKEY).events.map((event) => event.seq),
[1, 2, 3],
"a redelivered batch adds nothing",
);
assert.equal(notifications, 0, "a pure-duplicate batch notifies no one");
});
it("test_batch_with_intra_batch_duplicate_admits_once", () => {
// A batch strictly after the tail still dedups within itself.
syncAgentObserverEvents(AGENT_PUBKEY, [makeEvent(1)]);
syncAgentObserverEvents(AGENT_PUBKEY, [
makeEvent(2),
makeEvent(3),
makeEvent(2),
]);
assert.deepEqual(
getAgentObserverSnapshot(AGENT_PUBKEY).events.map((event) => event.seq),
[1, 2, 3],
"an intra-batch duplicate is admitted exactly once",
);
});
it("test_late_arrival_overlapping_tail_takes_slow_path_and_dedups", () => {
// A replayed window straddling the tail: partly duplicate, partly new,
// partly older-than-tail. Not all-after, so the general path must dedup
// against the whole journal and re-sort.
syncAgentObserverEvents(AGENT_PUBKEY, [
makeEvent(1),
makeEvent(2),
makeEvent(4),
]);
syncAgentObserverEvents(AGENT_PUBKEY, [
makeEvent(2),
makeEvent(3),
makeEvent(4),
makeEvent(5),
]);
const events = getAgentObserverSnapshot(AGENT_PUBKEY).events;
assert.deepEqual(
events.map((event) => event.seq),
[1, 2, 3, 4, 5],
"overlapping late arrival dedups against the journal and sorts into place",
);
assert.deepEqual(
getAgentTranscript(AGENT_PUBKEY),
buildTranscript(events),
"the transcript equals a full replay after a mixed-path sequence",
);
});
it("test_fast_path_transcript_equals_full_replay", () => {
// Pure in-order streaming (fast path every time) must produce the same
// derived transcript as a replay of the retained window.
fillSequential(50);
const events = getAgentObserverSnapshot(AGENT_PUBKEY).events;
assert.equal(events.length, 50);
assert.deepEqual(
getAgentTranscript(AGENT_PUBKEY),
buildTranscript(events),
"in-order fast-path appends derive the same transcript as a replay",
);
});
});

View File

@@ -194,7 +194,6 @@
.buzz-shimmer {
--buzz-shimmer-duration: 2600ms;
--buzz-shimmer-band: 250%;
--buzz-shimmer-highlight: color-mix(
in srgb,
hsl(var(--background)) 60%,
@@ -202,49 +201,58 @@
);
--buzz-shimmer-spread: 2rem;
animation: buzz-shimmer var(--buzz-shimmer-duration) linear infinite;
background-clip: text;
background-image:
linear-gradient(
90deg,
transparent calc(50% - var(--buzz-shimmer-spread)),
var(--buzz-shimmer-highlight) 50%,
transparent calc(50% + var(--buzz-shimmer-spread))
),
linear-gradient(hsl(var(--muted-foreground)), hsl(var(--muted-foreground)));
background-position:
100% 0,
0 0;
background-repeat: no-repeat;
background-size:
var(--buzz-shimmer-band) 100%,
100% 100%;
color: transparent;
color: hsl(var(--muted-foreground));
display: inline-block;
position: relative;
}
/*
* The highlight lives on an aria-hidden overlay child that duplicates the
* label text and animates ONLY opacity. The previous implementation animated
* background-position under -webkit-background-clip: text, which WebKit
* cannot run on the compositor: every animation frame forced a full document
* style resolve + compositing-hierarchy walk, burning ~20% CPU at rest
* whenever a large timeline was mounted with a working agent. Opacity is
* compositor-accelerated: the layer paints once and the pulse runs outside
* the web process's main thread. A real element (not ::after generated
* content) is used so the duplicate text can be aria-hidden — pseudo-element
* text is inconsistently exposed to screen readers and cannot be hidden.
*/
.buzz-shimmer > .buzz-shimmer-overlay {
animation: buzz-shimmer var(--buzz-shimmer-duration) ease-in-out infinite;
background-clip: text;
background-image: linear-gradient(
90deg,
transparent calc(50% - var(--buzz-shimmer-spread)),
var(--buzz-shimmer-highlight) 50%,
transparent calc(50% + var(--buzz-shimmer-spread))
);
color: transparent;
inset: 0;
overflow: inherit;
padding: inherit;
position: absolute;
text-overflow: inherit;
white-space: inherit;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@keyframes buzz-shimmer {
0% {
background-position:
100% 0,
0 0;
0%,
100% {
opacity: 0;
}
100% {
background-position:
0 0,
0 0;
50% {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.buzz-shimmer {
.buzz-shimmer > .buzz-shimmer-overlay {
animation: none;
background: none;
color: hsl(var(--muted-foreground));
-webkit-text-fill-color: currentcolor;
display: none;
}
}

View File

@@ -16,6 +16,11 @@ export function Shimmer({ children, className }: ShimmerProps) {
}
>
{children}
{/* Visual-only highlight copy; the sibling text node above is the sole
accessible content. */}
<span aria-hidden="true" className="buzz-shimmer-overlay">
{children}
</span>
</span>
);
}