Skip to content

fix: allow self-looping chain rules to continue evaluating subsequent rules instead of halting#3082

Merged
akshaydeo merged 1 commit intomainfrom
04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself
Apr 28, 2026
Merged

fix: allow self-looping chain rules to continue evaluating subsequent rules instead of halting#3082
akshaydeo merged 1 commit intomainfrom
04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself

Conversation

@Pratham-Mishra04
Copy link
Copy Markdown
Collaborator

Summary

Fixes a bug where a routing rule chain would incorrectly halt when a chain_rule=true rule resolved to the same provider/model as the current context (a self-loop). Previously, this was treated as a cycle and stopped chain evaluation immediately. Now, each rule is tracked by its ID rather than by the resulting provider/model state, allowing a self-looping rule to fire once and then be skipped so subsequent rules in the chain can continue to evaluate.

Changes

  • Replaced provider/model state-based cycle detection (visited map keyed on provider|model) with rule ID-based tracking (visitedRuleIDs map keyed on rule.ID). Each rule may now fire at most once per chain evaluation, regardless of whether its target matches the current state.
  • A self-looping chain rule (one that resolves to the same provider/model it was invoked with) now fires once and is then skipped, allowing lower-priority rules to match on subsequent chain steps.
  • Updated termination condition feat: plugin context added #3 in the EvaluateRoutingRules documentation to reflect that the chain stops when all candidate chain-rules have already fired, rather than when a previously visited provider/model state is encountered.
  • Renamed TestEvaluateRoutingRules_ConvergenceStopsChain to TestEvaluateRoutingRules_SelfLoopContinuesToNextRule and updated it to assert that a self-looping rule correctly yields to the next matching rule.
  • Added TestEvaluateRoutingRules_SelfLoopAloneTerminates to verify that a self-looping chain rule with no other rules terminates cleanly via the "no remaining rule matches" condition.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./plugins/governance/... -run TestEvaluateRoutingRules_SelfLoop
go test ./plugins/governance/...

Expected: TestEvaluateRoutingRules_SelfLoopContinuesToNextRule passes, asserting the chain resolves to anthropic/claude-3 after the self-looping rule fires once. TestEvaluateRoutingRules_SelfLoopAloneTerminates passes, asserting the chain resolves to openai/gpt-4o and terminates cleanly.

Breaking changes

  • Yes
  • No

Related issues

Security considerations

None.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@akshaydeo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 1 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb6697d1-2c4c-4f57-ba9b-67db75864c2f

📥 Commits

Reviewing files that changed from the base of the PR and between 201f9f6 and abbda48.

📒 Files selected for processing (4)
  • plugins/governance/changelog.md
  • plugins/governance/routing.go
  • plugins/governance/routing_test.go
  • transports/changelog.md
📝 Walkthrough

Walkthrough

Routing chain evaluation now tracks which routing rules have fired within a chain and skips re-matching the same rule, replacing prior provider/model-based cycle detection so chains continue until no candidate rules remain or max depth is reached.

Changes

Cohort / File(s) Summary
Changelog Entries
plugins/governance/changelog.md, transports/changelog.md
Add notes documenting that resolving a chain_rule to the same provider/model (self-loop) no longer halts chain processing; subsequent rules are evaluated.
Core Routing Logic
plugins/governance/routing.go
Remove provider/model visited state tracking; introduce per-chain visitedRuleIDs tracking. Skip rules whose rule.ID already fired in the current chain; update termination rationale to reflect exhaustion of chain-rule candidates.
Tests
plugins/governance/routing_test.go
Delete convergence/cycle-detection test; add two tests for self-loop behavior: (1) self-loop rule fires once then evaluation proceeds to next matching rule (returns downstream decision and MatchedRuleID), (2) lone self-loop rule terminates after firing (returns its decision and MatchedRuleID).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped a loop and gave a wink,
Fired once, then onward—none to sink.
Rules skip repeats, the chain moves on,
Through winding paths from dusk to dawn.
A rabbit's cheer for routing done!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: fixing self-looping chain rules to allow subsequent rules to be evaluated instead of halting.
Description check ✅ Passed The description is comprehensive and follows the template structure, covering Summary, Changes, Type of change, Affected areas, How to test, Breaking changes, Security considerations, and Checklist sections.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Confidence Score: 5/5

Safe to merge — targeted, well-reasoned bug fix with no regressions to existing termination guarantees

No P0 or P1 issues found. The cycle-detection replacement is logically sound: each rule fires at most once, so infinite loops are still impossible, and chainMaxDepth provides a hard upper bound. Both the self-loop-continues and self-loop-alone cases are covered by new/updated tests.

No files require special attention

Important Files Changed

Filename Overview
plugins/governance/routing.go Replaces provider/model state-based cycle detection with per-rule-ID tracking; each rule fires at most once per chain evaluation, allowing self-looping rules to yield to subsequent rules rather than halting the chain
plugins/governance/routing_test.go Renames the convergence-stops-chain test to reflect new semantics and adds a new test for the solo self-loop termination case; both test cases correctly exercise the changed termination logic
plugins/governance/changelog.md New changelog entry describing the self-loop fix
transports/changelog.md Same fix entry prepended to the transports changelog

Reviews (4): Last reviewed commit: "fix: routing rule chain no longer halts ..." | Re-trigger Greptile

Comment thread plugins/governance/routing.go
Comment thread plugins/governance/routing.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 27, 2026
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself branch from 2dea414 to 11db74d Compare April 27, 2026 14:52
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
plugins/governance/routing_test.go (1)

816-913: Add one non-self-loop cycle regression too.

This change replaces state-cycle detection for all chains, not just self-loops. A two-rule oscillation like A: gpt-4o -> gpt-4-turbo and B: gpt-4-turbo -> gpt-4o would pin the broader visitedRuleIDs behavior and guard against future regressions.

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

In `@plugins/governance/routing_test.go` around lines 816 - 913, Add a new test
(e.g., TestEvaluateRoutingRules_TwoRuleOscillationTerminates) that creates two
distinct chain rules which map between each other's models (RuleA: model ==
'gpt-4o' -> provider/openai model 'gpt-4-turbo'; RuleB: model == 'gpt-4-turbo'
-> provider/openai model 'gpt-4o'), register them with
store.UpdateRoutingRuleInMemory, set the initial RoutingContext to
Provider=schemas.OpenAI and Model="gpt-4o", call
engine.EvaluateRoutingRules(bgCtx, ctx) and assert it returns successfully (no
infinite loop) and produces a finite Decision (non-nil) — verifying the routing
engine's visitedRuleIDs cycle-detection stops the oscillation after the rules
fire once each; use NewLocalGovernanceStore, NewRoutingEngine and
EvaluateRoutingRules to locate relevant code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/governance/routing_test.go`:
- Around line 816-913: Add a new test (e.g.,
TestEvaluateRoutingRules_TwoRuleOscillationTerminates) that creates two distinct
chain rules which map between each other's models (RuleA: model == 'gpt-4o' ->
provider/openai model 'gpt-4-turbo'; RuleB: model == 'gpt-4-turbo' ->
provider/openai model 'gpt-4o'), register them with
store.UpdateRoutingRuleInMemory, set the initial RoutingContext to
Provider=schemas.OpenAI and Model="gpt-4o", call
engine.EvaluateRoutingRules(bgCtx, ctx) and assert it returns successfully (no
infinite loop) and produces a finite Decision (non-nil) — verifying the routing
engine's visitedRuleIDs cycle-detection stops the oscillation after the rules
fire once each; use NewLocalGovernanceStore, NewRoutingEngine and
EvaluateRoutingRules to locate relevant code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6435ba0d-c91d-4ab7-beeb-a2fb1841e180

📥 Commits

Reviewing files that changed from the base of the PR and between 2dea414 and 11db74d.

📒 Files selected for processing (4)
  • plugins/governance/changelog.md
  • plugins/governance/routing.go
  • plugins/governance/routing_test.go
  • transports/changelog.md
✅ Files skipped from review due to trivial changes (2)
  • transports/changelog.md
  • plugins/governance/changelog.md

Copy link
Copy Markdown
Contributor

akshaydeo commented Apr 28, 2026

Merge activity

@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 04-27-refactor_use_global_bifrost_client_in_semmantic_cache_plugin branch from 7fc9839 to 92ddedd Compare April 28, 2026 07:07
@Pratham-Mishra04 Pratham-Mishra04 force-pushed the 04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself branch from 11db74d to 201f9f6 Compare April 28, 2026 07:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
plugins/governance/routing.go (1)

157-161: Consider including rule ID in skip logs for disambiguation.

This logic is correct; optional tweak: add rule.ID to skip logs so duplicate names across scopes are easier to trace.

🔧 Optional logging improvement
- re.logger.Debug("[RoutingEngine] Skipping rule %s (already fired this chain)", rule.Name)
- ctx.AppendRoutingEngineLog(schemas.RoutingEngineRoutingRule, schemas.LogLevelInfo, fmt.Sprintf("Rule '%s' skipped: already fired in this chain", rule.Name))
+ re.logger.Debug("[RoutingEngine] Skipping rule %s (id=%s) (already fired this chain)", rule.Name, rule.ID)
+ ctx.AppendRoutingEngineLog(
+   schemas.RoutingEngineRoutingRule,
+   schemas.LogLevelInfo,
+   fmt.Sprintf("Rule '%s' (id=%s) skipped: already fired in this chain", rule.Name, rule.ID),
+ )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/governance/routing.go` around lines 157 - 161, Add rule.ID to the
skip log statements to disambiguate rules with duplicate names: update the
visitedRuleIDs check branch in the routing loop (where visitedRuleIDs, rule.ID,
rule.Name are used) so re.logger.Debug includes rule.ID and
ctx.AppendRoutingEngineLog (schemas.RoutingEngineRoutingRule,
schemas.LogLevelInfo) also logs the ID alongside the rule name in its formatted
message; keep existing messages and continue behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/governance/routing.go`:
- Around line 157-161: Add rule.ID to the skip log statements to disambiguate
rules with duplicate names: update the visitedRuleIDs check branch in the
routing loop (where visitedRuleIDs, rule.ID, rule.Name are used) so
re.logger.Debug includes rule.ID and ctx.AppendRoutingEngineLog
(schemas.RoutingEngineRoutingRule, schemas.LogLevelInfo) also logs the ID
alongside the rule name in its formatted message; keep existing messages and
continue behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 168c6b90-b3d8-4aea-a05c-817b4bb061b5

📥 Commits

Reviewing files that changed from the base of the PR and between 11db74d and 201f9f6.

📒 Files selected for processing (4)
  • plugins/governance/changelog.md
  • plugins/governance/routing.go
  • plugins/governance/routing_test.go
  • transports/changelog.md
✅ Files skipped from review due to trivial changes (1)
  • plugins/governance/changelog.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • transports/changelog.md

@akshaydeo akshaydeo changed the base branch from 04-27-refactor_use_global_bifrost_client_in_semmantic_cache_plugin to graphite-base/3082 April 28, 2026 07:20
@akshaydeo akshaydeo changed the base branch from graphite-base/3082 to main April 28, 2026 07:23
@akshaydeo akshaydeo dismissed coderabbitai[bot]’s stale review April 28, 2026 07:23

The base branch was changed.

@akshaydeo akshaydeo force-pushed the 04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself branch from 201f9f6 to abbda48 Compare April 28, 2026 07:24
@akshaydeo akshaydeo merged commit 956f124 into main Apr 28, 2026
13 of 17 checks passed
@akshaydeo akshaydeo deleted the 04-27-fix_routing_rule_chain_no_longer_halts_when_a_chain_rule_resolves_itself branch April 28, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants