@owlmeans/agent 0.1.18-rc.47 → 0.1.18-rc.49

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 (111) hide show
  1. package/README.md +4 -4
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/agent/SKILL.md +9 -9
  4. package/build/helpers/compaction/types.d.ts +49 -0
  5. package/build/helpers/compaction/types.d.ts.map +1 -0
  6. package/build/helpers/compaction/types.js.map +1 -0
  7. package/build/helpers/compaction.d.ts +6 -20
  8. package/build/helpers/compaction.d.ts.map +1 -1
  9. package/build/helpers/compaction.js +72 -81
  10. package/build/helpers/compaction.js.map +1 -1
  11. package/build/helpers/index.d.ts +3 -1
  12. package/build/helpers/index.d.ts.map +1 -1
  13. package/build/helpers/index.js.map +1 -1
  14. package/build/helpers/rolling/types.d.ts +16 -0
  15. package/build/helpers/rolling/types.d.ts.map +1 -0
  16. package/build/helpers/rolling/types.js +2 -0
  17. package/build/helpers/rolling/types.js.map +1 -0
  18. package/build/helpers/rolling.d.ts +1 -1
  19. package/build/helpers/rolling.d.ts.map +1 -1
  20. package/build/helpers/tools/types.d.ts +38 -0
  21. package/build/helpers/tools/types.d.ts.map +1 -0
  22. package/build/helpers/tools/types.js +2 -0
  23. package/build/helpers/{types.js.map → tools/types.js.map} +1 -1
  24. package/build/helpers/tools.d.ts +6 -26
  25. package/build/helpers/tools.d.ts.map +1 -1
  26. package/build/helpers/tools.js +27 -42
  27. package/build/helpers/tools.js.map +1 -1
  28. package/build/model.d.ts.map +1 -1
  29. package/build/model.js +4 -4
  30. package/build/model.js.map +1 -1
  31. package/build/pipeline/index.d.ts +2 -1
  32. package/build/pipeline/index.d.ts.map +1 -1
  33. package/build/pipeline/results/types.d.ts +139 -0
  34. package/build/pipeline/results/types.d.ts.map +1 -0
  35. package/build/pipeline/results/types.js +2 -0
  36. package/build/pipeline/results/types.js.map +1 -0
  37. package/build/pipeline/{types.local.d.ts → results/types.local.d.ts} +0 -18
  38. package/build/pipeline/results/types.local.d.ts.map +1 -0
  39. package/build/pipeline/results/types.local.js.map +1 -0
  40. package/build/pipeline/results.d.ts +2 -1
  41. package/build/pipeline/results.d.ts.map +1 -1
  42. package/build/pipeline/results.js +22 -22
  43. package/build/pipeline/results.js.map +1 -1
  44. package/build/pipeline/runner/types.d.ts +293 -0
  45. package/build/pipeline/runner/types.d.ts.map +1 -0
  46. package/build/pipeline/runner/types.js +2 -0
  47. package/build/pipeline/runner/types.js.map +1 -0
  48. package/build/pipeline/runner/types.local.d.ts +19 -0
  49. package/build/pipeline/runner/types.local.d.ts.map +1 -0
  50. package/build/pipeline/runner/types.local.js +2 -0
  51. package/build/pipeline/runner/types.local.js.map +1 -0
  52. package/build/pipeline/runner.d.ts +1 -1
  53. package/build/pipeline/runner.d.ts.map +1 -1
  54. package/build/pipeline/runner.js +10 -9
  55. package/build/pipeline/runner.js.map +1 -1
  56. package/build/pipeline/types.d.ts +2 -93
  57. package/build/pipeline/types.d.ts.map +1 -1
  58. package/build/plugins/export.d.ts +2 -2
  59. package/build/plugins/export.d.ts.map +1 -1
  60. package/build/plugins/export.js +2 -2
  61. package/build/plugins/export.js.map +1 -1
  62. package/build/plugins/inquiry.d.ts.map +1 -1
  63. package/build/plugins/inquiry.js +4 -4
  64. package/build/plugins/inquiry.js.map +1 -1
  65. package/build/plugins/memory-events.d.ts +3 -1
  66. package/build/plugins/memory-events.d.ts.map +1 -1
  67. package/build/plugins/memory-events.js +4 -2
  68. package/build/plugins/memory-events.js.map +1 -1
  69. package/build/plugins/memory-graph.d.ts +5 -1
  70. package/build/plugins/memory-graph.d.ts.map +1 -1
  71. package/build/plugins/memory-graph.js +4 -2
  72. package/build/plugins/memory-graph.js.map +1 -1
  73. package/build/plugins/summarize.d.ts.map +1 -1
  74. package/build/plugins/summarize.js +2 -2
  75. package/build/plugins/summarize.js.map +1 -1
  76. package/build/types.d.ts +5 -3
  77. package/build/types.d.ts.map +1 -1
  78. package/package.json +9 -9
  79. package/src/helpers/compaction/types.ts +51 -0
  80. package/src/helpers/compaction.ts +98 -101
  81. package/src/helpers/index.ts +3 -1
  82. package/src/helpers/rolling/types.ts +16 -0
  83. package/src/helpers/rolling.ts +1 -1
  84. package/src/helpers/tools/types.ts +37 -0
  85. package/src/helpers/tools.ts +39 -47
  86. package/src/model.ts +4 -4
  87. package/src/pipeline/index.ts +2 -1
  88. package/src/pipeline/{types.local.ts → results/types.local.ts} +0 -21
  89. package/src/pipeline/results/types.ts +152 -0
  90. package/src/pipeline/results.ts +25 -24
  91. package/src/pipeline/runner/types.local.ts +21 -0
  92. package/src/pipeline/{types.ts → runner/types.ts} +4 -156
  93. package/src/pipeline/runner.ts +14 -12
  94. package/src/plugins/export.ts +2 -2
  95. package/src/plugins/inquiry.ts +4 -4
  96. package/src/plugins/memory-events.ts +8 -2
  97. package/src/plugins/memory-graph.ts +8 -2
  98. package/src/plugins/summarize.ts +2 -2
  99. package/src/types.ts +4 -4
  100. package/tests/inquiry.spec.ts +14 -15
  101. package/tests/pipeline-inquiry.spec.ts +3 -3
  102. package/tests/plugins.spec.ts +10 -10
  103. package/tests/summary.spec.ts +8 -8
  104. package/tests/tools.spec.ts +11 -11
  105. package/build/helpers/types.d.ts +0 -42
  106. package/build/helpers/types.d.ts.map +0 -1
  107. package/build/pipeline/types.local.d.ts.map +0 -1
  108. package/build/pipeline/types.local.js.map +0 -1
  109. package/src/helpers/types.ts +0 -43
  110. /package/build/helpers/{types.js → compaction/types.js} +0 -0
  111. /package/build/pipeline/{types.local.js → results/types.local.js} +0 -0
@@ -0,0 +1,152 @@
1
+ import type { CumulativeResultEntry, CumulativeResultFact, PipelineSpec, PipelineState, ResultFactQuery, ResultViewMode, CumulativeResultsSpec, ResultSummarySpec } from '@owlmeans/agent-common'
2
+ import type { CumulativeResults } from '@owlmeans/llm-common'
3
+ import type { CumulativeResultStore } from '../../stores/types.js'
4
+ import type { PipelineEnterMode, PipelineParentRef } from '../runner/types.js'
5
+
6
+ /** What a step tells the results plugin about itself. Every call adds to the last. */
7
+ export interface ResultRecordInput {
8
+ /** Files the step wrote or rewrote, relative to the project root. */
9
+ files?: string[]
10
+ /** Facts the step already knows. Taken as they are, ahead of anything an extractor finds. */
11
+ facts?: CumulativeResultFact[]
12
+ }
13
+
14
+ /** One entry a step can see, and how its view shows it. */
15
+ export interface VisibleResultEntry {
16
+ entry: CumulativeResultEntry
17
+ mode: ResultViewMode
18
+ /** Left out of the prompt for BUDGET — named in `view.omitted` — rather than by declaration. */
19
+ dropped?: boolean
20
+ }
21
+
22
+ /**
23
+ * The results surface of ONE step: the view to put in its prompt, a query over the facts behind
24
+ * that view for code that wants them, and the way to report what the step produced.
25
+ */
26
+ export interface StepResults {
27
+ /** The ledger the step's entries belong to. */
28
+ ledger: string
29
+ /** Where the step sits in the ledger — the order key a run composed under it extends. */
30
+ order: readonly number[]
31
+ /** Serializable and frozen; `ExecutionService.withResults(exec, results.view)`. */
32
+ view: CumulativeResults
33
+ /** Every entry visible to the step, oldest first, with how the view shows it. */
34
+ entries: () => readonly VisibleResultEntry[]
35
+ /**
36
+ * Facts visible to the step, narrowed by kind, name and producing step — including those the
37
+ * PROMPT left out for space or by declaration: a budget is about what a model reads, and code
38
+ * asking for a fact is not a model reading a prompt.
39
+ */
40
+ facts: (query?: ResultFactQuery) => CumulativeResultFact[]
41
+ /** Report files touched and facts known. Read once, when the step finishes. */
42
+ record: (input: ResultRecordInput) => void
43
+ }
44
+
45
+ /** What a declaration resolver, a seed and a ledger resolver are told about the run. */
46
+ export interface ResultsRunInfo<S extends PipelineState, C> {
47
+ pipeline: string
48
+ spec: PipelineSpec
49
+ runId: string
50
+ scope: string
51
+ entityId?: string
52
+ deps: C
53
+ state: Readonly<S>
54
+ mode: PipelineEnterMode
55
+ parent?: PipelineParentRef
56
+ }
57
+
58
+ /** What an extractor reads facts from. */
59
+ export interface ResultExtractInput<S extends PipelineState, C> {
60
+ pipeline: string
61
+ runId: string
62
+ step: string
63
+ /** Durable: the run's state, restored from its row on a resume. */
64
+ state: Readonly<S>
65
+ deps: C
66
+ /** Every file the entry answers for — its durable scope and whatever its step recorded. */
67
+ files: readonly string[]
68
+ /** What was recorded as touched on THIS pass — empty on a rebuild. */
69
+ changed: readonly string[]
70
+ }
71
+
72
+ export interface ResultScopeInput<S extends PipelineState, C> extends Omit<
73
+ ResultExtractInput<S, C>, 'files' | 'changed'
74
+ > {}
75
+
76
+ /**
77
+ * Facts from files. DETERMINISTIC — the same inputs give the same facts — and never a model
78
+ * call: an extractor is what makes a fact a fact, and a rebuild after a crash must find exactly
79
+ * what the first pass found.
80
+ */
81
+ export interface ResultExtractFn<S extends PipelineState, C> { (input: ResultExtractInput<S, C>): Promise<CumulativeResultFact[]> | CumulativeResultFact[] }
82
+
83
+ export interface ResultExtractorSpec<S extends PipelineState, C> {
84
+ /**
85
+ * The files this extractor answers for, recomputed from DURABLE inputs — the state's keys, the
86
+ * file tree — and never from what a step happened to report. It is what a rebuild reads when
87
+ * nothing was recorded, so a step's facts can be found again after the process that ran it died.
88
+ */
89
+ scope?: (input: ResultScopeInput<S, C>) => Promise<string[]> | string[]
90
+ extract: ResultExtractFn<S, C>
91
+ }
92
+
93
+ // Kept as a type: a union of the bare function and the spec form.
94
+ export type ResultExtractor<S extends PipelineState, C> =
95
+ ResultExtractFn<S, C> | ResultExtractorSpec<S, C>
96
+
97
+ /** Facts known before any step of the run ran. Called on every entry of the run; deterministic. */
98
+ export interface ResultSeed<S extends PipelineState, C> { (run: ResultsRunInfo<S, C>): Promise<CumulativeResultFact[]> | CumulativeResultFact[] }
99
+
100
+ /** Everything a summarize callback needs, including a ready prompt. */
101
+ export interface ResultSummaryRequest<S extends PipelineState, C> extends ResultSummarySpec {
102
+ schema: Record<string, unknown>
103
+ maxChars: number
104
+ pipeline: string
105
+ runId: string
106
+ step: string
107
+ label: string
108
+ facts: readonly CumulativeResultFact[]
109
+ files: readonly string[]
110
+ state: Readonly<S>
111
+ deps: C
112
+ signal: AbortSignal
113
+ /** The instructions, the step's facts and its files, as one deterministic prompt. */
114
+ prompt: string
115
+ }
116
+
117
+ /**
118
+ * Ask a model for a step's summary; return its schema-shaped answer.
119
+ *
120
+ * Typically `request => model.invoke(request.prompt, request.schema, { action })` on a model
121
+ * resolved for `request.role`. A throw is a warning and the entry goes without a summary.
122
+ */
123
+ export interface ResultSummarizer<S extends PipelineState, C> { (request: ResultSummaryRequest<S, C>): Promise<unknown> }
124
+
125
+ export interface CumulativeResultsPluginOptions<S extends PipelineState, C> {
126
+ alias?: string
127
+ order?: number
128
+ /**
129
+ * The declaration, or a function of the run that returns one — or `null`/`false` to leave THIS
130
+ * run without results. Resolved once, on `enter`.
131
+ */
132
+ spec:
133
+ | CumulativeResultsSpec
134
+ | ((run: ResultsRunInfo<S, C>) =>
135
+ CumulativeResultsSpec | null | false | undefined
136
+ | Promise<CumulativeResultsSpec | null | false | undefined>)
137
+ /** Named deterministic extractors, referenced by name from `StepResultsSpec.extractors`. */
138
+ extractors?: Record<string, ResultExtractor<S, C>>
139
+ /** Named facts available before any step ran. Visible to every step, in name order. */
140
+ seeds?: Record<string, ResultSeed<S, C>>
141
+ /**
142
+ * Where entries live. A composed pipeline must be given the SAME store as the pipeline it is a
143
+ * step of, or neither sees what the other produced. Default: an in-process memory store of this
144
+ * plugin instance.
145
+ */
146
+ store?: CumulativeResultStore
147
+ /** Called only for a step whose declaration has a `summary`. */
148
+ summarize?: ResultSummarizer<S, C>
149
+ /** The ledger a run's entries belong to. Default: the parent's, else the root run's id. */
150
+ ledger?: (run: ResultsRunInfo<S, C>) => string
151
+ trace?: (line: string) => void
152
+ }
@@ -1,12 +1,13 @@
1
- import { CumulativeResultSource, DEFAULT_RESULT_SUMMARY_CHARS, DEFAULT_RESULTS_MAX_CHARS, DEFAULT_RESULTS_WINDOW, RESULTS_EVERY_STEP, ResultViewMode, compareResultOrder, factKey, orderPipelineSteps, pipelineAncestry, renderResultEntry, renderResultFact, renderResultSummary, resultLabel, rootRunOf, sortFacts, type CumulativeResultEntry, type CumulativeResultFact, type PipelineState, type ResultSummarySpec, type StepResultsSpec } from '@owlmeans/agent-common'
2
- import { prefixHash } from '@owlmeans/llm'
1
+ import { CumulativeResultSource, DEFAULT_RESULT_SUMMARY_CHARS, DEFAULT_RESULTS_MAX_CHARS, DEFAULT_RESULTS_WINDOW, RESULTS_EVERY_STEP, ResultViewMode, cumulativeResultsHelper, makePipelineSpecModel, type CumulativeResultEntry, type CumulativeResultFact, type PipelineState, type ResultSummarySpec, type StepResultsSpec } from '@owlmeans/agent-common'
2
+ import { promptRenderHelper } from '@owlmeans/llm'
3
3
  import { logger } from '@owlmeans/log'
4
4
  import type { CumulativeResults } from '@owlmeans/llm-common'
5
5
  import { createMemoryCumulativeResultStore } from '../stores/memory.js'
6
- import type { PipelinePlugin, PipelineRunContext, StepResults, VisibleResultEntry, CumulativeResultsPluginOptions, ResultExtractFn, ResultExtractor, ResultsRunInfo } from './types.js'
6
+ import type { PipelinePlugin, PipelineRunContext } from './runner/types.js'
7
+ import type { StepResults, VisibleResultEntry, CumulativeResultsPluginOptions, ResultExtractFn, ResultExtractor, ResultsRunInfo } from './results/types.js'
7
8
  import { SEPARATOR } from './consts.local.js'
8
9
  import { CUMULATIVE_RESULTS_PLUGIN, DEFAULT_RESULT_SUMMARY_SCHEMA } from './consts.js'
9
- import type { EntryParts, Item, Recorded, RunLedger } from './types.local.js'
10
+ import type { EntryParts, Item, Recorded, RunLedger } from './results/types.local.js'
10
11
 
11
12
  const log = logger('agent:pipeline:results')
12
13
 
@@ -28,7 +29,7 @@ const dedupe = (facts: readonly CumulativeResultFact[]): CumulativeResultFact[]
28
29
  const seen = new Set<string>()
29
30
 
30
31
  return facts.filter(fact => {
31
- const key = factKey(fact)
32
+ const key = cumulativeResultsHelper.factKey(fact)
32
33
  if (seen.has(key)) {
33
34
  return false
34
35
  }
@@ -71,7 +72,7 @@ const summaryPrompt = (
71
72
  `The step "${label}" has just finished. For the later steps of the same work, summarize what it `
72
73
  + 'decided or produced that the facts below do not already say. Do not repeat the facts and do not '
73
74
  + `guess at anything not shown. At most ${maxChars} characters.`,
74
- `# Facts read from the files after the step finished\n${facts.map(renderResultFact).join('\n') || '(none)'}`,
75
+ `# Facts read from the files after the step finished\n${facts.map(cumulativeResultsHelper.renderResultFact).join('\n') || '(none)'}`,
75
76
  `# Files the step answers for\n${files.map(file => `- ${file}`).join('\n') || '(none)'}`,
76
77
  ].join(SEPARATOR)
77
78
 
@@ -187,7 +188,7 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
187
188
  * necessarily run, so what it says cannot be what a step relies on.
188
189
  */
189
190
  const visibleTo = (run: RunLedger<C>, step: string): Item[] => {
190
- const depths = new Map(pipelineAncestry(run.spec, step).map(({ step: name, depth }) => [name, depth]))
191
+ const depths = new Map(makePipelineSpecModel(run.spec).ancestry(step).map(({ step: name, depth }) => [name, depth]))
191
192
  const items: Item[] = [
192
193
  ...run.upstream.map(visible => ({
193
194
  entry: visible.entry, mode: visible.mode, explicit: false, dropped: visible.dropped === true,
@@ -203,7 +204,7 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
203
204
  }
204
205
 
205
206
  return items.sort((a, b) =>
206
- compareResultOrder(a.entry.order, b.entry.order) || compare(a.entry.label, b.entry.label))
207
+ cumulativeResultsHelper.compareResultOrder(a.entry.order, b.entry.order) || compare(a.entry.label, b.entry.label))
207
208
  }
208
209
 
209
210
  const textOf = (item: Item): string =>
@@ -250,11 +251,11 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
250
251
  .map(item => item.entry.label)
251
252
  const joined = sections.map(section => section.text).join(SEPARATOR)
252
253
  const view: CumulativeResults = Object.freeze({
253
- step: resultLabel(run.ledger, run.runId, step),
254
+ step: cumulativeResultsHelper.resultLabel(run.ledger, run.runId, step),
254
255
  sections: Object.freeze(sections) as CumulativeResults['sections'],
255
256
  omitted: Object.freeze(omitted) as string[],
256
257
  chars: joined.length,
257
- digest: prefixHash(`${joined}\u0000${omitted.join('\u0000')}`),
258
+ digest: promptRenderHelper.prefixHash(`${joined}\u0000${omitted.join('\u0000')}`),
258
259
  })
259
260
  const visible: readonly VisibleResultEntry[] = Object.freeze(items.map(item => Object.freeze({
260
261
  entry: item.entry, mode: item.mode, ...(item.dropped ? { dropped: true } : {}),
@@ -347,18 +348,18 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
347
348
  const owned = new Set<string>()
348
349
  for (const item of visibleTo(run, step)) {
349
350
  for (const fact of item.entry.facts) {
350
- owned.add(factKey(fact))
351
+ owned.add(cumulativeResultsHelper.factKey(fact))
351
352
  }
352
353
  }
353
354
  for (const entry of run.entries.values()) {
354
355
  if (entry.runId.startsWith(`${run.runId}/${step}`) && anchorOf(run, entry) === step) {
355
356
  for (const fact of entry.facts) {
356
- owned.add(factKey(fact))
357
+ owned.add(cumulativeResultsHelper.factKey(fact))
357
358
  }
358
359
  }
359
360
  }
360
361
 
361
- return facts.filter(fact => !owned.has(factKey(fact)))
362
+ return facts.filter(fact => !owned.has(cumulativeResultsHelper.factKey(fact)))
362
363
  }
363
364
 
364
365
  /** A step's facts and files, from its durable scope, what it recorded, and its extractors. */
@@ -374,12 +375,12 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
374
375
 
375
376
  const compose = (run: RunLedger<C>, step: string, parts: EntryParts): CumulativeResultEntry => {
376
377
  const previous = run.entries.get(keyOf(run.runId, step))
377
- const label = resultLabel(run.ledger, run.runId, step)
378
+ const label = cumulativeResultsHelper.resultLabel(run.ledger, run.runId, step)
378
379
  const source = parts.facts.length === 0 && parts.summary != null
379
380
  ? CumulativeResultSource.Summarized
380
381
  : parts.source
381
- const facts = sortFacts(parts.facts)
382
- const rendered = renderResultEntry(
382
+ const facts = cumulativeResultsHelper.sortFacts(parts.facts)
383
+ const rendered = cumulativeResultsHelper.renderResultEntry(
383
384
  { label, source, facts, ...(parts.partial === true ? { partial: true } : {}), ...(parts.summary != null ? { summary: parts.summary } : {}) },
384
385
  {
385
386
  maxChars: parts.spec?.maxChars ?? run.results.maxEntryChars,
@@ -424,8 +425,8 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
424
425
  const mine = orderOf(run, step)
425
426
  const earlier = [...run.entries.values()]
426
427
  .filter(entry => entry.runId === run.runId && entry.step !== step
427
- && compareResultOrder(entry.order, mine) < 0)
428
- .sort((a, b) => compareResultOrder(a.order, b.order))
428
+ && cumulativeResultsHelper.compareResultOrder(entry.order, mine) < 0)
429
+ .sort((a, b) => cumulativeResultsHelper.compareResultOrder(a.order, b.order))
429
430
 
430
431
  for (const entry of earlier) {
431
432
  const spec = run.results.steps?.[entry.step]
@@ -437,8 +438,8 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
437
438
  continue
438
439
  }
439
440
  const fresh = await extract(run, entry.step, state, entry.files, overlap)
440
- const freshKeys = new Set(fresh.map(factKey))
441
- const kept = entry.facts.filter(fact => !freshKeys.has(factKey(fact))
441
+ const freshKeys = new Set(fresh.map(cumulativeResultsHelper.factKey))
442
+ const kept = entry.facts.filter(fact => !freshKeys.has(cumulativeResultsHelper.factKey(fact))
442
443
  && (fact.path == null || !overlap.includes(normalizePath(fact.path))))
443
444
  const facts = unowned(run, entry.step, dedupe([...fresh, ...kept]))
444
445
  const refreshed = compose(run, entry.step, {
@@ -466,7 +467,7 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
466
467
  return undefined
467
468
  }
468
469
  const maxChars = spec.maxChars ?? DEFAULT_RESULT_SUMMARY_CHARS
469
- const label = resultLabel(run.ledger, run.runId, step)
470
+ const label = cumulativeResultsHelper.resultLabel(run.ledger, run.runId, step)
470
471
  try {
471
472
  const answer = await options.summarize({
472
473
  ...spec,
@@ -483,7 +484,7 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
483
484
  signal: ctx.signal,
484
485
  prompt: summaryPrompt(spec, label, facts, files, maxChars),
485
486
  })
486
- const text = renderResultSummary(answer, maxChars)
487
+ const text = cumulativeResultsHelper.renderResultSummary(answer, maxChars)
487
488
 
488
489
  return text !== '' ? text : undefined
489
490
  } catch (e) {
@@ -516,14 +517,14 @@ export const cumulativeResultsPlugin = <S extends PipelineState, C>(
516
517
  }
517
518
 
518
519
  const parent = event.parent?.results
519
- const ledger = options.ledger?.(info) ?? parent?.ledger ?? rootRunOf(event.runId)
520
+ const ledger = options.ledger?.(info) ?? parent?.ledger ?? cumulativeResultsHelper.rootRunOf(event.runId)
520
521
  const run: RunLedger<C> = {
521
522
  results: declared,
522
523
  spec: event.spec,
523
524
  runId: event.runId,
524
525
  ledger,
525
526
  trail: parent != null ? [...parent.order] : [],
526
- index: new Map(orderPipelineSteps(event.spec).map((step, at) => [step, at + 1])),
527
+ index: new Map(makePipelineSpecModel(event.spec).orderSteps().map((step, at) => [step, at + 1])),
527
528
  entries: new Map(),
528
529
  upstream: parent != null ? parent.entries() : [],
529
530
  seeds: [],
@@ -0,0 +1,21 @@
1
+ import type { BaseCheckpointSaver } from '@langchain/langgraph'
2
+
3
+ /**
4
+ * The graph builder, seen loosely.
5
+ *
6
+ * `StateGraph`'s node names are a generic parameter that only a literal-typed builder chain can
7
+ * satisfy, and a pipeline's nodes come from data. The precision belongs at THIS package's public
8
+ * boundary — `PipelineSpec`, `PipelineStep`, `PipelineModel` are all exact — not in the three lines
9
+ * that hand LangGraph a name it will look up in a map either way.
10
+ */
11
+ export interface LooseGraph {
12
+ addNode: (
13
+ name: string,
14
+ fn: (state: unknown) => Promise<Record<string, unknown>>,
15
+ options?: Record<string, unknown>,
16
+ ) => LooseGraph
17
+ addEdge: (start: string | string[], end: string) => LooseGraph
18
+ compile: (options?: { checkpointer?: BaseCheckpointSaver }) => {
19
+ invoke: (input: unknown, config?: Record<string, unknown>) => Promise<unknown>
20
+ }
21
+ }
@@ -1,7 +1,8 @@
1
1
  import type { BaseCheckpointSaver } from '@langchain/langgraph'
2
- import type { CumulativeResultEntry, CumulativeResultFact, PipelineProgress, PipelineRun, PipelineRunInquiry, PipelineRunStatus, PipelineSpec, PipelineState, ResultFactQuery, ResultViewMode, CumulativeResultsSpec, ResultSummarySpec } from '@owlmeans/agent-common'
3
- import type { CumulativeResults, Inquiry, InquiryAnswer } from '@owlmeans/llm-common'
4
- import type { PipelineRunStore, CumulativeResultStore } from '../stores/types.js'
2
+ import type { PipelineProgress, PipelineRun, PipelineRunInquiry, PipelineRunStatus, PipelineSpec, PipelineState } from '@owlmeans/agent-common'
3
+ import type { Inquiry, InquiryAnswer } from '@owlmeans/llm-common'
4
+ import type { PipelineRunStore } from '../../stores/types.js'
5
+ import type { StepResults } from '../results/types.js'
5
6
 
6
7
  /**
7
8
  * What a step is handed while it runs.
@@ -72,45 +73,6 @@ export interface PipelineRunContext<S extends PipelineState, C> {
72
73
  results?: StepResults
73
74
  }
74
75
 
75
- /** What a step tells the results plugin about itself. Every call adds to the last. */
76
- export interface ResultRecordInput {
77
- /** Files the step wrote or rewrote, relative to the project root. */
78
- files?: string[]
79
- /** Facts the step already knows. Taken as they are, ahead of anything an extractor finds. */
80
- facts?: CumulativeResultFact[]
81
- }
82
-
83
- /** One entry a step can see, and how its view shows it. */
84
- export interface VisibleResultEntry {
85
- entry: CumulativeResultEntry
86
- mode: ResultViewMode
87
- /** Left out of the prompt for BUDGET — named in `view.omitted` — rather than by declaration. */
88
- dropped?: boolean
89
- }
90
-
91
- /**
92
- * The results surface of ONE step: the view to put in its prompt, a query over the facts behind
93
- * that view for code that wants them, and the way to report what the step produced.
94
- */
95
- export interface StepResults {
96
- /** The ledger the step's entries belong to. */
97
- ledger: string
98
- /** Where the step sits in the ledger — the order key a run composed under it extends. */
99
- order: readonly number[]
100
- /** Serializable and frozen; `ExecutionService.withResults(exec, results.view)`. */
101
- view: CumulativeResults
102
- /** Every entry visible to the step, oldest first, with how the view shows it. */
103
- entries: () => readonly VisibleResultEntry[]
104
- /**
105
- * Facts visible to the step, narrowed by kind, name and producing step — including those the
106
- * PROMPT left out for space or by declaration: a budget is about what a model reads, and code
107
- * asking for a fact is not a model reading a prompt.
108
- */
109
- facts: (query?: ResultFactQuery) => CumulativeResultFact[]
110
- /** Report files touched and facts known. Read once, when the step finishes. */
111
- record: (input: ResultRecordInput) => void
112
- }
113
-
114
76
  /**
115
77
  * The composing step of a parent pipeline, as a run composed under it (`asStep`) sees it —
116
78
  * read-only by construction: a composed run may learn where it sits, never write its parent.
@@ -350,117 +312,3 @@ export interface PipelineModel<S extends PipelineState, C> {
350
312
  step: string, mapping: PipelineStepMapping<S, PS>
351
313
  ) => PipelineStep<PS, C>
352
314
  }
353
-
354
- /** What a declaration resolver, a seed and a ledger resolver are told about the run. */
355
- export interface ResultsRunInfo<S extends PipelineState, C> {
356
- pipeline: string
357
- spec: PipelineSpec
358
- runId: string
359
- scope: string
360
- entityId?: string
361
- deps: C
362
- state: Readonly<S>
363
- mode: PipelineEnterMode
364
- parent?: PipelineParentRef
365
- }
366
-
367
- /** What an extractor reads facts from. */
368
- export interface ResultExtractInput<S extends PipelineState, C> {
369
- pipeline: string
370
- runId: string
371
- step: string
372
- /** Durable: the run's state, restored from its row on a resume. */
373
- state: Readonly<S>
374
- deps: C
375
- /** Every file the entry answers for — its durable scope and whatever its step recorded. */
376
- files: readonly string[]
377
- /** What was recorded as touched on THIS pass — empty on a rebuild. */
378
- changed: readonly string[]
379
- }
380
-
381
- export type ResultScopeInput<S extends PipelineState, C> = Omit<
382
- ResultExtractInput<S, C>, 'files' | 'changed'
383
- >
384
-
385
- /**
386
- * Facts from files. DETERMINISTIC — the same inputs give the same facts — and never a model
387
- * call: an extractor is what makes a fact a fact, and a rebuild after a crash must find exactly
388
- * what the first pass found.
389
- */
390
- export type ResultExtractFn<S extends PipelineState, C> = (
391
- input: ResultExtractInput<S, C>,
392
- ) => Promise<CumulativeResultFact[]> | CumulativeResultFact[]
393
-
394
- export interface ResultExtractorSpec<S extends PipelineState, C> {
395
- /**
396
- * The files this extractor answers for, recomputed from DURABLE inputs — the state's keys, the
397
- * file tree — and never from what a step happened to report. It is what a rebuild reads when
398
- * nothing was recorded, so a step's facts can be found again after the process that ran it died.
399
- */
400
- scope?: (input: ResultScopeInput<S, C>) => Promise<string[]> | string[]
401
- extract: ResultExtractFn<S, C>
402
- }
403
-
404
- export type ResultExtractor<S extends PipelineState, C> =
405
- ResultExtractFn<S, C> | ResultExtractorSpec<S, C>
406
-
407
- /** Facts known before any step of the run ran. Called on every entry of the run; deterministic. */
408
- export type ResultSeed<S extends PipelineState, C> = (
409
- run: ResultsRunInfo<S, C>,
410
- ) => Promise<CumulativeResultFact[]> | CumulativeResultFact[]
411
-
412
- /** Everything a summarize callback needs, including a ready prompt. */
413
- export interface ResultSummaryRequest<S extends PipelineState, C> extends ResultSummarySpec {
414
- schema: Record<string, unknown>
415
- maxChars: number
416
- pipeline: string
417
- runId: string
418
- step: string
419
- label: string
420
- facts: readonly CumulativeResultFact[]
421
- files: readonly string[]
422
- state: Readonly<S>
423
- deps: C
424
- signal: AbortSignal
425
- /** The instructions, the step's facts and its files, as one deterministic prompt. */
426
- prompt: string
427
- }
428
-
429
- /**
430
- * Ask a model for a step's summary; return its schema-shaped answer.
431
- *
432
- * Typically `request => model.invoke(request.prompt, request.schema, { action })` on a model
433
- * resolved for `request.role`. A throw is a warning and the entry goes without a summary.
434
- */
435
- export type ResultSummarizer<S extends PipelineState, C> = (
436
- request: ResultSummaryRequest<S, C>,
437
- ) => Promise<unknown>
438
-
439
- export interface CumulativeResultsPluginOptions<S extends PipelineState, C> {
440
- alias?: string
441
- order?: number
442
- /**
443
- * The declaration, or a function of the run that returns one — or `null`/`false` to leave THIS
444
- * run without results. Resolved once, on `enter`.
445
- */
446
- spec:
447
- | CumulativeResultsSpec
448
- | ((run: ResultsRunInfo<S, C>) =>
449
- CumulativeResultsSpec | null | false | undefined
450
- | Promise<CumulativeResultsSpec | null | false | undefined>)
451
- /** Named deterministic extractors, referenced by name from `StepResultsSpec.extractors`. */
452
- extractors?: Record<string, ResultExtractor<S, C>>
453
- /** Named facts available before any step ran. Visible to every step, in name order. */
454
- seeds?: Record<string, ResultSeed<S, C>>
455
- /**
456
- * Where entries live. A composed pipeline must be given the SAME store as the pipeline it is a
457
- * step of, or neither sees what the other produced. Default: an in-process memory store of this
458
- * plugin instance.
459
- */
460
- store?: CumulativeResultStore
461
- /** Called only for a step whose declaration has a `summary`. */
462
- summarize?: ResultSummarizer<S, C>
463
- /** The ledger a run's entries belong to. Default: the parent's, else the root run's id. */
464
- ledger?: (run: ResultsRunInfo<S, C>) => string
465
- trace?: (line: string) => void
466
- }
@@ -1,14 +1,15 @@
1
1
  import { Annotation, END, START, StateGraph } from '@langchain/langgraph'
2
- import { AgentRunStateError, DEFAULT_MAX_STATE_CHARS, INQUIRY_ANSWERS_KEY, PipelineNotIdempotentError, PipelineNotResumableError, PipelineRunStatus, PipelineSpecError, PipelineStateTooLargeError, PipelineVersionError, orderPipelineSteps, pipelineDescendants, pipelineStep, validatePipelineSpec, type PipelineProgress, type PipelineRun, type PipelineRunInquiry, type PipelineSpec, type PipelineState, type PipelineStepSpec } from '@owlmeans/agent-common'
3
- import { DEFAULT_INQUIRY_ANSWER_CHARS, capAnswer, stateAnswerOf, type InquiryAnswer } from '@owlmeans/llm-common'
2
+ import { AgentRunStateError, DEFAULT_MAX_STATE_CHARS, INQUIRY_ANSWERS_KEY, PipelineNotIdempotentError, PipelineNotResumableError, PipelineRunStatus, PipelineSpecError, PipelineStateTooLargeError, PipelineVersionError, type PipelineProgress, type PipelineRun, type PipelineRunInquiry, type PipelineSpec, type PipelineState, type PipelineStepSpec, makePipelineSpecModel } from '@owlmeans/agent-common'
3
+ import { DEFAULT_INQUIRY_ANSWER_CHARS, type InquiryAnswer, inquiryHelper } from '@owlmeans/llm-common'
4
4
  import { logger } from '@owlmeans/log'
5
5
  import type {
6
6
  PipelineEnterMode, PipelineInvokeArgs, PipelineModel, PipelineOptions, PipelineParentRef,
7
7
  PipelinePlugin, PipelineResult, PipelineRunContext, PipelineStep, PipelineStepContribution,
8
- PipelineStepMapping, StepResults,
9
- } from './types.js'
8
+ PipelineStepMapping,
9
+ } from './runner/types.js'
10
+ import type { StepResults } from './results/types.js'
10
11
  import { DEFAULT_PIPELINE_PLUGIN_ORDER, MAX_INQUIRY_ROUNDS } from './consts.local.js'
11
- import type { LooseGraph } from './types.local.js'
12
+ import type { LooseGraph } from './runner/types.local.js'
12
13
 
13
14
  const log = logger('agent:pipeline')
14
15
 
@@ -78,7 +79,8 @@ const answersIn = (state: unknown, key: string): Record<string, InquiryAnswer> =
78
79
  export const makePipeline = <S extends PipelineState, C>(
79
80
  spec: PipelineSpec, options: PipelineOptions<S, C>,
80
81
  ): PipelineModel<S, C> => {
81
- validatePipelineSpec(spec)
82
+ const specModel = makePipelineSpecModel(spec)
83
+ specModel.validate()
82
84
 
83
85
  const handlers = new Map<string, PipelineStep<S, C>>()
84
86
  const faults: string[] = []
@@ -100,7 +102,7 @@ export const makePipeline = <S extends PipelineState, C>(
100
102
  throw new PipelineSpecError(`${spec.alias}: ${faults.join('; ')}`)
101
103
  }
102
104
 
103
- const order = orderPipelineSteps(spec)
105
+ const order = specModel.orderSteps()
104
106
  const total = order.length
105
107
  const runs = options.runs
106
108
  const maxStateChars = options.maxStateChars ?? DEFAULT_MAX_STATE_CHARS
@@ -145,7 +147,7 @@ export const makePipeline = <S extends PipelineState, C>(
145
147
 
146
148
  /** The copy of an answer this pipeline's STATE is allowed to hold. */
147
149
  const forState = (answer: InquiryAnswer): InquiryAnswer =>
148
- stateAnswerOf(capAnswer(answer, maxAnswerChars))
150
+ inquiryHelper.stateAnswerOf(inquiryHelper.capAnswer(answer, maxAnswerChars))
149
151
 
150
152
  /**
151
153
  * Fold every source of answers into the live state, later sources winning per id.
@@ -321,7 +323,7 @@ export const makePipeline = <S extends PipelineState, C>(
321
323
  : null
322
324
 
323
325
  if (answer != null) {
324
- const capped = capAnswer({ ...answer, inquiryId: inquiry.id }, maxAnswerChars)
326
+ const capped = inquiryHelper.capAnswer({ ...answer, inquiryId: inquiry.id }, maxAnswerChars)
325
327
  // The state keeps the DECISION and a short excerpt of any prose; the step is handed the
326
328
  // whole answer. A state is scalars and keys, and two long answers would spend a
327
329
  // pipeline's whole state budget on text nothing replays from.
@@ -333,7 +335,7 @@ export const makePipeline = <S extends PipelineState, C>(
333
335
  // person has already answered to be asked again on the next entry.
334
336
  await ctx.mark({
335
337
  [answersKey]: {
336
- ...answersIn(live, answersKey), [inquiry.id]: stateAnswerOf(capped),
338
+ ...answersIn(live, answersKey), [inquiry.id]: inquiryHelper.stateAnswerOf(capped),
337
339
  },
338
340
  } as Partial<S>)
339
341
 
@@ -765,9 +767,9 @@ export const makePipeline = <S extends PipelineState, C>(
765
767
 
766
768
  let completed = [...existing.completed]
767
769
  if (args.from != null) {
768
- const rerun = pipelineDescendants(spec, args.from)
770
+ const rerun = specModel.descendants(args.from)
769
771
  const blocked = rerun.filter(step =>
770
- completed.includes(step) && pipelineStep(spec, step)?.nonIdempotent === true)
772
+ completed.includes(step) && specModel.stepOf(step)?.nonIdempotent === true)
771
773
  if (blocked.length > 0 && args.force !== true) {
772
774
  throw new PipelineNotIdempotentError(`${runId}:${blocked.join(',')}`)
773
775
  }
@@ -4,11 +4,11 @@ export { summarizePlugin } from './summarize.js'
4
4
  export { SUMMARIZE_PLUGIN } from './consts.js'
5
5
  export type { SummarizeOptions } from './types.js'
6
6
 
7
- export { memoryGraph, memoryGraphPlugin } from './memory-graph.js'
7
+ export { makeMemoryGraphApi, memoryGraph, memoryGraphPlugin } from './memory-graph.js'
8
8
  export { DEFAULT_FOLLOW, MEMORY_GRAPH_PLUGIN } from './consts.js'
9
9
  export type { MemoryGraphApi, MemoryGraphOptions } from './types.js'
10
10
 
11
- export { memoryEvents, memoryEventsPlugin } from './memory-events.js'
11
+ export { makeMemoryEventsApi, memoryEvents, memoryEventsPlugin } from './memory-events.js'
12
12
  export { DEFAULT_MEMORY_EVENT_CHARS, MEMORY_EVENTS_PLUGIN } from './consts.js'
13
13
  export type { MemoryEventsApi, MemoryEventsOptions } from './types.js'
14
14
 
@@ -1,12 +1,12 @@
1
1
  import { tool } from '@langchain/core/tools'
2
2
  import { IdStyle, createIdOfLength } from '@owlmeans/basic-ids'
3
- import { DEFAULT_INQUIRY_OPTIONS, InquiryKind, capAnswer, type Inquiry } from '@owlmeans/llm-common'
3
+ import { DEFAULT_INQUIRY_OPTIONS, InquiryKind, type Inquiry, inquiryHelper } from '@owlmeans/llm-common'
4
4
  import { InquiryDeclined, InquiryUnavailable } from '@owlmeans/llm'
5
- import { toErrorResponse } from '../helpers/tools.js'
6
5
  import type { AgentPlugin, AgentToolSet } from '../types.js'
7
6
  import { ASK_USER_TOOL, INQUIRY_PLUGIN } from './consts.js'
8
7
  import type { AskUserArgs } from './types.local.js'
9
8
  import type { InquiryPluginOptions } from './types.js'
9
+ import { toolHelper } from '../helpers/tools.js'
10
10
 
11
11
 
12
12
  /**
@@ -78,7 +78,7 @@ export const inquiryPlugin = (options: InquiryPluginOptions = {}): AgentPlugin =
78
78
  }
79
79
  }
80
80
 
81
- return JSON.stringify(capAnswer(answer, options.maxAnswerChars))
81
+ return JSON.stringify(inquiryHelper.capAnswer(answer, options.maxAnswerChars))
82
82
  } catch (e) {
83
83
  if (e instanceof InquiryUnavailable) {
84
84
  // The one throw. A channel that has gone cannot be argued with, and containing it
@@ -91,7 +91,7 @@ export const inquiryPlugin = (options: InquiryPluginOptions = {}): AgentPlugin =
91
91
  }
92
92
  }
93
93
 
94
- return toErrorResponse(e)
94
+ return toolHelper.toErrorResponse(e)
95
95
  }
96
96
  },
97
97
  {
@@ -16,7 +16,7 @@ import type { MemoryEventsApi, MemoryEventsOptions } from './types.js'
16
16
  * The bound is per scope, and pruning is per scope, so a busy subject cannot evict a quiet one's
17
17
  * whole history.
18
18
  */
19
- export const memoryEvents = (
19
+ export const makeMemoryEventsApi = (
20
20
  store: MemoryEventStore,
21
21
  options: Pick<MemoryEventsOptions, 'limit' | 'maxEventChars'> = {},
22
22
  ): MemoryEventsApi => {
@@ -35,7 +35,7 @@ export const memoryEvents = (
35
35
  export const memoryEventsPlugin = (options: MemoryEventsOptions = {}): AgentPlugin => {
36
36
  const { store, window = DEFAULT_EVENT_WINDOW, tools: withTools = true } = options
37
37
  const scopeOf = (run: AgentRun): string => options.scope?.(run) ?? run.conversation.scope
38
- const api = (): MemoryEventsApi | null => store == null ? null : memoryEvents(store, options)
38
+ const api = (): MemoryEventsApi | null => store == null ? null : makeMemoryEventsApi(store, options)
39
39
 
40
40
  return {
41
41
  alias: MEMORY_EVENTS_PLUGIN,
@@ -105,3 +105,9 @@ export const memoryEventsPlugin = (options: MemoryEventsOptions = {}): AgentPlug
105
105
  },
106
106
  }
107
107
  }
108
+
109
+ /** @deprecated compat:factory-refactor — use `makeMemoryEventsApi(…)` */
110
+ export const memoryEvents = (
111
+ store: MemoryEventStore,
112
+ options: Pick<MemoryEventsOptions, 'limit' | 'maxEventChars'> = {},
113
+ ): MemoryEventsApi => makeMemoryEventsApi(store, options)