Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ public class AgentRule
[JsonPropertyName("message")]
public string? Message { get; set; }

[JsonPropertyName("criteria")]
[JsonPropertyName("criteria_config")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public RuleCriteria? Criteria { get; set; }
public RuleCriteriaConfig? CriteriaConfig { get; set; }
Comment on lines +17 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. Legacy json criteria ignored 🐞 Bug ≡ Correctness

AgentRule/Mongo rule persistence renamed the criteria field from criteria/Criteria to
criteria_config/CriteriaConfig without a compatibility path, so legacy agent JSON, API payloads,
and previously stored Mongo documents deserialize with CriteriaConfig == null. As a result, a
rule’s saved criteria/mode can be silently dropped during domain conversion and evaluation.
Agent Prompt
## Issue description
The rule criteria field was renamed from `criteria`/`Criteria` to `criteria_config`/`CriteriaConfig` in both JSON and Mongo persistence, but deserialization currently accepts only the new name. This breaks backward compatibility for existing agent JSON in the file repository, API clients still sending `criteria`, and existing Mongo documents still storing `Criteria`, resulting in `CriteriaConfig == null` and silently dropping rule criteria/mode during domain conversion and evaluation.

## Issue Context
Agents are deserialized from JSON in the file repository (`JsonSerializer.Deserialize<Agent>`), and rules are also bound from OpenAPI request models containing `List<AgentRule>`, so legacy payloads using `criteria` will be ignored. In Mongo, `AgentDocument.Rules` stores `List<AgentRuleMongoElement>` which are transformed into domain `AgentRule` objects used by the rule engine, but the renamed property lacks BSON aliasing/migration so previously stored `Criteria` fields are ignored on read.

## Fix Focus Areas
- src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs[3-20]
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs[298-306]
- src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/Request/AgentUpdateModel.cs[94-99]
- src/Plugins/BotSharp.Plugin.MongoStorage/Models/AgentRuleMongoElement.cs[5-33]
- src/Plugins/BotSharp.Plugin.MongoStorage/Collections/AgentDocument.cs[24-29]
- src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs[753-781]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}

public class RuleCriteria
public class RuleCriteriaConfig
{
/// <summary>
/// Criteria mode: llm, python script, etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public LlmCriteriaEvaluator(
var templateName = !string.IsNullOrWhiteSpace(settings.TemplateName)
? settings.TemplateName! : (agentId == BuiltInAgentId.RulesInterpreter ? DefaultTemplateName : $"{trigger.Name}_criteria");

var input = BuildInput(rule?.Criteria, settings);
var input = BuildInput(rule?.CriteriaConfig, settings);
var msg = $"rule trigger ({trigger.Name}) llm criteria (agent {agentId}, template {templateName}).";

try
Expand Down Expand Up @@ -129,14 +129,14 @@ private static Dictionary<string, object> BuildRenderData(RuleCriteriaContext co
return data;
}

private static string BuildInput(RuleCriteria? ruleCriteria, LlmCriteriaSettings settings)
private static string BuildInput(RuleCriteriaConfig? criteriaConfig, LlmCriteriaSettings settings)
{
var sb = new StringBuilder();

if (!string.IsNullOrWhiteSpace(ruleCriteria?.Criteria))
if (!string.IsNullOrWhiteSpace(criteriaConfig?.Criteria))
{
sb.AppendLine("## Rule");
sb.AppendLine(ruleCriteria.Criteria);
sb.AppendLine(criteriaConfig.Criteria);
sb.AppendLine();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<IEnumerable<string>> Triggered(IRuleTrigger trigger, string te

// 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.Criteria?.Mode) ?? criteriaEvaluator;
var evaluator = ResolveCriteriaEvaluator(rule.CriteriaConfig?.Mode) ?? criteriaEvaluator;
if (evaluator != null && options?.Criteria != null)
{
var criteriaContext = new RuleCriteriaContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class AgentRuleMongoElement
public string TriggerName { get; set; } = default!;
public bool Disabled { get; set; }
public string? Message { get; set; }
public RuleCriteriaMongoModel? Criteria { get; set; }
public RuleCriteriaConfigMongoModel? CriteriaConfig { get; set; }

public static AgentRuleMongoElement ToMongoElement(AgentRule rule)
{
Expand All @@ -17,7 +17,7 @@ public static AgentRuleMongoElement ToMongoElement(AgentRule rule)
TriggerName = rule.TriggerName,
Disabled = rule.Disabled,
Message = rule.Message,
Criteria = RuleCriteriaMongoModel.ToMongoModel(rule.Criteria)
CriteriaConfig = RuleCriteriaConfigMongoModel.ToMongoModel(rule.CriteriaConfig)
};
}

Expand All @@ -28,42 +28,42 @@ public static AgentRule ToDomainElement(AgentRuleMongoElement rule)
TriggerName = rule.TriggerName,
Disabled = rule.Disabled,
Message = rule.Message,
Criteria = RuleCriteriaMongoModel.ToDomainModel(rule.Criteria)
CriteriaConfig = RuleCriteriaConfigMongoModel.ToDomainModel(rule.CriteriaConfig)
};
}
}

[BsonIgnoreExtraElements(Inherited = true)]
public class RuleCriteriaMongoModel
public class RuleCriteriaConfigMongoModel
{
public string? Mode { get; set; }
public string? Criteria { get; set; }

public static RuleCriteriaMongoModel? ToMongoModel(RuleCriteria? criteria)
public static RuleCriteriaConfigMongoModel? ToMongoModel(RuleCriteriaConfig? config)
{
if (criteria == null)
if (config == null)
{
return null;
}

return new RuleCriteriaMongoModel
return new RuleCriteriaConfigMongoModel
{
Mode = criteria.Mode,
Criteria = criteria.Criteria
Mode = config.Mode,
Criteria = config.Criteria
};
}

public static RuleCriteria? ToDomainModel(RuleCriteriaMongoModel? criteria)
public static RuleCriteriaConfig? ToDomainModel(RuleCriteriaConfigMongoModel? config)
{
if (criteria == null)
if (config == null)
{
return null;
}

return new RuleCriteria
return new RuleCriteriaConfig
{
Mode = criteria.Mode,
Criteria = criteria.Criteria
Mode = config.Mode,
Criteria = config.Criteria
};
}
}
Loading