Einheiten, Stufe 2: das Adressbuch durchsetzen und jedem Agenten seinen Platz sagen (#683)

Durchgesetzt vom Code, nicht nur im Prompt beschrieben:
- Nachricht und Spawn an einen Agenten ausserhalb des Adressbuchs werden vor
  jedem Modellaufruf abgelehnt; die Antwort nennt den offenen Weg (die eigene
  Leitung, sonst @human).
- list_agents zeigt nur noch das Adressbuch.
- task_create und task_update pruefen die Zuweisung an einen Agenten gegen
  dasselbe Adressbuch; sonst liesse es sich ueber eine Aufgabe umgehen.
- Die Delegationstiefe folgt der Tiefe des Baums (Tiefe + 1), der
  eingestellte Wert bleibt Untergrenze.

Unterrichtet werden, wie das Konzept verlangt:
- OrgBriefing erzeugt einen Abschnitt "# Organisation" im Systemprompt:
  Einheit mit Pfad und Ziel, seit wann (mit Hinweis, wenn frisch), die
  effektive Leitung oder "@human", als Leitung die Untereinheiten und neue
  Mitglieder, und wen man sonst erreicht. Er ist immer wahr, weil er aus dem
  Baum kommt; ein Umzug aendert ihn von selbst.
- In einem laufenden Chat wird nur dieser Abschnitt ersetzt; der uebrige
  Systemprompt bleibt, wie er war (#193).
- Ohne Einheiten gibt es keinen Abschnitt und keine Einschraenkung. Bestehende
  Instanzen behalten ihren Prompt und ihren Prompt-Cache.

AppHost setzt den Organisationsbaum als Provider (frisch je Frage, eine
Aenderung gilt ab dem naechsten Lauf) und protokolliert Probleme der Struktur
beim Start.

Tests: AddressBookEnforcementTests (10), AssignmentReachTests (4). 1.317 gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-24 12:43:10 +02:00
co-authored by Claude Opus 5.5
parent d014a20c38
commit 626fc8d593
7 changed files with 503 additions and 6 deletions
@@ -151,6 +151,12 @@ public sealed class TaskboardTool : IAgentTool
if (string.IsNullOrWhiteSpace(action))
return ToolResult.Fail("'action' is required.");
// #683: A task for another agent is a way of reaching it — the same address book
// applies as for a message, otherwise it could be walked around.
if (action is "task_create" or "task_update"
&& CheckAssignee(GetString(input, "assignee"), context) is { } outOfReach)
return ToolResult.Fail(outOfReach);
try
{
return action switch
@@ -170,6 +176,26 @@ public sealed class TaskboardTool : IAgentTool
}
}
private static string? CheckAssignee(string? assignee, AgentToolContext context)
{
if (context.Organisation is not { HasUnits: true } org || string.IsNullOrWhiteSpace(assignee))
return null;
if (TaskAssignee.KindOf(assignee) == TaskAssigneeKind.Human)
return null;
var target = TaskAssignee.AgentId(assignee);
if (target.Length == 0 || string.Equals(target, context.AgentId, StringComparison.OrdinalIgnoreCase))
return null;
if (org.CanAddress(context.AgentId, target))
return null;
var lead = org.EffectiveLeaderOf(context.AgentId);
return $"'{target}' is not in your address book, so you cannot assign tasks to it. " +
(lead is not null
? $"Assign it to your lead '{lead.AgentId}', who can pass it on."
: "Assign it to @human.");
}
// ─── Aktionen ───
/// <summary>