@spendgraph/workflows 0.2.0 → 0.3.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 (79) hide show
  1. package/README.md +59 -0
  2. package/dist/noesis/attribution.js +0 -20
  3. package/dist/noesis/context.js +0 -8
  4. package/dist/noesis/draft/draft.js +0 -13
  5. package/dist/noesis/events.js +0 -10
  6. package/dist/noesis/execute/execute.js +2 -17
  7. package/dist/noesis/execute/index.d.ts +2 -0
  8. package/dist/noesis/execute/index.js +1 -0
  9. package/dist/noesis/execute/order.js +0 -8
  10. package/dist/noesis/execute/spend.d.ts +36 -0
  11. package/dist/noesis/execute/spend.js +79 -0
  12. package/dist/noesis/execute/types.d.ts +2 -0
  13. package/dist/noesis/gates/closure-checklist/invoke.js +0 -1
  14. package/dist/noesis/gates/closure-checklist/prompt.js +0 -2
  15. package/dist/noesis/gates/cqot-gate/invoke.js +0 -1
  16. package/dist/noesis/gates/cqot-gate/prompt.js +0 -2
  17. package/dist/noesis/gates/premortem/invoke.js +0 -1
  18. package/dist/noesis/gates/premortem/prompt.js +0 -2
  19. package/dist/noesis/gates/red-team/invoke.js +0 -1
  20. package/dist/noesis/gates/red-team/prompt.js +0 -2
  21. package/dist/noesis/gates/standards-review/invoke.js +0 -1
  22. package/dist/noesis/gates/standards-review/prompt.js +0 -2
  23. package/dist/noesis/gates/think-checkpoint/invoke.js +0 -1
  24. package/dist/noesis/gates/think-checkpoint/prompt.js +0 -2
  25. package/dist/noesis/index.d.ts +2 -2
  26. package/dist/noesis/index.js +1 -1
  27. package/dist/noesis/producers/cove-answers/invoke.js +0 -1
  28. package/dist/noesis/producers/cove-answers/prompt.js +0 -2
  29. package/dist/noesis/producers/cove-questions/invoke.js +0 -1
  30. package/dist/noesis/producers/cove-questions/prompt.js +0 -2
  31. package/dist/noesis/producers/decompose/invoke.js +0 -1
  32. package/dist/noesis/producers/decompose/prompt.js +0 -18
  33. package/dist/noesis/producers/discriminating-test/invoke.js +0 -1
  34. package/dist/noesis/producers/discriminating-test/prompt.js +0 -2
  35. package/dist/noesis/producers/plan-candidates/invoke.js +0 -1
  36. package/dist/noesis/producers/plan-candidates/prompt.js +0 -2
  37. package/dist/noesis/producers/qn-brief/invoke.js +0 -1
  38. package/dist/noesis/producers/qn-brief/prompt.js +0 -9
  39. package/dist/noesis/producers/toulmin/answer.js +0 -21
  40. package/dist/noesis/producers/toulmin/invoke.js +0 -10
  41. package/dist/noesis/producers/toulmin/prompt.js +0 -9
  42. package/dist/noesis/producers/toulmin/render.js +0 -24
  43. package/dist/noesis/question.js +0 -19
  44. package/dist/noesis/repair/reflection/invoke.js +0 -1
  45. package/dist/noesis/repair/reflection/prompt.js +0 -2
  46. package/dist/noesis/retry.js +0 -34
  47. package/dist/noesis/route.js +0 -27
  48. package/dist/noesis/router/triage/invoke.js +0 -23
  49. package/dist/noesis/router/triage/prompt.js +0 -8
  50. package/dist/noesis/run/artifacts.js +0 -17
  51. package/dist/noesis/run/errors.js +0 -18
  52. package/dist/noesis/run/index.d.ts +2 -2
  53. package/dist/noesis/run/index.js +1 -1
  54. package/dist/noesis/run/ledger.d.ts +49 -1
  55. package/dist/noesis/run/ledger.js +19 -13
  56. package/dist/noesis/run/loop.js +4 -22
  57. package/dist/noesis/run/options.d.ts +11 -0
  58. package/dist/noesis/run/options.js +18 -0
  59. package/dist/noesis/run/plan.js +4 -16
  60. package/dist/noesis/run/produce.js +8 -37
  61. package/dist/noesis/run/redo.js +0 -20
  62. package/dist/noesis/run/result.js +1 -10
  63. package/dist/noesis/run/run.js +5 -21
  64. package/dist/noesis/run/stages/classify.js +2 -2
  65. package/dist/noesis/run/stages/direct.js +0 -1
  66. package/dist/noesis/run/stages/full.js +0 -9
  67. package/dist/noesis/run/stages/retrieve.js +0 -8
  68. package/dist/noesis/run/stages/standard.js +0 -7
  69. package/dist/noesis/run/state.d.ts +5 -0
  70. package/dist/noesis/run/state.js +5 -4
  71. package/dist/noesis/run/stream.js +0 -13
  72. package/dist/noesis/run/verify.js +5 -22
  73. package/dist/noesis/slugs.js +0 -14
  74. package/dist/noesis/stage.js +0 -10
  75. package/dist/noesis/started.js +0 -15
  76. package/dist/noesis/types.d.ts +17 -5
  77. package/dist/noesis/usage.js +0 -16
  78. package/dist/noesis/verify/verify.js +0 -29
  79. package/package.json +7 -7
package/README.md CHANGED
@@ -3,6 +3,10 @@
3
3
  Ready-made workflows assembled from the other packages. One so far: **noesis**,
4
4
  a question-answering loop that decides how much of itself a question is worth.
5
5
 
6
+ ```sh
7
+ npm install @spendgraph/workflows
8
+ ```
9
+
6
10
  ```ts
7
11
  import { run } from "@spendgraph/workflows";
8
12
 
@@ -66,6 +70,57 @@ answer. Stage 5 pairs 5A's claims back to 5B's blind answers, and it has to be
66
70
  code: whatever does it sees both the claim and the answer written without it,
67
71
  and a model given both is no longer a blind check of anything.
68
72
 
73
+ ## Choosing models
74
+
75
+ `stage` sets the model every stage runs on. `stages` overrides it by name, for
76
+ the stages worth paying more or less for.
77
+
78
+ ```ts
79
+ await run(client, llm, question, {
80
+ stage: { model: "claude-haiku-4-5" },
81
+ stages: {
82
+ plan_candidates: { model: "claude-sonnet-4-6" },
83
+ red_team: { model: "claude-opus-4-8" },
84
+ answer: { model: "claude-sonnet-4-6" },
85
+ },
86
+ });
87
+ ```
88
+
89
+ The keys are the stage names from the table above with the spaces closed up —
90
+ `triage`, `question_brief`, `plan_candidates`, `red_team`, `answer` — and
91
+ `STAGE_KEYS` maps every id to its own. `triage` and `answer` also have
92
+ `opts.triage` and `opts.answer`, which
93
+ take more than a model — a current task, and the answer's escalating tiers —
94
+ and win where both are set.
95
+
96
+ **`params` does not follow a model it was not written for.** A stage naming its
97
+ own `model` gets its own `params` or none: Haiku takes `temperature`, Sonnet 5
98
+ rejects it outright, and one shared request body fails on whichever it was not
99
+ written for. A stage overriding only `params` still merges over `stage`.
100
+
101
+ ## What a run cost
102
+
103
+ `usage` is the model spend: six numbers rather than two, summed across every
104
+ stage and every retry, split `byStage` and `byModel` because the answer can
105
+ cascade. A back edge refunds nothing.
106
+
107
+ `usage.tools` is what the tools reported, and it is deliberately **not** folded
108
+ in — a tool call is somebody else's API at somebody else's rates and is no
109
+ rollout of ours.
110
+
111
+ ```ts
112
+ result.usage.inputTokens; // the model side
113
+ result.usage.byModel["claude-sonnet-4-6"];
114
+
115
+ result.usage.tools.calls; // the tool side
116
+ result.usage.tools.costMicros; // where a tool priced itself
117
+ result.usage.tools.byTool.web_search; // tokens, keyed by the tool that ran
118
+ ```
119
+
120
+ Bill both or you undercharge every run that touched the web: `web_search`
121
+ reports Perplexity's four token counts and `deep_recall` reports micro-USD,
122
+ and stage 4 records zero tokens of its own.
123
+
69
124
  ## The ledger
70
125
 
71
126
  Every stage files its result, its rollouts and its tokens in one place. That
@@ -147,3 +202,7 @@ with `04-retrieve.mjs`: it is the cheapest, and it exercises triage, the
147
202
  dispatcher, the ledger and 6C. The routes that look something up need one of
148
203
  the two lookups configured — `LOCUSGRAPH_AGENT_SECRET` for `deep_recall`, or
149
204
  `PERPLEXITY_API_KEY` for `web_search` — and say so rather than half-running.
205
+
206
+ ## License
207
+
208
+ MIT
@@ -1,14 +1,6 @@
1
1
  import { STAGE_NAMES, STAGES } from "./run/ledger.js";
2
2
  const BY_NAME = new Map(Object.entries(STAGE_NAMES).map(([id, name]) => [name.toLowerCase(), id]));
3
3
  const IDS = new Set(STAGES);
4
- /**
5
- * The stage a ground says it came from, or nothing.
6
- *
7
- * Lenient because the field is free text on a model reply, and "5B", "stage 5B"
8
- * and "verification answers" are the same citation. It will not guess: a string
9
- * that resolves to nothing is a ground that cannot be traced, which is the
10
- * finding rather than a parsing problem to work around.
11
- */
12
4
  export function stageCited(source) {
13
5
  const raw = source.trim();
14
6
  const id = raw
@@ -19,18 +11,6 @@ export function stageCited(source) {
19
11
  return id;
20
12
  return BY_NAME.get(raw.toLowerCase());
21
13
  }
22
- /**
23
- * The grounds citing a stage this run never ran.
24
- *
25
- * 6C compiles `grounds[]` out of the artifacts and each one names its
26
- * `source_stage`; the ledger knows which stages were actually paid for. A
27
- * citation of anything else is the stage writing evidence rather than
28
- * assembling it, and it is the cheapest check available that catches a
29
- * fabricated answer without a judge and without a reference.
30
- *
31
- * Returns the `source_stage` strings as written, because what the model claimed
32
- * is the useful thing to read back.
33
- */
34
14
  export function unattributed(answer, ran) {
35
15
  const filed = new Set(ran);
36
16
  const grounds = answer.grounds ?? [];
@@ -1,11 +1,3 @@
1
- /**
2
- * The request with the `<context>` block ahead of it.
3
- *
4
- * The prompts name `now` and `locale` but never show the markup they expect
5
- * them in, so this is the form they are given. An absent fact is omitted rather
6
- * than sent empty: a missing one is recorded as something to find out, an empty
7
- * one reads as a fact already in hand.
8
- */
9
1
  export function asked(input) {
10
2
  const facts = [
11
3
  input.now ? `now: ${input.now}` : "",
@@ -1,11 +1,4 @@
1
1
  const NEVER_RAN = "It depended on something that was never answered.";
2
- /**
3
- * The sub-answers, paired back to the sub-questions that asked for them.
4
- *
5
- * A sub-question that produced nothing is named rather than dropped: 6A asks
6
- * whether every one was answered, and a draft that quietly omits the failures
7
- * would let it say yes.
8
- */
9
2
  export function assemble(question, decomposition, execution) {
10
3
  const asked = new Map(decomposition.sub_questions.map((sub) => [sub.id, sub.question]));
11
4
  const findings = [];
@@ -29,12 +22,6 @@ export function assemble(question, decomposition, execution) {
29
22
  }
30
23
  const block = (tag, body) => `<${tag}>\n${body}\n</${tag}>`;
31
24
  const asText = (answer) => typeof answer === "string" ? answer : JSON.stringify(answer, null, 2);
32
- /**
33
- * The draft as a stage reads it.
34
- *
35
- * Tagged the way `asked()` tags the context block, because every stage
36
- * downstream takes this through `{question}` and has to tell the parts apart.
37
- */
38
25
  export function render(draft) {
39
26
  const parts = [
40
27
  block("question", draft.question),
@@ -1,12 +1,3 @@
1
- /**
2
- * One emitter per run, stamping order and elapsed time.
3
- *
4
- * Deliberately synchronous and deliberately swallowing: a listener that throws
5
- * is a bug in the listener, and letting it take the run down would make
6
- * watching a run more dangerous than not watching one. Returns `undefined`
7
- * where nobody is listening, so every emit site can stay a `?.` call and cost
8
- * nothing.
9
- */
10
1
  export function emitter(listener, now = Date.now) {
11
2
  if (!listener)
12
3
  return undefined;
@@ -18,7 +9,6 @@ export function emitter(listener, now = Date.now) {
18
9
  listener(stamped);
19
10
  }
20
11
  catch {
21
- // A listener that throws does not get to fail the run.
22
12
  }
23
13
  };
24
14
  }
@@ -1,10 +1,5 @@
1
1
  import { order } from "./order.js";
2
- /**
3
- * What each hint reaches for, and where the question goes.
4
- *
5
- * `code_exec` and `get_documents` are unwired because nothing ships them —
6
- * a sub-question asking for one is reported rather than quietly dropped.
7
- */
2
+ import { toolSpendOf } from "./spend.js";
8
3
  export const DEFAULT_TOOLS = {
9
4
  calculator: { name: "calculate", arg: "expression" },
10
5
  deeprecall: { name: "deep_recall", arg: "question" },
@@ -54,17 +49,6 @@ async function answerOne(bus, wiring, sub, wave, emit) {
54
49
  ? { ...base, status: "failed", reason: result.error, latencyMs: result.latencyMs }
55
50
  : { ...base, status: "answered", result: parsed(result.output), latencyMs: result.latencyMs });
56
51
  }
57
- /**
58
- * Stage 4. Every sub-question put through the tool its hint named.
59
- *
60
- * The dispatcher dispatches and does not reason: `deep_recall` and `web_search`
61
- * already return a synthesised answer, and a sub-question with no tool is
62
- * recorded as unanswered rather than guessed at.
63
- *
64
- * `done_when` is carried rather than acted on. It describes an observable
65
- * condition, which is 4A's job to judge against what actually happened, not
66
- * something this can evaluate.
67
- */
68
52
  export async function execute(bus, plan, opts = {}) {
69
53
  const wiring = { ...DEFAULT_TOOLS, ...opts.tools };
70
54
  const { waves, unresolved } = order(plan);
@@ -80,6 +64,7 @@ export async function execute(bus, plan, opts = {}) {
80
64
  return {
81
65
  answers,
82
66
  unresolved,
67
+ spend: toolSpendOf(answers, wiring),
83
68
  toolMs: answers.reduce((total, answer) => total + answer.latencyMs, 0),
84
69
  };
85
70
  }
@@ -1,3 +1,5 @@
1
1
  export { DEFAULT_TOOLS, execute } from "./execute.js";
2
2
  export { order } from "./order.js";
3
3
  export type { ExecuteOptions, Execution, SubAnswer, Waves, Wiring } from "./types.js";
4
+ export type { ToolSpend, ToolUsage } from "./spend.js";
5
+ export { addToolSpend, NO_TOOL_SPEND, readToolUsage, toolSpendOf } from "./spend.js";
@@ -1,2 +1,3 @@
1
1
  export { DEFAULT_TOOLS, execute } from "./execute.js";
2
2
  export { order } from "./order.js";
3
+ export { addToolSpend, NO_TOOL_SPEND, readToolUsage, toolSpendOf } from "./spend.js";
@@ -1,11 +1,3 @@
1
- /**
2
- * The plan, grouped into what may run together.
3
- *
4
- * `read_only` siblings share a wave and `synthesis` takes one alone, which is
5
- * the distinction the stage went to the trouble of typing. A dependency on an
6
- * id nothing created can never be satisfied, so it lands in `unresolved`
7
- * alongside a cycle rather than being run in the wrong order.
8
- */
9
1
  export function order(plan) {
10
2
  const known = new Set(plan.map((sub) => sub.id));
11
3
  const done = new Set();
@@ -0,0 +1,36 @@
1
+ import type { SubAnswer, Wiring } from "./types.js";
2
+ import type { SubQuestionTool } from "../producers/decompose/index.js";
3
+ /** What one tool reported it cost, keyed by the tool that reported it. */
4
+ export interface ToolSpend {
5
+ calls: number;
6
+ /** Summed where a tool priced itself. Undefined where none did. */
7
+ costMicros?: number;
8
+ byTool: Record<string, ToolUsage>;
9
+ }
10
+ export interface ToolUsage {
11
+ calls: number;
12
+ model?: string;
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ citationTokens: number;
16
+ reasoningTokens: number;
17
+ costMicros?: number;
18
+ }
19
+ export declare const NO_TOOL_SPEND: ToolSpend;
20
+ /**
21
+ * What a tool result says it cost, read by duck typing.
22
+ *
23
+ * A tool is somebody else's API and prices itself however it likes: `web_search`
24
+ * reports Perplexity's four token counts, `deep_recall` reports micro-USD. Both
25
+ * are read where present and nothing is invented where absent.
26
+ */
27
+ export declare function readToolUsage(result: unknown): Omit<ToolUsage, "calls"> | null;
28
+ /**
29
+ * Every tool call in one execution, summed under the tool that actually ran.
30
+ *
31
+ * Keyed by the bus tool's name rather than the sub-question's, because that is
32
+ * the one a rate applies to and the one the bus offered.
33
+ */
34
+ export declare function toolSpendOf(answers: SubAnswer[], wiring?: Partial<Record<SubQuestionTool, Wiring>>): ToolSpend;
35
+ /** Two executions' tool spend, added. A retry does not refund the first attempt. */
36
+ export declare function addToolSpend(a: ToolSpend, b: ToolSpend): ToolSpend;
@@ -0,0 +1,79 @@
1
+ export const NO_TOOL_SPEND = { calls: 0, byTool: {} };
2
+ const numberAt = (source, field) => {
3
+ const value = source[field];
4
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5
+ };
6
+ export function readToolUsage(result) {
7
+ if (!result || typeof result !== "object")
8
+ return null;
9
+ const shape = result;
10
+ const usage = (shape.usage ?? {});
11
+ const tokens = {
12
+ inputTokens: numberAt(usage, "inputTokens"),
13
+ outputTokens: numberAt(usage, "outputTokens"),
14
+ citationTokens: numberAt(usage, "citationTokens"),
15
+ reasoningTokens: numberAt(usage, "reasoningTokens"),
16
+ };
17
+ const priced = typeof shape.costMicroUsd === "number" ? shape.costMicroUsd : undefined;
18
+ const model = typeof shape.model === "string" ? shape.model : undefined;
19
+ const spent = tokens.inputTokens + tokens.outputTokens + tokens.citationTokens + tokens.reasoningTokens;
20
+ if (spent === 0 && priced === undefined)
21
+ return null;
22
+ return { ...tokens, ...(model ? { model } : {}), ...(priced !== undefined ? { costMicros: priced } : {}) };
23
+ }
24
+ export function toolSpendOf(answers, wiring = {}) {
25
+ const byTool = {};
26
+ let calls = 0;
27
+ let costMicros;
28
+ for (const answer of answers) {
29
+ const one = readToolUsage(answer.result);
30
+ if (!one)
31
+ continue;
32
+ calls++;
33
+ const named = wiring[answer.tool]?.name ?? answer.tool;
34
+ const prior = byTool[named] ?? {
35
+ calls: 0,
36
+ inputTokens: 0,
37
+ outputTokens: 0,
38
+ citationTokens: 0,
39
+ reasoningTokens: 0,
40
+ };
41
+ const merged = {
42
+ calls: prior.calls + 1,
43
+ inputTokens: prior.inputTokens + one.inputTokens,
44
+ outputTokens: prior.outputTokens + one.outputTokens,
45
+ citationTokens: prior.citationTokens + one.citationTokens,
46
+ reasoningTokens: prior.reasoningTokens + one.reasoningTokens,
47
+ ...(one.model ?? prior.model ? { model: one.model ?? prior.model } : {}),
48
+ };
49
+ if (one.costMicros !== undefined || prior.costMicros !== undefined) {
50
+ merged.costMicros = (prior.costMicros ?? 0) + (one.costMicros ?? 0);
51
+ costMicros = (costMicros ?? 0) + (one.costMicros ?? 0);
52
+ }
53
+ byTool[named] = merged;
54
+ }
55
+ return { calls, byTool, ...(costMicros !== undefined ? { costMicros } : {}) };
56
+ }
57
+ export function addToolSpend(a, b) {
58
+ const byTool = { ...a.byTool };
59
+ for (const [tool, one] of Object.entries(b.byTool)) {
60
+ const prior = byTool[tool];
61
+ byTool[tool] = prior
62
+ ? {
63
+ calls: prior.calls + one.calls,
64
+ inputTokens: prior.inputTokens + one.inputTokens,
65
+ outputTokens: prior.outputTokens + one.outputTokens,
66
+ citationTokens: prior.citationTokens + one.citationTokens,
67
+ reasoningTokens: prior.reasoningTokens + one.reasoningTokens,
68
+ ...(one.model ?? prior.model ? { model: one.model ?? prior.model } : {}),
69
+ ...(prior.costMicros !== undefined || one.costMicros !== undefined
70
+ ? { costMicros: (prior.costMicros ?? 0) + (one.costMicros ?? 0) }
71
+ : {}),
72
+ }
73
+ : one;
74
+ }
75
+ const costMicros = a.costMicros !== undefined || b.costMicros !== undefined
76
+ ? (a.costMicros ?? 0) + (b.costMicros ?? 0)
77
+ : undefined;
78
+ return { calls: a.calls + b.calls, byTool, ...(costMicros !== undefined ? { costMicros } : {}) };
79
+ }
@@ -1,3 +1,4 @@
1
+ import type { ToolSpend } from "./spend.js";
1
2
  import type { Emit } from "../events.js";
2
3
  import type { SubQuestion, SubQuestionTool } from "../producers/decompose/index.js";
3
4
  /** Which tool answers a sub-question, and the argument its question fills. */
@@ -27,6 +28,7 @@ export interface Execution {
27
28
  answers: SubAnswer[];
28
29
  /** Ids that could never run: a cycle, or a dependency nothing created. */
29
30
  unresolved: string[];
31
+ spend: ToolSpend;
30
32
  /** Time spent inside tools. Not wall clock — a wave runs together. */
31
33
  toolMs: number;
32
34
  }
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { CLOSURE_SCHEMA, CLOSURE_SLUG } from "./prompt.js";
3
- /** Stage 6A. The last questions before an answer is allowed to close. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, CLOSURE_SLUG, CLOSURE_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 6A. Pulled by slug, so an edit needs no release here. */
2
1
  export const CLOSURE_SLUG = "ctr-6a-closure-checklist-292xfc";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const CLOSURE_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { GATE_SCHEMA, GATE_SLUG } from "./prompt.js";
3
- /** Stage 3C. Eight questions asked of the plan, and whether it may proceed. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, GATE_SLUG, GATE_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 3C. Pulled by slug, so an edit needs no release here. */
2
1
  export const GATE_SLUG = "ctr-3c-cqot-gate-7128w7";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const GATE_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { PREMORTEM_SCHEMA, PREMORTEM_SLUG } from "./prompt.js";
3
- /** Stage 3B. The plan assumed dead, and what killed it. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, PREMORTEM_SLUG, PREMORTEM_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 3B. Pulled by slug, so an edit needs no release here. */
2
1
  export const PREMORTEM_SLUG = "ctr-3b-premortem-0w34wx";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const PREMORTEM_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { RED_TEAM_SCHEMA, RED_TEAM_SLUG } from "./prompt.js";
3
- /** Stage 6B. The answer attacked, and whether it survived. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, RED_TEAM_SLUG, RED_TEAM_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 6B. Pulled by slug, so an edit needs no release here. */
2
1
  export const RED_TEAM_SLUG = "ctr-6b-red-team-f084c0";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const RED_TEAM_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { STANDARDS_SCHEMA, STANDARDS_SLUG } from "./prompt.js";
3
- /** Stage 5C. The answer against eight standards, each passed or failed on the evidence. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, STANDARDS_SLUG, STANDARDS_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 5C. Pulled by slug, so an edit needs no release here. */
2
1
  export const STANDARDS_SLUG = "ctr-5c-standards-review-17c6w4";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const STANDARDS_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { CHECKPOINT_SCHEMA, CHECKPOINT_SLUG } from "./prompt.js";
3
- /** Stage 4A. What was predicted against what happened, and whether to carry on. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, CHECKPOINT_SLUG, CHECKPOINT_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 4A. Pulled by slug, so an edit needs no release here. */
2
1
  export const CHECKPOINT_SLUG = "ctr-4a-think-checkpoint-8d5302";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const CHECKPOINT_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -38,8 +38,8 @@ export { invoke as reflection } from "./repair/reflection/index.js";
38
38
  export { routeFor } from "./route.js";
39
39
  export type { Difficulty, Stakes, TriageEstimates, TriageInput, TriageOptions, TriageResult, } from "./router/triage/index.js";
40
40
  export { invoke as triage, TriageReplyError } from "./router/triage/index.js";
41
- export type { Entry, Escalation, Ledger, NoesisStream, Recorded, StageId, StageUsage, } from "./run/index.js";
42
- export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_NAMES, STAGES, } from "./run/index.js";
41
+ export type { Entry, Escalation, Ledger, NoesisStream, Recorded, StageId, StageKey, StageName, StageUsage, } from "./run/index.js";
42
+ export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_KEYS, STAGE_NAMES, STAGES, } from "./run/index.js";
43
43
  export { dropPoint, parseStage, resumeAt } from "./run/redo.js";
44
44
  export { STAGE_OF_SLUG } from "./slugs.js";
45
45
  export type { StageOptions, StageOutcome } from "./stage.js";
@@ -19,7 +19,7 @@ export { answer, invoke as toulmin, NoAnswerError, render, ToulminReplyError, }
19
19
  export { invoke as reflection } from "./repair/reflection/index.js";
20
20
  export { routeFor } from "./route.js";
21
21
  export { invoke as triage, TriageReplyError } from "./router/triage/index.js";
22
- export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_NAMES, STAGES, } from "./run/index.js";
22
+ export { NoToolsError, newLedger, RouteFailedError, RouteNotBuiltError, run, runStream, STAGE_KEYS, STAGE_NAMES, STAGES, } from "./run/index.js";
23
23
  export { dropPoint, parseStage, resumeAt } from "./run/redo.js";
24
24
  export { STAGE_OF_SLUG } from "./slugs.js";
25
25
  export { runStage, StageReplyError } from "./stage.js";
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { COVE_ANSWERS_SCHEMA, COVE_ANSWERS_SLUG } from "./prompt.js";
3
- /** Stage 5B. Each verification question answered blind, without sight of the answer under test. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, COVE_ANSWERS_SLUG, COVE_ANSWERS_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 5B. Pulled by slug, so an edit needs no release here. */
2
1
  export const COVE_ANSWERS_SLUG = "ctr-5b-cove-blind-answer-1w1479";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const COVE_ANSWERS_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { COVE_QUESTIONS_SCHEMA, COVE_QUESTIONS_SLUG } from "./prompt.js";
3
- /** Stage 5A. The verification questions the answer's own claims invite. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, COVE_QUESTIONS_SLUG, COVE_QUESTIONS_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 5A. Pulled by slug, so an edit needs no release here. */
2
1
  export const COVE_QUESTIONS_SLUG = "ctr-5a-cove-questions-d9633d";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const COVE_QUESTIONS_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { DECOMPOSE_SCHEMA, DECOMPOSE_SLUG } from "./prompt.js";
3
- /** Stage 2. Splits the question into ordered sub-questions, and answers none. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, DECOMPOSE_SLUG, DECOMPOSE_SCHEMA, input, opts);
6
5
  }
@@ -1,22 +1,4 @@
1
- /** The stored decomposition prompt. Pulled by slug, so an edit needs no release here. */
2
1
  export const DECOMPOSE_SLUG = "ctr-2-decompose-b559f2";
3
- /**
4
- * What the reply must be, as the prompt itself specifies it.
5
- *
6
- * `maxItems` is 7 because the prompt caps it there, and the cap is the point:
7
- * unbounded splitting — task lists breeding task lists — is the failure this
8
- * stage exists to avoid.
9
- */
10
- /**
11
- * What each hint means, sent with the schema.
12
- *
13
- * Seven bare identifiers left the choice to be guessed from the name, and two of
14
- * them are wired to nothing by default — a sub-question hinting `code_exec` or
15
- * `get_documents` comes back skipped, so a guess between `deeprecall` and
16
- * `get_documents` silently costs an answer. Saying which is which is the whole
17
- * of the fix. `tests/tool-hints.test.ts` holds this text to what
18
- * `DEFAULT_TOOLS` actually wires.
19
- */
20
2
  export const TOOL_HINT = [
21
3
  "Which tool the executor should reach for. Name the cheapest one that can answer, and prefer none over a call that is not needed.",
22
4
  "- none: answerable from the question and the brief alone. Nothing is called.",
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { TEST_SCHEMA, TEST_SLUG } from "./prompt.js";
3
- /** Stage 4B. The one test whose result tells the live hypotheses apart. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, TEST_SLUG, TEST_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 4B. Pulled by slug, so an edit needs no release here. */
2
1
  export const TEST_SLUG = "ctr-4b-discriminating-test-w92055";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const TEST_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { PLAN_SCHEMA, PLAN_SLUG } from "./prompt.js";
3
- /** Stage 3A. Three approaches, the one chosen, and the steps that follow from it. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, PLAN_SLUG, PLAN_SCHEMA, input, opts);
6
5
  }
@@ -1,6 +1,4 @@
1
- /** Stage 3A. Pulled by slug, so an edit needs no release here. */
2
1
  export const PLAN_SLUG = "ctr-3a-plan-candidates-898095";
3
- /** What the reply must be, as the prompt itself specifies it. */
4
2
  export const PLAN_SCHEMA = {
5
3
  type: "object",
6
4
  properties: {
@@ -1,6 +1,5 @@
1
1
  import { runStage } from "../../stage.js";
2
2
  import { BRIEF_SCHEMA, BRIEF_SLUG } from "./prompt.js";
3
- /** Stage 1. Interrogates the question and hands back the brief, never an answer. */
4
3
  export function invoke(client, llm, input, opts = {}) {
5
4
  return runStage(client, llm, BRIEF_SLUG, BRIEF_SCHEMA, input, opts);
6
5
  }
@@ -1,15 +1,6 @@
1
- /** The stored question-brief prompt. Pulled by slug, so an edit needs no release here. */
2
1
  export const BRIEF_SLUG = "ctr-1-question-brief-f2w9fd";
3
2
  const STRINGS = { type: "array", items: { type: "string" } };
4
3
  const NULLABLE_STRING = { type: ["string", "null"] };
5
- /**
6
- * What the reply must be, as the prompt itself specifies it.
7
- *
8
- * Every key is required, including the two that are usually null: the prompt
9
- * returns `better_question` and `interpretation` as null rather than omitting
10
- * them, and a missing key would read downstream as "nothing was ambiguous"
11
- * instead of "the stage did not say".
12
- */
13
4
  export const BRIEF_SCHEMA = {
14
5
  type: "object",
15
6
  properties: {
@@ -3,19 +3,7 @@ import { addUsage, totalCost, NO_USAGE } from "../../usage.js";
3
3
  import { invoke } from "./invoke.js";
4
4
  import { render } from "./render.js";
5
5
  const DEFAULT_MIN_CONFIDENCE = 0.6;
6
- /**
7
- * The tiers to run, when the caller named none.
8
- *
9
- * One tier, and it is the model the `Llm` was built with. A default list of
10
- * model ids chosen here would answer on a model the caller never picked, and on
11
- * a provider that has never heard of those ids it fails outright — at the last
12
- * stage, after every earlier one has been paid for.
13
- *
14
- * So a cascade is something you ask for. Naming two tiers is what buys the
15
- * second opinion; naming none gets the model already configured, once.
16
- */
17
6
  const tiersFor = (llm, named) => named?.length ? named : [{ model: llm.model }];
18
- /** Every tier fell over, so there is no answer to render. */
19
7
  export class NoAnswerError extends Error {
20
8
  escalated;
21
9
  constructor(escalated) {
@@ -24,15 +12,6 @@ export class NoAnswerError extends Error {
24
12
  this.name = "NoAnswerError";
25
13
  }
26
14
  }
27
- /**
28
- * Stage 6C, cheapest model first.
29
- *
30
- * A tier is accepted when it committed to a claim and its own calibrated
31
- * confidence clears the bar; an abstention escalates whatever the bar is,
32
- * because a stage that could not answer has not answered. Nothing is rewritten
33
- * on the way up — each rung answers from the artifacts again, so an escalation
34
- * is a second opinion rather than a polish of the first.
35
- */
36
15
  export async function answer(client, llm, artifacts, opts = {}) {
37
16
  const tiers = tiersFor(llm, opts.tiers);
38
17
  const floor = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;