Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/journal/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@

**Learning:** Multiple agents often share the same source file (e.g., AGENTS.md) or target directories. Without caching, the Linker performs redundant file reads, string compression, and directory existence checks for every target.
**Action:** Implement `compression_cache` (HashMap) to memoize expensive text compression and `ensured_outputs` (HashSet) to track verified destination paths. This optimization reduced execution time by ~83% (from 0.055s to 0.0094s) in a 100-agent scenario.

## 2026-02-22 - Zero-Allocation Agent ID Matching

**Learning:** The `canonical_mcp_agent_id` function was performing a `to_lowercase()` allocation for every call, which is O(Agents * Filters) during synchronization. Replacing this with `eq_ignore_ascii_case` for known ASCII aliases eliminates these heap allocations. Additionally, `mcp_filter_matches` and `sync_filter_matches` were performing redundant `to_lowercase()` calls on strings that were already canonical (lowercase).

**Action:** Use `eq_ignore_ascii_case` for case-insensitive matching against known ASCII constants to avoid `String` allocations. Avoid calling `to_lowercase()` on strings that are already guaranteed to be lowercase by the system's architecture.
64 changes: 47 additions & 17 deletions src/agent_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,40 @@

/// Normalize a user-provided agent identifier to a canonical MCP ID.
pub fn canonical_mcp_agent_id(id: &str) -> Option<&'static str> {
match id.to_lowercase().as_str() {
"claude" | "claude-code" | "claude_code" => Some("claude"),
"copilot" | "github-copilot" | "github_copilot" => Some("copilot"),
"codex" | "codex-cli" | "codex_cli" => Some("codex"),
"gemini" | "gemini-cli" | "gemini_cli" => Some("gemini"),
"vscode" | "vs-code" | "vs_code" => Some("vscode"),
"cursor" => Some("cursor"),
"opencode" | "open-code" | "open_code" => Some("opencode"),
_ => None,
if id.eq_ignore_ascii_case("claude")
|| id.eq_ignore_ascii_case("claude-code")
|| id.eq_ignore_ascii_case("claude_code")
{
Some("claude")
} else if id.eq_ignore_ascii_case("copilot")
|| id.eq_ignore_ascii_case("github-copilot")
|| id.eq_ignore_ascii_case("github_copilot")
{
Some("copilot")
} else if id.eq_ignore_ascii_case("codex")
|| id.eq_ignore_ascii_case("codex-cli")
|| id.eq_ignore_ascii_case("codex_cli")
{
Some("codex")
} else if id.eq_ignore_ascii_case("gemini")
|| id.eq_ignore_ascii_case("gemini-cli")
|| id.eq_ignore_ascii_case("gemini_cli")
{
Some("gemini")
} else if id.eq_ignore_ascii_case("vscode")
|| id.eq_ignore_ascii_case("vs-code")
|| id.eq_ignore_ascii_case("vs_code")
{
Some("vscode")
} else if id.eq_ignore_ascii_case("cursor") {
Some("cursor")
} else if id.eq_ignore_ascii_case("opencode")
|| id.eq_ignore_ascii_case("open-code")
|| id.eq_ignore_ascii_case("open_code")
{
Some("opencode")
} else {
None
}
}

Expand Down Expand Up @@ -46,7 +71,8 @@ pub fn mcp_filter_matches(agent_id: &str, filter: &str) -> bool {
canonical_filter == agent_id
} else {
let filter_lower = filter.to_lowercase();
agent_id.to_lowercase().contains(&filter_lower)
// agent_id is already canonical (lowercase), so we don't need to lowercase it.
agent_id.contains(&filter_lower)
}
}

Expand All @@ -56,15 +82,19 @@ pub fn mcp_filter_matches(agent_id: &str, filter: &str) -> bool {
/// known, this performs exact canonical matching. Otherwise it falls back to
/// legacy case-insensitive substring matching against the configured name.
pub fn sync_filter_matches(config_agent_name: &str, filter: &str) -> bool {
if let Some(canonical_filter) = canonical_mcp_agent_id(filter) {
if let Some(canonical_agent) = canonical_mcp_agent_id(config_agent_name) {
canonical_agent == canonical_filter
} else {
let filter_lower = filter.to_lowercase();
config_agent_name.to_lowercase().contains(&filter_lower)
if let Some(cf) = canonical_mcp_agent_id(filter) {
if let Some(ca) = canonical_mcp_agent_id(config_agent_name) {
return ca == cf;
}
// filter is known, use its canonical (lowercase) form to avoid an allocation
return config_agent_name.to_lowercase().contains(cf);
}
Comment on lines 84 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Behavioral regression: containment against canonical form instead of original filter alias can produce false negatives.

In Path B (filter is a known alias, config_agent_name is NOT a known agent), the old code checked config_agent_name.to_lowercase().contains(&filter.to_lowercase()). The new code checks config_agent_name.to_lowercase().contains(cf) where cf is the canonical form of filter. Because aliases like "vs-code", "open-code", and "claude-code" are not substrings of their canonical forms ("vscode", "opencode", "claude"), the behaviour changes:

config_agent_name filter cf Old result New result
"vs-code-server" "vs-code" "vscode" true false
"open-code-runner" "open-code" "opencode" true false

Any user with a custom agent name that contains a hyphenated/underscored alias but not the canonical form will silently stop matching after this change.

The fix is to fall back to checking against the original filter string (lowercase) when the canonical form doesn't match as a substring, or to keep the original filter containment in Path B and only use canonical comparison in Path A:

🐛 Proposed fix
     if let Some(cf) = canonical_mcp_agent_id(filter) {
         if let Some(ca) = canonical_mcp_agent_id(config_agent_name) {
             return ca == cf;
         }
-        // filter is known, use its canonical (lowercase) form to avoid an allocation
-        return config_agent_name.to_lowercase().contains(cf);
+        // Fall back to literal filter substring to preserve legacy matching behaviour
+        // (e.g. "vs-code-server" should still match filter "vs-code").
+        let filter_lower = filter.to_lowercase();
+        let name_lower = config_agent_name.to_lowercase();
+        return name_lower.contains(cf) || name_lower.contains(filter_lower.as_str());
     }

Additionally, the comment on line 89 is slightly inaccurate: config_agent_name.to_lowercase() still performs one allocation; the comment should clarify it avoids allocating the filter string, not all allocations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/agent_ids.rs` around lines 84 - 91, sync_filter_matches currently uses
the canonical alias (cf) for substring checks when the filter maps to a
canonical id, which causes false negatives for hyphenated/underscored aliases;
change the Path B logic in sync_filter_matches so that if
canonical_mcp_agent_id(filter) returns Some(cf) but ca is None you first check
config_agent_name.to_lowercase().contains(&filter.to_lowercase()) (i.e. the
original filter lowercased) and only if that fails (or alternatively keep the
original filter containment check) fall back to comparing against cf,
referencing the variables cf, ca, filter and config_agent_name; also update the
nearby comment to state that to_lowercase() avoids allocating a lowercased
filter string (not that it avoids all allocations).


let filter_lower = filter.to_lowercase();
if let Some(ca) = canonical_mcp_agent_id(config_agent_name) {
// agent is known, its canonical ID is already lowercase
ca.contains(&filter_lower)
} else {
let filter_lower = filter.to_lowercase();
config_agent_name.to_lowercase().contains(&filter_lower)
}
}
Expand Down
Loading