@claudexor/orchestrator 4.1.1 → 4.2.0
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/account-pool.d.ts +11 -0
- package/dist/account-pool.d.ts.map +1 -1
- package/dist/account-pool.js +9 -0
- package/dist/account-pool.js.map +1 -1
- package/dist/account-resolution.d.ts +3 -0
- package/dist/account-resolution.d.ts.map +1 -1
- package/dist/account-resolution.js +1 -0
- package/dist/account-resolution.js.map +1 -1
- package/dist/attemptTelemetry.d.ts +4 -6
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +1 -1
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/budgetFailure.d.ts +4 -1
- package/dist/budgetFailure.d.ts.map +1 -1
- package/dist/budgetFailure.js +4 -2
- package/dist/budgetFailure.js.map +1 -1
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateEvidence.js +7 -2
- package/dist/candidateEvidence.js.map +1 -1
- package/dist/candidateOutputs.d.ts.map +1 -1
- package/dist/candidateOutputs.js +6 -0
- package/dist/candidateOutputs.js.map +1 -1
- package/dist/candidateTerminals.d.ts +12 -1
- package/dist/candidateTerminals.d.ts.map +1 -1
- package/dist/candidateTerminals.js +51 -21
- package/dist/candidateTerminals.js.map +1 -1
- package/dist/captureRefusal.d.ts +28 -0
- package/dist/captureRefusal.d.ts.map +1 -0
- package/dist/captureRefusal.js +54 -0
- package/dist/captureRefusal.js.map +1 -0
- package/dist/continuity-terminal.d.ts +3 -1
- package/dist/continuity-terminal.d.ts.map +1 -1
- package/dist/continuity-terminal.js.map +1 -1
- package/dist/credential-profile-rotation.js +2 -3
- package/dist/credential-profile-rotation.js.map +1 -1
- package/dist/delegationFailure.d.ts +104 -7
- package/dist/delegationFailure.d.ts.map +1 -1
- package/dist/delegationFailure.js +47 -31
- package/dist/delegationFailure.js.map +1 -1
- package/dist/inrun-continuity-types.d.ts +13 -1
- package/dist/inrun-continuity-types.d.ts.map +1 -1
- package/dist/inrun-continuity.d.ts +9 -2
- package/dist/inrun-continuity.d.ts.map +1 -1
- package/dist/inrun-continuity.js +43 -23
- package/dist/inrun-continuity.js.map +1 -1
- package/dist/inrun-continuity.test-support.d.ts +6 -2
- package/dist/inrun-continuity.test-support.d.ts.map +1 -1
- package/dist/inrun-continuity.test-support.js +4 -1
- package/dist/inrun-continuity.test-support.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts +5 -4
- package/dist/orchestrator-credentials.d.ts.map +1 -1
- package/dist/orchestrator-credentials.js +7 -4
- package/dist/orchestrator-credentials.js.map +1 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +277 -335
- package/dist/orchestrator.js.map +1 -1
- package/dist/persistedPatch.js +2 -2
- package/dist/persistedPatch.js.map +1 -1
- package/dist/pool-attempt-evidence.d.ts +62 -0
- package/dist/pool-attempt-evidence.d.ts.map +1 -0
- package/dist/pool-attempt-evidence.js +153 -0
- package/dist/pool-attempt-evidence.js.map +1 -0
- package/dist/pre-progress-refusal.d.ts +43 -6
- package/dist/pre-progress-refusal.d.ts.map +1 -1
- package/dist/pre-progress-refusal.js +53 -8
- package/dist/pre-progress-refusal.js.map +1 -1
- package/dist/readonlyTerminals.d.ts +55 -0
- package/dist/readonlyTerminals.d.ts.map +1 -0
- package/dist/readonlyTerminals.js +205 -0
- package/dist/readonlyTerminals.js.map +1 -0
- package/dist/rotation-predicate.d.ts +7 -23
- package/dist/rotation-predicate.d.ts.map +1 -1
- package/dist/rotation-predicate.js +45 -0
- package/dist/rotation-predicate.js.map +1 -1
- package/dist/runEventLog.d.ts +1 -0
- package/dist/runEventLog.d.ts.map +1 -1
- package/dist/runEventLog.js +3 -2
- package/dist/runEventLog.js.map +1 -1
- package/dist/runTerminalContext.d.ts +4 -0
- package/dist/runTerminalContext.d.ts.map +1 -1
- package/dist/runTerminalContext.js.map +1 -1
- package/dist/runTerminalHelpers.d.ts +25 -1
- package/dist/runTerminalHelpers.d.ts.map +1 -1
- package/dist/runTerminalHelpers.js +69 -6
- package/dist/runTerminalHelpers.js.map +1 -1
- package/dist/runTerminalResults.d.ts +25 -3
- package/dist/runTerminalResults.d.ts.map +1 -1
- package/dist/runTerminalResults.js +61 -29
- package/dist/runTerminalResults.js.map +1 -1
- package/dist/runTerminals.d.ts +0 -1
- package/dist/runTerminals.d.ts.map +1 -1
- package/dist/runTerminals.js +47 -11
- package/dist/runTerminals.js.map +1 -1
- package/dist/secretDiff.d.ts +14 -11
- package/dist/secretDiff.d.ts.map +1 -1
- package/dist/secretDiff.js +21 -28
- package/dist/secretDiff.js.map +1 -1
- package/dist/terminalOutcome.d.ts +24 -4
- package/dist/terminalOutcome.d.ts.map +1 -1
- package/dist/terminalOutcome.js +32 -5
- package/dist/terminalOutcome.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -4,11 +4,13 @@ import { openRunScopeCustody, scopedRouteContext, scopedSpecEnv, } from "./runSc
|
|
|
4
4
|
import { delegationBeltFor } from "./delegationBelt.js";
|
|
5
5
|
import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
6
6
|
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, failAfterCandidates, } from "./candidateTerminals.js";
|
|
7
|
+
import { deepScanFailureResult, readOnlyChainFailureResult, } from "./readonlyTerminals.js";
|
|
7
8
|
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, } from "./credential-profiles.js";
|
|
8
9
|
import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver, } from "./orchestrator-credentials.js";
|
|
9
10
|
import { continuityWorkspace, resumableOf, resumableTerminal, } from "./continuity-terminal.js";
|
|
10
11
|
import { openContinuity } from "./continue-from.js";
|
|
11
12
|
import { accountPoolRows } from "./account-pool.js";
|
|
13
|
+
import { PoolAttemptEvidence, terminalPoolAttempts, withPoolAttempts, } from "./pool-attempt-evidence.js";
|
|
12
14
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
13
15
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
14
16
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
@@ -16,7 +18,7 @@ import { captureDirectoryCandidate, directoryHasOutput, directoryCandidateStable
|
|
|
16
18
|
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, selectLaneHome, followSpecHome, } from "./delegatedHome.js";
|
|
17
19
|
import * as AC from "./attemptUsageCost.js";
|
|
18
20
|
import * as custody from "./continuation-custody.js";
|
|
19
|
-
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence,
|
|
21
|
+
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
|
|
20
22
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
21
23
|
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
22
24
|
import { join } from "node:path";
|
|
@@ -39,7 +41,7 @@ import { resolveRunInputDefaults } from "./run-input-resolution.js";
|
|
|
39
41
|
import { beginAnnouncedRun } from "./runEventLog.js";
|
|
40
42
|
import { arbitrationBudgetOptions } from "./decisionBudget.js";
|
|
41
43
|
import { buildRevisePrompt } from "./revisePrompt.js";
|
|
42
|
-
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure,
|
|
44
|
+
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure, cancelReasonFromSignalToken, } from "./runTerminals.js";
|
|
43
45
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
44
46
|
import { finalizeStructuredOutput } from "./structuredOutput.js";
|
|
45
47
|
import { admitRun } from "./runAdmission.js";
|
|
@@ -56,8 +58,7 @@ import { createAttemptTelemetry, observeAttemptTelemetry, setAttemptOutcome, tel
|
|
|
56
58
|
import { newAttemptOutputMarkers } from "./attemptOutputMarkers.js";
|
|
57
59
|
import * as delegateFailure from "./delegationFailure.js";
|
|
58
60
|
import * as secretDiff from "./secretDiff.js";
|
|
59
|
-
import {
|
|
60
|
-
import { finalizeAttempt, readOnlyNoSuccessTerminal, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
61
|
+
import { finalizeAttempt, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
61
62
|
import { resolveWorkReportEnvelope, unwrapWorkReportEnvelope, } from "./workReportEnvelope.js";
|
|
62
63
|
import { buildContinuationPacket, decideContinuation, synthesizeContinuationRequest, } from "./continuation.js";
|
|
63
64
|
import { interactionChannelFor } from "./interaction.js";
|
|
@@ -1312,7 +1313,10 @@ export class Orchestrator {
|
|
|
1312
1313
|
* must record what it ran under, not just why it stopped. REQUIRED (no
|
|
1313
1314
|
* default): a silent fallback here would spawn a delegated attempt on the
|
|
1314
1315
|
* operator's real home while the record still claimed scoped state. */
|
|
1315
|
-
harnessHome, observedPaths = new Set()
|
|
1316
|
+
harnessHome, observedPaths = new Set(),
|
|
1317
|
+
/** The caller's, like `harnessHome`: its catch keeps the tries' accounts.
|
|
1318
|
+
* REQUIRED, so no caller can drop them by omission again. */
|
|
1319
|
+
poolEvidence) {
|
|
1316
1320
|
const adapter = routed.adapter;
|
|
1317
1321
|
const knobs = this.routeSpecKnobs(routed, contract, modelHint, effortHint);
|
|
1318
1322
|
// Isolated scoped-home sessions are never retained after disposal.
|
|
@@ -1418,6 +1422,7 @@ export class Orchestrator {
|
|
|
1418
1422
|
let beltFailureDisclosed = false;
|
|
1419
1423
|
const errors = [];
|
|
1420
1424
|
let answer = new secretDiff.CountedAnswerAssembly();
|
|
1425
|
+
let currentCapture;
|
|
1421
1426
|
const retryPolicy = transientRetryPolicy(this.config(contract.repo.root));
|
|
1422
1427
|
// QA-024: the delegation belt is the ONLY engine-owned extra MCP server
|
|
1423
1428
|
// injected into an agent lane (the browser MCP rides its own field), so its
|
|
@@ -1444,9 +1449,11 @@ export class Orchestrator {
|
|
|
1444
1449
|
harnessHome,
|
|
1445
1450
|
workOrder: prompt,
|
|
1446
1451
|
firstPrompt: spec.prompt,
|
|
1452
|
+
poolEvidence,
|
|
1447
1453
|
}), { spec, continuation: runInput?.continuation, store, telemetry }));
|
|
1448
1454
|
try {
|
|
1449
1455
|
for (let nativeTry = 0; !signal?.aborted; nativeTry += 1) {
|
|
1456
|
+
currentCapture = undefined;
|
|
1450
1457
|
// A3 per-try isolation: neither output nor progress markers leak across tries.
|
|
1451
1458
|
if (nativeTry > 0) {
|
|
1452
1459
|
answer = new secretDiff.CountedAnswerAssembly();
|
|
@@ -1462,9 +1469,8 @@ export class Orchestrator {
|
|
|
1462
1469
|
? AbortSignal.any([signal, attemptAbort.signal])
|
|
1463
1470
|
: attemptAbort.signal;
|
|
1464
1471
|
activeSessionId = runSpec.session_id;
|
|
1465
|
-
continuity.beginTry(runSpec, nativeTry, attemptAbort);
|
|
1466
1472
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
1467
|
-
const refusal =
|
|
1473
|
+
const refusal = continuity.beginTry(runSpec, nativeTry, attemptAbort);
|
|
1468
1474
|
const transientStart = telemetry.transientFailures.length;
|
|
1469
1475
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
1470
1476
|
let rawPatch = null;
|
|
@@ -1588,30 +1594,42 @@ export class Orchestrator {
|
|
|
1588
1594
|
// #31: the centralized retry gate reads the classified `retryable` verdict.
|
|
1589
1595
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
1590
1596
|
const sawTypedLimit = telemetry.rateLimits.length > rateLimitStart;
|
|
1597
|
+
const tryFacts = {
|
|
1598
|
+
harnessErrored,
|
|
1599
|
+
originalError: errors[0],
|
|
1600
|
+
budgetStopped: streamBudgetDenied,
|
|
1601
|
+
aborted: signal?.aborted === true,
|
|
1602
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1603
|
+
newTransients,
|
|
1604
|
+
markers: telemetry.outputMarkers,
|
|
1605
|
+
};
|
|
1606
|
+
// Its account row first: an infrastructure failure below never erases it.
|
|
1607
|
+
continuity.settleTry(tryFacts);
|
|
1591
1608
|
if (directory)
|
|
1592
1609
|
await captureDirectory();
|
|
1593
|
-
|
|
1610
|
+
currentCapture = directory
|
|
1611
|
+
? undefined
|
|
1612
|
+
: await secretDiff.captureCandidateDiff(wsm, envelope);
|
|
1613
|
+
const currentDiff = secretDiff.capturedDiff(currentCapture);
|
|
1594
1614
|
const workspaceUnchanged = directory
|
|
1595
1615
|
? directoryCapture.files?.noChanges === true
|
|
1596
|
-
: currentDiff
|
|
1616
|
+
: currentDiff?.trim().length === 0;
|
|
1597
1617
|
const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
|
|
1598
1618
|
const delivered = !harnessErrored && !deliverableEmpty && !signal?.aborted;
|
|
1599
1619
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
1600
1620
|
const verdict = await continuity.afterTry({
|
|
1621
|
+
...tryFacts,
|
|
1622
|
+
// A cancel or deadline that landed during capture still stops the
|
|
1623
|
+
// attempt; the try's own facts above stay its evidence.
|
|
1624
|
+
stopped: signal?.aborted === true,
|
|
1601
1625
|
runSpec,
|
|
1602
1626
|
nativeTry,
|
|
1603
|
-
harnessErrored,
|
|
1604
|
-
budgetStopped: streamBudgetDenied,
|
|
1605
|
-
aborted: signal?.aborted === true,
|
|
1606
|
-
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1607
|
-
newTransients,
|
|
1608
1627
|
sawTypedLimit,
|
|
1609
1628
|
sawRetryable,
|
|
1610
1629
|
answer,
|
|
1611
1630
|
rawDeliverableEmpty: deliverableEmpty,
|
|
1612
1631
|
workspaceDiffNonEmpty: !workspaceUnchanged,
|
|
1613
|
-
currentDiff,
|
|
1614
|
-
markers: telemetry.outputMarkers,
|
|
1632
|
+
...(currentDiff === undefined ? {} : { currentDiff }),
|
|
1615
1633
|
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
1616
1634
|
refusal,
|
|
1617
1635
|
telemetry,
|
|
@@ -1635,8 +1653,13 @@ export class Orchestrator {
|
|
|
1635
1653
|
signal?.removeEventListener("abort", onAbort);
|
|
1636
1654
|
}
|
|
1637
1655
|
// A pool-exhausted terminal is rotation's verdict, not the transient
|
|
1638
|
-
// machinery's
|
|
1639
|
-
|
|
1656
|
+
// machinery's, and a stopped run exhausted no retries: no
|
|
1657
|
+
// `route.transient.exhausted` rides along with either.
|
|
1658
|
+
if (harnessErrored &&
|
|
1659
|
+
!poolExhausted &&
|
|
1660
|
+
!processingRefusal &&
|
|
1661
|
+
!telemetry.requestRefusal &&
|
|
1662
|
+
!signal?.aborted) {
|
|
1640
1663
|
emitTransientExhausted((t, p) => log?.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
1641
1664
|
}
|
|
1642
1665
|
const attemptStreamEndedMs = Date.now();
|
|
@@ -1656,9 +1679,10 @@ export class Orchestrator {
|
|
|
1656
1679
|
: await secretDiff.captureCandidateWorkspace({
|
|
1657
1680
|
...{ wsm, envelope, inPlace: inPlaceEnvelope, projectRoot: store.repoRoot },
|
|
1658
1681
|
...{ answerText, answerMatches: answer.secretLikeMatches() },
|
|
1682
|
+
holder: { runId: paths.runId, runDir: paths.root },
|
|
1683
|
+
incompleteCapture: currentCapture?.captureIncomplete ? currentCapture : undefined,
|
|
1659
1684
|
});
|
|
1660
1685
|
const { diff, captureRefusal } = captured;
|
|
1661
|
-
harnessErrored = secretDiff.recordCaptureRefusal(captureRefusal, errors, harnessErrored);
|
|
1662
1686
|
const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText) || directoryHasOutput(directoryCapture.files);
|
|
1663
1687
|
// Cancelled attempts skip gates: running a 600s-per-gate suite delays the ack
|
|
1664
1688
|
// and burns compute on a result nobody will adopt. Diff/attempt.yaml
|
|
@@ -1717,7 +1741,9 @@ export class Orchestrator {
|
|
|
1717
1741
|
if (finalized.outcomeClass === "contract_failure" && unwrapped.contractViolation)
|
|
1718
1742
|
errors.push(`work_report contract: ${unwrapped.contractViolation}`);
|
|
1719
1743
|
const deliverablePresent = finalized.deliverablePresent;
|
|
1720
|
-
|
|
1744
|
+
if (captureRefusal)
|
|
1745
|
+
errors.push(captureRefusal.detail);
|
|
1746
|
+
const errored = harnessErrored || webBlocked || captureRefusal !== undefined;
|
|
1721
1747
|
const resumable = await continuity.finish({
|
|
1722
1748
|
runSpec: spec,
|
|
1723
1749
|
errored,
|
|
@@ -1727,6 +1753,7 @@ export class Orchestrator {
|
|
|
1727
1753
|
workState: finalized.workState,
|
|
1728
1754
|
});
|
|
1729
1755
|
setAttemptOutcome(telemetry, {
|
|
1756
|
+
captureFailed: captureRefusal !== undefined,
|
|
1730
1757
|
deliverablePresent,
|
|
1731
1758
|
gatesPassed: gates.length > 0 ? gatesPassed(gates) : null,
|
|
1732
1759
|
harnessErrored,
|
|
@@ -1840,9 +1867,11 @@ export class Orchestrator {
|
|
|
1840
1867
|
probeReadyProfiles: (spec, tried) => runInput
|
|
1841
1868
|
? this.credentials.readyProfileIdsForRotation(runInput, adapter.id, spec.credential_profile ?? null, tried, spec.model_hint ?? null)
|
|
1842
1869
|
: Promise.resolve(new Set()),
|
|
1870
|
+
bindRefusal: (spec, producer) => this.credentials.bindPreProgressRefusal(adapter.id, spec, producer),
|
|
1843
1871
|
rotationObservations: (spec, transients, refusal) => this.credentials.rotationObservations(adapter, spec, transients, refusal),
|
|
1844
1872
|
emit: (type, payload) => log?.emit(type, payload),
|
|
1845
1873
|
route: routed,
|
|
1874
|
+
poolEvidence: lane.poolEvidence,
|
|
1846
1875
|
newSessionId: () => newId("ses"),
|
|
1847
1876
|
thread: runInput?.threadId
|
|
1848
1877
|
? {
|
|
@@ -1864,7 +1893,7 @@ export class Orchestrator {
|
|
|
1864
1893
|
const mutatingRun = isMutatingAccess(contract.access.effective_profile);
|
|
1865
1894
|
const planBrief = verifiedPlanBrief(input);
|
|
1866
1895
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
1867
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
1896
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
1868
1897
|
input,
|
|
1869
1898
|
contract,
|
|
1870
1899
|
quotaSnapshots,
|
|
@@ -2020,6 +2049,7 @@ export class Orchestrator {
|
|
|
2020
2049
|
});
|
|
2021
2050
|
}
|
|
2022
2051
|
const runsBySlot = new Array(slots.length);
|
|
2052
|
+
runContext.attempts = () => runsBySlot.filter((run) => run !== undefined);
|
|
2023
2053
|
// D-16d: one-shot continuation budget shared across the concurrent candidate
|
|
2024
2054
|
// slots (parity with the read-only chain's single counter). Claimed
|
|
2025
2055
|
// synchronously (check-then-increment with no await between), so two slots
|
|
@@ -2072,8 +2102,9 @@ export class Orchestrator {
|
|
|
2072
2102
|
const effectiveWeb = this.discloseWebUpgrade(log, slot.routed, knobs.webPolicy, slot.attemptId);
|
|
2073
2103
|
let envelope;
|
|
2074
2104
|
// Declared OUTSIDE the try so the catch below can state what the attempt
|
|
2075
|
-
// actually ran under, not merely that it stopped.
|
|
2105
|
+
// actually ran under, not merely that it stopped — and on which accounts.
|
|
2076
2106
|
let harnessHome;
|
|
2107
|
+
const tried = new PoolAttemptEvidence(adapter.id, slot.attemptId);
|
|
2077
2108
|
try {
|
|
2078
2109
|
log.emit("harness.started", {
|
|
2079
2110
|
harness_id: adapter.id,
|
|
@@ -2110,7 +2141,7 @@ export class Orchestrator {
|
|
|
2110
2141
|
budgetStopped = true;
|
|
2111
2142
|
return true;
|
|
2112
2143
|
}, input, requestedSingleCandidate, // W-C4 deltas: single-candidate chat lane only (racing = noise x N)
|
|
2113
|
-
undefined, undefined, harnessHome);
|
|
2144
|
+
undefined, undefined, harnessHome, undefined, tried);
|
|
2114
2145
|
ledger.settle(slot.lease.lease_id, attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2115
2146
|
log.emit("harness.completed", {
|
|
2116
2147
|
harness_id: adapter.id,
|
|
@@ -2175,6 +2206,7 @@ export class Orchestrator {
|
|
|
2175
2206
|
continuation_of: run.attemptId,
|
|
2176
2207
|
});
|
|
2177
2208
|
const contLeaseId = contLease.lease?.lease_id ?? "";
|
|
2209
|
+
const contTried = new PoolAttemptEvidence(adapter.id, contAttemptId);
|
|
2178
2210
|
try {
|
|
2179
2211
|
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2180
2212
|
lease: contLease.lease,
|
|
@@ -2188,7 +2220,7 @@ export class Orchestrator {
|
|
|
2188
2220
|
return false;
|
|
2189
2221
|
budgetStopped = true;
|
|
2190
2222
|
return true;
|
|
2191
|
-
}, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined, harnessHome);
|
|
2223
|
+
}, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined, harnessHome, undefined, contTried);
|
|
2192
2224
|
ledger.settle(contLeaseId, attemptUsageCostSettlement(contRun.cost, contRun.costEstimated, contRun.attemptId, contRun.harnessId, contRun.telemetry.authMode, contRun.telemetry.usageCost));
|
|
2193
2225
|
log.emit("harness.completed", {
|
|
2194
2226
|
harness_id: adapter.id,
|
|
@@ -2215,6 +2247,8 @@ export class Orchestrator {
|
|
|
2215
2247
|
error: safeErrorMessage(err),
|
|
2216
2248
|
});
|
|
2217
2249
|
}
|
|
2250
|
+
// Whichever attempt speaks names both attempts' failed tries, in order.
|
|
2251
|
+
effectiveRun.poolAttempts = [...(run.poolAttempts ?? []), ...contTried.snapshot()];
|
|
2218
2252
|
}
|
|
2219
2253
|
else {
|
|
2220
2254
|
ledger.cancel(contLease.lease?.lease_id ?? "");
|
|
@@ -2260,6 +2294,7 @@ export class Orchestrator {
|
|
|
2260
2294
|
slot.routed.delegationRequirement,
|
|
2261
2295
|
], knobs.model),
|
|
2262
2296
|
infraPhase,
|
|
2297
|
+
poolAttempts: tried.snapshot(),
|
|
2263
2298
|
// Keep a TYPED pre-spawn refusal (spent quota window + its reset)
|
|
2264
2299
|
// alive past this catch; `message` alone would force the terminal to
|
|
2265
2300
|
// read prose back out.
|
|
@@ -2274,10 +2309,17 @@ export class Orchestrator {
|
|
|
2274
2309
|
};
|
|
2275
2310
|
await runParallelCandidates(slots, this.deps.runtimeConcurrencyCaps, runSlot);
|
|
2276
2311
|
const runs = runsBySlot.filter((r) => r !== undefined);
|
|
2312
|
+
// From here `runs` is the lane's one attempt collection (a synthesis that
|
|
2313
|
+
// returns joins it); a synthesis that threw first leaves only its tries.
|
|
2314
|
+
let thrownSynthesis = null;
|
|
2315
|
+
const history = () => thrownSynthesis ? [...runs, thrownSynthesis] : runs;
|
|
2316
|
+
runContext.attempts = history;
|
|
2277
2317
|
// Fail-closed terminal: a delegated mutating run whose attempts state
|
|
2278
2318
|
// neither historical proof nor deliberate absence refuses instead of passing.
|
|
2279
2319
|
assertDelegatedEvidence(input.delegated === true, candidateAccess, runs);
|
|
2280
|
-
|
|
2320
|
+
// One candidate-terminal context for every race terminal below; its
|
|
2321
|
+
// telemetry writer reads the live roster, so a later synth attempt counts.
|
|
2322
|
+
const raceTerminal = {
|
|
2281
2323
|
store,
|
|
2282
2324
|
paths,
|
|
2283
2325
|
log,
|
|
@@ -2286,6 +2328,11 @@ export class Orchestrator {
|
|
|
2286
2328
|
mode,
|
|
2287
2329
|
ledger,
|
|
2288
2330
|
runs,
|
|
2331
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2332
|
+
};
|
|
2333
|
+
const cancelledRaceResult = () => cancelledCandidatesResult({
|
|
2334
|
+
...raceTerminal,
|
|
2335
|
+
attempts: history(),
|
|
2289
2336
|
git: input.workspaceKind !== "directory"
|
|
2290
2337
|
? {
|
|
2291
2338
|
live: input.inPlace === true &&
|
|
@@ -2297,7 +2344,6 @@ export class Orchestrator {
|
|
|
2297
2344
|
}
|
|
2298
2345
|
: undefined,
|
|
2299
2346
|
signal: input.signal,
|
|
2300
|
-
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2301
2347
|
});
|
|
2302
2348
|
// Revert divergence fence for the single-candidate in-place path: the
|
|
2303
2349
|
// candidate mutated the LIVE tree during execution above, so the post-turn
|
|
@@ -2324,31 +2370,34 @@ export class Orchestrator {
|
|
|
2324
2370
|
}
|
|
2325
2371
|
const failedDelegation = delegateFailure.dominantRaceCandidateFailure(runs);
|
|
2326
2372
|
if (failedDelegation) {
|
|
2327
|
-
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2328
2373
|
await disposeReviewEnvelopes();
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2374
|
+
const failedOutput = {
|
|
2375
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run: failedDelegation },
|
|
2376
|
+
live: input.inPlace === true &&
|
|
2377
|
+
input.workspaceKind !== "directory" &&
|
|
2378
|
+
failedDelegation.reviewCwd === execRoot,
|
|
2379
|
+
postTurnSha: earlyPostTurnSha,
|
|
2380
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
2381
|
+
};
|
|
2382
|
+
// A live candidate whose capture failed (no Delegate failure) ends in the
|
|
2383
|
+
// shared no-working-candidate terminal, as in the isolated race and
|
|
2384
|
+
// convergence: a recorded budget denial, a budget terminal the ledger
|
|
2385
|
+
// settled (stream hard cap, exact overshoot, unverifiable cash) or a
|
|
2386
|
+
// unanimous typed refusal leads, its live receipt carries the same facts,
|
|
2387
|
+
// and the capture facts and inspection actions follow. Any other dominant
|
|
2388
|
+
// capture failure joins it only under a settled budget terminal.
|
|
2389
|
+
if (delegateFailure.candidateFailureKind(failedDelegation) === "capture" &&
|
|
2390
|
+
(failedOutput.live || ledger.terminal() !== null))
|
|
2391
|
+
return failedCandidatesResult({
|
|
2392
|
+
...raceTerminal,
|
|
2393
|
+
budgetDenial,
|
|
2394
|
+
...(mutatingRun ? { liveCapture: failedOutput } : {}),
|
|
2348
2395
|
});
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2396
|
+
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2397
|
+
if (mutatingRun)
|
|
2398
|
+
await delegateFailure.persistFailedCandidateOutput(failedOutput);
|
|
2399
|
+
raceTerminal.writeTelemetry();
|
|
2400
|
+
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), { ...failure.metadata, attempts: runs });
|
|
2352
2401
|
}
|
|
2353
2402
|
if (runs.length === 0)
|
|
2354
2403
|
return emptyCandidateResult({
|
|
@@ -2370,18 +2419,7 @@ export class Orchestrator {
|
|
|
2370
2419
|
const workingRuns = partitionCandidates(runs).working;
|
|
2371
2420
|
if (workingRuns.length === 0) {
|
|
2372
2421
|
await disposeReviewEnvelopes();
|
|
2373
|
-
return failedCandidatesResult({
|
|
2374
|
-
store,
|
|
2375
|
-
paths,
|
|
2376
|
-
log,
|
|
2377
|
-
runId,
|
|
2378
|
-
taskId,
|
|
2379
|
-
mode,
|
|
2380
|
-
ledger,
|
|
2381
|
-
runs,
|
|
2382
|
-
budgetDenial,
|
|
2383
|
-
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2384
|
-
});
|
|
2422
|
+
return failedCandidatesResult({ ...raceTerminal, budgetDenial });
|
|
2385
2423
|
}
|
|
2386
2424
|
// QA-025: only announce that review STARTED when the panel will actually
|
|
2387
2425
|
// run. A candidate that changed no files is skipped inside reviewRuns; a
|
|
@@ -2423,7 +2461,7 @@ export class Orchestrator {
|
|
|
2423
2461
|
catch (err) {
|
|
2424
2462
|
// Review preflight/evidence failures end TERMINALLY with artifacts —
|
|
2425
2463
|
// never as an escaped throw that orphans the run dir.
|
|
2426
|
-
return failAfterCandidates(
|
|
2464
|
+
return failAfterCandidates(raceTerminal, "review", err);
|
|
2427
2465
|
}
|
|
2428
2466
|
finally {
|
|
2429
2467
|
// Review preflight failures must not leak candidate worktrees.
|
|
@@ -2453,6 +2491,7 @@ export class Orchestrator {
|
|
|
2453
2491
|
});
|
|
2454
2492
|
if (lease.granted) {
|
|
2455
2493
|
let envelope;
|
|
2494
|
+
const synthTried = new PoolAttemptEvidence(synthRouted.adapter.id, "synth");
|
|
2456
2495
|
try {
|
|
2457
2496
|
const plan = buildSynthesisPlan(evidences);
|
|
2458
2497
|
const synthesisInput = buildFileBackedSynthesisInput({
|
|
@@ -2481,7 +2520,7 @@ export class Orchestrator {
|
|
|
2481
2520
|
budgetStopped = true;
|
|
2482
2521
|
budgetDenial ??= denial;
|
|
2483
2522
|
},
|
|
2484
|
-
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome);
|
|
2523
|
+
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome, undefined, synthTried);
|
|
2485
2524
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2486
2525
|
reviewEnvelopes.push(envelope);
|
|
2487
2526
|
envelope = undefined;
|
|
@@ -2495,7 +2534,7 @@ export class Orchestrator {
|
|
|
2495
2534
|
workingRuns.push(run);
|
|
2496
2535
|
}
|
|
2497
2536
|
if (input.signal?.aborted) {
|
|
2498
|
-
return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs),
|
|
2537
|
+
return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs), raceTerminal.writeTelemetry, ledger.spend(), input.signal, store, undefined, undefined, history());
|
|
2499
2538
|
}
|
|
2500
2539
|
}
|
|
2501
2540
|
finally {
|
|
@@ -2503,6 +2542,9 @@ export class Orchestrator {
|
|
|
2503
2542
|
}
|
|
2504
2543
|
}
|
|
2505
2544
|
catch (err) {
|
|
2545
|
+
// Not yet in `runs`: the run goes on, so its tries' accounts stay in the history.
|
|
2546
|
+
if (!runs.some((run) => run.attemptId === "synth"))
|
|
2547
|
+
thrownSynthesis = { poolAttempts: synthTried.snapshot() };
|
|
2506
2548
|
ledger.settle(lease.lease?.lease_id ?? "", AC.attemptFailureCost(err, "synthesis-error").settlement);
|
|
2507
2549
|
log.emit("harness.completed", {
|
|
2508
2550
|
attempt_id: "synth",
|
|
@@ -2528,7 +2570,7 @@ export class Orchestrator {
|
|
|
2528
2570
|
}
|
|
2529
2571
|
catch (err) {
|
|
2530
2572
|
// Arbitration throws end terminally with artifacts, never as an orphan.
|
|
2531
|
-
return failAfterCandidates({
|
|
2573
|
+
return failAfterCandidates({ ...raceTerminal, attempts: history() }, "arbitration", err);
|
|
2532
2574
|
}
|
|
2533
2575
|
log.emit("arbitration.completed", {
|
|
2534
2576
|
winner: result.decision.winner,
|
|
@@ -2608,6 +2650,9 @@ export class Orchestrator {
|
|
|
2608
2650
|
});
|
|
2609
2651
|
store.writeYaml(join(paths.arbitrationDir, "pairwise.yaml"), result.pairwise);
|
|
2610
2652
|
const decisionPath = join(paths.arbitrationDir, "decision.yaml");
|
|
2653
|
+
// Every attempt's failed tries by the FINAL outcome (delivery below can
|
|
2654
|
+
// still fail the checks); a review or check block is not an account failure.
|
|
2655
|
+
const racePool = () => terminalPoolAttempts(facts, history());
|
|
2611
2656
|
if (winnerRun) {
|
|
2612
2657
|
for (const path of materializeWinnerOutputs({
|
|
2613
2658
|
attemptDir: join(paths.attemptsDir, winnerRun.attemptId),
|
|
@@ -2786,7 +2831,7 @@ export class Orchestrator {
|
|
|
2786
2831
|
},
|
|
2787
2832
|
});
|
|
2788
2833
|
}
|
|
2789
|
-
store.writeText(join(paths.finalDir, "summary.md"), renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)));
|
|
2834
|
+
store.writeText(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)), racePool()));
|
|
2790
2835
|
// summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
|
|
2791
2836
|
// longer carries primary-output authority. A clean applyable success or a
|
|
2792
2837
|
// winner answer still marks it ready for legacy INV-116 ordering; any
|
|
@@ -2812,8 +2857,14 @@ export class Orchestrator {
|
|
|
2812
2857
|
// (applyable, no_changes, or not-verified) is an honest completion.
|
|
2813
2858
|
const needsDecisionTerminal = facts.review === "blocked" || facts.checks === "failed";
|
|
2814
2859
|
const isFailureTerminal = facts.lifecycle !== "succeeded" || needsDecisionTerminal;
|
|
2860
|
+
const pool = racePool();
|
|
2815
2861
|
if (deliveryFailureReason && !finalVerifyFailed) {
|
|
2816
|
-
|
|
2862
|
+
const refused = deliveryRefusalFailure(deliveryFailureReason, paths.root);
|
|
2863
|
+
writeFailure(store, paths, {
|
|
2864
|
+
...refused,
|
|
2865
|
+
safeMessage: withPoolAttempts(refused.safeMessage, pool),
|
|
2866
|
+
poolAttempts: pool,
|
|
2867
|
+
});
|
|
2817
2868
|
}
|
|
2818
2869
|
else if (finalVerifyFailed) {
|
|
2819
2870
|
writeFailure(store, paths, {
|
|
@@ -2821,9 +2872,10 @@ export class Orchestrator {
|
|
|
2821
2872
|
// RunFailure.category is a closed enum; "validation" is the honest
|
|
2822
2873
|
// bucket (the winner failed re-validation on a fresh base).
|
|
2823
2874
|
category: "validation",
|
|
2824
|
-
safeMessage: finalVerify?.applied_cleanly === null
|
|
2875
|
+
safeMessage: withPoolAttempts(finalVerify?.applied_cleanly === null
|
|
2825
2876
|
? `final verify ERRORED before proving the patch against a clean base: ${finalVerify?.reason ?? "verify infrastructure error"}`
|
|
2826
|
-
: `final verify failed: ${finalVerify?.reason ?? (finalVerify?.gates_passed === false ? "deterministic gates failed on the fresh verify tree" : "unknown")}`,
|
|
2877
|
+
: `final verify failed: ${finalVerify?.reason ?? (finalVerify?.gates_passed === false ? "deterministic gates failed on the fresh verify tree" : "unknown")}`, pool),
|
|
2878
|
+
poolAttempts: pool,
|
|
2827
2879
|
runDir: paths.root,
|
|
2828
2880
|
nextActions: finalVerify?.applied_cleanly === null
|
|
2829
2881
|
? [
|
|
@@ -2855,11 +2907,12 @@ export class Orchestrator {
|
|
|
2855
2907
|
: "internal",
|
|
2856
2908
|
harnessId: winnerRun?.errored ? winnerRun.harnessId : undefined,
|
|
2857
2909
|
attemptId: winnerRun?.errored ? winnerRun.attemptId : undefined,
|
|
2858
|
-
safeMessage: needsHuman
|
|
2910
|
+
safeMessage: withPoolAttempts(needsHuman
|
|
2859
2911
|
? `review escalated to a human decision: ${result.decision.why_winner}`
|
|
2860
2912
|
: needsDecisionTerminal
|
|
2861
2913
|
? `review blocked before apply: ${result.decision.why_winner}`
|
|
2862
|
-
: result.decision.why_winner,
|
|
2914
|
+
: result.decision.why_winner, pool),
|
|
2915
|
+
poolAttempts: pool,
|
|
2863
2916
|
rawDetailRef: winnerRun?.errored
|
|
2864
2917
|
? `attempts/${winnerRun.attemptId}/attempt.yaml`
|
|
2865
2918
|
: undefined,
|
|
@@ -2879,7 +2932,7 @@ export class Orchestrator {
|
|
|
2879
2932
|
],
|
|
2880
2933
|
});
|
|
2881
2934
|
if (!winnerRun) {
|
|
2882
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: arbitration\n\n${result.decision.why_winner}\n`);
|
|
2935
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: arbitration\n\n${withPoolAttempts(result.decision.why_winner, pool)}\n`);
|
|
2883
2936
|
log.emit("output.ready", {
|
|
2884
2937
|
kind: "summary",
|
|
2885
2938
|
path: "final/summary.md",
|
|
@@ -2895,6 +2948,8 @@ export class Orchestrator {
|
|
|
2895
2948
|
lifecycle: facts.lifecycle,
|
|
2896
2949
|
facts,
|
|
2897
2950
|
reason: facts.reason,
|
|
2951
|
+
// A work-state veto is still a block: it keeps the earlier refusals.
|
|
2952
|
+
...(pool.length > 0 ? { pool_attempts: pool } : {}),
|
|
2898
2953
|
...resumableTerminal(store, paths, winnerRun ? [winnerRun] : runs, isFailureTerminal),
|
|
2899
2954
|
});
|
|
2900
2955
|
}
|
|
@@ -2904,6 +2959,7 @@ export class Orchestrator {
|
|
|
2904
2959
|
log.emit("run.blocked", {
|
|
2905
2960
|
lifecycle: facts.lifecycle,
|
|
2906
2961
|
facts,
|
|
2962
|
+
pool_attempts: pool,
|
|
2907
2963
|
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2908
2964
|
phase: deliveryFailureReason && !finalVerifyFailed
|
|
2909
2965
|
? "delivery"
|
|
@@ -2919,6 +2975,7 @@ export class Orchestrator {
|
|
|
2919
2975
|
facts,
|
|
2920
2976
|
reason: facts.reason,
|
|
2921
2977
|
phase: "arbitration",
|
|
2978
|
+
pool_attempts: pool,
|
|
2922
2979
|
failure_ref: "final/failure.yaml",
|
|
2923
2980
|
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2924
2981
|
});
|
|
@@ -2931,7 +2988,7 @@ export class Orchestrator {
|
|
|
2931
2988
|
facts,
|
|
2932
2989
|
winner: result.decision.winner,
|
|
2933
2990
|
runDir: paths.root,
|
|
2934
|
-
summary: result.decision.why_winner,
|
|
2991
|
+
summary: withPoolAttempts(result.decision.why_winner, pool),
|
|
2935
2992
|
candidates: candidateStatuses(runs),
|
|
2936
2993
|
decisionPath,
|
|
2937
2994
|
reviewVerified: actualReviewVerified,
|
|
@@ -3009,7 +3066,7 @@ export class Orchestrator {
|
|
|
3009
3066
|
const contract = this.buildContract(input, taskId, mode);
|
|
3010
3067
|
const planBrief = verifiedPlanBrief(input);
|
|
3011
3068
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
3012
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
3069
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
3013
3070
|
input,
|
|
3014
3071
|
contract,
|
|
3015
3072
|
quotaSnapshots,
|
|
@@ -3146,6 +3203,8 @@ export class Orchestrator {
|
|
|
3146
3203
|
let interrupted = false; // D-16 r8: terminalizes the run interrupted
|
|
3147
3204
|
let lastFindings = [];
|
|
3148
3205
|
let lastRun = null;
|
|
3206
|
+
const convergenceRuns = [];
|
|
3207
|
+
runContext.attempts = () => convergenceRuns;
|
|
3149
3208
|
let actualReviewVerified = false;
|
|
3150
3209
|
let lastFinalReviewClean = false;
|
|
3151
3210
|
// Honest apply-state for in-place convergence: the attempts mutate the LIVE
|
|
@@ -3235,6 +3294,7 @@ export class Orchestrator {
|
|
|
3235
3294
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
3236
3295
|
let run;
|
|
3237
3296
|
let harnessHome;
|
|
3297
|
+
const tried = new PoolAttemptEvidence(adapter.id, attemptId);
|
|
3238
3298
|
try {
|
|
3239
3299
|
harnessHome = this.harnessHomeFor(wsm, envelope, routed, input);
|
|
3240
3300
|
log.emit("harness.started", {
|
|
@@ -3253,7 +3313,7 @@ export class Orchestrator {
|
|
|
3253
3313
|
ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
|
|
3254
3314
|
return ledger.tier() === "hard";
|
|
3255
3315
|
}, input, true, // convergence runs one candidate: live deltas on (W-C4)
|
|
3256
|
-
undefined, undefined, harnessHome, observedPaths);
|
|
3316
|
+
undefined, undefined, harnessHome, observedPaths, tried);
|
|
3257
3317
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
3258
3318
|
log.emit("harness.completed", {
|
|
3259
3319
|
harness_id: adapter.id,
|
|
@@ -3290,6 +3350,7 @@ export class Orchestrator {
|
|
|
3290
3350
|
errored: true,
|
|
3291
3351
|
costEstimated: failureCost.estimated,
|
|
3292
3352
|
errors: [message],
|
|
3353
|
+
poolAttempts: tried.snapshot(),
|
|
3293
3354
|
...(declared.code ? { declaredFailure: declared } : {}),
|
|
3294
3355
|
telemetry: createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [routed.browserRequirement, routed.denyRequirement, routed.delegationRequirement], knobs.model),
|
|
3295
3356
|
};
|
|
@@ -3305,6 +3366,7 @@ export class Orchestrator {
|
|
|
3305
3366
|
}
|
|
3306
3367
|
}
|
|
3307
3368
|
lastRun = run;
|
|
3369
|
+
convergenceRuns.push(run);
|
|
3308
3370
|
// Fail-closed twin of the candidate lane's gate: this loop terminalizes
|
|
3309
3371
|
// per attempt, so the proof is spent per attempt.
|
|
3310
3372
|
assertDelegatedEvidence(input.delegated === true, convergenceAccess, [run]);
|
|
@@ -3324,34 +3386,28 @@ export class Orchestrator {
|
|
|
3324
3386
|
// Cancellation/deadline keeps priority over a belt failure finalized concurrently.
|
|
3325
3387
|
if (input.signal?.aborted || processingBudgetDenial)
|
|
3326
3388
|
break;
|
|
3389
|
+
// When this attempt's capture failed (and no Delegate failure leads), a
|
|
3390
|
+
// budget terminal the ledger settled on it (stream hard cap, exact overshoot,
|
|
3391
|
+
// unverifiable cash) outranks that capture failure and its typed refusal
|
|
3392
|
+
// (race parity): the budget-aware finalizer below names the budget cause and
|
|
3393
|
+
// remedy first, with the capture facts, source and inspection actions after.
|
|
3394
|
+
if (delegateFailure.candidateFailureKind(run) === "capture" && ledger.terminal()) {
|
|
3395
|
+
exhausted = true;
|
|
3396
|
+
break;
|
|
3397
|
+
}
|
|
3327
3398
|
if (run.telemetry.requestRefusal ||
|
|
3328
3399
|
run.telemetry.effortResolution?.resolution === "rejected")
|
|
3329
3400
|
break;
|
|
3330
3401
|
if (delegateFailure.candidateFailureKind(run)) {
|
|
3331
3402
|
const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
attemptId,
|
|
3339
|
-
harnessId: run.harnessId,
|
|
3340
|
-
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3341
|
-
log,
|
|
3342
|
-
});
|
|
3343
|
-
}
|
|
3344
|
-
else if (input.workspaceKind !== "directory") {
|
|
3345
|
-
await delegateFailure.persistFailedInPlaceWorkProduct({
|
|
3346
|
-
...{ store, log, paths, execRoot, preTurnSha, taskId, mode },
|
|
3347
|
-
live: input.inPlace === true,
|
|
3348
|
-
run,
|
|
3349
|
-
kind: input.create === true ? "new_repo" : "patch",
|
|
3350
|
-
attempts: attempt,
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3403
|
+
await delegateFailure.persistFailedCandidateOutput({
|
|
3404
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run },
|
|
3405
|
+
live: input.inPlace === true && input.workspaceKind !== "directory",
|
|
3406
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
3407
|
+
attempts: attempt,
|
|
3408
|
+
});
|
|
3353
3409
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null);
|
|
3354
|
-
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
3410
|
+
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), { ...failure.metadata, attempts: convergenceRuns });
|
|
3355
3411
|
}
|
|
3356
3412
|
if (input.workspaceKind === "directory" && run.captureRefusal) {
|
|
3357
3413
|
return failedCandidatesResult({
|
|
@@ -3363,6 +3419,7 @@ export class Orchestrator {
|
|
|
3363
3419
|
runId,
|
|
3364
3420
|
taskId,
|
|
3365
3421
|
runs: [run],
|
|
3422
|
+
attempts: convergenceRuns,
|
|
3366
3423
|
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null),
|
|
3367
3424
|
});
|
|
3368
3425
|
}
|
|
@@ -3422,7 +3479,17 @@ export class Orchestrator {
|
|
|
3422
3479
|
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3423
3480
|
log,
|
|
3424
3481
|
});
|
|
3425
|
-
return failAfterCandidates({
|
|
3482
|
+
return failAfterCandidates({
|
|
3483
|
+
log,
|
|
3484
|
+
store,
|
|
3485
|
+
paths,
|
|
3486
|
+
runId,
|
|
3487
|
+
taskId,
|
|
3488
|
+
mode,
|
|
3489
|
+
ledger,
|
|
3490
|
+
runs: [run],
|
|
3491
|
+
attempts: convergenceRuns,
|
|
3492
|
+
}, "review", err);
|
|
3426
3493
|
}
|
|
3427
3494
|
if (conv.converged) {
|
|
3428
3495
|
converged = true;
|
|
@@ -3541,11 +3608,12 @@ export class Orchestrator {
|
|
|
3541
3608
|
facts = decision.facts;
|
|
3542
3609
|
}
|
|
3543
3610
|
}
|
|
3544
|
-
|
|
3611
|
+
// Captured files decide noChanges; an incomplete capture leaves it unknown.
|
|
3612
|
+
if (lastRun?.files || lastRun?.captureRefusal)
|
|
3545
3613
|
facts = {
|
|
3546
3614
|
...facts,
|
|
3547
|
-
noChanges: lastRun.files
|
|
3548
|
-
reason: facts.reason === "no_changes" && lastRun.files
|
|
3615
|
+
noChanges: lastRun.files?.noChanges ?? null,
|
|
3616
|
+
reason: facts.reason === "no_changes" && lastRun.files?.noChanges !== true ? null : facts.reason,
|
|
3549
3617
|
};
|
|
3550
3618
|
// A budget terminal turns a succeeded lifecycle into a failed one (D8).
|
|
3551
3619
|
const convBudgetTerminal = ledger.terminal();
|
|
@@ -3556,6 +3624,19 @@ export class Orchestrator {
|
|
|
3556
3624
|
review_requested: contract.review_requested,
|
|
3557
3625
|
});
|
|
3558
3626
|
}
|
|
3627
|
+
// The shared classifier owns a budget terminal's cause and remedy: a
|
|
3628
|
+
// processing re-admission denial, or the ledger's settled terminal once it
|
|
3629
|
+
// stopped the loop over an incomplete capture (the capture facts and
|
|
3630
|
+
// inspection actions follow it in the failure record).
|
|
3631
|
+
const convBudgetMapping = processingBudgetDenial ||
|
|
3632
|
+
(convBudgetTerminal && lastRun?.captureRefusal && isBudgetTerminal(facts.reason))
|
|
3633
|
+
? classifyBudgetFailure({ denial: processingBudgetDenial, terminal: convBudgetTerminal })
|
|
3634
|
+
: null;
|
|
3635
|
+
if (convBudgetMapping &&
|
|
3636
|
+
facts.lifecycle === "failed" &&
|
|
3637
|
+
isBudgetTerminal(facts.reason) &&
|
|
3638
|
+
facts.reason !== convBudgetMapping.reason)
|
|
3639
|
+
facts = { ...facts, reason: convBudgetMapping.reason };
|
|
3559
3640
|
// A reviewer escalation to a human forces the REVIEW axis to blocked.
|
|
3560
3641
|
const needsHuman = lastFindings.some((f) => f.severity === "NEEDS_HUMAN" && isBlocking(f));
|
|
3561
3642
|
if (needsHuman && facts.lifecycle === "succeeded" && facts.review !== "blocked") {
|
|
@@ -3579,6 +3660,8 @@ export class Orchestrator {
|
|
|
3579
3660
|
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
3580
3661
|
}
|
|
3581
3662
|
const convNeedsDecision = facts.review === "blocked" || facts.checks === "failed";
|
|
3663
|
+
// Every attempt's failed tries by the actual outcome; a block is not an account failure.
|
|
3664
|
+
const convPool = terminalPoolAttempts(facts, convergenceRuns);
|
|
3582
3665
|
if (decision) {
|
|
3583
3666
|
// Shared honesty owner (same as the race path): a needs-decision terminal
|
|
3584
3667
|
// overrides the persisted decision; final_verify is recorded either way.
|
|
@@ -3600,16 +3683,13 @@ export class Orchestrator {
|
|
|
3600
3683
|
reviewAllowsApply(facts) &&
|
|
3601
3684
|
facts.checks !== "failed" &&
|
|
3602
3685
|
!workStateVetoes(facts);
|
|
3603
|
-
if (lastRun.files) {
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
store,
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
harnessId: lastRun.harnessId,
|
|
3611
|
-
facts,
|
|
3612
|
-
log,
|
|
3686
|
+
if (lastRun.files || lastRun.captureRefusal) {
|
|
3687
|
+
// An incomplete capture has no exact patch: never publish an empty one.
|
|
3688
|
+
await delegateFailure.persistFailedCandidateOutput({
|
|
3689
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, facts, run: lastRun },
|
|
3690
|
+
live: input.inPlace === true && input.workspaceKind !== "directory",
|
|
3691
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
3692
|
+
attempts: attempt,
|
|
3613
3693
|
});
|
|
3614
3694
|
}
|
|
3615
3695
|
else {
|
|
@@ -3656,7 +3736,7 @@ export class Orchestrator {
|
|
|
3656
3736
|
store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
|
|
3657
3737
|
log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
|
|
3658
3738
|
}
|
|
3659
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.summaryDisclosure(lastRun.secretLike)}\n`);
|
|
3739
|
+
store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.summaryDisclosure(lastRun.secretLike)}`, convPool)}\n`);
|
|
3660
3740
|
// Lifecycle invariant (all modes): output.ready precedes the terminal
|
|
3661
3741
|
// event so a client that applied the terminal event has the output.
|
|
3662
3742
|
log.emit("output.ready", {
|
|
@@ -3675,9 +3755,7 @@ export class Orchestrator {
|
|
|
3675
3755
|
const convDeclared = !convNeedsDecision && facts.lifecycle === "failed" && !isBudgetTerminal(facts.reason)
|
|
3676
3756
|
? lastRun?.declaredFailure
|
|
3677
3757
|
: undefined;
|
|
3678
|
-
const
|
|
3679
|
-
? classifyBudgetFailure({ denial: processingBudgetDenial, terminal: ledger.terminal() })
|
|
3680
|
-
: null;
|
|
3758
|
+
const inspect = secretDiff.captureInspectionActions(lastRun?.captureRefusal);
|
|
3681
3759
|
writeFailure(store, paths, {
|
|
3682
3760
|
phase: convNeedsDecision ? "review" : "convergence",
|
|
3683
3761
|
category: isBudgetTerminal(facts.reason)
|
|
@@ -3687,50 +3765,58 @@ export class Orchestrator {
|
|
|
3687
3765
|
: convNeedsDecision
|
|
3688
3766
|
? "policy"
|
|
3689
3767
|
: (convDeclared?.category ?? "internal"),
|
|
3690
|
-
code:
|
|
3768
|
+
code: convBudgetMapping?.code ?? convDeclared?.code ?? null,
|
|
3691
3769
|
resetsAt: convDeclared?.resetsAt ?? null,
|
|
3692
3770
|
requestRefusal: convDeclared?.requestRefusal,
|
|
3693
|
-
safeMessage: convNeedsDecision
|
|
3694
|
-
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
3695
|
-
: facts.reason === "stuck_no_progress"
|
|
3696
|
-
? (stuckNoProgressReason ?? `stuck_no_progress after ${attempt} attempt(s)`)
|
|
3697
|
-
: `${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`,
|
|
3698
3771
|
harnessId: lastRun?.harnessId,
|
|
3699
3772
|
attemptId: lastRun?.attemptId,
|
|
3700
3773
|
runDir: paths.root,
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
"Increase --max-seconds or narrow the scope, then re-run",
|
|
3706
|
-
]
|
|
3707
|
-
: ["Retry if cancellation was accidental"]
|
|
3708
|
-
: convNeedsDecision
|
|
3709
|
-
? [
|
|
3710
|
-
"Review the blocking findings on the run's turn",
|
|
3711
|
-
"Accept the risk to apply this exact patch, or discard the change",
|
|
3712
|
-
]
|
|
3713
|
-
: facts.reason === "stuck_no_progress"
|
|
3774
|
+
captureFailure: lastRun?.captureRefusal?.failure,
|
|
3775
|
+
nextActions: [
|
|
3776
|
+
...(facts.lifecycle === "cancelled"
|
|
3777
|
+
? facts.reason === "wall_clock_exceeded"
|
|
3714
3778
|
? [
|
|
3715
|
-
"Inspect the
|
|
3716
|
-
"
|
|
3717
|
-
"Fix the gate or provide a different repair instruction",
|
|
3779
|
+
"Inspect the partial work kept from before the deadline",
|
|
3780
|
+
"Increase --max-seconds or narrow the scope, then re-run",
|
|
3718
3781
|
]
|
|
3719
|
-
: [
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
"
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3782
|
+
: ["Retry if cancellation was accidental"]
|
|
3783
|
+
: convNeedsDecision
|
|
3784
|
+
? [
|
|
3785
|
+
"Review the blocking findings on the run's turn",
|
|
3786
|
+
"Accept the risk to apply this exact patch, or discard the change",
|
|
3787
|
+
]
|
|
3788
|
+
: facts.reason === "stuck_no_progress"
|
|
3789
|
+
? [
|
|
3790
|
+
"Inspect the stable patch",
|
|
3791
|
+
"Inspect the failing gate output",
|
|
3792
|
+
"Fix the gate or provide a different repair instruction",
|
|
3793
|
+
]
|
|
3794
|
+
: lastRun?.captureRefusal // no latest patch exists to inspect
|
|
3795
|
+
? secretDiff.captureRefusalNextActions(lastRun.captureRefusal)
|
|
3796
|
+
: [
|
|
3797
|
+
"Open diagnostics",
|
|
3798
|
+
"Inspect latest patch and review findings",
|
|
3799
|
+
"Retry with more attempts or a narrower prompt",
|
|
3800
|
+
]),
|
|
3801
|
+
...inspect,
|
|
3802
|
+
],
|
|
3803
|
+
...(convBudgetMapping
|
|
3804
|
+
? budgetFailureRecord(convBudgetMapping, { runDir: paths.root, followUps: inspect })
|
|
3726
3805
|
: {}),
|
|
3806
|
+
safeMessage: withPoolAttempts(convBudgetMapping?.safeMessage ??
|
|
3807
|
+
(convNeedsDecision
|
|
3808
|
+
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
3809
|
+
: facts.reason === "stuck_no_progress"
|
|
3810
|
+
? (stuckNoProgressReason ?? `stuck_no_progress after ${attempt} attempt(s)`)
|
|
3811
|
+
: `${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`), convPool),
|
|
3812
|
+
poolAttempts: convPool,
|
|
3727
3813
|
});
|
|
3728
3814
|
// D-16 r8/r9: an INTERRUPTED envelope run still gets its diagnostic
|
|
3729
3815
|
// summary + output.ready (only patch/work_product are withheld) — the
|
|
3730
3816
|
// ARCHITECTURE event contract guarantees output.ready precedes the
|
|
3731
3817
|
// terminal in every mode.
|
|
3732
3818
|
if (!lastRun || (interrupted && input.inPlace !== true && !lastRun.files)) {
|
|
3733
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n`);
|
|
3819
|
+
store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}`, convPool)}\n`);
|
|
3734
3820
|
log.emit("output.ready", {
|
|
3735
3821
|
kind: "summary",
|
|
3736
3822
|
path: "final/summary.md",
|
|
@@ -3738,8 +3824,9 @@ export class Orchestrator {
|
|
|
3738
3824
|
});
|
|
3739
3825
|
}
|
|
3740
3826
|
}
|
|
3741
|
-
// work_product.emitted only when a product was actually written (r9)
|
|
3742
|
-
|
|
3827
|
+
// work_product.emitted only when a product was actually written (r9); a
|
|
3828
|
+
// capture refusal's live record announces itself.
|
|
3829
|
+
if (lastRun && !lastRun.captureRefusal && (lastRun.files || !interrupted || input.inPlace)) {
|
|
3743
3830
|
log.emit("work_product.emitted", { winner: lastRun.attemptId });
|
|
3744
3831
|
}
|
|
3745
3832
|
if (!convIsFailureTerminal) {
|
|
@@ -3749,6 +3836,8 @@ export class Orchestrator {
|
|
|
3749
3836
|
facts,
|
|
3750
3837
|
reason: facts.reason,
|
|
3751
3838
|
attempts: attempt,
|
|
3839
|
+
// A work-state veto is still a block: it keeps the earlier refusals.
|
|
3840
|
+
...(convPool.length > 0 ? { pool_attempts: convPool } : {}),
|
|
3752
3841
|
});
|
|
3753
3842
|
}
|
|
3754
3843
|
else if (facts.lifecycle === "succeeded") {
|
|
@@ -3758,6 +3847,7 @@ export class Orchestrator {
|
|
|
3758
3847
|
facts,
|
|
3759
3848
|
attempts: attempt,
|
|
3760
3849
|
phase: "review",
|
|
3850
|
+
pool_attempts: convPool,
|
|
3761
3851
|
failure_ref: "final/failure.yaml",
|
|
3762
3852
|
});
|
|
3763
3853
|
}
|
|
@@ -3769,6 +3859,7 @@ export class Orchestrator {
|
|
|
3769
3859
|
reason: facts.reason,
|
|
3770
3860
|
attempts: attempt,
|
|
3771
3861
|
phase: "convergence",
|
|
3862
|
+
pool_attempts: convPool,
|
|
3772
3863
|
failure_ref: "final/failure.yaml",
|
|
3773
3864
|
// QA-041: surface the typed deadline reason on the terminal event so the
|
|
3774
3865
|
// daemon job result / Control API can distinguish it from a user cancel.
|
|
@@ -3791,9 +3882,9 @@ export class Orchestrator {
|
|
|
3791
3882
|
...(facts.lifecycle === "cancelled" && convAbortReason
|
|
3792
3883
|
? { cancelReason: convAbortReason }
|
|
3793
3884
|
: {}),
|
|
3794
|
-
summary: converged
|
|
3885
|
+
summary: withPoolAttempts(converged
|
|
3795
3886
|
? `converged in ${attempt} attempt(s)`
|
|
3796
|
-
: `${facts.lifecycle} after ${attempt} attempt(s)`,
|
|
3887
|
+
: `${facts.lifecycle} after ${attempt} attempt(s)`, convPool),
|
|
3797
3888
|
candidates: lastRun
|
|
3798
3889
|
? [
|
|
3799
3890
|
{
|
|
@@ -4227,7 +4318,7 @@ export class Orchestrator {
|
|
|
4227
4318
|
// recorded user intent is the CALLER's goal.
|
|
4228
4319
|
const contract = this.buildContract({ ...input, prompt: opts.contractIntent ?? prompt }, taskId, opts.mode);
|
|
4229
4320
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
4230
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
4321
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
4231
4322
|
input,
|
|
4232
4323
|
contract,
|
|
4233
4324
|
quotaSnapshots,
|
|
@@ -4287,6 +4378,14 @@ export class Orchestrator {
|
|
|
4287
4378
|
};
|
|
4288
4379
|
}
|
|
4289
4380
|
const attempts = [];
|
|
4381
|
+
// A launched attempt's tries' accounts, owned here until its record joins
|
|
4382
|
+
// `attempts` (as an Agent slot owns them): a throw after its tries settled
|
|
4383
|
+
// still names them on the run's terminal, and never twice.
|
|
4384
|
+
const launched = new Set();
|
|
4385
|
+
runContext.attempts = () => [
|
|
4386
|
+
...attempts,
|
|
4387
|
+
...[...launched].map((tried) => ({ poolAttempts: tried.snapshot() })),
|
|
4388
|
+
];
|
|
4290
4389
|
const attemptTelemetries = [];
|
|
4291
4390
|
let fallbackOpen = false;
|
|
4292
4391
|
let budgetStopped = false;
|
|
@@ -4498,12 +4597,15 @@ export class Orchestrator {
|
|
|
4498
4597
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4499
4598
|
let activeSessionId = spec.session_id;
|
|
4500
4599
|
const live = liveAttempt(input, routed, paths, contract, attemptId, () => activeSessionId);
|
|
4600
|
+
const tried = new PoolAttemptEvidence(adapter.id, attemptId);
|
|
4601
|
+
launched.add(tried);
|
|
4501
4602
|
let continuity;
|
|
4502
4603
|
({ continuity, spec } = await openContinuity(this.continuityDeps(adapter, routed, contract, attemptId, paths, input, log, {
|
|
4503
4604
|
envelope: null,
|
|
4504
4605
|
harnessHome: null,
|
|
4505
4606
|
workOrder: input.prompt,
|
|
4506
4607
|
firstPrompt: spec.prompt,
|
|
4608
|
+
poolEvidence: tried,
|
|
4507
4609
|
}), { spec, continuation: input.continuation, store, telemetry }));
|
|
4508
4610
|
const onAbort = () => void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4509
4611
|
if (input.signal) {
|
|
@@ -4538,9 +4640,8 @@ export class Orchestrator {
|
|
|
4538
4640
|
runSpec.extra["abortSignal"] = input.signal
|
|
4539
4641
|
? AbortSignal.any([input.signal, tryAbort.signal])
|
|
4540
4642
|
: tryAbort.signal;
|
|
4541
|
-
continuity.beginTry(runSpec, nativeTry, tryAbort);
|
|
4542
4643
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
4543
|
-
const refusal =
|
|
4644
|
+
const refusal = continuity.beginTry(runSpec, nativeTry, tryAbort);
|
|
4544
4645
|
const transientStart = telemetry.transientFailures.length;
|
|
4545
4646
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
4546
4647
|
log.emit("harness.started", {
|
|
@@ -4615,8 +4716,6 @@ export class Orchestrator {
|
|
|
4615
4716
|
if (!recordAdapterThrow(telemetry, err))
|
|
4616
4717
|
break;
|
|
4617
4718
|
}
|
|
4618
|
-
if (streamBudgetDenied)
|
|
4619
|
-
break;
|
|
4620
4719
|
if (telemetry.requestRefusal)
|
|
4621
4720
|
break;
|
|
4622
4721
|
const newTransients = telemetry.transientFailures.slice(transientStart);
|
|
@@ -4625,10 +4724,14 @@ export class Orchestrator {
|
|
|
4625
4724
|
const reportSoFar = answer.text();
|
|
4626
4725
|
const delivered = !harnessError && reportSoFar.length > 0 && !input.signal?.aborted;
|
|
4627
4726
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
4727
|
+
// A budget stop ends the attempt; typed progress before it still served the account.
|
|
4728
|
+
if (streamBudgetDenied)
|
|
4729
|
+
break;
|
|
4628
4730
|
const verdict = await continuity.afterTry({
|
|
4629
4731
|
runSpec,
|
|
4630
4732
|
nativeTry,
|
|
4631
4733
|
harnessErrored: harnessError !== null,
|
|
4734
|
+
originalError: harnessError,
|
|
4632
4735
|
aborted: input.signal?.aborted === true,
|
|
4633
4736
|
requestRefused: Boolean(telemetry.requestRefusal),
|
|
4634
4737
|
newTransients,
|
|
@@ -4746,6 +4849,7 @@ export class Orchestrator {
|
|
|
4746
4849
|
: {}),
|
|
4747
4850
|
...continuity.terminalFacts(roResumable),
|
|
4748
4851
|
});
|
|
4852
|
+
launched.delete(tried);
|
|
4749
4853
|
if (opts.deepScan) {
|
|
4750
4854
|
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
|
|
4751
4855
|
}
|
|
@@ -4771,6 +4875,7 @@ export class Orchestrator {
|
|
|
4771
4875
|
...(scoutInterrupted ? { interrupted: true } : {}),
|
|
4772
4876
|
...continuity.terminalFacts(roResumable),
|
|
4773
4877
|
});
|
|
4878
|
+
launched.delete(tried);
|
|
4774
4879
|
if (opts.deepScan) {
|
|
4775
4880
|
const warningNote = toolWarnings(telemetry).length
|
|
4776
4881
|
? `\n\n> Tool warnings: ${toolWarnings(telemetry)
|
|
@@ -4931,188 +5036,20 @@ export class Orchestrator {
|
|
|
4931
5036
|
harnessId: a.harnessId,
|
|
4932
5037
|
status: a.status,
|
|
4933
5038
|
}));
|
|
4934
|
-
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"));
|
|
5039
|
+
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"), attempts);
|
|
4935
5040
|
if (input.signal?.aborted)
|
|
4936
5041
|
return cancelledTerminal();
|
|
4937
5042
|
const succeededReadonly = attempts.filter((a) => a.status === "success");
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
? classifyBudgetFailure({ denial: budgetDenial, terminal: ledger.terminal() })
|
|
4946
|
-
: null;
|
|
4947
|
-
const singleError = budgetMapping?.safeMessage ??
|
|
4948
|
-
last?.error ??
|
|
4949
|
-
(budgetStopped ? "budget exhausted before any attempt" : "harness failed");
|
|
4950
|
-
if (fallbackOpen || webBlocked) {
|
|
4951
|
-
log.emit("route.fallback.exhausted", {
|
|
4952
|
-
harness_id: last?.harnessId ?? null,
|
|
4953
|
-
attempt_id: last?.attemptId ?? null,
|
|
4954
|
-
reason: "web_evidence_unsatisfied",
|
|
4955
|
-
error: singleError,
|
|
4956
|
-
});
|
|
4957
|
-
fallbackOpen = false;
|
|
4958
|
-
}
|
|
4959
|
-
const partialReport = [...attempts].reverse().find((a) => a.report)?.report ?? "";
|
|
4960
|
-
this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null);
|
|
4961
|
-
store.writeText(join(paths.contextDir, "context_error.md"), `# ${budgetMapping ? "Budget Denied" : "Harness Error"}\n\n${singleError}\n`);
|
|
4962
|
-
const roEventRefs = attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`);
|
|
4963
|
-
if (budgetMapping) {
|
|
4964
|
-
writeFailure(store, paths, budgetFailureRecord(budgetMapping, { eventRefs: roEventRefs, runDir: paths.root }));
|
|
4965
|
-
}
|
|
4966
|
-
else {
|
|
4967
|
-
// #31: classify the harness cause across the read-only attempts so auth
|
|
4968
|
-
// guidance appears only on a real auth failure.
|
|
4969
|
-
const roCategory = dominantHarnessFailureCategory(attemptTelemetries.flatMap((a) => a.telemetry.transientFailures));
|
|
4970
|
-
// A5: the terminal already speaks with the LAST attempt's message; its
|
|
4971
|
-
// typed refusal rides along machine-readably instead of reducing to it.
|
|
4972
|
-
const roDeclared = webBlocked ? undefined : last?.declaredFailure;
|
|
4973
|
-
writeFailure(store, paths, {
|
|
4974
|
-
phase: "harness",
|
|
4975
|
-
category: webBlocked ? "policy" : (roDeclared?.category ?? "harness_error"),
|
|
4976
|
-
code: roDeclared?.code ?? null,
|
|
4977
|
-
harnessId: last?.harnessId,
|
|
4978
|
-
attemptId: last?.attemptId,
|
|
4979
|
-
safeMessage: singleError,
|
|
4980
|
-
eventRefs: roEventRefs,
|
|
4981
|
-
runDir: paths.root,
|
|
4982
|
-
resetsAt: roDeclared?.resetsAt ?? null,
|
|
4983
|
-
requestRefusal: roDeclared?.requestRefusal,
|
|
4984
|
-
vendorFailure: attemptVendorFailure(attemptTelemetries, last?.attemptId),
|
|
4985
|
-
nextActions: harnessFailureNextActions(roCategory),
|
|
4986
|
-
});
|
|
4987
|
-
}
|
|
4988
|
-
// QA-036: re-check the DELIVERABLE through the shared finalizer helper —
|
|
4989
|
-
// a blocked Ask that produced NO answer can no longer read as a succeeded
|
|
4990
|
-
// "Needs review" run (exit 0); it is an honest failure (exit 1).
|
|
4991
|
-
const roTerminal = readOnlyNoSuccessTerminal({
|
|
4992
|
-
webBlocked,
|
|
4993
|
-
hasDeliverable: partialReport.trim().length > 0,
|
|
4994
|
-
budgetStopped,
|
|
4995
|
-
attemptsCount: attempts.length,
|
|
4996
|
-
});
|
|
4997
|
-
const terminalFacts = makeOutcomeFacts(roTerminal.lifecycle, {
|
|
4998
|
-
...(roTerminal.review ? { review: roTerminal.review } : {}),
|
|
4999
|
-
reason: budgetMapping?.reason ?? roTerminal.reason,
|
|
5000
|
-
});
|
|
5001
|
-
const terminalHarnessId = budgetMapping?.harnessId ?? last?.harnessId;
|
|
5002
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harness: ${terminalHarnessId ?? "none"}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}\n\n${singleError}\n`);
|
|
5003
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
5004
|
-
if (terminalFacts.lifecycle === "succeeded") {
|
|
5005
|
-
log.emit("run.blocked", {
|
|
5006
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5007
|
-
facts: terminalFacts,
|
|
5008
|
-
harness_id: terminalHarnessId,
|
|
5009
|
-
error: singleError,
|
|
5010
|
-
failure_ref: "final/failure.yaml",
|
|
5011
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5012
|
-
});
|
|
5013
|
-
}
|
|
5014
|
-
else {
|
|
5015
|
-
log.emit("run.failed", {
|
|
5016
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5017
|
-
facts: terminalFacts,
|
|
5018
|
-
reason: terminalFacts.reason,
|
|
5019
|
-
phase: budgetMapping?.phase,
|
|
5020
|
-
harness_id: terminalHarnessId,
|
|
5021
|
-
error: singleError,
|
|
5022
|
-
failure_ref: "final/failure.yaml",
|
|
5023
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5024
|
-
});
|
|
5025
|
-
}
|
|
5026
|
-
return {
|
|
5027
|
-
spendUsd: ledger.spend(),
|
|
5028
|
-
runId,
|
|
5029
|
-
taskId,
|
|
5030
|
-
mode: opts.mode,
|
|
5031
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5032
|
-
facts: terminalFacts,
|
|
5033
|
-
winner: null,
|
|
5034
|
-
runDir: paths.root,
|
|
5035
|
-
summary: singleError,
|
|
5036
|
-
candidates: attempts.map((a) => ({
|
|
5037
|
-
attemptId: a.attemptId,
|
|
5038
|
-
harnessId: a.harnessId,
|
|
5039
|
-
status: a.status,
|
|
5040
|
-
})),
|
|
5041
|
-
};
|
|
5042
|
-
}
|
|
5043
|
+
const readonlyTerminal = {
|
|
5044
|
+
...{ store, paths, log, runId, taskId, mode: opts.mode, ledger, attempts },
|
|
5045
|
+
...{ attemptTelemetries, budgetStopped, budgetDenial },
|
|
5046
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null),
|
|
5047
|
+
};
|
|
5048
|
+
if (!opts.deepScan && succeededReadonly.length === 0)
|
|
5049
|
+
return readOnlyChainFailureResult({ ...readonlyTerminal, fallbackOpen });
|
|
5043
5050
|
const succeeded = succeededReadonly;
|
|
5044
|
-
if (opts.deepScan && succeeded.length === 0)
|
|
5045
|
-
|
|
5046
|
-
// QA-050/QA-019: only a pure-denial scan may claim a budget failure.
|
|
5047
|
-
const scanBudgetMapping = budgetStopped && !blocked && attempts.every((a) => a.budgetDenied === true)
|
|
5048
|
-
? classifyBudgetFailure({ denial: budgetDenial, terminal: ledger.terminal() })
|
|
5049
|
-
: null;
|
|
5050
|
-
// D-16 r8: ALL scouts out of context → the aggregate is interrupted.
|
|
5051
|
-
const allInterrupted = attempts.length > 0 && attempts.every((a) => a.interrupted === true);
|
|
5052
|
-
const scanFailFacts = allInterrupted
|
|
5053
|
-
? makeOutcomeFacts("interrupted", { reason: "context_capacity_exhausted" })
|
|
5054
|
-
: makeOutcomeFacts("failed", {
|
|
5055
|
-
reason: scanBudgetMapping ? scanBudgetMapping.reason : "harness_failed",
|
|
5056
|
-
});
|
|
5057
|
-
const message = scanBudgetMapping
|
|
5058
|
-
? scanBudgetMapping.safeMessage
|
|
5059
|
-
: attempts.map((a) => `${a.attemptId}/${a.harnessId}: ${a.error ?? "failed"}`).join("\n");
|
|
5060
|
-
this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null);
|
|
5061
|
-
if (scanBudgetMapping) {
|
|
5062
|
-
writeFailure(store, paths, budgetFailureRecord(scanBudgetMapping, {
|
|
5063
|
-
eventRefs: attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`),
|
|
5064
|
-
runDir: paths.root,
|
|
5065
|
-
}));
|
|
5066
|
-
}
|
|
5067
|
-
else {
|
|
5068
|
-
// Mixed causes stay generic; only unanimous evidence may name the scan's cause.
|
|
5069
|
-
const declared = blocked ? null : unanimousDeclaredFailure(attempts);
|
|
5070
|
-
const scanCategory = dominantHarnessFailureCategory(attemptTelemetries.flatMap((a) => a.telemetry.transientFailures));
|
|
5071
|
-
writeFailure(store, paths, {
|
|
5072
|
-
phase: "harness",
|
|
5073
|
-
category: blocked ? "policy" : (declared?.category ?? "harness_error"),
|
|
5074
|
-
code: declared?.code,
|
|
5075
|
-
resetsAt: declared?.resetsAt,
|
|
5076
|
-
requestRefusal: declared?.requestRefusal,
|
|
5077
|
-
safeMessage: message || "all explorers failed",
|
|
5078
|
-
eventRefs: attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`),
|
|
5079
|
-
runDir: paths.root,
|
|
5080
|
-
nextActions: [
|
|
5081
|
-
...harnessFailureNextActions(scanCategory).filter((a) => !a.startsWith("Retry")),
|
|
5082
|
-
"Reduce explore width",
|
|
5083
|
-
"Retry after setup",
|
|
5084
|
-
],
|
|
5085
|
-
});
|
|
5086
|
-
}
|
|
5087
|
-
// QA-036: no successful explorer means no synthesizable deliverable.
|
|
5088
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Lifecycle: ${scanFailFacts.lifecycle}${scanFailFacts.reason ? ` (${scanFailFacts.reason})` : ""}\n\n${message}\n`);
|
|
5089
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
5090
|
-
log.emit("run.failed", {
|
|
5091
|
-
lifecycle: scanFailFacts.lifecycle,
|
|
5092
|
-
facts: scanFailFacts,
|
|
5093
|
-
reason: scanFailFacts.reason,
|
|
5094
|
-
phase: scanBudgetMapping ? scanBudgetMapping.phase : "harness",
|
|
5095
|
-
error: message,
|
|
5096
|
-
failure_ref: "final/failure.yaml",
|
|
5097
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5098
|
-
});
|
|
5099
|
-
return {
|
|
5100
|
-
spendUsd: ledger.spend(),
|
|
5101
|
-
runId,
|
|
5102
|
-
taskId,
|
|
5103
|
-
mode: opts.mode,
|
|
5104
|
-
lifecycle: scanFailFacts.lifecycle,
|
|
5105
|
-
facts: scanFailFacts,
|
|
5106
|
-
winner: null,
|
|
5107
|
-
runDir: paths.root,
|
|
5108
|
-
summary: message,
|
|
5109
|
-
candidates: attempts.map((a) => ({
|
|
5110
|
-
attemptId: a.attemptId,
|
|
5111
|
-
harnessId: a.harnessId,
|
|
5112
|
-
status: a.status,
|
|
5113
|
-
})),
|
|
5114
|
-
};
|
|
5115
|
-
}
|
|
5051
|
+
if (opts.deepScan && succeeded.length === 0)
|
|
5052
|
+
return deepScanFailureResult(readonlyTerminal);
|
|
5116
5053
|
const unsuccessful = attempts.filter((a) => a.status !== "success");
|
|
5117
5054
|
// #27 / D-6: a multi-scout deep scan runs ONE bounded synthesis reducer over
|
|
5118
5055
|
// the raw scout reports so the final artifact is a real merge, not a
|
|
@@ -5216,7 +5153,9 @@ export class Orchestrator {
|
|
|
5216
5153
|
.map((a) => `${a.attemptId}:${a.harnessId}:${a.status}`)
|
|
5217
5154
|
.join(", ");
|
|
5218
5155
|
const secretLike = secretDiff.answerSecretLikeFinding(answerMatches);
|
|
5219
|
-
|
|
5156
|
+
// A delivered report that still ends unsuccessfully or blocked keeps it and names every failed try.
|
|
5157
|
+
const reportPool = terminalPoolAttempts(terminalFacts, attempts);
|
|
5158
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}${secretDiff.summaryDisclosure(secretLike)}\n\n${withPoolAttempts(report, reportPool)}\n`);
|
|
5220
5159
|
const reportProducerAttemptId = opts.deepScan &&
|
|
5221
5160
|
deepScanSynthesis?.status === "succeeded" &&
|
|
5222
5161
|
deepScanSynthesis.reducer_attempt_id
|
|
@@ -5243,7 +5182,8 @@ export class Orchestrator {
|
|
|
5243
5182
|
writeFailure(store, paths, {
|
|
5244
5183
|
phase: "executor",
|
|
5245
5184
|
category: terminalFacts.reason === "context_capacity_exhausted" ? "harness_error" : "budget",
|
|
5246
|
-
safeMessage: `read-only report ended ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason.replaceAll("_", " ")})` : ""}`,
|
|
5185
|
+
safeMessage: withPoolAttempts(`read-only report ended ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason.replaceAll("_", " ")})` : ""}`, reportPool),
|
|
5186
|
+
poolAttempts: reportPool,
|
|
5247
5187
|
runDir: paths.root,
|
|
5248
5188
|
nextActions: terminalFacts.reason === "context_capacity_exhausted"
|
|
5249
5189
|
? ["Inspect the partial report", "Re-run with a narrower scope"]
|
|
@@ -5254,6 +5194,7 @@ export class Orchestrator {
|
|
|
5254
5194
|
facts: terminalFacts,
|
|
5255
5195
|
reason: terminalFacts.reason,
|
|
5256
5196
|
phase: "executor",
|
|
5197
|
+
pool_attempts: reportPool,
|
|
5257
5198
|
failure_ref: "final/failure.yaml",
|
|
5258
5199
|
...resumableTerminal(store, paths, attempts, true),
|
|
5259
5200
|
});
|
|
@@ -5263,6 +5204,7 @@ export class Orchestrator {
|
|
|
5263
5204
|
lifecycle: terminalFacts.lifecycle,
|
|
5264
5205
|
facts: terminalFacts,
|
|
5265
5206
|
reason: terminalFacts.reason,
|
|
5207
|
+
pool_attempts: reportPool,
|
|
5266
5208
|
...resumableTerminal(store, paths, attempts, true),
|
|
5267
5209
|
});
|
|
5268
5210
|
}
|
|
@@ -5283,7 +5225,7 @@ export class Orchestrator {
|
|
|
5283
5225
|
facts: terminalFacts,
|
|
5284
5226
|
winner: null,
|
|
5285
5227
|
runDir: paths.root,
|
|
5286
|
-
summary: redactSecrets(report).slice(0, 400),
|
|
5228
|
+
summary: withPoolAttempts(redactSecrets(report).slice(0, 400), reportPool),
|
|
5287
5229
|
candidates: candidateSummaries,
|
|
5288
5230
|
};
|
|
5289
5231
|
}
|