When Notifications Go Unheard: The Silent Failure of Event-Driven Agent Activation
Introduction
In the complex dance between autonomous agents and the infrastructure they manage, few things are more frustrating than building a notification system only to discover the intended recipient is deaf to it. This exact scenario unfolds in message [msg 4689], a user message that captures a critical moment in the development of an LLM-driven fleet management agent for a distributed GPU proving infrastructure. The message is deceptively simple—a truncated user complaint accompanied by a raw dump of the agent's conversation log—but it reveals a fundamental architectural flaw: the agent's fast-path optimization was silently defeating the entire event notification system that had just been painstakingly built.
The Message in Full
The user's message reads:
sounds agent doesn't activate on notification -> nning, we'll reach ~488-498 p/h, very close to target. No additional laun...
>
OBSERVE 13:46:39 r#13 ~68tok [Run #13 — 2026-03-17 13:46 UTC] Demand: active=True. Queue: PSProve 5p/39r. Throughput: 383/hr (1h), 105 (15m). Fleet: 9 running (412 p/h), 0 loading. Projected: 412 p/h. $4.78/hr. Target: 500 p/h. Budget headroom: $5.22/hr, 11 slots. ProofShare: 0 waiting, 44 computing. AGENT 13:46:43 r#13 ~101tok Analysis: All loading instances completed. Fleet at 412 p/h is 17.6% below 500 p/h target. Need ~88 p/h more capacity. Have budget headroom ($5.22/hr) and slots (11). ... TOOL:launch_instance 13:47:12 r#13 ~9tok { "ok": true, "vast_id": 33018051 } TOOL:launch_instance 13:47:14 r#13 ~9tok { "ok": true, "vast_id": 33018053 } AGENT 13:47:17 r#13 ~1tok OBSERVE 13:48:42 r#0 ~17tok [Config changed] target_proofs_hr: 500 → 300 (set by operator) OBSERVE 13:48:42 r#0 ~17tok [Config changed] target_proofs_hr: 300 → 500 (set by operator) OBSERVE 14:08:15 r#0 ~21tok [Instance 02c31a5c] registered → params_done: params fetched, starting benchmark OBSERVE 14:09:41 r#0 ~21tok [Instance 724e9454] registered → params_done: params fetched, starting benchmark OBSERVE 14:12:26 r#0 ~23tok [Instance C.33008932] params_done → bench_done: bench=36.6 p/h (min=30.0), ready to run OBSERVE 14:12:32 r#0 ~20tok [Instance 98e4a731] bench_done → running: instance is now producing proofs OBSERVE 14:16:10 r#0 ~21tok [Instance bcf00efd] registered → params_done: params fetched, starting benchmark OBSERVE 14:18:51 r#0 ~22tok [Instance C.33018053] params_done → killed: bench=0.0 p/h below min=30.0, destroyed
The message is truncated at the beginning—the user's sentence cuts off mid-word ("-> nning, we'll reach ~488-498 p/h"), suggesting they were typing a longer thought that got interrupted or the conversation_data capture clipped the leading text. What remains is unmistakable: a log of the agent's Run #13 showing it making scaling decisions, followed by a series of state change notifications that were injected into the conversation but never acted upon.
The Context: A Notification System Built on Faith
To understand why this message was written, we must trace back to the events immediately preceding it. Just three messages earlier, in [msg 4659], the user had issued a clear directive: "The agent should also be notified when instance changes state, e.g. Params Ok->Running, or Any->Killed." The assistant responded by implementing a comprehensive notification system across all state transition points in the Go backend ([msg 4660] through [msg 4688]).
The implementation was thorough. The assistant added a notifyAgentStateChange() helper function to agent_api.go and injected calls at every state transition in main.go: registered→params_done, params_done→bench_done, bench_done→running, and every path to killed (UI manual kill, instance disappearance, monitor timeout, benchmark failure). Each notification was injected into the agent's conversation as a user-role message with run_id=0 (system-generated), following the same pattern as the config change notifications already in place.
The assistant's closing message in [msg 4688] expressed confidence: "These inject directly into the agent conversation as user role messages with run_id=0 (system-generated). The agent will see them on its next run and can reason about fleet changes: 'Instance X just came online, capacity increased' or 'Instance Y was killed, need to replace.'"
This assumption—that the agent would "see them on its next run"—was the critical error. The assistant assumed that injecting messages into the conversation database was sufficient, but it overlooked how the agent's execution flow actually worked.
The Silent Defeat: How the Fast-Path Swallowed the Notifications
The conversation log that the user pasted in [msg 4689] tells the story vividly. At 13:46 UTC, the agent ran its observe cycle (Run #13). It analyzed the fleet state, determined it was 17.6% below the 500 p/h target, and launched two new instances. This was normal operation. But then, between 14:08 and 14:18, a cascade of state change notifications appeared in the conversation:
- An instance completed parameter fetching and started benchmarking
- Another instance did the same
- One instance passed its benchmark and became ready to run
- An instance transitioned from bench_done to running—it was now producing proofs
- A newly launched instance failed its benchmark with 0.0 p/h and was killed These are precisely the events the notification system was built to capture. The agent should have seen "[Instance 98e4a731] bench_done → running: instance is now producing proofs" and recognized that its fleet capacity had increased. It should have seen "[Instance C.33018053] params_done → killed: bench=0.0 p/h below min=30.0, destroyed" and known that one of its newly launched instances had failed, requiring a replacement. But the agent did nothing. The conversation log shows no subsequent agent analysis, no tool calls, no decisions. The notifications accumulated silently in the conversation database, never triggering a response.
Root Cause: The Fast-Path Bypass
The root cause, which the assistant would diagnose in the very next message ([msg 4690]), was a design flaw in the agent's fast-path logic. The agent had been optimized to skip the LLM call when projected capacity was close to the target—a sensible optimization to save tokens and API costs when no action was needed. The fast-path check ran before examining the conversation for new notifications. Since the state change notifications were injected with run_id=0 (a special value indicating system-generated messages), they were invisible to the fast-path's decision logic.
The agent's execution flow was:
- Fetch fleet state and demand
- Compute projected capacity
- Fast-path check: If projected capacity is within threshold of target, skip LLM call
- Load conversation context
- Call LLM with full context Step 3 was the bottleneck. The projected capacity after the new instances came online was close to the 500 p/h target (the user's truncated message mentions "~488-498 p/h, very close to target"). The fast-path saw "close enough, no action needed" and skipped the LLM call entirely, never reaching step 4 where it would have loaded the conversation and seen the notifications. This is a classic systems design failure: the optimization (fast-path) was correct for its original purpose (avoiding unnecessary LLM calls when the fleet is balanced), but it was not updated when the notification system was added. The fast-path operated on a stale model of what constituted "new information." It only considered the quantitative fleet metrics (projected capacity vs. target) and completely ignored the qualitative state change signals that had been injected into the conversation.
Assumptions and Their Consequences
Several assumptions converged to create this failure:
The assistant's assumption in [msg 4688] that injecting messages into the conversation was sufficient for the agent to act on them. This assumed that the agent's execution flow always loaded and processed the full conversation, which was false when the fast-path triggered.
The original fast-path designer's assumption that projected capacity relative to target was the only signal worth acting on. This was a reasonable assumption for a system designed purely around scaling to meet demand, but it became incorrect once state change notifications were introduced as a separate communication channel.
The user's assumption that the notification system would work as intended. The user's message—"sounds agent doesn't activate on notification"—is a polite but pointed observation that the feature they requested is not functioning. The user had enough visibility into the system to notice that state changes were being logged but not acted upon.
Input and Output Knowledge
To fully understand this message, one needs input knowledge of:
- The agent architecture: The agent runs on a 5-minute timer (systemd timer) and optionally via event-driven triggers (systemd.path unit). Each run follows a pipeline: fetch state, check fast-path, load conversation, call LLM, execute decisions.
- The notification system: Just built in messages [msg 4660]-[msg 4688], the
notifyAgentStateChange()function injectsuser-role messages withrun_id=0into the SQLite conversation table at every instance state transition. - The fast-path optimization: A cost-saving measure that skips the LLM call when projected fleet capacity is within a threshold of the target, avoiding unnecessary token consumption when the fleet is already balanced.
- The conversation format: Messages in the conversation have a
run_idfield. System-generated notifications userun_id=0, while agent-generated messages use the sequential run number. The output knowledge created by this message is: - Evidence of the failure mode: The conversation log shows notifications accumulating without agent response, providing concrete data for debugging.
- The user's diagnosis: The truncated text "sounds agent doesn't activate on notification" is the user's own hypothesis, formed by observing the system behavior.
- A debugging trail: The timestamps show that notifications appeared between 14:08 and 14:18, but no agent run occurred after 13:47 (Run #13), confirming the agent was not triggered by these events.
The Thinking Process Revealed
The user's message reveals a sophisticated understanding of the system. Rather than simply reporting "the agent isn't working," the user provides evidence in the form of the conversation log, allowing the assistant to trace the exact failure mode. The user has clearly been monitoring the system closely, watching for the state change notifications they requested and noticing their lack of effect.
The user's truncated sentence—"sounds agent doesn't activate on notification -> nning, we'll reach ~488-498 p/h, very close to target. No additional laun..."—shows their reasoning process. They observed that the fleet was approaching the target capacity, and they hypothesized that the agent's fast-path was skipping the LLM call because the fleet was "close enough," thereby missing the notifications. The "->" suggests a causal chain they were tracing: "agent doesn't activate on notification [because] we'll reach ~488-498 p/h, very close to target."
This is impressive systems thinking from the user. They didn't just observe a symptom; they connected it to a specific mechanism (the fast-path) and provided the evidence needed to confirm the hypothesis. The truncated "No additional laun..." suggests they were about to note that no additional launches were happening despite the killed instance needing replacement, further reinforcing the point.
The Broader Architectural Lesson
This message captures a classic tension in autonomous system design: the conflict between efficiency and responsiveness. The fast-path optimization was introduced to save money on LLM API calls—a legitimate concern given that each agent run consumes tokens and costs money. But the optimization was too aggressive, operating on a narrow view of what constitutes "actionable information."
The deeper issue is one of event granularity. The notification system treated each state transition as a meaningful event worthy of agent attention. But the fast-path treated only aggregate capacity metrics as meaningful. These two views of "what matters" were never reconciled during the notification system's implementation.
The fix, which the assistant would implement in [msg 4690], was straightforward: count pending notifications in the conversation and force an LLM call when unprocessed state changes exist. But the conceptual lesson is broader: when adding a new communication channel to an autonomous system, every optimization point along the processing pipeline must be audited to ensure the new signals are not silently discarded.
Conclusion
Message [msg 4689] is a masterclass in user-driven debugging. In a few truncated sentences and a raw conversation dump, the user identified a critical failure mode, provided the evidence needed to diagnose it, and pointed toward the root cause. The message reveals the brittleness of event-driven architectures where optimizations can accidentally silence the very signals they were meant to amplify. For the assistant, it was a humbling reminder that "the agent will see them on its next run" is not the same as "the agent will act on them." For the system, it was the discovery of a silent failure that, left unfixed, would have rendered the entire notification system useless—a ghost channel where important events were broadcast but never received.