Eskalation, Konflikt und Beschluss als eigene Vorgaenge (#689, Stufe 1+2)

- Cases/Decisions in der Instanz-DB; CaseDesk fuehrt die Regeln, der Brett-Task traegt
  den Vorgang zum jeweiligen Halter
- Eskalation nur nach oben, eine Ebene je Schritt; die Leitung entscheidet (mit Begruendung,
  bindet ihren Bereich), erledigt selbst oder reicht mit Einschaetzung weiter
- Erst nachsehen: bindende Beschluesse werden vor dem Eskalieren gezeigt; dieselbe Frage
  aus derselben Einheit schliesst sich dem offenen Vorgang an
- Tagesdeckel je Einheit; nach der Frist wird ein Mensch gerufen (Anschluss fuer #644)
- Konflikt: zuerst die Gegenseite, dann Zustellung an die niedrigste gemeinsame Leitung;
  Ebenen dazwischen vermerkt, nicht befragt
- Fremdinhalt-Stufe (#715): Eskalation ohne eigenen Text, kein Konflikt mit Position
- Jede Ebene haengt an (#697), beim Halter gerahmt als Daten
- Escalation ist ein implizites Werkzeug jedes Agenten; Briefing nennt den Weg
- Scanner behaelt einen im Lauf gesetzten Status blockiert/abgebrochen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-25 09:46:20 +02:00
co-authored by Claude Opus 5.5
parent c03664cf29
commit 36fe1ad2aa
19 changed files with 1878 additions and 7 deletions
+13 -2
View File
@@ -491,6 +491,7 @@ public sealed class AppHost : IAsyncDisposable
registry.Register(new AgentEditorTool());
registry.Register(new Tools.Memory.MemoryTool());
registry.Register(new Tools.Taskboard.TaskboardTool());
registry.Register(new Tools.Taskboard.EscalationTool());
registry.Register(new Tools.RocketChat.RocketChatTool());
return registry;
@@ -540,6 +541,7 @@ public sealed class AppHost : IAsyncDisposable
var stagingGate = new StagingGate(new StagingPolicy(), stagingRepository, dauerfreigaben: dauerfreigaben);
var usage = new SqliteUsageRepository(storage);
var transcripts = new Core.Transcripts.SqliteRunTranscriptStore(storage);
var cases = new Core.Cases.SqliteCaseRepository(storage);
// Preise fürs Budget: Ohne sie greift nur die Token-Grenze.
var pricing = new ModelPricingCatalog();
@@ -579,7 +581,7 @@ public sealed class AppHost : IAsyncDisposable
var (scanner, staging, board) = BuildTaskboard(
instance, taskRepository, engine, tools, stateStore, audit,
stagingRepository, dauerfreigaben, loggerFactory, logger);
stagingRepository, dauerfreigaben, cases, loggerFactory, logger);
var host = new AppHost
{
@@ -725,6 +727,7 @@ public sealed class AppHost : IAsyncDisposable
SqliteAuditRepository audit,
SqliteStagingRepository stagingRepository,
SqliteDauerfreigabeRepository dauerfreigaben,
Core.Cases.ICaseRepository cases,
ILoggerFactory loggerFactory,
ILogger logger)
{
@@ -744,6 +747,12 @@ public sealed class AppHost : IAsyncDisposable
engine, () => instance.Agents, instance.InstanceId, tools, stateStore, loggerFactory,
tasks: taskRepository);
// #689: Escalations, conflicts and decisions ride on the board; the engine hands the desk to the tools.
var caseDesk = new Core.Cases.CaseDesk(
cases, board, () => engine.OrganisationProvider?.Invoke(), () => instance.Cases,
loggerFactory.CreateLogger("ClawdDotNet.Core.Cases"));
engine.CaseDesk = caseDesk;
var headroom = engine.BudgetGuard is { } guard
? new BudgetHeadroom(guard, () => instance.Agents, () => engine.InstanceBudget)
: null;
@@ -776,7 +785,9 @@ public sealed class AppHost : IAsyncDisposable
stateStore,
() => instance.Agents,
tools.Get,
loggerFactory.CreateLogger("ClawdDotNet.Core.Tasks.Reports"))));
loggerFactory.CreateLogger("ClawdDotNet.Core.Tasks.Reports")),
// #689: A case nobody solves in time goes to a human.
caseDesk));
// Reconciliation nicht blockierend: Der Start soll nicht auf das Dateisystem warten.
_ = Task.Run(async () =>