namespace Lilith.Agent.ScenarioSystem;/// <summary>Runs scenario steps as normal Lilith chat turns (tool calling allowed).</summary>internal static class ScenarioExecutor{ public static async Task<string?> RunChatStepsAsync( Lilith lilith, IEnumerable<ScenarioStepXml> steps, string? scenarioId = null, string? scenarioTitle = null) { var stepList = steps.OfType<ChatStepXml>().ToList(); int total = stepList.Count; string? lastReply = null; using var scenarioScope = scenarioId == null ? null : ScenarioLog.BeginScenario(lilith, scenarioId, scenarioTitle); for (int i = 0; i < stepList.Count; i++) { var chat = stepList[i]; using var stepScope = ScenarioLog.BeginStep(lilith, i + 1, total, chat.Prompt); var (_, reply) = await lilith.HandleInputAsync(chat.Prompt); lastReply = reply; } return lastReply; } /// <summary>Runs steps for child scenario worker; validates optional expect_contains on assistant/tool output in transcript.</summary> public static async Task<ScenarioRunResult> RunChatStepsWithChecksAsync( Lilith lilith, ScenarioXml scenario, CancellationToken ct = default) { var trace = new List<string>(); string? lastReply = null; using var scenarioScope = ScenarioLog.BeginScenario(lilith, scenario.Id, scenario.Title); var stepList = scenario.Steps.OfType<ChatStepXml>().ToList(); int total = stepList.Count; for (int i = 0; i < stepList.Count; i++) { ct.ThrowIfCancellationRequested(); var chat = stepList[i]; using var stepScope = ScenarioLog.BeginStep(lilith, i + 1, total, chat.Prompt); var (_, reply) = await lilith.HandleInputAsync(chat.Prompt); lastReply = reply ?? ""; trace.Add($"step: {Trunc(chat.Prompt)} -> {Trunc(lastReply)}"); if (!string.IsNullOrWhiteSpace(chat.ExpectContains) && !(lastReply ?? "").Contains(chat.ExpectContains, StringComparison.OrdinalIgnoreCase)) { return ScenarioRunResult.Fail(scenario.Id, $"missing expect_contains '{chat.ExpectContains}'", trace, lastReply); } if (!string.IsNullOrWhiteSpace(chat.ExpectEquals) && !(lastReply ?? "").Trim().Equals(chat.ExpectEquals.Trim(), StringComparison.OrdinalIgnoreCase)) { return ScenarioRunResult.Fail(scenario.Id, $"reply not equals '{chat.ExpectEquals}'", trace, lastReply); } } return ScenarioRunResult.Pass(scenario.Id, lastReply ?? "", trace); } private static string Trunc(string s) { s = (s ?? "").Replace("\r", " ").Replace("\n", " ").Trim(); return s.Length > 120 ? s[..120] + "…" : s; }}internal sealed record ScenarioRunResult(bool Ok, string ScenarioId, string Summary, string Trace, string? LastReply){ public static ScenarioRunResult Pass(string id, string summary, List<string> trace) => new(true, id, summary, string.Join(" | ", trace), summary); public static ScenarioRunResult Fail(string id, string reason, List<string> trace, string? lastReply) => new(false, id, reason, string.Join(" | ", trace), lastReply);}
Documentation
Lilith (Version 3)
Version 3 Lilith agent library. Entry point: Lilith.cs with partials under Boot/, Chat/, Ollama/, and SystemPrompt/.
Build the solution or run python ../Build-v3.py from this tree.