diff --git a/src/ClawdDotNet.App/AppHost.cs b/src/ClawdDotNet.App/AppHost.cs
index f8d59ab..2ab982a 100644
--- a/src/ClawdDotNet.App/AppHost.cs
+++ b/src/ClawdDotNet.App/AppHost.cs
@@ -60,6 +60,15 @@ public sealed class AppHost : IAsyncDisposable
public required ILoggerFactory LoggerFactory { get; init; }
public required ToolRegistry Tools { get; init; }
+ ///
+ /// Zustandsspeicher und Audit-Log der Instanz. Nicht oeffentlich — sie gehoeren dem
+ /// Aufbau, nicht den Oberflaechen; gebraucht werden sie hier fuer die Sichtung
+ /// offener Werkzeugaufrufe beim Start (#554).
+ ///
+ internal IStateStore? StateStore { get; private init; }
+
+ internal IAuditRepository? AuditRepository { get; private init; }
+
///
/// Das geordnete Ende dieser Instanz (#528).
///
@@ -389,6 +398,15 @@ public sealed class AppHost : IAsyncDisposable
var host = BuildCore(settings, directories, instance, instancePath,
logDirectory, loggerFactory, tools, telegram, logger);
+ // #554: Nachsehen, was beim letzten Ende offen geblieben ist. Ein geordnetes Ende
+ // hinterlaesst nichts; ist hier etwas, ist die Instanz gestorben, waehrend
+ // Werkzeuge liefen.
+ //
+ // Bewusst nur sichten und protokollieren, nicht ausfuehren: Ob die Wirkung eines
+ // Aufrufs eingetreten ist, weiss auch dieser Dienst nicht. Ein automatisches
+ // "dann eben nochmal" versendete im Zweifel die Mail ein zweites Mal.
+ await host.SichteOffeneAufrufeAsync(logger);
+
// Nichts freizugeben: Der Torwächter nutzt den gemeinsamen HttpClient des SDK.
host.License = license;
host.IsManaged = gefuehrt;
@@ -539,6 +557,8 @@ public sealed class AppHost : IAsyncDisposable
LogDirectory = logDirectory,
LoggerFactory = loggerFactory,
Tools = tools,
+ StateStore = stateStore,
+ AuditRepository = audit,
Engine = engine,
Scanner = scanner,
Staging = staging,
@@ -569,6 +589,71 @@ public sealed class AppHost : IAsyncDisposable
/// Taskboard und Scanner. Der Scanner ist der einzige periodische Treiber (A1) —
/// geplante Agentenläufe wie Tool-Job-Polls sind Tasks.
///
+ ///
+ /// Sichtet beim Start die Werkzeugaufrufe, die beim letzten Ende offen blieben (#554),
+ /// und schreibt das Ergebnis ins Protokoll.
+ ///
+ /// Scheitert die Sichtung selbst, hält sie den Start nicht auf. Eine Instanz,
+ /// die wegen einer misslungenen Nachschau gar nicht erst hochkommt, wäre schlimmer
+ /// als eine, die mit einer Wissenslücke startet.
+ ///
+ private async Task SichteOffeneAufrufeAsync(ILogger logger)
+ {
+ // Ohne Protokoll gibt es nichts zu sichten, ohne Zustandsspeicher keine
+ // Nachschau. Beides fehlt nur beim Aufbau ohne Datenbank — dann gab es auch
+ // keine Werkzeugaufrufe, die offen sein könnten.
+ if (AuditRepository is not { } audit || StateStore is not { } stateStore)
+ return;
+
+ var instance = Instance;
+ var tools = Tools;
+ var loggerFactory = LoggerFactory;
+
+ try
+ {
+ // Die Nachschau braucht die Arbeitsverzeichnisse des Agenten — die kennt nur
+ // die Instanzkonfiguration. Ist der Agent inzwischen weg, gibt es keinen
+ // Kontext und damit keine Nachschau; die Einstufung bleibt trotzdem.
+ AgentToolContext? KontextFuer(string agentId, string toolName)
+ {
+ var agent = instance.Agents.FirstOrDefault(a => a.AgentId == agentId);
+ if (agent is null)
+ return null;
+
+ var toolConfig = agent.Tools.TryGetValue(toolName, out var cfg)
+ ? cfg.AsReadOnly()
+ : new Dictionary().AsReadOnly();
+
+ return new AgentToolContext(
+ agent.AgentId,
+ instance.InstanceId,
+ toolConfig,
+ stateStore,
+ loggerFactory.CreateLogger($"ClawdDotNet.Tools.{toolName}.Probe"),
+ CancellationToken.None,
+ agent.WorkspacePath,
+ agent.SharedWorkspacePath);
+ }
+
+ var recovery = new CrashRecovery(audit, tools, loggerFactory, KontextFuer);
+ var faelle = await recovery.SichtenAsync(CancellationToken.None);
+
+ var offen = faelle.Where(f => !f.Erledigt).ToList();
+ if (offen.Count > 0)
+ {
+ logger.LogWarning(
+ "{Anzahl} Werkzeugaufruf(e) brauchen eine Entscheidung. Sie wurden NICHT " +
+ "wiederholt — ob ihre Wirkung eintrat, ist nicht feststellbar.",
+ offen.Count);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning(ex,
+ "Die Sichtung offener Werkzeugaufrufe ist gescheitert. Der Start geht weiter.");
+ }
+ }
+
private static (TaskScanner?, StagingService?, TaskboardService?) BuildTaskboard(
InstanceConfig instance,
SqliteTaskRepository taskRepository,