@caupulican/pi-agent-core 0.93.7 → 0.93.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +179 -155
- package/dist/agent-loop.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/tool-failure-memory.d.ts +17 -5
- package/dist/tool-failure-memory.d.ts.map +1 -1
- package/dist/tool-failure-memory.js +128 -82
- package/dist/tool-failure-memory.js.map +1 -1
- package/dist/tool-failure-recovery-gate.d.ts +32 -42
- package/dist/tool-failure-recovery-gate.d.ts.map +1 -1
- package/dist/tool-failure-recovery-gate.js +161 -267
- package/dist/tool-failure-recovery-gate.js.map +1 -1
- package/dist/tool-failure-recovery-protocol.d.ts +2 -0
- package/dist/tool-failure-recovery-protocol.d.ts.map +1 -1
- package/dist/tool-failure-recovery-protocol.js +7 -6
- package/dist/tool-failure-recovery-protocol.js.map +1 -1
- package/dist/tool-protocol-residue.d.ts +9 -0
- package/dist/tool-protocol-residue.d.ts.map +1 -1
- package/dist/tool-protocol-residue.js +28 -1
- package/dist/tool-protocol-residue.js.map +1 -1
- package/dist/types.d.ts +50 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-failure-memory.d.ts","sourceRoot":"","sources":["../src/tool-failure-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,gBAAgB,EACrB,MAAM,wCAAwC,CAAC;AAChD,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-failure-memory.d.ts","sourceRoot":"","sources":["../src/tool-failure-memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getToolExecutionAttemptMemory, getToolExecutionErrorPolicy, } from "@caupulican/pi-ai/tool-repair-registry";
|
|
2
|
-
import { MANDATORY_TOOL_FAILURE_RECOVERY_PROTOCOL_PROMPT, mandatoryToolFailureRecoveryMetadata, } from "./tool-failure-recovery-protocol.js";
|
|
2
|
+
import { MANDATORY_TOOL_FAILURE_RECOVERY_PROTOCOL_PROMPT, mandatoryToolFailureRecoveryMetadata, TOOL_FAILURE_READMISSION_RULE, } from "./tool-failure-recovery-protocol.js";
|
|
3
3
|
import { sanitizeBinaryOutput } from "./utils/shell-output.js";
|
|
4
4
|
const TOOL_FAILURE_MEMORY_VERSION = 1;
|
|
5
5
|
const TOOL_FAILURE_DIRECTIVE_VERSION = 1;
|
|
@@ -9,15 +9,16 @@ const MAX_FAILURE_CODE_CHARS = 48;
|
|
|
9
9
|
const MAX_DIAGNOSTIC_CHARS = 240;
|
|
10
10
|
const MAX_CORRECTION_CHARS = 480;
|
|
11
11
|
const MAX_TOOL_FAILURE_EVIDENCE_CHARS = 1_600;
|
|
12
|
-
const MAX_ACTIVE_FAILURE_EVIDENCE_CHARS = 2_400;
|
|
13
12
|
const MAX_TOOL_NAME_CHARS = 64;
|
|
14
13
|
const TOOL_SIGNATURE_HEX_CHARS = 32;
|
|
14
|
+
const TOOL_FALLBACK_OUTPUT_SIGNATURE_BASE62_CHARS = 22;
|
|
15
|
+
const TOOL_OUTPUT_SIGNATURE_BASE64URL_CHARS = 43;
|
|
16
|
+
const LEGACY_TOOL_OUTPUT_SIGNATURE_HEX_CHARS = 64;
|
|
15
17
|
const MAX_ACTIVE_FAILURES = 8;
|
|
16
18
|
const MAX_TRACKED_FAILURES = 64;
|
|
17
19
|
const REPAIRABLE_REJECTION_CODES = new Set(["invalid_arguments", "malformed_call", "unknown_tool"]);
|
|
18
20
|
const LEGACY_GENERIC_EXECUTION_CORRECTION = "Change the arguments or approach before retrying; do not resend the unchanged operation.";
|
|
19
|
-
const
|
|
20
|
-
const CLOSED_OPERATION_CAVEMAN_CORRECTION = "Closed: this exact operation was not executed and will not run again this session — use a different operation or continue other work.";
|
|
21
|
+
const BLOCKED_REPLAY_CORRECTION = `Not executed: its last result is already above. Do corrective work or use a different operation. ${TOOL_FAILURE_READMISSION_RULE}`;
|
|
21
22
|
function truncate(value, maxChars) {
|
|
22
23
|
if (value.length <= maxChars)
|
|
23
24
|
return value;
|
|
@@ -165,10 +166,29 @@ function renderSignatureHash(hash) {
|
|
|
165
166
|
.map((part) => (part >>> 0).toString(16).padStart(8, "0"))
|
|
166
167
|
.join("");
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
+
const COMPACT_SIGNATURE_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
170
|
+
function renderCompactSignatureHash(hash) {
|
|
171
|
+
let remaining = (BigInt(hash.first >>> 0) << 96n) |
|
|
172
|
+
(BigInt(hash.second >>> 0) << 64n) |
|
|
173
|
+
(BigInt(hash.third >>> 0) << 32n) |
|
|
174
|
+
BigInt(hash.fourth >>> 0);
|
|
175
|
+
let output = "";
|
|
176
|
+
for (let index = 0; index < TOOL_FALLBACK_OUTPUT_SIGNATURE_BASE62_CHARS; index++) {
|
|
177
|
+
output = COMPACT_SIGNATURE_ALPHABET[Number(remaining % 62n)] + output;
|
|
178
|
+
remaining /= 62n;
|
|
179
|
+
}
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
function calculateStructuredHash(value, normalizeVolatile) {
|
|
169
183
|
const hash = createSignatureHash();
|
|
170
184
|
updateStructuredHash(hash, value, new Set(), 0, normalizeVolatile ? updateNormalizedHashString : updateExactHashString);
|
|
171
|
-
return
|
|
185
|
+
return hash;
|
|
186
|
+
}
|
|
187
|
+
function structuredHash(value, normalizeVolatile) {
|
|
188
|
+
return renderSignatureHash(calculateStructuredHash(value, normalizeVolatile));
|
|
189
|
+
}
|
|
190
|
+
function structuredOutputSignature(output) {
|
|
191
|
+
return renderCompactSignatureHash(calculateStructuredHash(output, false));
|
|
172
192
|
}
|
|
173
193
|
function boundedJsonPreview(value, maxChars) {
|
|
174
194
|
let output = "";
|
|
@@ -351,9 +371,6 @@ export function readVisibleToolFailureCode(result) {
|
|
|
351
371
|
}
|
|
352
372
|
return undefined;
|
|
353
373
|
}
|
|
354
|
-
export function isClosedOperationFailureCode(code) {
|
|
355
|
-
return code === "operation_recovery_exhausted" || code === "recovery_exhausted";
|
|
356
|
-
}
|
|
357
374
|
/** Failures that only a new owner prompt can clear. Do not restore their circuit across user turns. */
|
|
358
375
|
export function isPromptScopedFailureCode(code) {
|
|
359
376
|
return code === "owner_authorization_required";
|
|
@@ -368,6 +385,10 @@ export function restoreToolFailureRecord(result, tool, args) {
|
|
|
368
385
|
};
|
|
369
386
|
}
|
|
370
387
|
const identity = operationIdentity(tool, args);
|
|
388
|
+
// A completed operation that reported a negative status keeps its own raw output, so there is no
|
|
389
|
+
// harness record to read back. Recover its terminal status from that output rather than flattening
|
|
390
|
+
// every such result to a generic `tool_error`.
|
|
391
|
+
const visibleCode = readVisibleToolFailureCode(result);
|
|
371
392
|
return {
|
|
372
393
|
version: TOOL_FAILURE_MEMORY_VERSION,
|
|
373
394
|
failureKey: identity.failureKey,
|
|
@@ -377,7 +398,7 @@ export function restoreToolFailureRecord(result, tool, args) {
|
|
|
377
398
|
occurrence: 1,
|
|
378
399
|
state: "failed",
|
|
379
400
|
phase: "execution",
|
|
380
|
-
failureCode: boundedFailureCode(
|
|
401
|
+
failureCode: boundedFailureCode(visibleCode ?? classifyToolFailure(firstText(result))),
|
|
381
402
|
correction: fallbackFailureGuidance("failed", false, "execution"),
|
|
382
403
|
};
|
|
383
404
|
}
|
|
@@ -407,19 +428,6 @@ export function forEachPairedToolResult(messages, visit) {
|
|
|
407
428
|
}
|
|
408
429
|
}
|
|
409
430
|
}
|
|
410
|
-
export function transcriptHasClosedToolOperation(messages) {
|
|
411
|
-
const closedByExecutionKey = new Map();
|
|
412
|
-
forEachPairedToolResult(messages, ({ executionKey, result }) => {
|
|
413
|
-
if (!result.isError) {
|
|
414
|
-
closedByExecutionKey.delete(executionKey);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (isClosedOperationFailureCode(readVisibleToolFailureCode(result))) {
|
|
418
|
-
closedByExecutionKey.set(executionKey, true);
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
return closedByExecutionKey.size > 0;
|
|
422
|
-
}
|
|
423
431
|
function boundedFailureCode(value) {
|
|
424
432
|
const normalized = value
|
|
425
433
|
.trim()
|
|
@@ -619,6 +627,16 @@ export function assessToolFailure(message, state, errorClass) {
|
|
|
619
627
|
function isRecord(value) {
|
|
620
628
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
621
629
|
}
|
|
630
|
+
function isOutputSignature(value) {
|
|
631
|
+
if (typeof value !== "string")
|
|
632
|
+
return false;
|
|
633
|
+
if (value.length === TOOL_FALLBACK_OUTPUT_SIGNATURE_BASE62_CHARS)
|
|
634
|
+
return /^[0-9A-Za-z]+$/.test(value);
|
|
635
|
+
if (value.length === TOOL_OUTPUT_SIGNATURE_BASE64URL_CHARS)
|
|
636
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
637
|
+
return ((value.length === TOOL_SIGNATURE_HEX_CHARS || value.length === LEGACY_TOOL_OUTPUT_SIGNATURE_HEX_CHARS) &&
|
|
638
|
+
/^[0-9a-f]+$/.test(value));
|
|
639
|
+
}
|
|
622
640
|
function readFailureRecord(details) {
|
|
623
641
|
if (!isRecord(details) || !isRecord(details.piToolFailureMemory))
|
|
624
642
|
return undefined;
|
|
@@ -635,6 +653,7 @@ function readFailureRecord(details) {
|
|
|
635
653
|
return undefined;
|
|
636
654
|
}
|
|
637
655
|
const diagnostic = typeof candidate.diagnostic === "string" ? truncate(candidate.diagnostic, MAX_DIAGNOSTIC_CHARS) : undefined;
|
|
656
|
+
const outputSignature = isOutputSignature(candidate.outputSignature) ? candidate.outputSignature : undefined;
|
|
638
657
|
const note = typeof candidate.note === "string" ? truncate(candidate.note, MAX_DIAGNOSTIC_CHARS) : undefined;
|
|
639
658
|
const evidence = sanitizeToolFailureEvidence(candidate.evidence);
|
|
640
659
|
const retainedCorrection = typeof candidate.correction === "string" ? truncate(candidate.correction, MAX_CORRECTION_CHARS) : undefined;
|
|
@@ -659,6 +678,7 @@ function readFailureRecord(details) {
|
|
|
659
678
|
state: candidate.state,
|
|
660
679
|
phase,
|
|
661
680
|
failureCode: boundedFailureCode(candidate.failureCode),
|
|
681
|
+
outputSignature,
|
|
662
682
|
diagnostic,
|
|
663
683
|
note,
|
|
664
684
|
evidence,
|
|
@@ -728,8 +748,18 @@ function fastTextSignature(text) {
|
|
|
728
748
|
}
|
|
729
749
|
function analyzeToolFailureContext(messages) {
|
|
730
750
|
const callById = new Map();
|
|
751
|
+
/**
|
|
752
|
+
* A call the agent made is erased only in two cases: a superseded success, whose newer identical
|
|
753
|
+
* call is still present, and a discard-attempt directive, where the harness has taken ownership of
|
|
754
|
+
* the attempt and the original arguments must not come back — either because it holds the payload
|
|
755
|
+
* by reference (a retarget) or because replaying those bytes is itself the hazard (an encoding
|
|
756
|
+
* corruption). Never for an ordinary failure: being told to change an operation is unactionable
|
|
757
|
+
* once the operation itself is gone.
|
|
758
|
+
*/
|
|
731
759
|
const omittedCallIds = new Set();
|
|
732
|
-
const
|
|
760
|
+
const omittedResults = new Set();
|
|
761
|
+
/** Unbounded failure results to replace in place with their bounded record. */
|
|
762
|
+
const boundedReplacements = new Map();
|
|
733
763
|
const active = new Map();
|
|
734
764
|
const activeDirectives = new Map();
|
|
735
765
|
let sequence = 0;
|
|
@@ -754,8 +784,18 @@ function analyzeToolFailureContext(messages) {
|
|
|
754
784
|
const call = callById.get(message.toolCallId);
|
|
755
785
|
callById.delete(message.toolCallId);
|
|
756
786
|
const textPayload = firstText(message);
|
|
757
|
-
|
|
758
|
-
|
|
787
|
+
// A completed operation reporting its own negative status is evidence, not a mistake: it never
|
|
788
|
+
// enters the ledger and its output stays exactly as the tool wrote it. It does resolve any
|
|
789
|
+
// earlier failure recorded for the same operation, though — a tool that could not run before
|
|
790
|
+
// has now run — so a transcript that predates this classification cannot keep an obsolete
|
|
791
|
+
// record active across the boundary.
|
|
792
|
+
if (message.errorKind === "operation_outcome") {
|
|
793
|
+
if (call)
|
|
794
|
+
active.delete(getToolFailureKey(call.name, call.arguments));
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
const isHarnessFailure = message.isError === true || textPayload.startsWith("[harness] ");
|
|
798
|
+
if (isHarnessFailure) {
|
|
759
799
|
const toolName = call?.name ?? message.toolName;
|
|
760
800
|
const kindCount = (kindMistakesMap.get(toolName) ?? 0) + 1;
|
|
761
801
|
kindMistakesMap.set(toolName, kindCount);
|
|
@@ -763,10 +803,13 @@ function analyzeToolFailureContext(messages) {
|
|
|
763
803
|
if (directive) {
|
|
764
804
|
activeDirectives.delete(directive.failureCode);
|
|
765
805
|
activeDirectives.set(directive.failureCode, directive);
|
|
806
|
+
// The harness has taken ownership of this attempt: the directive replaces it, and the
|
|
807
|
+
// original arguments must not reappear — a retarget keeps the payload by reference, and a
|
|
808
|
+
// corrupt payload must never be replayed at all. Either way the call comes out.
|
|
766
809
|
if (call)
|
|
767
810
|
omittedCallIds.add(call.id);
|
|
768
811
|
else
|
|
769
|
-
|
|
812
|
+
omittedResults.add(message);
|
|
770
813
|
continue;
|
|
771
814
|
}
|
|
772
815
|
const retained = readFailureRecord(message.details);
|
|
@@ -805,6 +848,7 @@ function analyzeToolFailureContext(messages) {
|
|
|
805
848
|
state,
|
|
806
849
|
phase: retained?.phase ?? assessment?.phase ?? inferToolFailurePhase(state, "tool_error"),
|
|
807
850
|
failureCode: retained?.failureCode ?? assessment?.failureCode ?? "tool_error",
|
|
851
|
+
outputSignature: retained ? retained.outputSignature : structuredOutputSignature(textPayload),
|
|
808
852
|
diagnostic: retained?.diagnostic ?? assessment?.diagnostic,
|
|
809
853
|
evidence: retained?.evidence,
|
|
810
854
|
correction: retained?.correction ??
|
|
@@ -819,10 +863,12 @@ function analyzeToolFailureContext(messages) {
|
|
|
819
863
|
break;
|
|
820
864
|
active.delete(oldest);
|
|
821
865
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
866
|
+
// A failure result written before this harness bounded its own output — a legacy transcript,
|
|
867
|
+
// or a host that set isError directly — can carry unbounded raw text. Bounding it in place
|
|
868
|
+
// is the same treatment createToolFailureResult gives a fresh failure, applied late; the
|
|
869
|
+
// call that produced it still stands.
|
|
870
|
+
if (!retained)
|
|
871
|
+
boundedReplacements.set(message, record);
|
|
826
872
|
continue;
|
|
827
873
|
}
|
|
828
874
|
if (call) {
|
|
@@ -843,16 +889,26 @@ function analyzeToolFailureContext(messages) {
|
|
|
843
889
|
}
|
|
844
890
|
}
|
|
845
891
|
const kindMistakesSummary = Object.fromEntries(kindMistakesMap);
|
|
846
|
-
|
|
847
|
-
|
|
892
|
+
const activeRecords = [...active.values()]
|
|
893
|
+
.sort((left, right) => left.sequence - right.sequence)
|
|
894
|
+
.map(({ record }) => record);
|
|
895
|
+
if (omittedCallIds.size === 0 && omittedResults.size === 0 && boundedReplacements.size === 0) {
|
|
896
|
+
return { messages, activeRecords, activeDirectives: [...activeDirectives.values()], kindMistakesSummary };
|
|
848
897
|
}
|
|
849
898
|
const filteredMessages = [];
|
|
850
899
|
for (let index = 0; index < messages.length; index++) {
|
|
851
900
|
const message = messages[index];
|
|
852
901
|
if (message.role === "toolResult") {
|
|
853
|
-
if (omittedCallIds.has(message.toolCallId) ||
|
|
902
|
+
if (omittedCallIds.has(message.toolCallId) || omittedResults.has(message))
|
|
854
903
|
continue;
|
|
855
|
-
|
|
904
|
+
const bounded = boundedReplacements.get(message);
|
|
905
|
+
filteredMessages.push(bounded
|
|
906
|
+
? {
|
|
907
|
+
...message,
|
|
908
|
+
content: [{ type: "text", text: `[harness] ${formatRecordJson(bounded, false)}` }],
|
|
909
|
+
details: { piToolFailureMemory: bounded },
|
|
910
|
+
}
|
|
911
|
+
: message);
|
|
856
912
|
continue;
|
|
857
913
|
}
|
|
858
914
|
if (message.role !== "assistant") {
|
|
@@ -884,9 +940,6 @@ function analyzeToolFailureContext(messages) {
|
|
|
884
940
|
filteredMessages.push({ ...message, content: retainedContent });
|
|
885
941
|
}
|
|
886
942
|
}
|
|
887
|
-
const activeRecords = [...active.values()]
|
|
888
|
-
.sort((left, right) => left.sequence - right.sequence)
|
|
889
|
-
.map(({ record }) => record);
|
|
890
943
|
return {
|
|
891
944
|
messages: filteredMessages,
|
|
892
945
|
activeRecords,
|
|
@@ -897,7 +950,7 @@ function analyzeToolFailureContext(messages) {
|
|
|
897
950
|
export function createToolFailureMemoryTracker(messages) {
|
|
898
951
|
return new Map(analyzeToolFailureContext(messages).activeRecords.map((record) => [record.failureKey, record]));
|
|
899
952
|
}
|
|
900
|
-
export function rememberToolFailure(tracker, tool, args, state, failureCode, correction, diagnostic, phase = inferToolFailurePhase(state, failureCode), evidence) {
|
|
953
|
+
export function rememberToolFailure(tracker, tool, args, state, failureCode, correction, diagnostic, phase = inferToolFailurePhase(state, failureCode), evidence, outputIdentity) {
|
|
901
954
|
let kindCount = 1;
|
|
902
955
|
for (const previous of tracker.values()) {
|
|
903
956
|
if (previous.tool === tool) {
|
|
@@ -925,6 +978,9 @@ export function rememberToolFailure(tracker, tool, args, state, failureCode, cor
|
|
|
925
978
|
state,
|
|
926
979
|
phase,
|
|
927
980
|
failureCode: boundedFailureCode(failureCode),
|
|
981
|
+
outputSignature: isOutputSignature(outputIdentity?.outputSignature)
|
|
982
|
+
? outputIdentity.outputSignature
|
|
983
|
+
: structuredOutputSignature(outputIdentity?.output ?? ""),
|
|
928
984
|
diagnostic: diagnostic ? truncate(diagnostic, MAX_DIAGNOSTIC_CHARS) : undefined,
|
|
929
985
|
evidence: sanitizeToolFailureEvidence(evidence),
|
|
930
986
|
correction: truncate(correction, MAX_CORRECTION_CHARS),
|
|
@@ -944,6 +1000,29 @@ export function rememberToolFailure(tracker, tool, args, state, failureCode, cor
|
|
|
944
1000
|
}
|
|
945
1001
|
return record;
|
|
946
1002
|
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Describe a completed operation that reported a negative status, without recording it as a mistake.
|
|
1005
|
+
*
|
|
1006
|
+
* The tool did its job, so nothing enters failure memory, nothing rewrites the tool's output, and no
|
|
1007
|
+
* correction is owed. The record exists only so the repetition governor can name the operation if the
|
|
1008
|
+
* agent replays it while nothing has changed.
|
|
1009
|
+
*/
|
|
1010
|
+
export function describeOperationOutcome(tool, args, failureCode, diagnostic) {
|
|
1011
|
+
const identity = operationIdentity(tool, args);
|
|
1012
|
+
return {
|
|
1013
|
+
version: TOOL_FAILURE_MEMORY_VERSION,
|
|
1014
|
+
failureKey: identity.failureKey,
|
|
1015
|
+
[TOOL_FAILURE_EXECUTION_KEY]: identity.executionKey,
|
|
1016
|
+
tool: identity.tool,
|
|
1017
|
+
operation: identity.operation,
|
|
1018
|
+
occurrence: 1,
|
|
1019
|
+
state: "failed",
|
|
1020
|
+
phase: "execution",
|
|
1021
|
+
failureCode: boundedFailureCode(failureCode),
|
|
1022
|
+
...(diagnostic ? { diagnostic: truncate(diagnostic, MAX_DIAGNOSTIC_CHARS) } : {}),
|
|
1023
|
+
correction: BLOCKED_REPLAY_CORRECTION,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
947
1026
|
export function clearToolFailure(tracker, tool, args) {
|
|
948
1027
|
const failureKey = getToolFailureKey(tool, args);
|
|
949
1028
|
const record = tracker.get(failureKey);
|
|
@@ -1002,14 +1081,19 @@ export function createToolFailureResult(record, terminate) {
|
|
|
1002
1081
|
}
|
|
1003
1082
|
export function createRepeatedToolFailureResult(record) {
|
|
1004
1083
|
const retainedRecord = retainBlockedToolFailure(record);
|
|
1005
|
-
|
|
1084
|
+
// The note states why this call did not run and what makes it runnable again; the retained
|
|
1085
|
+
// root-cause correction stays the next_action, because that is the actionable half. Evidence is
|
|
1086
|
+
// left out: nothing executed, so there is none — the prior run's evidence is still in the
|
|
1087
|
+
// transcript, and repeating it here would only pay for the same bytes twice.
|
|
1088
|
+
const replayNotice = truncateMiddle(`Not executed: unchanged. ${TOOL_FAILURE_READMISSION_RULE}`, MAX_DIAGNOSTIC_CHARS);
|
|
1006
1089
|
const blockedResult = createToolFailureResult({
|
|
1007
1090
|
...retainedRecord,
|
|
1091
|
+
evidence: undefined,
|
|
1008
1092
|
state: "rejected",
|
|
1009
1093
|
failureCode: "repeated_failed_operation",
|
|
1010
1094
|
...(retainedRecord.diagnostic
|
|
1011
1095
|
? { note: replayNotice }
|
|
1012
|
-
: { diagnostic: replayNotice, correction:
|
|
1096
|
+
: { diagnostic: replayNotice, correction: BLOCKED_REPLAY_CORRECTION }),
|
|
1013
1097
|
});
|
|
1014
1098
|
return {
|
|
1015
1099
|
...blockedResult,
|
|
@@ -1019,36 +1103,6 @@ export function createRepeatedToolFailureResult(record) {
|
|
|
1019
1103
|
details: { piToolFailureMemory: retainedRecord },
|
|
1020
1104
|
};
|
|
1021
1105
|
}
|
|
1022
|
-
export function createToolFailureRecoveryExhaustedResult(record, note) {
|
|
1023
|
-
const retainedRecord = retainBlockedToolFailure(record);
|
|
1024
|
-
const exhaustedResult = createToolFailureResult({
|
|
1025
|
-
...retainedRecord,
|
|
1026
|
-
state: "rejected",
|
|
1027
|
-
failureCode: "recovery_exhausted",
|
|
1028
|
-
note: truncateMiddle(note, MAX_DIAGNOSTIC_CHARS),
|
|
1029
|
-
correction: "Stop retrying tools in this run. Report the unresolved failure and the user or environment action required to continue.",
|
|
1030
|
-
}, true);
|
|
1031
|
-
return {
|
|
1032
|
-
...exhaustedResult,
|
|
1033
|
-
details: { piToolFailureMemory: retainedRecord },
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
export function createToolFailureOperationExhaustedResult(record, note) {
|
|
1037
|
-
const retainedRecord = {
|
|
1038
|
-
...retainBlockedToolFailure(record),
|
|
1039
|
-
correction: CLOSED_OPERATION_CAVEMAN_CORRECTION,
|
|
1040
|
-
};
|
|
1041
|
-
const exhaustedResult = createToolFailureResult({
|
|
1042
|
-
...retainedRecord,
|
|
1043
|
-
state: "rejected",
|
|
1044
|
-
failureCode: "operation_recovery_exhausted",
|
|
1045
|
-
note: truncateMiddle(note, MAX_DIAGNOSTIC_CHARS),
|
|
1046
|
-
}, false);
|
|
1047
|
-
return {
|
|
1048
|
-
...exhaustedResult,
|
|
1049
|
-
details: { piToolFailureMemory: retainedRecord },
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
1106
|
function retainBlockedToolFailure(record) {
|
|
1053
1107
|
return {
|
|
1054
1108
|
...record,
|
|
@@ -1069,17 +1123,9 @@ export function sanitizeToolFailureContext(messages, systemPrompt) {
|
|
|
1069
1123
|
const kindSummary = Object.entries(analysis.kindMistakesSummary)
|
|
1070
1124
|
.map(([kind, count]) => `${kind}:${count}`)
|
|
1071
1125
|
.join(", ");
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
const record = records[index];
|
|
1076
|
-
if (!record.evidence)
|
|
1077
|
-
continue;
|
|
1078
|
-
const retained = truncate(record.evidence, remainingEvidenceChars);
|
|
1079
|
-
evidenceByFailureKey.set(record.failureKey, retained);
|
|
1080
|
-
remainingEvidenceChars -= retained.length;
|
|
1081
|
-
}
|
|
1082
|
-
const lines = records.map((record) => escapePromptData(formatRecordJson(record, true, evidenceByFailureKey.get(record.failureKey) ?? null)));
|
|
1126
|
+
// Evidence is not repeated here: the failed call and its bounded record both remain in the
|
|
1127
|
+
// transcript, so the ledger only has to name what is still unresolved and what to do about it.
|
|
1128
|
+
const lines = records.map((record) => escapePromptData(formatRecordJson(record, true, null)));
|
|
1083
1129
|
for (const directive of analysis.activeDirectives) {
|
|
1084
1130
|
lines.push(escapePromptData(JSON.stringify({
|
|
1085
1131
|
...mandatoryToolFailureRecoveryMetadata(),
|