Wiederaufnahme nach Absturz, Nachschau und Auswertung (#553-#556)
#553 FileRW schreibt atomar. Die Vorkehrung lag seit #241 im selben Repository und wurde ueberall benutzt - Einstellungen, Sicherungen, Chat-Zustand, Identity.md - nur der Weg, ueber den Agenten ihre Arbeitsergebnisse ablegen, ging daran vorbei. Damit stand das Verhaeltnis auf dem Kopf: geschuetzt war das Eigene, ungeschuetzt das Ergebnis. append bleibt bewusst nicht atomar; das ginge nur, indem die ganze Datei neu geschrieben wird, und bei einer grossen Protokolldatei waere das der falsche Handel. #554 Wiederaufnahme. Neuer Eintrag AuditStatus.Started, geschrieben VOR der Wirkung und als einziger nicht "best effort" - auf ihn stuetzt sich alles Weitere, also laeuft der Aufruf gar nicht erst, wenn er nicht durchkommt. Neue Kennzeichnung IsRepeatable, getrennt von IsParallelSafe: Das eine heisst "stoert keinen gleichzeitigen Aufruf", das andere "richtet zweimal keinen Schaden an". CrashRecovery sichtet beim Start, was offen blieb, und stuft ein - fuehrt aber nichts aus. Es sichtet und berichtet; entscheiden soll der Aufrufer. Dabei kippte eine alte Regel: Bisher wurde ein Abbruch bewusst NICHT protokolliert. Das war richtig, solange es keinen Eintrag vor dem Aufruf gab. Jetzt saehe sonst jeder Benutzerabbruch beim naechsten Start aus wie ein Absturz - bis niemand mehr hinsieht. Deshalb AuditStatus.Cancelled. #555 Optionale Zustandspruefung IToolStateProbe, umgesetzt in FileRW. Unknown ist eine vollwertige Antwort, kein Fehlschlag: Ob ein SMTP-Server angenommen hat, laesst sich nicht feststellen, und ein Werkzeug, das hier raet, waere schlimmer als eines, das schweigt. Partial gilt ausdruecklich nicht als geklaert - einen halb angehaengten Text sollte niemand automatisch geraderuecken. #556 Auswertung. ListForAgentAsync und SummarizeToolUsageAsync. Die Dauer wird nur ueber abgeschlossene Aufrufe gemittelt; zaehlten die Started-Eintraege mit ihrer Dauer 0 mit, saehe jedes Werkzeug doppelt so schnell aus, wie es ist. Nebenbei: SqliteStorage kann jetzt Spalten nachruesten. CREATE TABLE IF NOT EXISTS allein genuegte nicht - bei einer bestehenden Datenbank tut die Anweisung nichts, und erst das naechste INSERT waere gescheitert, im Betrieb beim Anwender. Vier Audit-Tests angepasst: Ein ausgefuehrter Aufruf erzeugt jetzt zwei Eintraege statt einem. Ein Aufruf, der gar nicht erst laeuft - verweigert, unbekannt, vorgelegt - hat weiterhin genau einen; der Beginn wird erst nach Rechtepruefung und Staging geschrieben. 23 neue Tests, 912 gesamt, keine Regression. Build ohne Warnungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,25 @@ public interface IAgentTool
|
||||
/// </summary>
|
||||
bool IsParallelSafe => false;
|
||||
|
||||
/// <summary>
|
||||
/// Ob ein zweiter, gleicher Aufruf schadlos bleibt (#554).
|
||||
///
|
||||
/// <para>Gebraucht wird das nach einem harten Absturz: Im Protokoll steht, dass ein
|
||||
/// Aufruf begann, nicht ob er wirkte. Bei einem wiederholbaren Werkzeug muss man das
|
||||
/// auch nicht wissen — man ruft es einfach noch einmal. Bei allen anderen bleibt nur,
|
||||
/// den Fall vorzulegen statt zu raten.</para>
|
||||
///
|
||||
/// <para>Nicht zu verwechseln mit <see cref="IsParallelSafe"/>. Das heißt „stört
|
||||
/// keinen gleichzeitigen Aufruf", dies hier heißt „richtet zweimal keinen Schaden an".
|
||||
/// Ein Werkzeug kann das eine sein und das andere nicht: Eine Datei zu überschreiben
|
||||
/// ist wiederholbar — zweimal derselbe Inhalt ergibt denselben Zustand —, verträgt
|
||||
/// aber keinen zweiten Schreiber zur selben Zeit.</para>
|
||||
///
|
||||
/// <para>Vorgabe <c>false</c>, aus demselben Grund wie oben: Wer sich irrt, versendet
|
||||
/// die Mail zweimal.</para>
|
||||
/// </summary>
|
||||
bool IsRepeatable => false;
|
||||
|
||||
Task<ToolResult> ExecuteAsync(
|
||||
JsonElement input,
|
||||
AgentToolContext context,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ClawdDotNet.Core.Tools;
|
||||
|
||||
/// <summary>Was die Nachschau über einen unterbrochenen Aufruf ergeben hat.</summary>
|
||||
public enum ProbeOutcome
|
||||
{
|
||||
/// <summary>
|
||||
/// Nicht feststellbar — und das ist eine vollwertige Antwort, kein Fehlschlag.
|
||||
///
|
||||
/// <para>Die meisten Werkzeuge landen hier, und zwar zu Recht: Ob ein SMTP-Server
|
||||
/// eine Nachricht angenommen hat, lässt sich im Nachhinein nicht feststellen, wenn
|
||||
/// die Gegenseite keine Kennung führt. Ein Werkzeug, das in so einem Fall
|
||||
/// „vermutlich nicht" meldete, wäre schlimmer als eines, das schweigt.</para>
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>Nachweislich nichts geschehen — der Aufruf kam nicht bis zur Wirkung.</summary>
|
||||
NotStarted,
|
||||
|
||||
/// <summary>Die Wirkung ist eingetreten, aber unvollständig.</summary>
|
||||
Partial,
|
||||
|
||||
/// <summary>Die Wirkung ist vollständig eingetreten. Wiederholen wäre überflüssig.</summary>
|
||||
Complete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein Befund der Nachschau. <paramref name="Evidence"/> sagt, <b>woran</b> es erkannt
|
||||
/// wurde — der Befund ist Beleg, nicht Urteil, und wer ihn liest, soll ihn beurteilen
|
||||
/// können statt ihm glauben zu müssen.
|
||||
/// </summary>
|
||||
public sealed record ProbeFinding(ProbeOutcome Outcome, string Evidence)
|
||||
{
|
||||
public static ProbeFinding Unknown(string grund) => new(ProbeOutcome.Unknown, grund);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional von einem Werkzeug zu bedienen (#555): Nach einem Absturz die Welt befragen,
|
||||
/// wie weit ein unterbrochener Aufruf gekommen ist.
|
||||
///
|
||||
/// <para>Das Protokoll sagt, dass ein Aufruf begann, nicht ob er wirkte. Manche Werkzeuge
|
||||
/// können diese Lücke selbst schließen, weil ihr Wirkungsort befragbar ist — ein
|
||||
/// Dateisystem ist es, ein fremder Mailserver nicht. Deshalb ist die Schnittstelle
|
||||
/// optional, und ihr Fehlen ist eine Aussage: Bei diesem Werkzeug bleibt es ungewiss.</para>
|
||||
///
|
||||
/// <para><b>Was hier nicht hingehört:</b> die Frage, ob ein Inhalt „mitten im Satz"
|
||||
/// endet, weil das Modell an seine Ausgabegrenze stieß. Die Datei ist dann fehlerfrei
|
||||
/// geschrieben — nur der Inhalt ist halb. Vom Wirkungsort aus lässt sich eine kurze
|
||||
/// Notiz nicht von einem abgeschnittenen Bericht unterscheiden. Diese Auskunft gibt es
|
||||
/// zuverlässig an der Quelle, über <c>finish_reason</c> (#548).</para>
|
||||
/// </summary>
|
||||
public interface IToolStateProbe
|
||||
{
|
||||
/// <summary>
|
||||
/// Sieht nach, was der Aufruf mit diesen Argumenten hinterlassen hat.
|
||||
///
|
||||
/// <para>Darf nichts verändern. Wirft nicht: Was sich nicht feststellen lässt, wird
|
||||
/// als <see cref="ProbeOutcome.Unknown"/> gemeldet, mit Begründung.</para>
|
||||
/// </summary>
|
||||
Task<ProbeFinding> ProbeAsync(JsonElement arguments, AgentToolContext context, CancellationToken ct);
|
||||
}
|
||||
Reference in New Issue
Block a user