From 473d01ece96dca6fea59663752f7e473ef3b339a Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Fri, 28 Aug 2026 14:57:11 -0500
Subject: [PATCH 1/2] allow execute rules for each agent
---
.../Rules/IRuleCriteriaEvaluator.cs | 6 ++-
.../Criteria/Code/CodeCriteriaEvaluator.cs | 2 +-
.../Criteria/Llm/LlmCriteriaEvaluator.cs | 5 +-
.../BotSharp.Core.Rules/Engines/RuleEngine.cs | 46 ++++++++++---------
4 files changed, 33 insertions(+), 26 deletions(-)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs
index 4f14ee622..e5f39aeb1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Rules/IRuleCriteriaEvaluator.cs
@@ -18,6 +18,10 @@ public interface IRuleCriteriaEvaluator
/// Evaluate the criteria for a single agent's rule.
///
/// The agent whose rule is being considered
+ ///
+ /// The rule being considered. An agent can carry more than one rule on the same trigger, so the
+ /// evaluator is handed the one under evaluation rather than resolving it from the agent itself.
+ ///
/// The rule trigger
/// The per-request criteria context
///
@@ -25,5 +29,5 @@ public interface IRuleCriteriaEvaluator
/// or null when the evaluator could not produce an answer (missing script/template,
/// failed execution, error).
///
- Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context);
+ Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context);
}
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs
index 31a712944..618c2140e 100644
--- a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs
+++ b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Code/CodeCriteriaEvaluator.cs
@@ -25,7 +25,7 @@ public CodeCriteriaEvaluator(
public string Type => BuiltInRuleCriteria.PythonScript;
- public async Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context)
+ public async Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context)
{
var settings = context.Options.GetData() ?? new();
var provider = settings.CodeProcessor ?? BuiltInCodeProcessor.PyInterpreter;
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs
index 839c740fa..6aaab0844 100644
--- a/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs
+++ b/src/Infrastructure/BotSharp.Core.Rules/Criteria/Llm/LlmCriteriaEvaluator.cs
@@ -30,17 +30,16 @@ public LlmCriteriaEvaluator(
public string Type => BuiltInRuleCriteria.Llm;
- public async Task EvaluateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context)
+ public async Task EvaluateAsync(Agent agent, AgentRule agentRule, IRuleTrigger trigger, RuleCriteriaContext context)
{
var settings = context.Options.GetData() ?? new();
- var rule = agent.Rules.FirstOrDefault(x => x.TriggerName.IsEqualTo(trigger.Name));
// The Rules agent hosts the criteria-check template by default.
var agentId = !string.IsNullOrWhiteSpace(settings.AgentId) ? settings.AgentId! : BuiltInAgentId.RulesInterpreter;
var templateName = !string.IsNullOrWhiteSpace(settings.TemplateName)
? settings.TemplateName! : (agentId == BuiltInAgentId.RulesInterpreter ? DefaultTemplateName : $"{trigger.Name}_criteria");
- var input = BuildInput(rule?.CriteriaConfig, settings);
+ var input = BuildInput(agentRule.CriteriaConfig, settings);
var msg = $"rule trigger ({trigger.Name}) llm criteria (agent {agentId}, template {templateName}).";
try
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
index 697fdaeab..5458ab7b5 100644
--- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
+++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
@@ -44,33 +44,36 @@ public async Task> Triggered(IRuleTrigger trigger, string te
var filteredAgents = agents.Items.Where(x => x.Rules.Exists(r => r.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled)).ToList();
foreach (var agent in filteredAgents)
{
- var rule = agent.Rules.FirstOrDefault(x => x.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled);
- if (rule == null)
+ var rules = agent.Rules.Where(x => x.TriggerName.IsEqualTo(trigger.Name) && !x.Disabled).ToList();
+ if (rules.IsNullOrEmpty())
{
continue;
}
- // The rule's own mode wins over the mode carried on the trigger options, so an agent can
- // pick how its criteria is judged without the caller knowing.
- var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator;
- if (evaluator != null && options?.Criteria != null)
+ foreach (var rule in rules)
{
- var criteriaContext = new RuleCriteriaContext
+ // The rule's own mode wins over the mode carried on the trigger options, so an agent can
+ // pick how its criteria is judged without the caller knowing.
+ var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator;
+ if (evaluator != null && options?.Criteria != null)
{
- Options = options.Criteria,
- States = states
- };
-
- var isTriggered = await EvaluateCriteria(evaluator, agent, trigger, criteriaContext);
- if (!isTriggered)
- {
- continue;
+ var criteriaContext = new RuleCriteriaContext
+ {
+ Options = options.Criteria,
+ States = states
+ };
+
+ var isTriggered = await EvaluateCriteria(evaluator, agent, rule, trigger, criteriaContext);
+ if (!isTriggered)
+ {
+ continue;
+ }
}
- }
- var msg = !string.IsNullOrWhiteSpace(rule.Message) ? rule.Message : text;
- var convId = await SendMessageToAgent(agent, trigger, text, msg, states);
- newConversationIds.Add(convId);
+ var msg = !string.IsNullOrWhiteSpace(rule.Message) ? rule.Message : text;
+ var convId = await SendMessageToAgent(agent, trigger, text, msg, states);
+ newConversationIds.Add(convId);
+ }
}
return newConversationIds;
@@ -96,10 +99,11 @@ public async Task> Triggered(IRuleTrigger trigger, string te
private async Task EvaluateCriteria(
IRuleCriteriaEvaluator evaluator,
Agent agent,
+ AgentRule agentRule,
IRuleTrigger trigger,
RuleCriteriaContext context)
{
- var isTriggered = await evaluator.EvaluateAsync(agent, trigger, context);
+ var isTriggered = await evaluator.EvaluateAsync(agent, agentRule, trigger, context);
if (isTriggered != null)
{
return isTriggered.Value;
@@ -118,7 +122,7 @@ private async Task EvaluateCriteria(
}
_logger.LogInformation($"Rule criteria evaluator ({evaluator.Type}) returned no result, falling back to llm for agent ({agent.Name}) and trigger ({trigger.Name}).");
- return await llmEvaluator.EvaluateAsync(agent, trigger, context) ?? false;
+ return await llmEvaluator.EvaluateAsync(agent, agentRule, trigger, context) ?? false;
}
#endregion
From 111a8240f21dbbbad35fd103561f69bb38c9c2dd Mon Sep 17 00:00:00 2001
From: Jicheng Lu <103353@smsassist.com>
Date: Fri, 28 Aug 2026 14:58:37 -0500
Subject: [PATCH 2/2] minor change
---
src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
index 5458ab7b5..f6b906bc8 100644
--- a/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
+++ b/src/Infrastructure/BotSharp.Core.Rules/Engines/RuleEngine.cs
@@ -52,6 +52,11 @@ public async Task> Triggered(IRuleTrigger trigger, string te
foreach (var rule in rules)
{
+ if (rule == null)
+ {
+ continue;
+ }
+
// The rule's own mode wins over the mode carried on the trigger options, so an agent can
// pick how its criteria is judged without the caller knowing.
var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator;