-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat: composable EnableCondition with bitmask optimization #1641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add a composable EnableCondition system for tool filtering that:
1. **User-facing API** (conditions.go):
- EnableCondition interface with Evaluate(ctx) method
- Primitives: FeatureFlag(), ContextBool(), Static(), Always(), Never()
- Combinators: And(), Or(), Not() with short-circuit evaluation
- All bitmask complexity hidden from users
2. **Bitmask compiler** (condition_compiler.go):
- Compiles conditions to O(1) bitmask evaluators at build time
- RequestMask holds pre-computed uint64 bitmask per request
- AND/OR of flags compile to single bitmask operations
- Falls back gracefully for custom ConditionFunc
3. **Pre-sorting optimization** (builder.go):
- Tools, resources, prompts sorted once at build time
- Filtering preserves order, eliminating per-request sorting
- ~45% faster request handling in benchmarks
4. **Integration** (filters.go, registry.go):
- Builder.Build() compiles all EnableConditions
- AvailableTools() builds RequestMask once, evaluates via bitmask
- Backward compatible with legacy Enabled func and feature flags
Usage example:
tool.EnableCondition = Or(
ContextBool("is_cca"), // CCA users bypass flag
FeatureFlag("my_feature"), // Others need flag enabled
)
Benchmarks (1000 requests × 50 tools):
- Before: 23.7ms (with per-request sorting)
- After: 12.9ms (pre-sorted + bitmask)
- Improvement: 46% faster
This makes it easy for remote server to adopt - just set EnableCondition
on tools and the optimization is automatic.
d64e314 to
3a52f7b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a composable EnableCondition system for tool filtering with bitmask optimization, providing a cleaner API and significant performance improvements (~46% faster in benchmarks). The implementation includes primitives (FeatureFlag, ContextBool, Static), combinators (And, Or, Not), a compile-time bitmask optimizer, and pre-sorting optimization.
Key Changes
- Composable API: New
EnableConditioninterface with declarative condition primitives and combinators for building complex tool availability logic - Bitmask optimization: Conditions compile to O(1) bitmask evaluators at build time, with
RequestMaskfor efficient per-request evaluation - Pre-sorting optimization: Tools/resources/prompts sorted once at build time rather than on every request
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/inventory/conditions.go | New file: Core EnableCondition API with primitives and combinators |
| pkg/inventory/condition_compiler.go | New file: Bitmask compiler for O(1) condition evaluation (has a critical bug) |
| pkg/inventory/server_tool.go | Added EnableCondition field with deprecation notices for legacy fields (documentation issues) |
| pkg/inventory/filters.go | Integrated EnableCondition evaluation with bitmask support and buildRequestMask |
| pkg/inventory/builder.go | Added condition compilation and pre-sorting at build time |
| pkg/inventory/registry.go | Added compiler/conditions fields and propagated them in ForMCPRequest |
| pkg/inventory/conditions_test.go | New file: Comprehensive unit tests for condition primitives and combinators |
| pkg/inventory/condition_compiler_test.go | New file: Tests for compiler logic and bitmask operations |
| pkg/inventory/registry_test.go | Added integration tests for EnableCondition system |
| pkg/inventory/enable_bench_test.go | New file: Extensive benchmarks comparing old vs new approach |
pkg/inventory/filters.go
Outdated
| } else if tool.EnableCondition != nil { | ||
| // Fallback to tree-based evaluation if no request mask | ||
| enabled, err := tool.EnableCondition.Evaluate(ctx) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Tool.EnableCondition check error for %q: %v\n", tool.Tool.Name, err) | ||
| return false | ||
| } | ||
| if !enabled { | ||
| return false | ||
| } | ||
| } | ||
| } else if tool.EnableCondition != nil { | ||
| // Fallback to tree-based evaluation if no compiled condition | ||
| enabled, err := tool.EnableCondition.Evaluate(ctx) | ||
| if err != nil { | ||
| fmt.Fprintf(os.Stderr, "Tool.EnableCondition check error for %q: %v\n", tool.Tool.Name, err) | ||
| return false | ||
| } | ||
| if !enabled { | ||
| return false | ||
| } | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicated condition evaluation logic with inconsistent error messages. Lines 129-139 and 140-150 both evaluate tool.EnableCondition using the tree-based approach and have identical error handling, differing only in when they're triggered. This code duplication makes the logic harder to maintain and increases the risk of inconsistencies.
Consider extracting the tree-based evaluation into a helper function to reduce duplication and ensure consistent behavior across both paths.
| // This field is checked before EnableCondition for backward compatibility. | ||
| FeatureFlagEnable string | ||
|
|
||
| // FeatureFlagDisable specifies a feature flag that, when enabled, causes this tool | ||
| // to be omitted. Used to disable tools when a feature flag is on. | ||
| // | ||
| // Deprecated: Use EnableCondition with Not(FeatureFlag()) instead for composable conditions. | ||
| // This field is checked before EnableCondition for backward compatibility. |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect documentation about filtering order. The comment states "This field is checked before EnableCondition for backward compatibility" (lines 63 and 70), but the implementation in filters.go shows that legacy feature flags (FeatureFlagEnable/FeatureFlagDisable) are checked AFTER EnableCondition, not before.
The actual order from filters.go:isToolEnabled is:
- Tool.Enabled (legacy)
- Tool.EnableCondition
- FeatureFlagEnable/FeatureFlagDisable (legacy)
The documentation should be corrected to reflect that these legacy fields are checked after EnableCondition.
| // This field is checked before EnableCondition for backward compatibility. | |
| FeatureFlagEnable string | |
| // FeatureFlagDisable specifies a feature flag that, when enabled, causes this tool | |
| // to be omitted. Used to disable tools when a feature flag is on. | |
| // | |
| // Deprecated: Use EnableCondition with Not(FeatureFlag()) instead for composable conditions. | |
| // This field is checked before EnableCondition for backward compatibility. | |
| // This field is checked after EnableCondition, as an additional legacy filter. | |
| FeatureFlagEnable string | |
| // FeatureFlagDisable specifies a feature flag that, when enabled, causes this tool | |
| // to be omitted. Used to disable tools when a feature flag is on. | |
| // | |
| // Deprecated: Use EnableCondition with Not(FeatureFlag()) instead for composable conditions. | |
| // This field is checked after EnableCondition, as an additional legacy filter. |
| // If both Enabled and EnableCondition are set, Enabled takes precedence for | ||
| // backward compatibility. Migrate to EnableCondition for new tools. |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect documentation about precedence. The comment states "If both Enabled and EnableCondition are set, Enabled takes precedence for backward compatibility" (line 97-98), but this is misleading.
The actual behavior is that both checks are applied sequentially - Enabled is checked first, and if it passes (or is nil), then EnableCondition is checked. Neither "takes precedence" - both must pass for the tool to be enabled. The comment should clarify that both conditions must be satisfied when both are present.
| // If both Enabled and EnableCondition are set, Enabled takes precedence for | |
| // backward compatibility. Migrate to EnableCondition for new tools. | |
| // When both Enabled and EnableCondition are set, Enabled is evaluated first for | |
| // backward compatibility, and EnableCondition is then evaluated; both must pass | |
| // for the tool to be enabled. Migrate to EnableCondition for new tools. |
| prefix := key[:3] | ||
| name := key[3:] | ||
|
|
||
| switch prefix { | ||
| case "ctx": | ||
| if bools != nil && bools[name] { | ||
| bits |= 1 << bit | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prefix check logic is inconsistent with the key format. The code checks prefix := key[:3] and then compares with "ctx", but context bool keys are formatted as "ctx:key_name" (4 characters for the prefix). This will cause all context bool keys to be incorrectly parsed.
The switch case case "ctx": will never match because context bool keys have the format "ctx:" (4 chars), not "ctx" (3 chars). This should be case "ctx:" to match the actual key format.
| bits |= 1 << bit | ||
| } | ||
| case "ff:": | ||
| name = key[3:] // "ff:" is 3 chars |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant name reassignment. Line 431 reassigns name = key[3:] even though it was already set on line 423. Since the "ff:" prefix is 3 characters, the name extraction on line 423 is already correct for feature flags. This redundant assignment is unnecessary and could cause confusion.
| name = key[3:] // "ff:" is 3 chars |
| for key, bit := range r.conditionCompiler.keyToBit { | ||
| // Keys are formatted as "ctx:key_name" or "ff:flag_name" | ||
| if len(key) < 4 { // Minimum: "ff:x" or "ctx:" prefix + 1 char | ||
| continue | ||
| } | ||
|
|
||
| switch { | ||
| case len(key) > 4 && key[:4] == "ctx:": | ||
| // Context bool: "ctx:key_name" | ||
| name := key[4:] | ||
| if bools != nil && bools[name] { | ||
| bits |= 1 << bit | ||
| } | ||
| case len(key) > 3 && key[:3] == "ff:": | ||
| // Feature flag: "ff:flag_name" | ||
| name := key[3:] | ||
| if checker != nil { | ||
| enabled, err := checker(ctx, name) | ||
| if err == nil && enabled { | ||
| bits |= 1 << bit | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent prefix checking logic. In buildRequestMask (filters.go), the code uses key[:4] == "ctx:" and key[:3] == "ff:" which correctly matches the key formats. However, in BuildFromContext (condition_compiler.go line 422-438), the code uses prefix := key[:3] and then compares with string literals, which is inconsistent and error-prone.
The two implementations should use the same approach for consistency and maintainability. Consider extracting this key parsing logic into a shared helper function.
| } | ||
|
|
||
| func TestEnableConditionAndLegacyEnabledInteraction(t *testing.T) { | ||
| // When both Enabled and EnableCondition are set, both must pass |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test comment incorrectly states the behavior. The comment says "When both Enabled and EnableCondition are set, both must pass", but the implementation in filters.go shows that if either check fails, the tool is excluded. The two checks are sequential, not a logical AND at the same level.
The actual behavior is: if Enabled returns false, the tool is excluded immediately. If Enabled passes (or is nil), then EnableCondition is checked. This is more of a filter chain than a logical AND. Consider clarifying the comment to reflect the sequential nature of the checks.
| // When both Enabled and EnableCondition are set, both must pass | |
| // When both Enabled and EnableCondition are set, Enabled is checked first and must pass; | |
| // if it does, EnableCondition is then evaluated and must also pass for the tool to be included. |
Instead of maintaining a separate compiledConditions slice indexed by tool position, embed the *CompiledCondition directly in each ServerTool. This eliminates the index alignment issue where filtering to a single tool (ForMCPRequest with ToolsCall) would break the mapping between tools and their compiled conditions. Benefits: - Compiled condition travels with the tool during any filtering - No index bookkeeping needed - Simpler code in filters.go (no toolIndex parameter) - ForMCPRequest works correctly for single-tool lookups - Same O(1) bitmask evaluation performance
Summary
This PR adds a composable
EnableConditionsystem for tool filtering that encapsulates bitmask complexity behind a clean API.Key Features
1. User-facing API (
conditions.go)EnableConditioninterface withEvaluate(ctx)methodFeatureFlag(),ContextBool(),Static(),Always(),Never()And(),Or(),Not()with short-circuit evaluation2. Bitmask Compiler (
condition_compiler.go)RequestMaskholds pre-computeduint64bitmask per requestConditionFunc3. Pre-sorting Optimization (
builder.go)4. Integration (
filters.go,registry.go)Builder.Build()compiles allEnableConditionsAvailableTools()buildsRequestMaskonce, evaluates via bitmaskEnabledfunc and feature flagsUsage Example
Benchmarks
1000 requests × 50 tools each:
Why This Approach?
EnableConditionon tools - optimization is automaticEnabledfunc and feature flag fields still workSupersedes #1637