Verlorene Updates erkennen statt warten: optimistische Pruefung beim Ersetzen, Konflikte im Audit und im Lagebild (#720)

FileLedger merkt je Agent die gelesene Fassung einer Datei in gemeinsamen
Bereichen. write, copy und Downloads (FTP, RocketChat) ersetzen eine Datei nur,
wenn sie seit dem Lesen unveraendert ist; eine fremde Datei nicht ungelesen.
append bleibt ungeprueft, macht aeltere Lesestaende aber ungueltig.
Abgelehnte Schreibvorgaenge: eigener Audit-Status Conflict, Tabelle
FileConflicts, Hotspots (>= 3 in 7 Tagen) in der Teamuebersicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-28 10:45:46 +02:00
co-authored by Claude Opus 5.5
parent 8a7212c240
commit 79a3a7854f
21 changed files with 818 additions and 11 deletions
+6 -2
View File
@@ -60,6 +60,9 @@ public sealed class AgentEngine : IAgentMessageRouter, Staging.IFrozenCallExecut
/// </summary>
public Budget.ISpecialBudgetRepository? SpecialBudgets { get; set; }
/// <summary>#720: Which version of a shared file each agent read — so a write cannot silently lose another.</summary>
public Files.FileLedger? Files { get; set; }
/// <summary>#654: The sandbox tools run code in.</summary>
public Sandbox.ISandboxRunner? Sandbox { get; set; }
@@ -1665,7 +1668,7 @@ public sealed class AgentEngine : IAgentMessageRouter, Staging.IFrozenCallExecut
if (!result.Success)
{
await Audit(AuditStatus.Error, result.ErrorMessage ?? "");
await Audit(result.IsConflict ? AuditStatus.Conflict : AuditStatus.Error, result.ErrorMessage ?? "");
return JsonSerializer.Serialize(new { error = result.ErrorMessage });
}
@@ -1856,6 +1859,7 @@ public sealed class AgentEngine : IAgentMessageRouter, Staging.IFrozenCallExecut
Cases = CaseDesk,
SpecialBudgets = SpecialBudgets,
Sandbox = Sandbox,
Files = Files,
// #697: Where something an agent hands on came from — known to the engine, not claimed by the agent.
RunId = runId,
RunTaskId = runId is null ? null : Activity.Find(runId)?.TaskId
@@ -1890,7 +1894,7 @@ public sealed class AgentEngine : IAgentMessageRouter, Staging.IFrozenCallExecut
var context = BuildToolContext(config, _instanceId, tool, ct, runId: runId) with { FreigabeGeprueft = true };
var result = await agentTool.ExecuteAsync(input, context, ct);
var status = result.Success ? AuditStatus.Ok : AuditStatus.Error;
var status = result.Success ? AuditStatus.Ok : result.IsConflict ? AuditStatus.Conflict : AuditStatus.Error;
await RecordAuditAsync(runId, config, AuditSource.Approval, tool, argumentsJson,
status, result.Success ? "Freigegeben ausgeführt" : (result.ErrorMessage ?? ""), sw.ElapsedMilliseconds);