Entscheidungsinstanz an der Spitze: Mensch, Agent oder Mensch zuerst mit Frist (#690)
- cases.top in instance.json: Human | Agent | HumanFirst, agentId, humanFirstHours - TopRole: der entscheidende Agent steht in keiner Einheit, liest keine Fremdinhalte und hat nur Taskboard, Escalation, Memory, FileRW, AgentComm; geprueft beim Setzen, bei jeder Zustellung und vor jeder Entscheidung - CaseDesk: was bisher an der Spitze beim Menschen landete, geht nach Einstellung an den Agenten; Tagesdeckel und Frist gehen weiter an den Menschen. handle verwehrt, forward geht an den Menschen. Mensch zuerst: Uebergabe nach Ablauf, eigene Frist. Der Mensch kann einen Fall des Agenten selbst entscheiden. - Beschluesse des Agenten: ByTopAgent, zum Lesen vorgelegt; jeder Beschluss laesst sich mit Begruendung zuruecknehmen und bindet dann niemanden mehr - Briefing fuer den Agenten an der Spitze, auch ohne Einheiten - OrgAdministration.SetTop mit Audit; Werkzeugsperre und Verschiebesperre - WebUI Vorgaenge: Reiter Spitze, Lesen/Zuruecknehmen, Zaehler; i18n en/de - Tests: TopRoleTests (15), OrgAdministrationTests (+2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e22edb418a
commit
e8964e3c74
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.Audit;
|
||||
using ClawdDotNet.Core.Cases;
|
||||
using ClawdDotNet.Core.Config;
|
||||
using ClawdDotNet.Core.Organisation;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -194,10 +195,52 @@ public sealed class OrgAdministration(AppHost host, ILogger logger)
|
||||
problems.Add($"There is no agent '{agentId}'.");
|
||||
if (!string.IsNullOrWhiteSpace(targetUnitId) && chart.Unit(targetUnitId) is null)
|
||||
problems.Add($"There is no unit '{targetUnitId}'.");
|
||||
if (!string.IsNullOrWhiteSpace(targetUnitId) && IsTopAgent(agentId))
|
||||
problems.Add($"'{agentId}' is the deciding agent at the top and stands above all units. Set another top first.");
|
||||
|
||||
return Preview([agentId], NullIfEmpty(targetUnitId), movingUnit: null, problems);
|
||||
}
|
||||
|
||||
// ─── the top (#690) ───
|
||||
|
||||
private bool IsTopAgent(string agentId)
|
||||
=> host.Instance.Cases.Top.UsesAgent && Same(host.Instance.Cases.Top.AgentId?.Trim(), agentId);
|
||||
|
||||
/// <summary>
|
||||
/// #690: Who decides at the top — a human, an agent, or the human first with the agent behind.
|
||||
/// An agent has to fit the role (<see cref="TopRole.Unfit"/>) when it is set; a change of
|
||||
/// the top is a change of rights and goes into the audit log with its reason.
|
||||
/// </summary>
|
||||
public AdminResult SetTop(TopMode mode, string? agentId, double? humanFirstHours = null, string reason = "")
|
||||
{
|
||||
agentId = NullIfEmpty(agentId);
|
||||
if (mode != TopMode.Human)
|
||||
{
|
||||
if (agentId is null)
|
||||
return AdminResult.Failure("Name the agent that decides at the top.");
|
||||
if (TopRole.Unfit(host.AgentAdmin.Find(agentId), agentId) is { } unfit)
|
||||
return AdminResult.Failure(unfit);
|
||||
agentId = host.AgentAdmin.Find(agentId)!.AgentId;
|
||||
}
|
||||
if (humanFirstHours is <= 0)
|
||||
return AdminResult.Failure("The time the human has comes in hours, above zero.");
|
||||
|
||||
var top = host.Instance.Cases.Top;
|
||||
top.Mode = mode;
|
||||
top.AgentId = mode == TopMode.Human ? null : agentId;
|
||||
if (humanFirstHours is { } hours)
|
||||
top.HumanFirstHours = hours;
|
||||
|
||||
host.SaveInstance(new InstanceChange(InstanceChangeKind.Settings));
|
||||
Audit(agentId ?? "", "top_set", new { mode = mode.ToString(), agentId, top.HumanFirstHours }, reason);
|
||||
return AdminResult.Success(mode switch
|
||||
{
|
||||
TopMode.Human => "A human decides at the top.",
|
||||
TopMode.Agent => $"'{agentId}' decides at the top. Its decisions are presented to you to read.",
|
||||
_ => $"A human decides at the top first; after {top.HumanFirst.TotalHours:0.##} h without an answer, '{agentId}' decides and presents it."
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an agent. Tools beyond the new unit's ceiling are removed only with
|
||||
/// <paramref name="dropExcessTools"/> — otherwise the move is refused and says which.
|
||||
@@ -323,6 +366,10 @@ public sealed class OrgAdministration(AppHost host, ILogger logger)
|
||||
/// </summary>
|
||||
public string? CeilingRefusal(AgentConfig agent, string toolName)
|
||||
{
|
||||
// #690: The deciding agent decides, it does not carry out.
|
||||
if (IsTopAgent(agent.AgentId) && !TopRole.AllowedTools.Contains(toolName))
|
||||
return $"'{agent.AgentId}' is the deciding agent at the top and may not hold '{toolName}' — it decides, it does not carry out. " +
|
||||
$"Allowed are {string.Join(", ", TopRole.AllowedTools.Order(StringComparer.OrdinalIgnoreCase))}.";
|
||||
if (Chart.ToolCeiling(agent.UnitId) is not { } ceiling || ceiling.Contains(toolName))
|
||||
return null;
|
||||
return $"'{toolName}' is beyond the tool ceiling of the unit '{Chart.UnitOf(agent.AgentId)?.Name}'. " +
|
||||
|
||||
Reference in New Issue
Block a user