anchor-ai-sdk 0.1.9 → 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.
Files changed (104) hide show
  1. package/README.md +82 -42
  2. package/dist/agents/coding/index.d.ts +1 -1
  3. package/dist/agents/coding/index.d.ts.map +1 -1
  4. package/dist/agents/coding/index.js +1 -1
  5. package/dist/agents/coding/index.js.map +1 -1
  6. package/dist/agents/coding/prompts/index.d.ts +11 -0
  7. package/dist/agents/coding/prompts/index.d.ts.map +1 -1
  8. package/dist/agents/coding/prompts/index.js +11 -0
  9. package/dist/agents/coding/prompts/index.js.map +1 -1
  10. package/dist/anchor.d.ts +35 -0
  11. package/dist/anchor.d.ts.map +1 -0
  12. package/dist/anchor.js +57 -0
  13. package/dist/anchor.js.map +1 -0
  14. package/dist/engine/events.d.ts +20 -2
  15. package/dist/engine/events.d.ts.map +1 -1
  16. package/dist/engine/inbox.d.ts +7 -9
  17. package/dist/engine/inbox.d.ts.map +1 -1
  18. package/dist/engine/inbox.js +10 -13
  19. package/dist/engine/inbox.js.map +1 -1
  20. package/dist/engine/loop.d.ts +4 -3
  21. package/dist/engine/loop.d.ts.map +1 -1
  22. package/dist/engine/loop.js +470 -225
  23. package/dist/engine/loop.js.map +1 -1
  24. package/dist/engine/strategy.d.ts +0 -1
  25. package/dist/engine/strategy.d.ts.map +1 -1
  26. package/dist/engine/types.d.ts +19 -11
  27. package/dist/engine/types.d.ts.map +1 -1
  28. package/dist/engine/types.js +13 -1
  29. package/dist/engine/types.js.map +1 -1
  30. package/dist/index.d.ts +4 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +3 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/ledger/alignment.d.ts +1 -1
  35. package/dist/ledger/alignment.d.ts.map +1 -1
  36. package/dist/ledger/alignment.js +1 -1
  37. package/dist/ledger/alignment.js.map +1 -1
  38. package/dist/ledger/compiler.d.ts +1 -1
  39. package/dist/ledger/compiler.d.ts.map +1 -1
  40. package/dist/ledger/compiler.js +11 -5
  41. package/dist/ledger/compiler.js.map +1 -1
  42. package/dist/ledger/config.d.ts +10 -3
  43. package/dist/ledger/config.d.ts.map +1 -1
  44. package/dist/ledger/index.d.ts +1 -0
  45. package/dist/ledger/index.d.ts.map +1 -1
  46. package/dist/ledger/index.js +1 -0
  47. package/dist/ledger/index.js.map +1 -1
  48. package/dist/ledger/ledger.d.ts +2 -1
  49. package/dist/ledger/ledger.d.ts.map +1 -1
  50. package/dist/ledger/ledger.js +10 -4
  51. package/dist/ledger/ledger.js.map +1 -1
  52. package/dist/ledger/types.d.ts +5 -0
  53. package/dist/ledger/types.d.ts.map +1 -1
  54. package/dist/llm/from-catalog.d.ts +2 -0
  55. package/dist/llm/from-catalog.d.ts.map +1 -1
  56. package/dist/llm/from-catalog.js +3 -0
  57. package/dist/llm/from-catalog.js.map +1 -1
  58. package/dist/llm/index.d.ts +2 -0
  59. package/dist/llm/index.d.ts.map +1 -1
  60. package/dist/llm/index.js +1 -0
  61. package/dist/llm/index.js.map +1 -1
  62. package/dist/llm/openai-compatible.d.ts +7 -1
  63. package/dist/llm/openai-compatible.d.ts.map +1 -1
  64. package/dist/llm/openai-compatible.js +45 -21
  65. package/dist/llm/openai-compatible.js.map +1 -1
  66. package/dist/llm/types.d.ts +16 -0
  67. package/dist/llm/types.d.ts.map +1 -1
  68. package/dist/lsp/index.d.ts +10 -5
  69. package/dist/lsp/index.d.ts.map +1 -1
  70. package/dist/lsp/index.js +6 -4
  71. package/dist/lsp/index.js.map +1 -1
  72. package/dist/lsp/python.d.ts +22 -0
  73. package/dist/lsp/python.d.ts.map +1 -0
  74. package/dist/lsp/python.js +33 -0
  75. package/dist/lsp/python.js.map +1 -0
  76. package/dist/lsp/server.d.ts +184 -0
  77. package/dist/lsp/server.d.ts.map +1 -0
  78. package/dist/lsp/server.js +687 -0
  79. package/dist/lsp/server.js.map +1 -0
  80. package/dist/lsp/tools.d.ts +16 -2
  81. package/dist/lsp/tools.d.ts.map +1 -1
  82. package/dist/lsp/tools.js +565 -27
  83. package/dist/lsp/tools.js.map +1 -1
  84. package/dist/lsp/typescript.d.ts +13 -54
  85. package/dist/lsp/typescript.d.ts.map +1 -1
  86. package/dist/lsp/typescript.js +37 -262
  87. package/dist/lsp/typescript.js.map +1 -1
  88. package/dist/tools/index.d.ts +2 -0
  89. package/dist/tools/index.d.ts.map +1 -1
  90. package/dist/tools/index.js +1 -0
  91. package/dist/tools/index.js.map +1 -1
  92. package/dist/tools/view-image.d.ts +20 -0
  93. package/dist/tools/view-image.d.ts.map +1 -0
  94. package/dist/tools/view-image.js +111 -0
  95. package/dist/tools/view-image.js.map +1 -0
  96. package/dist/verify/index.d.ts +3 -0
  97. package/dist/verify/index.d.ts.map +1 -0
  98. package/dist/verify/index.js +2 -0
  99. package/dist/verify/index.js.map +1 -0
  100. package/dist/verify/judge.d.ts +42 -0
  101. package/dist/verify/judge.d.ts.map +1 -0
  102. package/dist/verify/judge.js +119 -0
  103. package/dist/verify/judge.js.map +1 -0
  104. package/package.json +1 -1
@@ -2,23 +2,22 @@ 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
4
  import { compactConversation, compactConversationStructured, DEFAULT_COMPACTION, estimateTokens, renderCompactionHandoff, } from "./compaction.js";
5
- import { assistantMessage, conversationHistory, userMessage, } from "./context.js";
6
- import { renderInboxTask, } from "./inbox.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
12
  import { renderPromptBlocks } from "./prompt.js";
13
- import { resolveTurnIntent } from "./intent.js";
14
- import { addRequirement, compileLedger, ledgerGate, linkEvidence, recordEvidence, renderLedger, setRequirementStatus, verifyAlignment, } from "../ledger/index.js";
15
- import { chatAgent } from "../agents/chat/index.js";
16
- import { codingAgent } from "../agents/coding/index.js";
13
+ import { addRequirement, compileLedger, createLedger, ledgerGate, linkEvidence, recordEvidence, renderLedger, setRequirementStatus, } from "../ledger/index.js";
14
+ import { llmJudge } from "../verify/index.js";
17
15
  import { dispatchToolCalls, } from "./dispatch.js";
18
16
  import { mintOperationId, reconcilePendingOperations, settleFromResult, } from "./operations.js";
19
17
  import { RepeatTracker, cappedReplyNudge, createCourseCorrect, emptyTurnNudge, proseCheckpoint as proseCheckpointMessage, repeatedCallNudge, silentNudge, verboseNudge, visibleOutputTokens, } from "./guardrails.js";
20
18
  import { pollBackgroundJobs, renderBackgroundResult, renderLostJob, waitForBackgroundJobs, } from "./background.js";
21
- async function runOne(task, opts, chain = {}) {
19
+ export { userTurn } from "./types.js";
20
+ async function runOne(input, opts, chain = {}) {
22
21
  const strategy = opts.strategy ?? opts.agent?.strategy;
23
22
  if (!strategy)
24
23
  throw new Error("runAgent needs an agent or a strategy — pass opts.agent (chatAgent, codingAgent, …) or opts.strategy");
@@ -115,7 +114,11 @@ async function runOne(task, opts, chain = {}) {
115
114
  !strategy.phases.some((p) => p.id === opts.startPhase)) {
116
115
  throw new Error(`startPhase "${opts.startPhase}" is not a phase of strategy "${strategy.name}"`);
117
116
  }
118
- const context = task.context ?? (await strategy.prepareContext?.(opts.sandbox, task.task));
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));
119
122
  const prior = await opts.thread?.store.load(opts.thread.id);
120
123
  if (!opts.startPhase &&
121
124
  prior?.phase &&
@@ -124,50 +127,53 @@ async function runOne(task, opts, chain = {}) {
124
127
  let summary = prior?.summary;
125
128
  let compaction = prior?.compaction;
126
129
  const budget = chain.budget ?? new BudgetTracker(opts.budgets);
127
- // ---- Task verification ------------------------------------------------
130
+ // ---- Task contracts and the judge --------------------------------------
128
131
  const verifierOn = opts.verifier !== undefined && opts.verifier !== false;
129
132
  const verifierConfig = verifierOn && typeof opts.verifier === "object" ? opts.verifier : {};
130
- const verifierLlm = verifierConfig.llm ?? opts.llm;
131
133
  const verifierGates = new Set(verifierConfig.gates ?? ["phase", "finish"]);
132
- const maxVerifierCalls = verifierConfig.maxCallsPerTask ?? 6;
133
- let verifierCalls = 0;
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()}`;
134
143
  let ledger = opts.ledger ?? (verifierOn ? prior?.ledger : undefined);
135
- if (verifierOn && !ledger) {
136
- const compiled = await compileLedger({
137
- llm: verifierLlm,
138
- taskId: opts.taskId ?? opts.thread?.id ?? `task-${Date.now()}`,
139
- task: task.task,
140
- phases: strategy.phases,
141
- reasoningEffort: "low",
142
- });
143
- ledger = compiled.ledger;
144
- if (compiled.usage)
145
- budget.record(compiled.usage);
146
- }
147
- const recentEvidence = () => {
148
- if (!ledger)
149
- return "(none)";
150
- return Object.values(ledger.evidence)
151
- .slice(-6)
152
- .map((item) => `${item.id} ${item.tool}${item.summary ? `: ${item.summary}` : ""}${item.ok ? "" : " (failed)"}`)
153
- .join("\n");
154
- };
155
- const callVerifier = async (phaseGoal) => {
156
- if (!verifierOn || !ledger || verifierCalls >= maxVerifierCalls) {
157
- return { allow: true };
158
- }
159
- 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;
160
151
  try {
161
- const { verdict, usage } = await verifyAlignment({
162
- llm: verifierLlm,
163
- task: task.task,
152
+ const verdict = await judge.judge({
153
+ request: requestText,
164
154
  phase: phaseGoal,
165
155
  ledger,
166
- recentEvidence: recentEvidence(),
167
- reasoningEffort: "low",
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
+ })),
168
176
  });
169
- if (usage)
170
- budget.record(usage);
171
177
  return verdict;
172
178
  }
173
179
  catch (error) {
@@ -176,112 +182,23 @@ async function runOne(task, opts, chain = {}) {
176
182
  kind: "verifier_failed",
177
183
  detail: error instanceof Error ? error.message : String(error),
178
184
  });
179
- return {
180
- allow: verifierConfig.onError !== "block",
181
- aligned: false,
182
- justified: true,
183
- reason: "verifier unavailable",
184
- requiredAction: "none",
185
- confidence: 0,
186
- };
187
- }
188
- };
189
- if (verifierOn && ledger) {
190
- completionGate = async (summaryText) => {
191
- const structural = ledgerGate(ledger, {
192
- allowWaived: verifierConfig.allowWaived,
193
- });
194
- if (!structural.allow) {
185
+ if (verifierConfig.onError === "block") {
195
186
  return {
196
- ok: false,
197
- content: [
198
- "Verification gate: required work is not complete.",
199
- `open: ${structural.open.join(", ") || "none"}`,
200
- `missingEvidence: ${structural.missingEvidence.join(", ") || "none"}`,
201
- `failures: ${structural.failures.join(" | ") || "none"}`,
202
- "Attach evidence to each requirement with update_ledger, resolve failures, and try again.",
203
- ].join("\n"),
187
+ cleared: false,
188
+ requirements: requirements.map((item) => ({
189
+ id: item.id,
190
+ status: "unproven",
191
+ })),
192
+ confidence: 0,
204
193
  };
205
194
  }
206
- if (!verifierGates.has("finish")) {
207
- verified = true;
208
- return { ok: true, content: "" };
209
- }
210
- const verdict = await callVerifier({
211
- id: "finish",
212
- goal: "Deliver the task outcome with evidence.",
213
- });
214
- if (verdict.allow) {
215
- verified = true;
216
- return { ok: true, content: "" };
217
- }
218
- return {
219
- ok: false,
220
- content: `Verification gate rejected completion: ${verdict.reason ?? "unsupported claim"}. ` +
221
- `Required action: ${verdict.requiredAction ?? "gather_evidence"}. ` +
222
- `Claim: ${summaryText.slice(0, 400)}`,
223
- };
224
- };
225
- }
226
- const currentRequest = userMessage(task.task, task.images);
227
- const revivalOn = !!opts.revival;
228
- const revivalWarnAt = revivalWarnPercent(opts.revival ? opts.revival.warnAtPercent : undefined);
229
- let revivalWarned = false;
230
- let revivalRequest;
231
- const phaseOn = strategy.phases.length > 1 && opts.intrinsics?.phase !== false;
232
- /**
233
- * Stable blocks are the cacheable prefix; turn-scoped blocks are appended
234
- * after the conversation. The ledger changes after every tool result, so it
235
- * must never live in the system message.
236
- */
237
- const promptBlocks = () => [
238
- {
239
- id: "agent",
240
- text: strategy.systemPrompt({ task: task.task, context, summary }),
241
- stability: "stable",
242
- },
243
- {
244
- id: "phase-discipline",
245
- text: phaseOn ? phaseDisciplineBlock(strategy.phases) : undefined,
246
- stability: "stable",
247
- },
248
- {
249
- id: "budget-revival",
250
- text: revivalOn ? revivalBlock(revivalWarnAt) : undefined,
251
- stability: "stable",
252
- },
253
- {
254
- id: "ledger",
255
- text: ledger
256
- ? [
257
- "## Runtime task state",
258
- "This is authoritative state, not a new user request.",
259
- renderLedger(ledger),
260
- "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.",
261
- ].join("\n\n")
262
- : undefined,
263
- stability: "turn",
264
- },
265
- {
266
- id: "compaction",
267
- text: compaction ? renderCompactionHandoff(compaction) : undefined,
268
- stability: "turn",
269
- },
270
- {
271
- id: "phase-state",
272
- text: phase ? `Current phase: ${phase}` : undefined,
273
- stability: "turn",
274
- },
275
- ];
276
- const system = () => ({
277
- role: "system",
278
- content: renderPromptBlocks(promptBlocks(), "stable"),
279
- });
280
- const turnState = () => renderPromptBlocks(promptBlocks(), "turn");
281
- if (ledger) {
282
- const updateLedger = defineTool({
195
+ return undefined;
196
+ }
197
+ };
198
+ const updateLedger = () => {
199
+ const tool = defineTool({
283
200
  name: "update_ledger",
284
- description: "Update the task contract. Use it after gathering evidence: link a tool result to a requirement, mark it verified, record an open failure, or set the next step. Never invent evidence ids.",
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.",
285
202
  parameters: {
286
203
  type: "object",
287
204
  properties: {
@@ -290,6 +207,7 @@ async function runOne(task, opts, chain = {}) {
290
207
  enum: [
291
208
  "add_requirement",
292
209
  "link_evidence",
210
+ "verify",
293
211
  "set_status",
294
212
  "set_next_step",
295
213
  "add_failure",
@@ -298,8 +216,10 @@ async function runOne(task, opts, chain = {}) {
298
216
  },
299
217
  text: { type: "string" },
300
218
  required: { type: "boolean" },
219
+ phase: { type: "string" },
301
220
  requirementId: { type: "string" },
302
221
  evidenceId: { type: "string" },
222
+ claim: { type: "string" },
303
223
  status: {
304
224
  type: "string",
305
225
  enum: ["open", "in_progress", "verified", "blocked", "waived"],
@@ -315,16 +235,42 @@ async function runOne(task, opts, chain = {}) {
315
235
  const text = String(input.text ?? "").trim();
316
236
  if (!text)
317
237
  return { ok: false, content: "text is required" };
318
- const requirement = addRequirement(ledger, { text, required: input.required !== false }, "agent");
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");
319
245
  return {
320
246
  ok: true,
321
247
  content: `added ${requirement.id}: ${requirement.text}`,
322
248
  };
323
249
  }
324
250
  if (op === "link_evidence") {
325
- linkEvidence(ledger, String(input.requirementId ?? ""), String(input.evidenceId ?? ""));
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);
326
259
  return { ok: true, content: "evidence linked" };
327
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
+ }
328
274
  if (op === "set_status") {
329
275
  const status = String(input.status ?? "");
330
276
  if (status === "superseded") {
@@ -361,12 +307,167 @@ async function runOne(task, opts, chain = {}) {
361
307
  }
362
308
  return { ok: false, content: `unknown ledger op "${op}"` };
363
309
  }, { verb: "Updating the progress ledger" });
364
- available.set("update_ledger", updateLedger);
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);
365
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");
366
467
  const messages = [
367
468
  system(),
368
469
  ...conversationHistory(prior?.messages ?? []),
369
- currentRequest,
470
+ ...currentRequests,
370
471
  ];
371
472
  const compact = opts.compaction === false
372
473
  ? null
@@ -391,6 +492,29 @@ async function runOne(task, opts, chain = {}) {
391
492
  }
392
493
  return out;
393
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
+ };
394
518
  const evidence = [];
395
519
  // Commands the provider detached. The engine owns their results: it polls at
396
520
  // every turn boundary and, before a normal finish, waits for what is left.
@@ -571,31 +695,76 @@ async function runOne(task, opts, chain = {}) {
571
695
  content: `Unknown phase. Available: ${strategy.phases.map((item) => item.id).join(", ")}.`,
572
696
  };
573
697
  const previous = phase;
574
- if (verifierOn &&
575
- ledger &&
576
- verifierGates.has("phase") &&
577
- target.id !== previous) {
578
- const verdict = await callVerifier(target);
579
- if (!verdict.allow) {
580
- emit({
581
- type: "guardrail",
582
- kind: "transition_rejected",
583
- detail: `${previous} -> ${target.id}: ${verdict.reason}`,
584
- });
585
- return {
586
- ok: false,
587
- content: [
588
- `Phase transition rejected: ${verdict.reason}`,
589
- `Required action: ${verdict.requiredAction}`,
590
- "Gather the missing evidence or update the ledger, then transition again.",
591
- ].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,
592
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
+ }
593
756
  }
594
757
  }
595
758
  phase = target.id;
759
+ phaseRejections = 0;
760
+ phaseEvidence = [];
596
761
  pauseRequested =
597
762
  opts.pauseAfterPhase === previous && previous !== phase;
598
- emit({ type: "phase_started", phase });
763
+ emit({
764
+ type: "phase_started",
765
+ phase,
766
+ ...(forced ? { via: "advance_forced" } : {}),
767
+ });
599
768
  return {
600
769
  ok: true,
601
770
  content: `Current approach: ${target.id} — ${target.goal}`,
@@ -650,6 +819,122 @@ async function runOne(task, opts, chain = {}) {
650
819
  };
651
820
  }, { verb: "Requesting budget for the remaining work" }));
652
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
+ }
653
938
  /**
654
939
  * Settle operations only AFTER the conversation that contains their results
655
940
  * is durable. The opposite order opens the window this whole layer exists to
@@ -848,7 +1133,8 @@ async function runOne(task, opts, chain = {}) {
848
1133
  }
849
1134
  emit({
850
1135
  type: "run_started",
851
- task: task.task,
1136
+ text: requestText,
1137
+ ...(input.messages.length ? { messages: input.messages } : {}),
852
1138
  phases: strategy.phases.map((item) => item.id),
853
1139
  });
854
1140
  if (chain.delivered?.length)
@@ -1041,7 +1327,7 @@ async function runOne(task, opts, chain = {}) {
1041
1327
  };
1042
1328
  // Preserve the actual current request even if its earlier position
1043
1329
  // was folded. Do not replace it with a generic "Begin" instruction.
1044
- messages.splice(0, messages.length, system(), ...(tail.includes(currentRequest) ? [] : [currentRequest]), foldNote, ...tail);
1330
+ messages.splice(0, messages.length, system(), ...currentRequests.filter((message) => !tail.includes(message)), foldNote, ...tail);
1045
1331
  emit({
1046
1332
  type: "compacted",
1047
1333
  removedMessages: folded.removedCount,
@@ -1067,8 +1353,10 @@ async function runOne(task, opts, chain = {}) {
1067
1353
  await pollJobs();
1068
1354
  steps++;
1069
1355
  emit({ type: "llm_call_started", phase, step: steps });
1356
+ const modelMessages = forModel();
1070
1357
  const response = await opts.llm.stream({
1071
- messages: forModel(),
1358
+ messages: modelMessages,
1359
+ cacheBreakpoints: cacheBreakpointsFor(modelMessages),
1072
1360
  tools: [...available.values()].map((tool) => ({
1073
1361
  type: "function",
1074
1362
  function: tool.definition,
@@ -1257,13 +1545,14 @@ async function runOne(task, opts, chain = {}) {
1257
1545
  call.name === "update_ledger") {
1258
1546
  return;
1259
1547
  }
1260
- recordEvidence(ledger, {
1548
+ const evidenceRef = recordEvidence(ledger, {
1261
1549
  tool: call.name,
1262
1550
  ref: `call:${call.id}`,
1263
1551
  ok: result.ok,
1264
1552
  summary: result.content.replace(/\s+/g, " ").slice(0, 500),
1265
1553
  ...(result.meta ? { meta: result.meta } : {}),
1266
1554
  });
1555
+ phaseEvidence.push(evidenceRef);
1267
1556
  },
1268
1557
  });
1269
1558
  runaway = outcome.runaway;
@@ -1398,48 +1687,7 @@ function usageDelta(now, before) {
1398
1687
  function hasActiveLedger(ledger) {
1399
1688
  if (!ledger)
1400
1689
  return false;
1401
- const open = ledger.requirements.some((item) => item.required &&
1402
- item.status !== "verified" &&
1403
- item.status !== "waived" &&
1404
- item.status !== "superseded");
1405
- return open || ledger.openFailures.length > 0;
1406
- }
1407
- async function priorLedgerFor(opts, verifierOn) {
1408
- if (!verifierOn)
1409
- return undefined;
1410
- if (opts.ledger)
1411
- return opts.ledger;
1412
- if (!opts.thread)
1413
- return undefined;
1414
- return (await opts.thread.store.load(opts.thread.id))?.ledger;
1415
- }
1416
- /**
1417
- * Resolve the route for one run. `intent: "auto"` is ledger-aware: an active
1418
- * task contract outranks text heuristics. Explicit chat/clarify/task still
1419
- * wins, and turns without verifier mode keep their existing behavior.
1420
- */
1421
- function routeTurn(task, opts, priorLedger) {
1422
- const verifierOn = opts.verifier !== undefined && opts.verifier !== false;
1423
- if (!verifierOn)
1424
- return { ...opts, agent: opts.agent ?? chatAgent };
1425
- const explicit = opts.intent ?? "auto";
1426
- const intent = explicit === "auto" && hasActiveLedger(priorLedger)
1427
- ? "task"
1428
- : resolveTurnIntent(task.task, explicit);
1429
- if (intent !== "task") {
1430
- return {
1431
- ...opts,
1432
- agent: chatAgent,
1433
- verifier: false,
1434
- intent: "chat",
1435
- ledger: undefined,
1436
- };
1437
- }
1438
- return {
1439
- ...opts,
1440
- agent: opts.agent ?? codingAgent,
1441
- intent: "task",
1442
- };
1690
+ return !ledgerGate(ledger).allow;
1443
1691
  }
1444
1692
  /**
1445
1693
  * A run chain (decision #82): one run at a time, with the consumer's inbox as
@@ -1451,7 +1699,7 @@ function routeTurn(task, opts, priorLedger) {
1451
1699
  * cancellation, a caller-requested pause, or an exhausted budget — and returns
1452
1700
  * the last run's result with every run summarized on `.runs`.
1453
1701
  */
1454
- async function runChain(task, opts) {
1702
+ async function runChain(input, opts) {
1455
1703
  const inbox = opts.inbox;
1456
1704
  const { store, channel } = inbox;
1457
1705
  if (!channel)
@@ -1470,14 +1718,13 @@ async function runChain(task, opts) {
1470
1718
  };
1471
1719
  let stepsBefore = 0;
1472
1720
  let jobs;
1473
- let current = task;
1721
+ let current = input;
1474
1722
  let delivered;
1475
1723
  let claimed = [];
1476
1724
  let result;
1477
1725
  for (;;) {
1478
1726
  const budget = seededBudget(opts, spend);
1479
- const routed = routeTurn(current, opts, await priorLedgerFor(opts, opts.verifier !== undefined && opts.verifier !== false));
1480
- result = await runOne(current, routed, {
1727
+ result = await runOne(current, opts, {
1481
1728
  budget,
1482
1729
  stepsBefore,
1483
1730
  jobs,
@@ -1538,17 +1785,15 @@ async function runChain(task, opts) {
1538
1785
  return { ...result, runs };
1539
1786
  claimed = batch.map((message) => message.id);
1540
1787
  delivered = batch;
1541
- current = inbox.render ? inbox.render(batch) : renderInboxTask(batch);
1788
+ current = inbox.render ? inbox.render(batch) : renderInboxTurn(batch);
1542
1789
  }
1543
1790
  // The zero-progress guard fired. Report the last run as it stands; `.runs`
1544
1791
  // shows how many interruptions it took to get here.
1545
1792
  return { ...result, runs };
1546
1793
  }
1547
- export async function runAgent(task, opts) {
1794
+ export async function runAgent(input, opts) {
1548
1795
  if (opts.inbox)
1549
- return runChain(task, opts);
1550
- const verifierOn = opts.verifier !== undefined && opts.verifier !== false;
1551
- const priorLedger = await priorLedgerFor(opts, verifierOn);
1552
- return runOne(task, routeTurn(task, opts, priorLedger));
1796
+ return runChain(input, opts);
1797
+ return runOne(input, opts);
1553
1798
  }
1554
1799
  //# sourceMappingURL=loop.js.map