anchor-ai-sdk 0.1.8 → 0.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/README.md +186 -59
- package/dist/agents/coding/index.d.ts +1 -1
- package/dist/agents/coding/index.d.ts.map +1 -1
- package/dist/agents/coding/index.js +1 -1
- package/dist/agents/coding/index.js.map +1 -1
- package/dist/agents/coding/prompts/index.d.ts +11 -0
- package/dist/agents/coding/prompts/index.d.ts.map +1 -1
- package/dist/agents/coding/prompts/index.js +11 -0
- package/dist/agents/coding/prompts/index.js.map +1 -1
- package/dist/anchor.d.ts +35 -0
- package/dist/anchor.d.ts.map +1 -0
- package/dist/anchor.js +57 -0
- package/dist/anchor.js.map +1 -0
- package/dist/engine/compaction.d.ts +48 -1
- package/dist/engine/compaction.d.ts.map +1 -1
- package/dist/engine/compaction.js +210 -0
- package/dist/engine/compaction.js.map +1 -1
- package/dist/engine/context.d.ts +1 -1
- package/dist/engine/context.d.ts.map +1 -1
- package/dist/engine/context.js +6 -2
- package/dist/engine/context.js.map +1 -1
- package/dist/engine/events.d.ts +23 -2
- package/dist/engine/events.d.ts.map +1 -1
- package/dist/engine/inbox.d.ts +9 -8
- package/dist/engine/inbox.d.ts.map +1 -1
- package/dist/engine/inbox.js +13 -11
- package/dist/engine/inbox.js.map +1 -1
- package/dist/engine/loop.d.ts +4 -3
- package/dist/engine/loop.d.ts.map +1 -1
- package/dist/engine/loop.js +542 -179
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/prompt.d.ts +16 -0
- package/dist/engine/prompt.d.ts.map +1 -0
- package/dist/engine/prompt.js +8 -0
- package/dist/engine/prompt.js.map +1 -0
- package/dist/engine/strategy.d.ts +0 -1
- package/dist/engine/strategy.d.ts.map +1 -1
- package/dist/engine/thread.d.ts +4 -1
- package/dist/engine/thread.d.ts.map +1 -1
- package/dist/engine/thread.js.map +1 -1
- package/dist/engine/types.d.ts +25 -14
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/types.js +13 -1
- package/dist/engine/types.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger/alignment.d.ts +1 -1
- package/dist/ledger/alignment.d.ts.map +1 -1
- package/dist/ledger/alignment.js +1 -1
- package/dist/ledger/alignment.js.map +1 -1
- package/dist/ledger/compiler.d.ts +1 -1
- package/dist/ledger/compiler.d.ts.map +1 -1
- package/dist/ledger/compiler.js +11 -5
- package/dist/ledger/compiler.js.map +1 -1
- package/dist/ledger/config.d.ts +10 -3
- package/dist/ledger/config.d.ts.map +1 -1
- package/dist/ledger/index.d.ts +1 -0
- package/dist/ledger/index.d.ts.map +1 -1
- package/dist/ledger/index.js +1 -0
- package/dist/ledger/index.js.map +1 -1
- package/dist/ledger/ledger.d.ts +2 -1
- package/dist/ledger/ledger.d.ts.map +1 -1
- package/dist/ledger/ledger.js +10 -4
- package/dist/ledger/ledger.js.map +1 -1
- package/dist/ledger/types.d.ts +5 -0
- package/dist/ledger/types.d.ts.map +1 -1
- package/dist/llm/from-catalog.d.ts +2 -0
- package/dist/llm/from-catalog.d.ts.map +1 -1
- package/dist/llm/from-catalog.js +3 -0
- package/dist/llm/from-catalog.js.map +1 -1
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +1 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai-compatible.d.ts +7 -1
- package/dist/llm/openai-compatible.d.ts.map +1 -1
- package/dist/llm/openai-compatible.js +47 -11
- package/dist/llm/openai-compatible.js.map +1 -1
- package/dist/llm/types.d.ts +29 -0
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/lsp/index.d.ts +10 -5
- package/dist/lsp/index.d.ts.map +1 -1
- package/dist/lsp/index.js +6 -4
- package/dist/lsp/index.js.map +1 -1
- package/dist/lsp/python.d.ts +22 -0
- package/dist/lsp/python.d.ts.map +1 -0
- package/dist/lsp/python.js +33 -0
- package/dist/lsp/python.js.map +1 -0
- package/dist/lsp/server.d.ts +184 -0
- package/dist/lsp/server.d.ts.map +1 -0
- package/dist/lsp/server.js +687 -0
- package/dist/lsp/server.js.map +1 -0
- package/dist/lsp/tools.d.ts +16 -2
- package/dist/lsp/tools.d.ts.map +1 -1
- package/dist/lsp/tools.js +565 -27
- package/dist/lsp/tools.js.map +1 -1
- package/dist/lsp/typescript.d.ts +13 -54
- package/dist/lsp/typescript.d.ts.map +1 -1
- package/dist/lsp/typescript.js +37 -262
- package/dist/lsp/typescript.js.map +1 -1
- package/dist/sandbox/daytona/index.d.ts +3 -1
- package/dist/sandbox/daytona/index.d.ts.map +1 -1
- package/dist/sandbox/daytona/index.js +9 -0
- package/dist/sandbox/daytona/index.js.map +1 -1
- package/dist/sandbox/docker/index.d.ts.map +1 -1
- package/dist/sandbox/docker/index.js +1 -0
- package/dist/sandbox/docker/index.js.map +1 -1
- package/dist/sandbox/e2b/index.d.ts +2 -1
- package/dist/sandbox/e2b/index.d.ts.map +1 -1
- package/dist/sandbox/e2b/index.js +9 -0
- package/dist/sandbox/e2b/index.js.map +1 -1
- package/dist/sandbox/heyo/index.d.ts.map +1 -1
- package/dist/sandbox/heyo/index.js +1 -0
- package/dist/sandbox/heyo/index.js.map +1 -1
- package/dist/sandbox/local/index.d.ts.map +1 -1
- package/dist/sandbox/local/index.js +1 -0
- package/dist/sandbox/local/index.js.map +1 -1
- package/dist/sandbox/provider.d.ts +2 -0
- package/dist/sandbox/provider.d.ts.map +1 -1
- package/dist/sandbox/types.d.ts +11 -0
- package/dist/sandbox/types.d.ts.map +1 -1
- package/dist/sandbox/types.js.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +16 -0
- package/dist/testing.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/view-image.d.ts +20 -0
- package/dist/tools/view-image.d.ts.map +1 -0
- package/dist/tools/view-image.js +111 -0
- package/dist/tools/view-image.js.map +1 -0
- package/dist/verify/index.d.ts +3 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +2 -0
- package/dist/verify/index.js.map +1 -0
- package/dist/verify/judge.d.ts +42 -0
- package/dist/verify/judge.d.ts.map +1 -0
- package/dist/verify/judge.js +119 -0
- package/dist/verify/judge.js.map +1 -0
- package/package.json +1 -1
package/dist/engine/loop.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { createFinishTool } from "../tools/finish.js";
|
|
2
2
|
import { BudgetTracker, DEFAULT_BUDGET_LIMITS, DEFAULT_MAX_OUTPUT_TOKENS, } from "./budgets.js";
|
|
3
3
|
import { DEFAULT_TRUNCATED_REPLY_CHARS, DEFAULT_MAX_ASSISTANT_CHARS, elideReply, spillReply, } from "./elision.js";
|
|
4
|
-
import { compactConversation, DEFAULT_COMPACTION, estimateTokens, } from "./compaction.js";
|
|
5
|
-
import { assistantMessage, conversationHistory,
|
|
6
|
-
import {
|
|
4
|
+
import { compactConversation, compactConversationStructured, DEFAULT_COMPACTION, estimateTokens, renderCompactionHandoff, } from "./compaction.js";
|
|
5
|
+
import { assistantMessage, conversationHistory, } from "./context.js";
|
|
6
|
+
import { renderInboxTurn, } from "./inbox.js";
|
|
7
7
|
import { defineTool } from "../tools/types.js";
|
|
8
8
|
import { gitSnapshot } from "./snapshot.js";
|
|
9
9
|
import { TraceFanout } from "../trace/sink.js";
|
|
10
10
|
import { budgetWarning, revivalBlock, warnPercent as revivalWarnPercent, } from "./revival.js";
|
|
11
11
|
import { phaseDisciplineBlock } from "./phase-discipline.js";
|
|
12
|
-
import {
|
|
13
|
-
import { addRequirement, compileLedger, ledgerGate, linkEvidence, recordEvidence, renderLedger, setRequirementStatus,
|
|
14
|
-
import {
|
|
15
|
-
import { codingAgent } from "../agents/coding/index.js";
|
|
12
|
+
import { renderPromptBlocks } from "./prompt.js";
|
|
13
|
+
import { addRequirement, compileLedger, createLedger, ledgerGate, linkEvidence, recordEvidence, renderLedger, setRequirementStatus, } from "../ledger/index.js";
|
|
14
|
+
import { llmJudge } from "../verify/index.js";
|
|
16
15
|
import { dispatchToolCalls, } from "./dispatch.js";
|
|
17
16
|
import { mintOperationId, reconcilePendingOperations, settleFromResult, } from "./operations.js";
|
|
18
17
|
import { RepeatTracker, cappedReplyNudge, createCourseCorrect, emptyTurnNudge, proseCheckpoint as proseCheckpointMessage, repeatedCallNudge, silentNudge, verboseNudge, visibleOutputTokens, } from "./guardrails.js";
|
|
19
18
|
import { pollBackgroundJobs, renderBackgroundResult, renderLostJob, waitForBackgroundJobs, } from "./background.js";
|
|
20
|
-
|
|
19
|
+
export { userTurn } from "./types.js";
|
|
20
|
+
async function runOne(input, opts, chain = {}) {
|
|
21
21
|
const strategy = opts.strategy ?? opts.agent?.strategy;
|
|
22
22
|
if (!strategy)
|
|
23
23
|
throw new Error("runAgent needs an agent or a strategy — pass opts.agent (chatAgent, codingAgent, …) or opts.strategy");
|
|
@@ -114,58 +114,66 @@ async function runOne(task, opts, chain = {}) {
|
|
|
114
114
|
!strategy.phases.some((p) => p.id === opts.startPhase)) {
|
|
115
115
|
throw new Error(`startPhase "${opts.startPhase}" is not a phase of strategy "${strategy.name}"`);
|
|
116
116
|
}
|
|
117
|
-
const
|
|
117
|
+
const requestText = input.messages
|
|
118
|
+
.filter((message) => message.role === "user" && message.content?.trim())
|
|
119
|
+
.map((message) => message.content)
|
|
120
|
+
.join("\n\n");
|
|
121
|
+
const context = input.context ?? (await strategy.prepareContext?.(opts.sandbox, requestText));
|
|
118
122
|
const prior = await opts.thread?.store.load(opts.thread.id);
|
|
119
123
|
if (!opts.startPhase &&
|
|
120
124
|
prior?.phase &&
|
|
121
125
|
strategy.phases.some((item) => item.id === prior.phase))
|
|
122
126
|
phase = prior.phase;
|
|
123
127
|
let summary = prior?.summary;
|
|
128
|
+
let compaction = prior?.compaction;
|
|
124
129
|
const budget = chain.budget ?? new BudgetTracker(opts.budgets);
|
|
125
|
-
// ---- Task
|
|
130
|
+
// ---- Task contracts and the judge --------------------------------------
|
|
126
131
|
const verifierOn = opts.verifier !== undefined && opts.verifier !== false;
|
|
127
132
|
const verifierConfig = verifierOn && typeof opts.verifier === "object" ? opts.verifier : {};
|
|
128
|
-
const verifierLlm = verifierConfig.llm ?? opts.llm;
|
|
129
133
|
const verifierGates = new Set(verifierConfig.gates ?? ["phase", "finish"]);
|
|
130
|
-
const
|
|
131
|
-
|
|
134
|
+
const judge = verifierOn
|
|
135
|
+
? (verifierConfig.judge ?? llmJudge(verifierConfig.llm ?? opts.llm))
|
|
136
|
+
: undefined;
|
|
137
|
+
const maxJudgeCalls = verifierConfig.maxCallsPerTask ?? 8;
|
|
138
|
+
const maxPhaseRejections = verifierConfig.maxRejectionsPerPhase ?? 3;
|
|
139
|
+
let judgeCalls = 0;
|
|
140
|
+
let phaseRejections = 0;
|
|
141
|
+
let phaseEvidence = [];
|
|
142
|
+
const contractTaskId = opts.taskId ?? opts.thread?.id ?? `task-${Date.now()}`;
|
|
132
143
|
let ledger = opts.ledger ?? (verifierOn ? prior?.ledger : undefined);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
});
|
|
141
|
-
ledger = compiled.ledger;
|
|
142
|
-
if (compiled.usage)
|
|
143
|
-
budget.record(compiled.usage);
|
|
144
|
-
}
|
|
145
|
-
const recentEvidence = () => {
|
|
146
|
-
if (!ledger)
|
|
147
|
-
return "(none)";
|
|
148
|
-
return Object.values(ledger.evidence)
|
|
149
|
-
.slice(-6)
|
|
150
|
-
.map((item) => `${item.id} ${item.tool}${item.summary ? `: ${item.summary}` : ""}${item.ok ? "" : " (failed)"}`)
|
|
151
|
-
.join("\n");
|
|
152
|
-
};
|
|
153
|
-
const callVerifier = async (phaseGoal) => {
|
|
154
|
-
if (!verifierOn || !ledger || verifierCalls >= maxVerifierCalls) {
|
|
155
|
-
return { allow: true };
|
|
156
|
-
}
|
|
157
|
-
verifierCalls += 1;
|
|
144
|
+
// A settled ledger is history, not an active contract.
|
|
145
|
+
if (ledger && !hasActiveLedger(ledger))
|
|
146
|
+
ledger = undefined;
|
|
147
|
+
const callJudge = async (phaseGoal, requirements, agentReason) => {
|
|
148
|
+
if (!judge || !ledger || judgeCalls >= maxJudgeCalls)
|
|
149
|
+
return undefined;
|
|
150
|
+
judgeCalls += 1;
|
|
158
151
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
task: task.task,
|
|
152
|
+
const verdict = await judge.judge({
|
|
153
|
+
request: requestText,
|
|
162
154
|
phase: phaseGoal,
|
|
163
155
|
ledger,
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
evidenceSincePhase: [...phaseEvidence],
|
|
157
|
+
...(agentReason ? { agentReason } : {}),
|
|
158
|
+
}, signal);
|
|
159
|
+
if (verdict.usage)
|
|
160
|
+
budget.record(verdict.usage);
|
|
161
|
+
const wanted = new Set(requirements.map((item) => item.id));
|
|
162
|
+
const judged = verdict.requirements.filter((item) => wanted.has(item.id));
|
|
163
|
+
emit({
|
|
164
|
+
type: "phase_verdict",
|
|
165
|
+
phase: phaseGoal.id,
|
|
166
|
+
cleared: verdict.cleared,
|
|
167
|
+
confidence: verdict.confidence,
|
|
168
|
+
judge: judge.name,
|
|
169
|
+
requirements: judged.map((item) => ({
|
|
170
|
+
id: item.id,
|
|
171
|
+
status: item.status,
|
|
172
|
+
...(item.probability !== undefined
|
|
173
|
+
? { probability: item.probability }
|
|
174
|
+
: {}),
|
|
175
|
+
})),
|
|
166
176
|
});
|
|
167
|
-
if (usage)
|
|
168
|
-
budget.record(usage);
|
|
169
177
|
return verdict;
|
|
170
178
|
}
|
|
171
179
|
catch (error) {
|
|
@@ -174,72 +182,23 @@ async function runOne(task, opts, chain = {}) {
|
|
|
174
182
|
kind: "verifier_failed",
|
|
175
183
|
detail: error instanceof Error ? error.message : String(error),
|
|
176
184
|
});
|
|
177
|
-
|
|
178
|
-
allow: verifierConfig.onError !== "block",
|
|
179
|
-
aligned: false,
|
|
180
|
-
justified: true,
|
|
181
|
-
reason: "verifier unavailable",
|
|
182
|
-
requiredAction: "none",
|
|
183
|
-
confidence: 0,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
if (verifierOn && ledger) {
|
|
188
|
-
completionGate = async (summaryText) => {
|
|
189
|
-
const structural = ledgerGate(ledger, {
|
|
190
|
-
allowWaived: verifierConfig.allowWaived,
|
|
191
|
-
});
|
|
192
|
-
if (!structural.allow) {
|
|
185
|
+
if (verifierConfig.onError === "block") {
|
|
193
186
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"Attach evidence to each requirement with update_ledger, resolve failures, and try again.",
|
|
201
|
-
].join("\n"),
|
|
187
|
+
cleared: false,
|
|
188
|
+
requirements: requirements.map((item) => ({
|
|
189
|
+
id: item.id,
|
|
190
|
+
status: "unproven",
|
|
191
|
+
})),
|
|
192
|
+
confidence: 0,
|
|
202
193
|
};
|
|
203
194
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
id: "finish",
|
|
210
|
-
goal: "Deliver the task outcome with evidence.",
|
|
211
|
-
});
|
|
212
|
-
if (verdict.allow) {
|
|
213
|
-
verified = true;
|
|
214
|
-
return { ok: true, content: "" };
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
ok: false,
|
|
218
|
-
content: `Verification gate rejected completion: ${verdict.reason ?? "unsupported claim"}. ` +
|
|
219
|
-
`Required action: ${verdict.requiredAction ?? "gather_evidence"}. ` +
|
|
220
|
-
`Claim: ${summaryText.slice(0, 400)}`,
|
|
221
|
-
};
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
const currentRequest = userMessage(task.task);
|
|
225
|
-
const revivalOn = !!opts.revival;
|
|
226
|
-
const revivalWarnAt = revivalWarnPercent(opts.revival ? opts.revival.warnAtPercent : undefined);
|
|
227
|
-
let revivalWarned = false;
|
|
228
|
-
let revivalRequest;
|
|
229
|
-
const phaseOn = strategy.phases.length > 1 && opts.intrinsics?.phase !== false;
|
|
230
|
-
const system = () => ({
|
|
231
|
-
role: "system",
|
|
232
|
-
content: strategy.systemPrompt({ task: task.task, context, summary }) +
|
|
233
|
-
(phaseOn ? `\n\n${phaseDisciplineBlock(strategy.phases)}` : "") +
|
|
234
|
-
(revivalOn ? `\n\n${revivalBlock(revivalWarnAt)}` : "") +
|
|
235
|
-
(ledger
|
|
236
|
-
? `\n\nPROGRESS LEDGER (authoritative task state):\n${renderLedger(ledger)}\n\nUse update_ledger to link evidence to each requirement and mark it verified. Do not claim completion while a required item is open or lacks evidence.`
|
|
237
|
-
: ""),
|
|
238
|
-
});
|
|
239
|
-
if (ledger) {
|
|
240
|
-
const updateLedger = defineTool({
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const updateLedger = () => {
|
|
199
|
+
const tool = defineTool({
|
|
241
200
|
name: "update_ledger",
|
|
242
|
-
description: "Update the task contract.
|
|
201
|
+
description: "Update the task contract. Link a tool result to a requirement with a claim, verify it, record an open failure, or set the next step. Never invent evidence ids.",
|
|
243
202
|
parameters: {
|
|
244
203
|
type: "object",
|
|
245
204
|
properties: {
|
|
@@ -248,6 +207,7 @@ async function runOne(task, opts, chain = {}) {
|
|
|
248
207
|
enum: [
|
|
249
208
|
"add_requirement",
|
|
250
209
|
"link_evidence",
|
|
210
|
+
"verify",
|
|
251
211
|
"set_status",
|
|
252
212
|
"set_next_step",
|
|
253
213
|
"add_failure",
|
|
@@ -256,8 +216,10 @@ async function runOne(task, opts, chain = {}) {
|
|
|
256
216
|
},
|
|
257
217
|
text: { type: "string" },
|
|
258
218
|
required: { type: "boolean" },
|
|
219
|
+
phase: { type: "string" },
|
|
259
220
|
requirementId: { type: "string" },
|
|
260
221
|
evidenceId: { type: "string" },
|
|
222
|
+
claim: { type: "string" },
|
|
261
223
|
status: {
|
|
262
224
|
type: "string",
|
|
263
225
|
enum: ["open", "in_progress", "verified", "blocked", "waived"],
|
|
@@ -273,16 +235,42 @@ async function runOne(task, opts, chain = {}) {
|
|
|
273
235
|
const text = String(input.text ?? "").trim();
|
|
274
236
|
if (!text)
|
|
275
237
|
return { ok: false, content: "text is required" };
|
|
276
|
-
const requirement = addRequirement(ledger, {
|
|
238
|
+
const requirement = addRequirement(ledger, {
|
|
239
|
+
text,
|
|
240
|
+
required: input.required !== false,
|
|
241
|
+
...(typeof input.phase === "string" && input.phase
|
|
242
|
+
? { phase: input.phase }
|
|
243
|
+
: {}),
|
|
244
|
+
}, "agent");
|
|
277
245
|
return {
|
|
278
246
|
ok: true,
|
|
279
247
|
content: `added ${requirement.id}: ${requirement.text}`,
|
|
280
248
|
};
|
|
281
249
|
}
|
|
282
250
|
if (op === "link_evidence") {
|
|
283
|
-
|
|
251
|
+
const claim = String(input.claim ?? "").trim();
|
|
252
|
+
if (!claim) {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
content: "claim is required: explain why this evidence establishes the requirement",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
linkEvidence(ledger, String(input.requirementId ?? ""), String(input.evidenceId ?? ""), claim);
|
|
284
259
|
return { ok: true, content: "evidence linked" };
|
|
285
260
|
}
|
|
261
|
+
if (op === "verify") {
|
|
262
|
+
const claim = String(input.claim ?? "").trim();
|
|
263
|
+
if (!claim) {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
content: "claim is required: explain why this evidence establishes the requirement",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const requirementId = String(input.requirementId ?? "");
|
|
270
|
+
linkEvidence(ledger, requirementId, String(input.evidenceId ?? ""), claim);
|
|
271
|
+
setRequirementStatus(ledger, requirementId, "verified");
|
|
272
|
+
return { ok: true, content: "evidence linked and requirement verified" };
|
|
273
|
+
}
|
|
286
274
|
if (op === "set_status") {
|
|
287
275
|
const status = String(input.status ?? "");
|
|
288
276
|
if (status === "superseded") {
|
|
@@ -319,12 +307,167 @@ async function runOne(task, opts, chain = {}) {
|
|
|
319
307
|
}
|
|
320
308
|
return { ok: false, content: `unknown ledger op "${op}"` };
|
|
321
309
|
}, { verb: "Updating the progress ledger" });
|
|
322
|
-
available.set("update_ledger",
|
|
310
|
+
available.set("update_ledger", tool);
|
|
311
|
+
return tool;
|
|
312
|
+
};
|
|
313
|
+
const installCompletionGate = () => {
|
|
314
|
+
completionGate = async (summaryText, success) => {
|
|
315
|
+
const active = ledger;
|
|
316
|
+
const structural = ledgerGate(active, {
|
|
317
|
+
allowWaived: verifierConfig.allowWaived,
|
|
318
|
+
});
|
|
319
|
+
if (!structural.allow) {
|
|
320
|
+
if (!success) {
|
|
321
|
+
// The agent explicitly reported a blocked/unsuccessful outcome. End
|
|
322
|
+
// honestly instead of forcing it into the stalled path.
|
|
323
|
+
for (const id of [...structural.open, ...structural.missingEvidence]) {
|
|
324
|
+
const requirement = active.requirements.find((item) => item.id === id);
|
|
325
|
+
if (requirement) {
|
|
326
|
+
const note = `${id}: ${requirement.text}`;
|
|
327
|
+
if (!active.openFailures.includes(note))
|
|
328
|
+
active.openFailures.push(note);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
for (const failure of structural.failures) {
|
|
332
|
+
if (!active.openFailures.includes(failure)) {
|
|
333
|
+
active.openFailures.push(failure);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { ok: true, content: "" };
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
ok: false,
|
|
340
|
+
content: [
|
|
341
|
+
"Verification gate: required work is not complete.",
|
|
342
|
+
`open: ${structural.open.join(", ") || "none"}`,
|
|
343
|
+
`missingEvidence: ${structural.missingEvidence.join(", ") || "none"}`,
|
|
344
|
+
`failures: ${structural.failures.join(" | ") || "none"}`,
|
|
345
|
+
"Attach evidence to each requirement with update_ledger, resolve failures, and try again.",
|
|
346
|
+
].join("\n"),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
if (!success)
|
|
350
|
+
return { ok: true, content: "" };
|
|
351
|
+
if (!verifierGates.has("finish")) {
|
|
352
|
+
verified = true;
|
|
353
|
+
return { ok: true, content: "" };
|
|
354
|
+
}
|
|
355
|
+
const required = active.requirements.filter((item) => item.required && item.status !== "superseded");
|
|
356
|
+
const verdict = await callJudge({ id: "finish", goal: "Deliver the task outcome with evidence." }, required, summaryText);
|
|
357
|
+
if (!verdict) {
|
|
358
|
+
verified = verifierConfig.onError !== "block";
|
|
359
|
+
return verified
|
|
360
|
+
? { ok: true, content: "" }
|
|
361
|
+
: {
|
|
362
|
+
ok: false,
|
|
363
|
+
content: "Verification gate unavailable and onError is block; retry the judge before finishing.",
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const wanted = new Set(required.map((item) => item.id));
|
|
367
|
+
const unmet = verdict.requirements.filter((item) => wanted.has(item.id) && item.status !== "cleared");
|
|
368
|
+
if (verdict.cleared && unmet.length === 0) {
|
|
369
|
+
verified = true;
|
|
370
|
+
return { ok: true, content: "" };
|
|
371
|
+
}
|
|
372
|
+
const details = unmet.map((item) => {
|
|
373
|
+
const requirement = active.requirements.find((r) => r.id === item.id);
|
|
374
|
+
const claim = requirement?.evidenceIds
|
|
375
|
+
.map((id) => active.evidence[id]?.claim)
|
|
376
|
+
.find(Boolean);
|
|
377
|
+
const probability = item.probability !== undefined
|
|
378
|
+
? ` p=${item.probability.toFixed(2)}`
|
|
379
|
+
: "";
|
|
380
|
+
return `${item.id}${probability} ${item.status}: ${requirement?.text ?? "unknown requirement"}${claim ? `; claim: ${claim}` : ""}`;
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
ok: false,
|
|
384
|
+
content: `Verification gate rejected completion: ${verdict.rationale ?? "unsupported claim"}.\n` +
|
|
385
|
+
(details.length
|
|
386
|
+
? `Unmet requirements:\n${details.join("\n")}`
|
|
387
|
+
: "The judge did not clear the contract."),
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
};
|
|
391
|
+
const attachLedger = (next) => {
|
|
392
|
+
ledger = next;
|
|
393
|
+
phaseRejections = 0;
|
|
394
|
+
phaseEvidence = [];
|
|
395
|
+
updateLedger();
|
|
396
|
+
installCompletionGate();
|
|
397
|
+
};
|
|
398
|
+
if (verifierOn && ledger)
|
|
399
|
+
attachLedger(ledger);
|
|
400
|
+
else if (verifierOn && verifierConfig.create === "start") {
|
|
401
|
+
const compiled = await compileLedger({
|
|
402
|
+
llm: verifierConfig.llm ?? opts.llm,
|
|
403
|
+
taskId: contractTaskId,
|
|
404
|
+
source: requestText,
|
|
405
|
+
phases: strategy.phases,
|
|
406
|
+
reasoningEffort: "low",
|
|
407
|
+
});
|
|
408
|
+
if (compiled.usage)
|
|
409
|
+
budget.record(compiled.usage);
|
|
410
|
+
attachLedger(compiled.ledger);
|
|
323
411
|
}
|
|
412
|
+
const currentRequests = input.messages;
|
|
413
|
+
const revivalOn = !!opts.revival;
|
|
414
|
+
const revivalWarnAt = revivalWarnPercent(opts.revival ? opts.revival.warnAtPercent : undefined);
|
|
415
|
+
let revivalWarned = false;
|
|
416
|
+
let revivalRequest;
|
|
417
|
+
const phaseOn = strategy.phases.length > 1 && opts.intrinsics?.phase !== false;
|
|
418
|
+
/**
|
|
419
|
+
* Stable blocks are the cacheable prefix; turn-scoped blocks are appended
|
|
420
|
+
* after the conversation. The ledger changes after every tool result, so it
|
|
421
|
+
* must never live in the system message.
|
|
422
|
+
*/
|
|
423
|
+
const promptBlocks = () => [
|
|
424
|
+
{
|
|
425
|
+
id: "agent",
|
|
426
|
+
text: strategy.systemPrompt({ context, summary }),
|
|
427
|
+
stability: "stable",
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "phase-discipline",
|
|
431
|
+
text: phaseOn ? phaseDisciplineBlock(strategy.phases) : undefined,
|
|
432
|
+
stability: "stable",
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: "budget-revival",
|
|
436
|
+
text: revivalOn ? revivalBlock(revivalWarnAt) : undefined,
|
|
437
|
+
stability: "stable",
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: "ledger",
|
|
441
|
+
text: ledger
|
|
442
|
+
? [
|
|
443
|
+
"## Runtime task state",
|
|
444
|
+
"This is authoritative state, not a new user request.",
|
|
445
|
+
renderLedger(ledger),
|
|
446
|
+
"Use update_ledger to link evidence to each requirement and mark it verified. Do not claim completion while a required item is open or lacks evidence.",
|
|
447
|
+
].join("\n\n")
|
|
448
|
+
: undefined,
|
|
449
|
+
stability: "turn",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "compaction",
|
|
453
|
+
text: compaction ? renderCompactionHandoff(compaction) : undefined,
|
|
454
|
+
stability: "turn",
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: "phase-state",
|
|
458
|
+
text: phase ? `Current phase: ${phase}` : undefined,
|
|
459
|
+
stability: "turn",
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
const system = () => ({
|
|
463
|
+
role: "system",
|
|
464
|
+
content: renderPromptBlocks(promptBlocks(), "stable"),
|
|
465
|
+
});
|
|
466
|
+
const turnState = () => renderPromptBlocks(promptBlocks(), "turn");
|
|
324
467
|
const messages = [
|
|
325
468
|
system(),
|
|
326
469
|
...conversationHistory(prior?.messages ?? []),
|
|
327
|
-
|
|
470
|
+
...currentRequests,
|
|
328
471
|
];
|
|
329
472
|
const compact = opts.compaction === false
|
|
330
473
|
? null
|
|
@@ -332,7 +475,46 @@ async function runOne(task, opts, chain = {}) {
|
|
|
332
475
|
// The model's view of the transcript, bounded by assistant-text elision
|
|
333
476
|
// (#77). `messages` stays whole — this only changes what is SENT.
|
|
334
477
|
const elidedByMessage = new WeakMap();
|
|
335
|
-
const forModel = (
|
|
478
|
+
const forModel = (includeTurn = true) => {
|
|
479
|
+
const out = messages.map((m) => elidedByMessage.get(m) ?? m);
|
|
480
|
+
if (!includeTurn)
|
|
481
|
+
return out;
|
|
482
|
+
const turn = turnState();
|
|
483
|
+
if (turn) {
|
|
484
|
+
// Keep the turn's final message last (tests and tool protocol both rely
|
|
485
|
+
// on it); put the changing runtime state immediately before it so the
|
|
486
|
+
// stable prefix is still reused.
|
|
487
|
+
const insertAt = Math.max(1, out.length - 1);
|
|
488
|
+
out.splice(insertAt, 0, {
|
|
489
|
+
role: "user",
|
|
490
|
+
content: turn,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
return out;
|
|
494
|
+
};
|
|
495
|
+
/**
|
|
496
|
+
* Cache the stable prefix, never the turn-scoped block. The adapter only
|
|
497
|
+
* emits `cache_control` when the consumer enabled `promptCache`.
|
|
498
|
+
*/
|
|
499
|
+
const cacheBreakpointsFor = (modelMessages) => {
|
|
500
|
+
const markable = (message) => !!message &&
|
|
501
|
+
(message.role === "system" ||
|
|
502
|
+
message.role === "user" ||
|
|
503
|
+
(message.role === "assistant" && !!message.content));
|
|
504
|
+
const indexes = new Set();
|
|
505
|
+
if (markable(modelMessages[0]))
|
|
506
|
+
indexes.add(0);
|
|
507
|
+
const turn = turnState();
|
|
508
|
+
const turnIndex = turn
|
|
509
|
+
? modelMessages.findIndex((message) => message.role === "user" && message.content === turn)
|
|
510
|
+
: -1;
|
|
511
|
+
let end = turnIndex > 0 ? turnIndex - 1 : modelMessages.length - 1;
|
|
512
|
+
while (end >= 0 && !markable(modelMessages[end]))
|
|
513
|
+
end--;
|
|
514
|
+
if (end >= 0)
|
|
515
|
+
indexes.add(end);
|
|
516
|
+
return [...indexes];
|
|
517
|
+
};
|
|
336
518
|
const evidence = [];
|
|
337
519
|
// Commands the provider detached. The engine owns their results: it polls at
|
|
338
520
|
// every turn boundary and, before a normal finish, waits for what is left.
|
|
@@ -513,31 +695,76 @@ async function runOne(task, opts, chain = {}) {
|
|
|
513
695
|
content: `Unknown phase. Available: ${strategy.phases.map((item) => item.id).join(", ")}.`,
|
|
514
696
|
};
|
|
515
697
|
const previous = phase;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return {
|
|
528
|
-
ok: false,
|
|
529
|
-
content: [
|
|
530
|
-
`Phase transition rejected: ${verdict.reason}`,
|
|
531
|
-
`Required action: ${verdict.requiredAction}`,
|
|
532
|
-
"Gather the missing evidence or update the ledger, then transition again.",
|
|
533
|
-
].join("\n"),
|
|
698
|
+
let forced = false;
|
|
699
|
+
if (target.id !== previous && ledger && verifierGates.has("phase")) {
|
|
700
|
+
const owned = ledger.requirements.filter((item) => item.required &&
|
|
701
|
+
item.phase === previous &&
|
|
702
|
+
item.status !== "verified" &&
|
|
703
|
+
item.status !== "waived" &&
|
|
704
|
+
item.status !== "superseded");
|
|
705
|
+
if (owned.length > 0) {
|
|
706
|
+
const outgoing = strategy.phases.find((item) => item.id === previous) ?? {
|
|
707
|
+
id: previous,
|
|
708
|
+
goal: previous,
|
|
534
709
|
};
|
|
710
|
+
const verdict = await callJudge(outgoing, owned, typeof input.reason === "string" ? input.reason : undefined);
|
|
711
|
+
const wanted = new Set(owned.map((item) => item.id));
|
|
712
|
+
const unmet = verdict
|
|
713
|
+
? verdict.requirements.filter((item) => wanted.has(item.id) && item.status !== "cleared")
|
|
714
|
+
: [];
|
|
715
|
+
if (verdict && (!verdict.cleared || unmet.length > 0)) {
|
|
716
|
+
phaseRejections += 1;
|
|
717
|
+
const details = unmet.map((item) => {
|
|
718
|
+
const requirement = ledger.requirements.find((row) => row.id === item.id);
|
|
719
|
+
const claim = requirement?.evidenceIds
|
|
720
|
+
.map((id) => ledger.evidence[id]?.claim)
|
|
721
|
+
.find(Boolean);
|
|
722
|
+
const probability = item.probability !== undefined
|
|
723
|
+
? ` p=${item.probability.toFixed(2)}`
|
|
724
|
+
: "";
|
|
725
|
+
return `${item.id}${probability} ${item.status}: ${requirement?.text ?? "unknown requirement"}${claim ? `; claim: ${claim}` : ""}`;
|
|
726
|
+
});
|
|
727
|
+
const detail = `Phase ${previous} has unmet requirements.` +
|
|
728
|
+
(details.length ? `\n${details.join("\n")}` : "");
|
|
729
|
+
emit({
|
|
730
|
+
type: "guardrail",
|
|
731
|
+
kind: "transition_rejected",
|
|
732
|
+
detail: `${previous} -> ${target.id}: ${detail}`,
|
|
733
|
+
});
|
|
734
|
+
if (phaseRejections <= maxPhaseRejections) {
|
|
735
|
+
return {
|
|
736
|
+
ok: false,
|
|
737
|
+
content: [
|
|
738
|
+
`Phase transition rejected: ${previous} is not complete.`,
|
|
739
|
+
...(details.length ? details : ["The judge did not clear the phase."]),
|
|
740
|
+
`Stay in ${previous}, finish the owned requirements, then transition again.`,
|
|
741
|
+
].join("\n"),
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
for (const item of owned) {
|
|
745
|
+
if (item.status !== "blocked" && item.status !== "verified") {
|
|
746
|
+
setRequirementStatus(ledger, item.id, "in_progress");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
forced = true;
|
|
750
|
+
emit({
|
|
751
|
+
type: "guardrail",
|
|
752
|
+
kind: "transition_rejected",
|
|
753
|
+
detail: `forcing ${previous} -> ${target.id} after ${phaseRejections} rejection(s); requirements remain open and finish is still gated`,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
535
756
|
}
|
|
536
757
|
}
|
|
537
758
|
phase = target.id;
|
|
759
|
+
phaseRejections = 0;
|
|
760
|
+
phaseEvidence = [];
|
|
538
761
|
pauseRequested =
|
|
539
762
|
opts.pauseAfterPhase === previous && previous !== phase;
|
|
540
|
-
emit({
|
|
763
|
+
emit({
|
|
764
|
+
type: "phase_started",
|
|
765
|
+
phase,
|
|
766
|
+
...(forced ? { via: "advance_forced" } : {}),
|
|
767
|
+
});
|
|
541
768
|
return {
|
|
542
769
|
ok: true,
|
|
543
770
|
content: `Current approach: ${target.id} — ${target.goal}`,
|
|
@@ -592,6 +819,122 @@ async function runOne(task, opts, chain = {}) {
|
|
|
592
819
|
};
|
|
593
820
|
}, { verb: "Requesting budget for the remaining work" }));
|
|
594
821
|
}
|
|
822
|
+
if (verifierOn && opts.intrinsics?.task !== false) {
|
|
823
|
+
available.set("create_task", defineTool({
|
|
824
|
+
name: "create_task",
|
|
825
|
+
description: "Create the task contract for this turn when the user asked for real work. " +
|
|
826
|
+
"State a measurable goal and 1-12 independently checkable requirements, each assigned " +
|
|
827
|
+
"to one of the strategy's phases. Do not include a source field; the engine derives " +
|
|
828
|
+
"the authoritative request from the user's messages.",
|
|
829
|
+
parameters: {
|
|
830
|
+
type: "object",
|
|
831
|
+
properties: {
|
|
832
|
+
goal: { type: "string" },
|
|
833
|
+
requirements: {
|
|
834
|
+
type: "array",
|
|
835
|
+
minItems: 1,
|
|
836
|
+
maxItems: 12,
|
|
837
|
+
items: {
|
|
838
|
+
type: "object",
|
|
839
|
+
properties: {
|
|
840
|
+
text: { type: "string" },
|
|
841
|
+
required: { type: "boolean" },
|
|
842
|
+
phase: {
|
|
843
|
+
type: "string",
|
|
844
|
+
enum: strategy.phases.map((item) => item.id),
|
|
845
|
+
},
|
|
846
|
+
sourceQuote: { type: "string" },
|
|
847
|
+
},
|
|
848
|
+
required: ["text", "phase"],
|
|
849
|
+
additionalProperties: false,
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
nextStep: { type: "string" },
|
|
853
|
+
},
|
|
854
|
+
required: ["goal", "requirements"],
|
|
855
|
+
additionalProperties: false,
|
|
856
|
+
},
|
|
857
|
+
}, async (input) => {
|
|
858
|
+
if (ledger) {
|
|
859
|
+
return {
|
|
860
|
+
ok: false,
|
|
861
|
+
content: "the contract is already active; use update_ledger to change its state",
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
const goal = String(input.goal ?? "").trim();
|
|
865
|
+
if (!goal)
|
|
866
|
+
return { ok: false, content: "goal is required" };
|
|
867
|
+
const rows = Array.isArray(input.requirements)
|
|
868
|
+
? input.requirements
|
|
869
|
+
: [];
|
|
870
|
+
if (rows.length < 1 || rows.length > 12) {
|
|
871
|
+
return {
|
|
872
|
+
ok: false,
|
|
873
|
+
content: "requirements must contain 1-12 items",
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
const phaseIds = new Set(strategy.phases.map((item) => item.id));
|
|
877
|
+
const requirements = [];
|
|
878
|
+
for (const [index, row] of rows.entries()) {
|
|
879
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
880
|
+
return {
|
|
881
|
+
ok: false,
|
|
882
|
+
content: `requirement ${index} must be an object`,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
const entry = row;
|
|
886
|
+
const text = String(entry.text ?? "").trim();
|
|
887
|
+
const phase = String(entry.phase ?? "");
|
|
888
|
+
if (!text) {
|
|
889
|
+
return { ok: false, content: `requirement ${index} needs text` };
|
|
890
|
+
}
|
|
891
|
+
if (!phaseIds.has(phase)) {
|
|
892
|
+
return {
|
|
893
|
+
ok: false,
|
|
894
|
+
content: `requirement ${index} has unknown phase "${phase}"`,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const sourceQuote = typeof entry.sourceQuote === "string"
|
|
898
|
+
? entry.sourceQuote
|
|
899
|
+
: undefined;
|
|
900
|
+
if (sourceQuote && !requestText.includes(sourceQuote)) {
|
|
901
|
+
return {
|
|
902
|
+
ok: false,
|
|
903
|
+
content: `requirement ${index} sourceQuote is not in the user's request`,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
requirements.push({
|
|
907
|
+
text,
|
|
908
|
+
required: entry.required !== false,
|
|
909
|
+
phase,
|
|
910
|
+
...(sourceQuote ? { sourceQuote } : {}),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
const next = createLedger({
|
|
914
|
+
taskId: contractTaskId,
|
|
915
|
+
source: { kind: "user", text: requestText },
|
|
916
|
+
goal,
|
|
917
|
+
requirements: requirements.map((item) => ({
|
|
918
|
+
...item,
|
|
919
|
+
origin: "agent",
|
|
920
|
+
})),
|
|
921
|
+
});
|
|
922
|
+
if (typeof input.nextStep === "string" && input.nextStep.trim()) {
|
|
923
|
+
next.nextStep = input.nextStep.trim();
|
|
924
|
+
}
|
|
925
|
+
attachLedger(next);
|
|
926
|
+
emit({ type: "task_created", ledger: next });
|
|
927
|
+
return {
|
|
928
|
+
ok: true,
|
|
929
|
+
content: [
|
|
930
|
+
`Contract created: ${next.goal}`,
|
|
931
|
+
...next.requirements.map((item) => `${item.id}: ${item.text} (phase ${item.phase ?? "any"})`),
|
|
932
|
+
"Use update_ledger to link evidence with a claim and verify each requirement.",
|
|
933
|
+
].join("\n"),
|
|
934
|
+
meta: { ledger: next },
|
|
935
|
+
};
|
|
936
|
+
}, { verb: "Creating the task contract" }));
|
|
937
|
+
}
|
|
595
938
|
/**
|
|
596
939
|
* Settle operations only AFTER the conversation that contains their results
|
|
597
940
|
* is durable. The opposite order opens the window this whole layer exists to
|
|
@@ -613,6 +956,7 @@ async function runOne(task, opts, chain = {}) {
|
|
|
613
956
|
summary,
|
|
614
957
|
messages: conversationHistory(messages),
|
|
615
958
|
phase: done ? resumePhase : phase,
|
|
959
|
+
...(compaction ? { compaction } : {}),
|
|
616
960
|
...(ledger ?? prior?.ledger
|
|
617
961
|
? { ledger: ledger ?? prior?.ledger }
|
|
618
962
|
: {}),
|
|
@@ -696,6 +1040,7 @@ async function runOne(task, opts, chain = {}) {
|
|
|
696
1040
|
phase,
|
|
697
1041
|
evidence,
|
|
698
1042
|
...(ledger ? { ledger } : {}),
|
|
1043
|
+
...(compaction ? { compaction } : {}),
|
|
699
1044
|
...(verifierOn ? { verified } : {}),
|
|
700
1045
|
...(revivalRequest &&
|
|
701
1046
|
(reason === "budget_exceeded" || reason === "max_steps")
|
|
@@ -788,7 +1133,8 @@ async function runOne(task, opts, chain = {}) {
|
|
|
788
1133
|
}
|
|
789
1134
|
emit({
|
|
790
1135
|
type: "run_started",
|
|
791
|
-
|
|
1136
|
+
text: requestText,
|
|
1137
|
+
...(input.messages.length ? { messages: input.messages } : {}),
|
|
792
1138
|
phases: strategy.phases.map((item) => item.id),
|
|
793
1139
|
});
|
|
794
1140
|
if (chain.delivered?.length)
|
|
@@ -931,34 +1277,64 @@ async function runOne(task, opts, chain = {}) {
|
|
|
931
1277
|
}
|
|
932
1278
|
}
|
|
933
1279
|
if (compact && estimateTokens(messages) > compact.maxEstimatedTokens) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1280
|
+
let folded = null;
|
|
1281
|
+
if (ledger) {
|
|
1282
|
+
try {
|
|
1283
|
+
folded = await compactConversationStructured({
|
|
1284
|
+
llm: opts.llm,
|
|
1285
|
+
messages: forModel(false),
|
|
1286
|
+
...(summary ? { previousSummary: summary } : {}),
|
|
1287
|
+
...(compaction ? { previousHandoff: compaction } : {}),
|
|
1288
|
+
ledger,
|
|
1289
|
+
...(checkpointRef ? { git: { checkpoint: checkpointRef } } : {}),
|
|
1290
|
+
keepRecentTokens: compact.keepRecentTokens,
|
|
1291
|
+
signal,
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
folded = null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (!folded) {
|
|
1299
|
+
folded = await compactConversation({
|
|
1300
|
+
llm: opts.llm,
|
|
1301
|
+
messages: forModel(false),
|
|
1302
|
+
runningSummary: summary,
|
|
1303
|
+
keepRecentTokens: compact.keepRecentTokens,
|
|
1304
|
+
signal,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
941
1307
|
if (folded) {
|
|
942
1308
|
budget.record(folded.usage);
|
|
943
|
-
|
|
1309
|
+
if (folded.handoff) {
|
|
1310
|
+
compaction = folded.handoff;
|
|
1311
|
+
summary = undefined;
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
summary = folded.summary;
|
|
1315
|
+
compaction = undefined;
|
|
1316
|
+
}
|
|
944
1317
|
const tail = folded.keptMessages;
|
|
945
|
-
// After a
|
|
946
|
-
//
|
|
1318
|
+
// After a task reset the handoff is the durable record. A legacy
|
|
1319
|
+
// fold still points at the working-notes file.
|
|
947
1320
|
const foldNote = {
|
|
948
1321
|
role: "user",
|
|
949
|
-
content:
|
|
950
|
-
"
|
|
951
|
-
"
|
|
1322
|
+
content: folded.handoff
|
|
1323
|
+
? "[runtime] Context was reset to a durable task handoff. The authoritative task, requirements, evidence, failures, next steps, and recent observations are in the turn-scoped handoff; recent messages are retained verbatim."
|
|
1324
|
+
: "[runtime] Context was folded to stay within the window. Your working notes " +
|
|
1325
|
+
"file (.anchor/notes.md, git-excluded) is the durable record of findings and " +
|
|
1326
|
+
"next steps — re-read it when you need detail from before the fold.",
|
|
952
1327
|
};
|
|
953
1328
|
// Preserve the actual current request even if its earlier position
|
|
954
1329
|
// was folded. Do not replace it with a generic "Begin" instruction.
|
|
955
|
-
messages.splice(0, messages.length, system(), ...(tail.includes(
|
|
1330
|
+
messages.splice(0, messages.length, system(), ...currentRequests.filter((message) => !tail.includes(message)), foldNote, ...tail);
|
|
956
1331
|
emit({
|
|
957
1332
|
type: "compacted",
|
|
958
1333
|
removedMessages: folded.removedCount,
|
|
959
1334
|
keptMessages: tail.length,
|
|
960
|
-
summaryChars: summary.length,
|
|
961
|
-
summary,
|
|
1335
|
+
summaryChars: folded.summary.length,
|
|
1336
|
+
summary: folded.summary,
|
|
1337
|
+
...(folded.handoff ? { compaction: folded.handoff } : {}),
|
|
962
1338
|
folding: folded.foldingPreview,
|
|
963
1339
|
usage: folded.usage,
|
|
964
1340
|
});
|
|
@@ -975,14 +1351,12 @@ async function runOne(task, opts, chain = {}) {
|
|
|
975
1351
|
// fold. A job that finished since the previous turn (including one
|
|
976
1352
|
// carried in from a previous run) rides this call, not the one after.
|
|
977
1353
|
await pollJobs();
|
|
978
|
-
// Refresh the authoritative contract every turn so evidence ids and
|
|
979
|
-
// status changes are visible after each tool result.
|
|
980
|
-
if (ledger)
|
|
981
|
-
messages[0] = system();
|
|
982
1354
|
steps++;
|
|
983
1355
|
emit({ type: "llm_call_started", phase, step: steps });
|
|
1356
|
+
const modelMessages = forModel();
|
|
984
1357
|
const response = await opts.llm.stream({
|
|
985
|
-
messages:
|
|
1358
|
+
messages: modelMessages,
|
|
1359
|
+
cacheBreakpoints: cacheBreakpointsFor(modelMessages),
|
|
986
1360
|
tools: [...available.values()].map((tool) => ({
|
|
987
1361
|
type: "function",
|
|
988
1362
|
function: tool.definition,
|
|
@@ -1171,13 +1545,14 @@ async function runOne(task, opts, chain = {}) {
|
|
|
1171
1545
|
call.name === "update_ledger") {
|
|
1172
1546
|
return;
|
|
1173
1547
|
}
|
|
1174
|
-
recordEvidence(ledger, {
|
|
1548
|
+
const evidenceRef = recordEvidence(ledger, {
|
|
1175
1549
|
tool: call.name,
|
|
1176
1550
|
ref: `call:${call.id}`,
|
|
1177
1551
|
ok: result.ok,
|
|
1178
1552
|
summary: result.content.replace(/\s+/g, " ").slice(0, 500),
|
|
1179
1553
|
...(result.meta ? { meta: result.meta } : {}),
|
|
1180
1554
|
});
|
|
1555
|
+
phaseEvidence.push(evidenceRef);
|
|
1181
1556
|
},
|
|
1182
1557
|
});
|
|
1183
1558
|
runaway = outcome.runaway;
|
|
@@ -1308,6 +1683,12 @@ function usageDelta(now, before) {
|
|
|
1308
1683
|
: {}),
|
|
1309
1684
|
};
|
|
1310
1685
|
}
|
|
1686
|
+
/** A required obligation still needs work, or an open failure blocks finish. */
|
|
1687
|
+
function hasActiveLedger(ledger) {
|
|
1688
|
+
if (!ledger)
|
|
1689
|
+
return false;
|
|
1690
|
+
return !ledgerGate(ledger).allow;
|
|
1691
|
+
}
|
|
1311
1692
|
/**
|
|
1312
1693
|
* A run chain (decision #82): one run at a time, with the consumer's inbox as
|
|
1313
1694
|
* the source of what comes next. The chain shares one budget and carries
|
|
@@ -1318,7 +1699,7 @@ function usageDelta(now, before) {
|
|
|
1318
1699
|
* cancellation, a caller-requested pause, or an exhausted budget — and returns
|
|
1319
1700
|
* the last run's result with every run summarized on `.runs`.
|
|
1320
1701
|
*/
|
|
1321
|
-
async function runChain(
|
|
1702
|
+
async function runChain(input, opts) {
|
|
1322
1703
|
const inbox = opts.inbox;
|
|
1323
1704
|
const { store, channel } = inbox;
|
|
1324
1705
|
if (!channel)
|
|
@@ -1337,7 +1718,7 @@ async function runChain(task, opts) {
|
|
|
1337
1718
|
};
|
|
1338
1719
|
let stepsBefore = 0;
|
|
1339
1720
|
let jobs;
|
|
1340
|
-
let current =
|
|
1721
|
+
let current = input;
|
|
1341
1722
|
let delivered;
|
|
1342
1723
|
let claimed = [];
|
|
1343
1724
|
let result;
|
|
@@ -1404,33 +1785,15 @@ async function runChain(task, opts) {
|
|
|
1404
1785
|
return { ...result, runs };
|
|
1405
1786
|
claimed = batch.map((message) => message.id);
|
|
1406
1787
|
delivered = batch;
|
|
1407
|
-
current = inbox.render ? inbox.render(batch) :
|
|
1788
|
+
current = inbox.render ? inbox.render(batch) : renderInboxTurn(batch);
|
|
1408
1789
|
}
|
|
1409
1790
|
// The zero-progress guard fired. Report the last run as it stands; `.runs`
|
|
1410
1791
|
// shows how many interruptions it took to get here.
|
|
1411
1792
|
return { ...result, runs };
|
|
1412
1793
|
}
|
|
1413
|
-
export async function runAgent(
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
// coding phases. A clear task uses the caller's agent, or the coding agent.
|
|
1418
|
-
if (verifierOn && intent !== "task") {
|
|
1419
|
-
const conversation = {
|
|
1420
|
-
...opts,
|
|
1421
|
-
agent: chatAgent,
|
|
1422
|
-
verifier: false,
|
|
1423
|
-
intent: "chat",
|
|
1424
|
-
ledger: undefined,
|
|
1425
|
-
};
|
|
1426
|
-
return opts.inbox
|
|
1427
|
-
? runChain(task, conversation)
|
|
1428
|
-
: runOne(task, conversation);
|
|
1429
|
-
}
|
|
1430
|
-
const effective = {
|
|
1431
|
-
...opts,
|
|
1432
|
-
agent: opts.agent ?? (verifierOn ? codingAgent : chatAgent),
|
|
1433
|
-
};
|
|
1434
|
-
return opts.inbox ? runChain(task, effective) : runOne(task, effective);
|
|
1794
|
+
export async function runAgent(input, opts) {
|
|
1795
|
+
if (opts.inbox)
|
|
1796
|
+
return runChain(input, opts);
|
|
1797
|
+
return runOne(input, opts);
|
|
1435
1798
|
}
|
|
1436
1799
|
//# sourceMappingURL=loop.js.map
|