Verweis statt Nacherzaehlung: die Engine stempelt den Ursprung (#697, #677)

- 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>
This commit is contained in:
Richard
2026-09-25 09:27:47 +02:00
co-authored by Claude Opus 5.5
parent 3f90e0d0cd
commit f812dbd856
26 changed files with 493 additions and 47 deletions
@@ -46,11 +46,11 @@ public sealed class SqliteAuditRepository : IAuditRepository
INSERT INTO RunReceipts
(RunId, AgentId, Model, Source, TaskId, Status, StepCount,
PromptTokens, CompletionTokens, CachedTokens, CostUsd, CostIsKnown,
DurationMs, ResultRef, OccurredAt)
DurationMs, ResultRef, OccurredAt, CallerRunId)
VALUES
(@runId, @agentId, @model, @source, @taskId, @status, @stepCount,
@prompt, @completion, @cached, @cost, @costKnown,
@durationMs, @resultRef, @occurredAt)
@durationMs, @resultRef, @occurredAt, @callerRunId)
""";
cmd.Parameters.AddWithValue("@runId", receipt.RunId);
cmd.Parameters.AddWithValue("@agentId", receipt.AgentId);
@@ -67,6 +67,7 @@ public sealed class SqliteAuditRepository : IAuditRepository
cmd.Parameters.AddWithValue("@durationMs", receipt.DurationMs);
cmd.Parameters.AddWithValue("@resultRef", receipt.ResultRef);
cmd.Parameters.AddWithValue("@occurredAt", Format(receipt.OccurredAt));
cmd.Parameters.AddWithValue("@callerRunId", (object?)receipt.CallerRunId ?? DBNull.Value);
await cmd.ExecuteNonQueryAsync(ct);
}, ct);
@@ -117,6 +118,20 @@ public sealed class SqliteAuditRepository : IAuditRepository
return results;
}
public async Task<IReadOnlyList<RunReceipt>> ListReceiptsCalledFromAsync(string runId, CancellationToken ct)
{
await using var conn = await _storage.OpenConnectionAsync(ct);
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT " + ReceiptColumns + " FROM RunReceipts WHERE CallerRunId = @runId ORDER BY Id";
cmd.Parameters.AddWithValue("@runId", runId);
var results = new List<RunReceipt>();
await using var reader = await cmd.ExecuteReaderAsync(ct);
while (await reader.ReadAsync(ct))
results.Add(ReadReceipt(reader));
return results;
}
public async Task<IReadOnlyList<RunReceipt>> ListReceiptsForTaskAsync(string taskId, CancellationToken ct)
{
await using var conn = await _storage.OpenConnectionAsync(ct);
@@ -249,7 +264,7 @@ public sealed class SqliteAuditRepository : IAuditRepository
private const string ReceiptColumns =
"Id, RunId, AgentId, Model, Source, TaskId, Status, StepCount, PromptTokens, " +
"CompletionTokens, CachedTokens, CostUsd, CostIsKnown, DurationMs, ResultRef, OccurredAt";
"CompletionTokens, CachedTokens, CostUsd, CostIsKnown, DurationMs, ResultRef, OccurredAt, CallerRunId";
private static async Task<IReadOnlyList<AuditEntry>> ReadEntriesAsync(SqliteCommand cmd, CancellationToken ct)
{
@@ -293,7 +308,8 @@ public sealed class SqliteAuditRepository : IAuditRepository
CostIsKnown = r.GetInt32(12) != 0,
DurationMs = r.GetInt64(13),
ResultRef = r.GetString(14),
OccurredAt = Parse(r.GetString(15))
OccurredAt = Parse(r.GetString(15)),
CallerRunId = r.IsDBNull(16) ? null : r.GetString(16)
};
private static string Format(DateTime value) => value.ToUniversalTime().ToString("O");