Kontrollpflicht der Leitung: Stichproben gegen das Audit (#693)
A lead looks at the runs of the agents it leads - held against the task and against what the engine stamped, never by asking the agent. - RunInspector (Core/Inspection): the mechanical check and first profile of the inspector foundation (#204). Flags failed runs, tool errors, refusals, unfinished calls, tasks answered with bookkeeping alone, and operations the agent names but never called in the run (the 2026-09-14 pattern). - SpotChecks: picks runs per unit (rate, denser for new agents, new models and after a failure, daily cap), deterministic by run id; one task per lead. - Escalation tool: spot_checks, inspect_run, check_done, propose_measure. Measures go to a human; nothing is carried out automatically. A security suspicion is its own case, not a personnel matter. - Organisation view: settings per unit, recent checks, measures to decide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ab1bd67464
commit
68e723a9c0
@@ -135,6 +135,9 @@ public sealed class AppHost : IAsyncDisposable
|
||||
/// <summary>#698: The units' doctrines.</summary>
|
||||
public Core.Organisation.IDoctrineStore? Doctrines { get; private set; }
|
||||
|
||||
/// <summary>#693: The leads' spot checks and the measures they proposed.</summary>
|
||||
public Core.Inspection.SpotChecks? SpotChecks { get; private set; }
|
||||
|
||||
/// <summary>Modellpreise von OpenRouter - fuer Budget und Kostenprognose (#595). Laedt im Hintergrund.</summary>
|
||||
public ModelPricingCatalog? Pricing { get; private init; }
|
||||
|
||||
@@ -717,6 +720,18 @@ public sealed class AppHost : IAsyncDisposable
|
||||
engine.Doctrines = doctrines;
|
||||
host.Doctrines = doctrines;
|
||||
|
||||
// #693: the leads look at the runs of the agents they lead.
|
||||
var spotChecks = new Core.Inspection.SpotChecks(
|
||||
storage, audit,
|
||||
new Core.Inspection.RunInspector(audit, taskRepository, transcripts,
|
||||
() => Core.Inspection.RunInspector.VocabularyOf(tools.GetAll())),
|
||||
() => Core.Organisation.OrgChart.From(instance), board,
|
||||
loggerFactory.CreateLogger("ClawdDotNet.Core.Inspection.SpotChecks"));
|
||||
engine.SpotChecks = spotChecks;
|
||||
host.SpotChecks = spotChecks;
|
||||
spotChecks.Start();
|
||||
host._shutdown.Add(spotChecks.DisposeAsync);
|
||||
|
||||
// #699: costs per unit, and what changed in them.
|
||||
var costWatch = new Core.Accounting.CostWatch(
|
||||
storage, usage, () => Core.Organisation.OrgChart.From(instance), () => instance.CostWatch, board,
|
||||
|
||||
@@ -271,6 +271,52 @@ public sealed class OrgAdministration(AppHost host, ILogger logger)
|
||||
return AdminResult.Success(trimmed.Count == 0 ? message : message + $" Lowered with it: {string.Join(", ", trimmed)}.");
|
||||
}
|
||||
|
||||
// ─── spot checks (#693) ───
|
||||
|
||||
/// <summary>
|
||||
/// #693: How often the lead of a unit looks at the runs of the agents it leads. Only a human
|
||||
/// sets it — every check costs the lead a look, and the cap is what keeps the control from
|
||||
/// costing more than the work.
|
||||
/// </summary>
|
||||
public AdminResult SetInspection(string unitId, bool enabled, double? rate = null, double? denseRate = null,
|
||||
int? newDays = null, int? perDay = null, string reason = "")
|
||||
{
|
||||
if (FindUnit(unitId) is not { } unit)
|
||||
return AdminResult.Failure($"There is no unit '{unitId}'.");
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
unit.Inspection = null;
|
||||
Save(null, "unit_inspection", new { unitId, enabled }, reason);
|
||||
return AdminResult.Success($"The lead of '{unit.Name}' takes no spot checks now.");
|
||||
}
|
||||
|
||||
if (rate is < 0 or > 1 || denseRate is < 0 or > 1)
|
||||
return AdminResult.Failure("A share lies between 0 and 1.");
|
||||
if (perDay is < 0 or > 100)
|
||||
return AdminResult.Failure("Checks a day lie between 0 and 100.");
|
||||
if (newDays is < 0 or > 365)
|
||||
return AdminResult.Failure("An agent counts as new for 0 to 365 days.");
|
||||
|
||||
var current = unit.Inspection ?? new UnitInspection();
|
||||
unit.Inspection = new UnitInspection
|
||||
{
|
||||
Rate = rate ?? current.Rate,
|
||||
DenseRate = denseRate ?? current.DenseRate,
|
||||
NewDays = newDays ?? current.NewDays,
|
||||
PerDay = perDay ?? current.PerDay
|
||||
};
|
||||
if (unit.Inspection.DenseRate < unit.Inspection.Rate)
|
||||
unit.Inspection.DenseRate = unit.Inspection.Rate;
|
||||
|
||||
var lead = Chart.LeaderOf(unit.Id);
|
||||
var i = unit.Inspection;
|
||||
Save(null, "unit_inspection", new { unitId, enabled, i.Rate, i.DenseRate, i.NewDays, i.PerDay }, reason);
|
||||
return AdminResult.Success(
|
||||
$"The lead of '{unit.Name}' checks {i.Rate:P0} of the runs ({i.DenseRate:P0} for new agents, new models and after a failure), at most {i.PerDay} a day."
|
||||
+ (lead is null ? " The unit has no lead: its agents are checked under the settings of the unit whose lead they answer to." : ""));
|
||||
}
|
||||
|
||||
/// <summary>Whether a grant covers an operation string — "Tool" covers every "Tool.x".</summary>
|
||||
private static bool Within(UnitApprovals granted, string op)
|
||||
{
|
||||
@@ -885,7 +931,8 @@ public sealed class OrgAdministration(AppHost host, ILogger logger)
|
||||
private static UnitConfig Clone(UnitConfig u) => new()
|
||||
{
|
||||
Id = u.Id, Name = u.Name, ParentId = u.ParentId, LeaderAgentId = u.LeaderAgentId,
|
||||
Goal = u.Goal, ToolCeiling = u.ToolCeiling?.ToList(), Defaults = u.Defaults, Approvals = u.Approvals
|
||||
Goal = u.Goal, ToolCeiling = u.ToolCeiling?.ToList(), Defaults = u.Defaults, Approvals = u.Approvals,
|
||||
Inspection = u.Inspection
|
||||
};
|
||||
|
||||
private static bool Same(string? a, string? b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Reference in New Issue
Block a user