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:
Richard
2026-10-01 11:40:20 +02:00
co-authored by Claude Opus 5.5
parent ab1bd67464
commit 68e723a9c0
19 changed files with 1621 additions and 6 deletions
+15
View File
@@ -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);