- Werkzeugkontext kennt Lauf und Aufgabe des Laufs (RunId, RunTaskId); die Delegationskette traegt den Lauf des Aufrufers weiter (RunId, CallerRunId) - Aufgaben tragen origin_run / origin_task (Datei, DB, Taskboard "Arose from"), gestempelt, nie vom Agenten behauptet - Kommentare nennen den Lauf, in dem sie geschrieben wurden - Laufbeleg: CallerRunId; Laufansicht fuehrt zu "Delegiert von" und "Delegiert an" - #677: task_update ersetzt den Rumpf nicht mehr, Abnahmekriterien aendert nur der Ersteller; Kommentare der Fremdinhalt-Stufe auf fremden Aufgaben kommen gerahmt an - WebUI: Herkunft auf der Aufgabenkarte mit Link zum Lauf Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
3.5 KiB
C#
78 lines
3.5 KiB
C#
namespace ClawdDotNet.Core.Tools;
|
|
|
|
/// <summary>
|
|
/// Wer an diesem Aufruf schon beteiligt ist — die Kette vom ersten Agenten bis zum
|
|
/// gerade laufenden.
|
|
///
|
|
/// <para><b>Warum es die Kette gibt (B8).</b> <c>send_message</c> ist ein synchroner
|
|
/// Aufruf: A hält sein Gate, während es auf B wartet. Ruft B nun A, wartet B auf ein
|
|
/// Gate, das A hält und erst freigibt, wenn B geantwortet hat. Das löste nur der
|
|
/// Timeout des äußeren Laufs auf — bis dahin standen beide Agenten.</para>
|
|
///
|
|
/// <para><b>Die Zusagen</b>, auf denen alles Weitere aufsetzt:</para>
|
|
/// <list type="number">
|
|
/// <item>Ein Agent kommt in einer Kette höchstens einmal vor. Damit wartet niemand
|
|
/// auf ein Gate, das er in derselben Kette schon hält — der Verklemmung ist die
|
|
/// Grundlage entzogen, nicht nur die Wahrscheinlichkeit genommen.</item>
|
|
/// <item>Eine Kette ist höchstens <c>MaxDelegationDepth</c> Schritte lang. Zyklen
|
|
/// sind damit ausgeschlossen, eine lange Staffel A→B→C→D→… aber nicht; die
|
|
/// kostet Zeit und Tokens, ohne dass jemand sie beauftragt hätte.</item>
|
|
/// <item>Die Kette gehört zum <b>Aufruf</b>, nicht zum Agenten. Zwei unabhängige
|
|
/// Läufe desselben Agenten behindern einander nicht.</item>
|
|
/// </list>
|
|
///
|
|
/// <para>Unveränderlich: <see cref="Append"/> gibt eine neue Kette zurück. Ein Aufruf
|
|
/// kann die Kette seines Aufrufers deshalb nicht verändern, und die Kette lässt sich
|
|
/// gefahrlos über Fadengrenzen reichen.</para>
|
|
/// </summary>
|
|
public sealed class DelegationChain
|
|
{
|
|
private readonly string[] _agents;
|
|
|
|
private DelegationChain(string[] agents, string? runId = null, string? callerRunId = null)
|
|
{
|
|
_agents = agents;
|
|
RunId = runId;
|
|
CallerRunId = callerRunId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// #697: The run of the last agent in the chain — the one that is handing on. Stamped by
|
|
/// the engine when it builds a tool's context, so a delegated run can name the run it
|
|
/// came from. <c>null</c> outside a run.
|
|
/// </summary>
|
|
public string? RunId { get; }
|
|
|
|
/// <summary>
|
|
/// #697: The run of the agent before the last — the run a delegated run was started from.
|
|
/// Carried over by <see cref="Append"/>, so the new run can record its origin.
|
|
/// </summary>
|
|
public string? CallerRunId { get; }
|
|
|
|
/// <summary>#697: The same chain, stamped with the run of its last agent.</summary>
|
|
public DelegationChain WithRun(string runId) => new(_agents, runId, CallerRunId);
|
|
|
|
/// <summary>Der Anfang: ein Lauf, den niemand delegiert hat.</summary>
|
|
public static DelegationChain Root(string agentId) => new([agentId]);
|
|
|
|
/// <summary>Die Agenten in der Reihenfolge, in der sie beauftragt wurden.</summary>
|
|
public IReadOnlyList<string> Agents => _agents;
|
|
|
|
/// <summary>
|
|
/// Die Zahl der Weitergaben — nicht die der Agenten. Ein Lauf ohne Delegation hat
|
|
/// die Tiefe 0, A→B die Tiefe 1.
|
|
/// </summary>
|
|
public int Depth => _agents.Length - 1;
|
|
|
|
/// <summary>Ist dieser Agent in der Kette schon vertreten?</summary>
|
|
public bool Contains(string agentId)
|
|
=> Array.IndexOf(_agents, agentId) >= 0;
|
|
|
|
/// <summary>Die Kette mit einem weiteren Agenten am Ende.</summary>
|
|
public DelegationChain Append(string agentId)
|
|
=> new([.. _agents, agentId], runId: null, callerRunId: RunId); // the new agent's run is not known yet
|
|
|
|
/// <summary>Für Meldungen und Protokoll: <c>A -> B -> C</c>.</summary>
|
|
public override string ToString() => string.Join(" -> ", _agents);
|
|
}
|