Zielkette, Stufe 1: jede Aufgabe kann sagen, wofuer sie da ist (#714)
- TaskItem.ParentId, in der Datei als parent, in der DB als ParentId. Die
Elternaufgabe ist der Zweck ("wofuer"), blocked_by die Voraussetzung
("wann").
- GoalChain: Eltern bis zu acht Stufen, Zyklen brechen den Gang ab und
werden beim Setzen abgelehnt. Oben steht das Ziel der Einheit - der
obersten Aufgabe, die zu einer Einheit gehoert (ein Ziel, das ein Mensch
auf der obersten Ebene setzt, gehoert keiner), sonst der naechsten Einheit
darueber mit Ziel.
- Der Lauf einer Aufgabe bekommt den Absatz "Why this task exists" und die
Bitte, zu melden, wenn die Aufgabe ihrem Zweck nicht mehr dient.
- Taskboard-Tool: parent bei task_create/task_update, geprueft auf Existenz,
Sichtbarkeit (#684) und Zyklen; task_list zeigt "Serves".
- WebUI: Feld im Aufgabendialog, Anzeige auf der Karte; die Verwaltung
prueft wie das Tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b75b68c786
commit
b22272345e
@@ -30,6 +30,7 @@ public sealed class EngineTaskDispatcher : ITaskDispatcher
|
||||
private readonly IStateStore _stateStore;
|
||||
private readonly ILogger _logger;
|
||||
private readonly TimeProvider _clock;
|
||||
private readonly ITaskRepository? _tasks;
|
||||
|
||||
public EngineTaskDispatcher(
|
||||
AgentEngine engine,
|
||||
@@ -38,8 +39,10 @@ public sealed class EngineTaskDispatcher : ITaskDispatcher
|
||||
ToolRegistry toolRegistry,
|
||||
IStateStore stateStore,
|
||||
ILoggerFactory loggerFactory,
|
||||
TimeProvider? clock = null)
|
||||
TimeProvider? clock = null,
|
||||
ITaskRepository? tasks = null)
|
||||
{
|
||||
_tasks = tasks;
|
||||
_engine = engine;
|
||||
_agents = agents;
|
||||
_instanceId = instanceId;
|
||||
@@ -56,7 +59,7 @@ public sealed class EngineTaskDispatcher : ITaskDispatcher
|
||||
return await DispatchToolJobAsync(task, ct);
|
||||
|
||||
var agents = _agents();
|
||||
var message = BuildMessage(task);
|
||||
var message = BuildMessage(task, await WhyAsync(task, ct));
|
||||
var kind = TaskAssignee.KindOf(task.Assignee);
|
||||
|
||||
AgentRunResult result;
|
||||
@@ -158,7 +161,27 @@ public sealed class EngineTaskDispatcher : ITaskDispatcher
|
||||
return agents.Count == 1 ? agents[0] : null;
|
||||
}
|
||||
|
||||
internal string BuildMessage(TaskItem task)
|
||||
/// <summary>
|
||||
/// #714: The goal chain of the task — its parents up to the goal of the unit. A lookup
|
||||
/// that fails costs the paragraph, never the run.
|
||||
/// </summary>
|
||||
private async Task<string?> WhyAsync(TaskItem task, CancellationToken ct)
|
||||
{
|
||||
if (_tasks is null)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
var parents = await GoalChain.ParentsAsync(task, _tasks.GetAsync, ct);
|
||||
return GoalChain.Describe(task, parents, _engine.OrganisationProvider?.Invoke());
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogWarning(ex, "Goal chain of task {TaskId} could not be read; the run goes ahead without it", task.Id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal string BuildMessage(TaskItem task, string? why = null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"[Aufgabe {task.Id}] {task.Title}");
|
||||
@@ -168,6 +191,12 @@ public sealed class EngineTaskDispatcher : ITaskDispatcher
|
||||
// und ein Modell ohne diese Zeile raet aus seinem Trainingsstand.
|
||||
sb.AppendLine(Zeitangabe(task, _clock.GetUtcNow()));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(why))
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(why.Trim());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(task.Body))
|
||||
{
|
||||
sb.AppendLine();
|
||||
|
||||
Reference in New Issue
Block a user