From 2a7aea5950b20b308e7431e8a26cddab29214728 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Mon, 23 Mar 2026 13:48:26 -0400 Subject: [PATCH] Replace ContextWindowWarning with generic Warning event Introduce a reusable Warning { kind, message, details } variant in AgentEvent so non-fatal warnings (context window usage, deprecation, etc.) share a single event shape. The context_window warning preserves all original fields inside the JSON details object. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs-internal/events-strategy.md | 2 +- docs/execution/context.mdx | 2 +- docs/execution/observability.mdx | 2 +- lib/crates/fabro-agent/README.md | 2 +- lib/crates/fabro-agent/src/compaction.rs | 24 +++++++++++-------- lib/crates/fabro-agent/src/session.rs | 10 +++----- lib/crates/fabro-agent/src/types.rs | 21 +++++++--------- .../fabro-cli/src/commands/run_progress.rs | 8 ++++++- 8 files changed, 36 insertions(+), 35 deletions(-) diff --git a/docs-internal/events-strategy.md b/docs-internal/events-strategy.md index e7fb4021e..564692cac 100644 --- a/docs-internal/events-strategy.md +++ b/docs-internal/events-strategy.md @@ -253,7 +253,7 @@ All agent events include `node_id` and `node_label`. | `Agent.ToolCallOutputDelta` | `delta` | | `Agent.ToolCallCompleted` | `tool_name`, `tool_call_id`, `output`, `is_error` | | `Agent.Error` | `error` | -| `Agent.ContextWindowWarning` | `estimated_tokens`, `context_window_size`, `usage_percent` | +| `Agent.Warning` | `kind`, `message`, `details` | | `Agent.LoopDetected` | _(none)_ | | `Agent.TurnLimitReached` | `max_turns` | | `Agent.SkillExpanded` | `skill_name` | diff --git a/docs/execution/context.mdx b/docs/execution/context.mdx index 1e7b5d0aa..09c1afeac 100644 --- a/docs/execution/context.mdx +++ b/docs/execution/context.mdx @@ -240,6 +240,6 @@ Compaction emits three events to the [event stream](/execution/observability#eve | Event | When | Key fields | |---|---|---| -| `Agent.ContextWindowWarning` | Token estimate exceeds 80% of context window | `estimated_tokens`, `context_window_size`, `usage_percent` | +| `Agent.Warning` (kind: `context_window`) | Token estimate exceeds 80% of context window | `kind`, `message`, `details` (contains `estimated_tokens`, `context_window_size`, `usage_percent`) | | `Agent.CompactionStarted` | Compaction begins | `estimated_tokens`, `context_window_size` | | `Agent.CompactionCompleted` | Summary generated and history replaced | `original_turn_count`, `preserved_turn_count`, `summary_token_estimate`, `tracked_file_count` | diff --git a/docs/execution/observability.mdx b/docs/execution/observability.mdx index a3a3898d7..0956fea7c 100644 --- a/docs/execution/observability.mdx +++ b/docs/execution/observability.mdx @@ -45,7 +45,7 @@ Events fall into several categories: | `Agent.Error` | `stage`, `error` | Agent-level error | | `Agent.LoopDetected` | `stage` | Repeated tool call pattern detected | | `Agent.SteeringInjected` | `stage`, `text` | Human steering message injected | -| `Agent.ContextWindowWarning` | `stage`, `estimated_tokens`, `context_window_size`, `usage_percent` | Token usage exceeds context window threshold | +| `Agent.Warning` | `stage`, `kind`, `message`, `details` | Non-fatal warning (e.g. context window usage) | | `Agent.CompactionStarted` | `stage`, `estimated_tokens`, `context_window_size` | Context compaction triggered | | `Agent.CompactionCompleted` | `stage`, `original_turn_count`, `preserved_turn_count`, `summary_token_estimate`, `tracked_file_count` | Context compaction finished | | `Agent.LlmRetry` | `stage`, `provider`, `model`, `attempt`, `delay_secs` | LLM API call retried | diff --git a/lib/crates/fabro-agent/README.md b/lib/crates/fabro-agent/README.md index dd5728de2..7b1442e97 100644 --- a/lib/crates/fabro-agent/README.md +++ b/lib/crates/fabro-agent/README.md @@ -227,7 +227,7 @@ profile.register_subagent_tools(manager, factory, 0); ## Safety Features - **Loop detection** -- Detects repeating tool call patterns (period 1, 2, or 3) and injects a steering warning -- **Context window monitoring** -- Emits `ContextWindowWarning` events when estimated usage exceeds 80% +- **Context window monitoring** -- Emits `Warning` events (kind `"context_window"`) when estimated usage exceeds 80% - **Tool argument validation** -- Validates arguments against JSON Schema before execution - **Tool output truncation** -- Per-tool character and line limits with head/tail or tail-only truncation modes - **Environment variable filtering** -- `LocalSandbox` strips secrets (`*_API_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, `*_CREDENTIAL`) from subprocess environments diff --git a/lib/crates/fabro-agent/src/compaction.rs b/lib/crates/fabro-agent/src/compaction.rs index cd56d4a16..886429d27 100644 --- a/lib/crates/fabro-agent/src/compaction.rs +++ b/lib/crates/fabro-agent/src/compaction.rs @@ -9,7 +9,7 @@ use fabro_llm::types::{Message, Request}; use tracing::debug; /// Check whether the context window usage exceeds the configured threshold. -/// Emits a `ContextWindowWarning` event when over the threshold. +/// Emits a `Warning` event with kind `"context_window"` when over the threshold. /// Returns `true` if the threshold is exceeded. pub fn check_context_usage( system_prompt: &str, @@ -26,10 +26,17 @@ pub fn check_context_usage( if estimated_tokens > threshold { emitter.emit( session_id.to_owned(), - AgentEvent::ContextWindowWarning { - estimated_tokens, - context_window_size: context_window, - usage_percent: estimated_tokens * 100 / context_window, + AgentEvent::Warning { + kind: "context_window".into(), + message: format!( + "Context window usage: {}%", + estimated_tokens * 100 / context_window + ), + details: serde_json::json!({ + "estimated_tokens": estimated_tokens, + "context_window_size": context_window, + "usage_percent": estimated_tokens * 100 / context_window, + }), }, ); true @@ -345,11 +352,8 @@ mod tests { let over = check_context_usage("prompt", &history, &profile, 80, &emitter, "sess"); assert!(over); - // Should have emitted a ContextWindowWarning + // Should have emitted a Warning let event = rx.try_recv().unwrap(); - assert!(matches!( - event.event, - AgentEvent::ContextWindowWarning { .. } - )); + assert!(matches!(event.event, AgentEvent::Warning { .. })); } } diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs index b493b62cd..6915e9736 100644 --- a/lib/crates/fabro-agent/src/session.rs +++ b/lib/crates/fabro-agent/src/session.rs @@ -1608,13 +1608,9 @@ mod tests { let mut found_warning = false; while let Ok(event) = rx.try_recv() { - if let AgentEvent::ContextWindowWarning { - context_window_size, - .. - } = &event.event - { + if let AgentEvent::Warning { details, .. } = &event.event { found_warning = true; - assert_eq!(*context_window_size, 100); + assert_eq!(details["context_window_size"], 100); } } assert!(found_warning); @@ -1660,7 +1656,7 @@ mod tests { let mut found_warning = false; while let Ok(event) = rx.try_recv() { - if matches!(event.event, AgentEvent::ContextWindowWarning { .. }) { + if matches!(event.event, AgentEvent::Warning { .. }) { found_warning = true; } } diff --git a/lib/crates/fabro-agent/src/types.rs b/lib/crates/fabro-agent/src/types.rs index 5583664de..d92e9f1f7 100644 --- a/lib/crates/fabro-agent/src/types.rs +++ b/lib/crates/fabro-agent/src/types.rs @@ -130,10 +130,10 @@ pub enum AgentEvent { Error { error: crate::error::AgentError, }, - ContextWindowWarning { - estimated_tokens: usize, - context_window_size: usize, - usage_percent: usize, + Warning { + kind: String, + message: String, + details: serde_json::Value, }, LoopDetected, TurnLimitReached { @@ -264,17 +264,12 @@ impl AgentEvent { Self::Error { error } => { error!(session_id, error = %error, "Agent error"); } - Self::ContextWindowWarning { - estimated_tokens, - context_window_size, - usage_percent, - } => { + Self::Warning { kind, message, .. } => { warn!( session_id, - estimated_tokens, - context_window_size, - usage_percent, - "Context window usage high" + kind = kind.as_str(), + message = message.as_str(), + "Warning" ); } Self::LoopDetected => { diff --git a/lib/crates/fabro-cli/src/commands/run_progress.rs b/lib/crates/fabro-cli/src/commands/run_progress.rs index 5f3928afa..0e0ecde20 100644 --- a/lib/crates/fabro-cli/src/commands/run_progress.rs +++ b/lib/crates/fabro-cli/src/commands/run_progress.rs @@ -1380,7 +1380,13 @@ impl ProgressUI { } self.on_tool_call_completed(stage_node_id, tool_call_id, *is_error); } - AgentEvent::ContextWindowWarning { usage_percent, .. } if self.verbose => { + AgentEvent::Warning { kind, details, .. } + if kind == "context_window" && self.verbose => + { + let usage_percent = details + .get("usage_percent") + .and_then(|v| v.as_u64()) + .unwrap_or(0); let yellow = Style::new().yellow(); self.insert_info_line_for_stage( stage_node_id,