V9 / fa079a940f8f
SubagentRegistry.cs
Lilith/Core/Scenarios/SubagentRegistry.cs
Unchanged from the preceding versionCS
namespace Lilith.Core.Scenarios;
public sealed record SubagentRun(long AgentId, string RunId, string ScenarioId, string Task, string Status,
DateTimeOffset StartedAt, DateTimeOffset? FinishedAt = null);
public sealed record SubagentSnapshot(int RunningCount, long TotalCreated, long CompletedCount, long FailedCount,
long CanceledCount, long WaitingApprovalCount, SubagentRun[] Running, SubagentRun[] Recent);
public sealed record SubagentOutput(long AgentId, string RunId, string Kind, string Text, bool IsSummary);
/// <summary>Session-level execution registry. IDs and conversations are never reused.</summary>
public sealed class SubagentRegistry
{
private readonly object _sync = new();
private readonly Dictionary<string, SubagentRun> _active = new(StringComparer.Ordinal);
private readonly Queue<SubagentRun> _recent = new();
private long _created, _completed, _failed, _canceled, _waiting;
public SubagentSnapshot Snapshot()
{
lock (_sync) return new(_active.Count, _created, _completed, _failed, _canceled, _waiting,
_active.Values.OrderBy(r => r.AgentId).ToArray(), _recent.Reverse().ToArray());
}
internal SubagentRun? Observe(ScenarioProgress progress)
{
lock (_sync)
{
if (progress.Kind == "Started")
{
if (_active.ContainsKey(progress.RunId)) throw new InvalidOperationException("Subagent run already started.");
var started = new SubagentRun(++_created, progress.RunId, progress.ScenarioId, progress.Text, "running", DateTimeOffset.UtcNow);
_active.Add(progress.RunId, started);
return started;
}
if (!_active.TryGetValue(progress.RunId, out var run)) return null;
string? status = progress.Kind switch { "Completed" => "completed", "Failed" => "failed", "Canceled" => "canceled", "NeedsApproval" => "needs_approval", _ => null };
if (status is null) return run;
_active.Remove(progress.RunId);
var finished = run with { Status = status, FinishedAt = DateTimeOffset.UtcNow };
switch (status)
{
case "completed": _completed++; break;
case "failed": _failed++; break;
case "canceled": _canceled++; break;
case "needs_approval": _waiting++; break;
}
_recent.Enqueue(finished);
if (_recent.Count > 128) _recent.Dequeue();
return finished;
}
}
internal static SubagentOutput? Present(ScenarioProgress progress, SubagentRun? run, bool details)
{
if (run is null) return null;
string? outcome = progress.Kind switch { "Completed" => "completed", "Failed" => "failed", "Canceled" => "canceled", "NeedsApproval" => "is waiting for approval for", _ => null };
if (outcome is not null) return new(run.AgentId, run.RunId, progress.Kind,
$"Subagent {run.AgentId} {outcome} {run.Task} ({run.ScenarioId}).", true);
return details ? new(run.AgentId, run.RunId, progress.Kind, progress.Text, false) : null;
}
}