@owlmeans/llm 0.1.18-rc.3 → 0.1.18-rc.31

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 (103) hide show
  1. package/README.md +2 -2
  2. package/agent-meta/manifest.json +9 -2
  3. package/agent-meta/skills/inquiry/SKILL.md +200 -0
  4. package/agent-meta/skills/llm/SKILL.md +248 -117
  5. package/agent-meta/skills/llm-prompt-caching/SKILL.md +37 -3
  6. package/build/consts.d.ts +4 -0
  7. package/build/consts.d.ts.map +1 -1
  8. package/build/consts.js +4 -0
  9. package/build/consts.js.map +1 -1
  10. package/build/execution/service.d.ts.map +1 -1
  11. package/build/execution/service.js +60 -11
  12. package/build/execution/service.js.map +1 -1
  13. package/build/execution/types.d.ts +91 -13
  14. package/build/execution/types.d.ts.map +1 -1
  15. package/build/execution/utils.d.ts.map +1 -1
  16. package/build/execution/utils.js +15 -9
  17. package/build/execution/utils.js.map +1 -1
  18. package/build/helpers/retry.d.ts +14 -0
  19. package/build/helpers/retry.d.ts.map +1 -1
  20. package/build/helpers/retry.js +14 -0
  21. package/build/helpers/retry.js.map +1 -1
  22. package/build/helpers/spectate.d.ts.map +1 -1
  23. package/build/helpers/spectate.js +12 -5
  24. package/build/helpers/spectate.js.map +1 -1
  25. package/build/index.d.ts +3 -2
  26. package/build/index.d.ts.map +1 -1
  27. package/build/index.js +3 -2
  28. package/build/index.js.map +1 -1
  29. package/build/inquiry/bridge.d.ts +22 -0
  30. package/build/inquiry/bridge.d.ts.map +1 -0
  31. package/build/inquiry/bridge.js +29 -0
  32. package/build/inquiry/bridge.js.map +1 -0
  33. package/build/inquiry/errors.d.ts +29 -0
  34. package/build/inquiry/errors.d.ts.map +1 -0
  35. package/build/inquiry/errors.js +40 -0
  36. package/build/inquiry/errors.js.map +1 -0
  37. package/build/inquiry/index.d.ts +4 -0
  38. package/build/inquiry/index.d.ts.map +1 -0
  39. package/build/inquiry/index.js +4 -0
  40. package/build/inquiry/index.js.map +1 -0
  41. package/build/inquiry/transport.d.ts +12 -0
  42. package/build/inquiry/transport.d.ts.map +1 -0
  43. package/build/inquiry/transport.js +35 -0
  44. package/build/inquiry/transport.js.map +1 -0
  45. package/build/model.d.ts +1 -1
  46. package/build/model.d.ts.map +1 -1
  47. package/build/model.js +200 -141
  48. package/build/model.js.map +1 -1
  49. package/build/plugins/anthropic.d.ts +40 -0
  50. package/build/plugins/anthropic.d.ts.map +1 -1
  51. package/build/plugins/anthropic.js +79 -6
  52. package/build/plugins/anthropic.js.map +1 -1
  53. package/build/plugins/openai.d.ts +12 -0
  54. package/build/plugins/openai.d.ts.map +1 -1
  55. package/build/plugins/openai.js +29 -3
  56. package/build/plugins/openai.js.map +1 -1
  57. package/build/plugins/types.d.ts +7 -0
  58. package/build/plugins/types.d.ts.map +1 -1
  59. package/build/prompt/service.d.ts.map +1 -1
  60. package/build/prompt/service.js +11 -0
  61. package/build/prompt/service.js.map +1 -1
  62. package/build/prompt/types.d.ts +20 -0
  63. package/build/prompt/types.d.ts.map +1 -1
  64. package/build/service.d.ts.map +1 -1
  65. package/build/service.js +42 -7
  66. package/build/service.js.map +1 -1
  67. package/build/types.d.ts +83 -1
  68. package/build/types.d.ts.map +1 -1
  69. package/build/utils/config.d.ts +11 -0
  70. package/build/utils/config.d.ts.map +1 -1
  71. package/build/utils/config.js +21 -1
  72. package/build/utils/config.js.map +1 -1
  73. package/build/utils/null-report.d.ts.map +1 -1
  74. package/build/utils/null-report.js +9 -1
  75. package/build/utils/null-report.js.map +1 -1
  76. package/package.json +13 -13
  77. package/src/consts.ts +4 -0
  78. package/src/execution/service.ts +62 -11
  79. package/src/execution/types.ts +99 -15
  80. package/src/execution/utils.ts +16 -9
  81. package/src/helpers/retry.ts +16 -0
  82. package/src/helpers/spectate.ts +14 -5
  83. package/src/index.ts +5 -2
  84. package/src/inquiry/bridge.ts +33 -0
  85. package/src/inquiry/errors.ts +46 -0
  86. package/src/inquiry/index.ts +3 -0
  87. package/src/inquiry/transport.ts +42 -0
  88. package/src/model.ts +113 -37
  89. package/src/plugins/anthropic.ts +89 -6
  90. package/src/plugins/openai.ts +33 -3
  91. package/src/plugins/types.ts +8 -0
  92. package/src/prompt/service.ts +12 -0
  93. package/src/prompt/types.ts +20 -0
  94. package/src/service.ts +53 -7
  95. package/src/types.ts +84 -1
  96. package/src/utils/config.ts +23 -1
  97. package/src/utils/null-report.ts +9 -1
  98. package/tests/context.ts +3 -0
  99. package/tests/execution.spec.ts +148 -13
  100. package/tests/helpers.spec.ts +4 -1
  101. package/tests/inquiry.spec.ts +200 -0
  102. package/tests/plugins.spec.ts +211 -0
  103. package/tests/prompt.spec.ts +43 -0
package/README.md CHANGED
@@ -20,7 +20,7 @@ abstraction that resolves models from an inheritable policy.
20
20
  ## Installation
21
21
 
22
22
  ```bash
23
- bun add @owlmeans/llm @owlmeans/llm-common
23
+ bun add @owlmeans/llm@^0.1.18-rc.31 @owlmeans/llm-common@^0.1.18-rc.30
24
24
  bun add @langchain/core @langchain/openai @langchain/anthropic # peer dependencies
25
25
  ```
26
26
 
@@ -184,7 +184,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
184
184
  your project's skill store (`.agents/skills/`):
185
185
 
186
186
  ```sh
187
- npx @owlmeans/agent-skills
187
+ npx @owlmeans/agent-skills@^0.1.18-rc.31
188
188
  ```
189
189
 
190
190
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/llm",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.505Z",
4
+ "version": "0.1.18-rc.31",
5
+ "generatedAt": "2026-09-21T21:54:22.955Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -12,6 +12,13 @@
12
12
  "file": "skills/llm/SKILL.md",
13
13
  "canonicalPath": ".agents/skills/llm/SKILL.md"
14
14
  },
15
+ {
16
+ "kind": "skill",
17
+ "name": "inquiry",
18
+ "category": "multi-package",
19
+ "file": "skills/inquiry/SKILL.md",
20
+ "canonicalPath": ".agents/skills/inquiry/SKILL.md"
21
+ },
15
22
  {
16
23
  "kind": "skill",
17
24
  "name": "llm-prompt-caching",
@@ -0,0 +1,200 @@
1
+ ---
2
+ name: inquiry
3
+ description: The human-in-the-loop primitive — one question put to a person while a run is in flight. Covers the llm-common contracts (Inquiry, InquiryAnswer, InquiryPolicy, the one answer ceiling), the llm transport registry and ExecutionService.ask, the executionInquiry bridge, the agent ask_user plugin, and the pipeline Waiting/resume path. Use when a run needs a decision that is not its own, when seating or releasing an inquiry channel, or when a run parked Waiting and nothing answered it.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # Inquiry — asking a person mid-run
9
+
10
+ One question, put to whoever is behind a run, answered in seconds or not at all. It is the only
11
+ supported way for an execution, an agent or a pipeline to obtain a decision that is genuinely not
12
+ its own — a missing sub-project, a choice between two products the code could be, a relocation
13
+ nobody may perform unasked.
14
+
15
+ It is deliberately NOT a form, a chat or a review queue: a question offers a choice, free text or a
16
+ yes/no, and a run that is not being watched must never block on one.
17
+
18
+ ## Where each half lives
19
+
20
+ | Layer | Package | What it owns |
21
+ |---|---|---|
22
+ | Contracts | `@owlmeans/llm-common` (`src/inquiry/`) | `Inquiry`, `InquiryAnswer`, `InquiryOption`, `InquiryTransport`, `InquiryConfig`, `InquiryKind`, `InquiryPolicy`, the constants, and the pure helpers |
23
+ | Runtime | `@owlmeans/llm` (`src/inquiry/`) | The transport registry, `InquiryError`/`InquiryUnavailable`/`InquiryDeclined`, `executionInquiry`, and `ExecutionService.ask` |
24
+ | Pipeline | `@owlmeans/agent-common` | `PipelineRunStatus.Waiting`, `PipelineRunInquiry`, `INQUIRY_ANSWERS_KEY`, `PipelineNotResumableError` |
25
+ | Runner + tool | `@owlmeans/agent` | `PipelineRunContext.ask`, the `inquiry` stop reason, the answers merge, `inquiryPlugin` / the `ask_user` tool |
26
+ | Wire | `@owlmeans/viable-common` (`src/connect/`) | The connector's own copy — `ConnectInquiryKind`, `InquiryPayload`, `InquiryAnswerPayload` — renamed so both vocabularies can be imported into one file |
27
+
28
+ ## The contracts
29
+
30
+ ```typescript
31
+ enum InquiryKind { Choice = 'choice', Text = 'text', Confirm = 'confirm' }
32
+ enum InquiryPolicy { Ask = 'ask', Default = 'default', Refuse = 'refuse' }
33
+
34
+ interface Inquiry {
35
+ id: string // chosen by whoever asks; the ONLY thing that routes an answer back
36
+ kind: InquiryKind
37
+ question: string // one question, in plain words
38
+ context?: string // one or two sentences of background — never the whole task
39
+ options?: InquiryOption[] // required for Choice, ignored otherwise
40
+ multiple?: boolean
41
+ allowText?: boolean // a Choice the answerer may answer in their own words instead
42
+ default?: string | string[] // what to assume when nobody answers
43
+ expiresAt?: string
44
+ }
45
+
46
+ interface InquiryAnswer {
47
+ inquiryId: string
48
+ value?: string | string[]
49
+ text?: string
50
+ declined?: boolean // nobody could decide — an ANSWER, never a failure
51
+ truncated?: boolean // set by capAnswer or stateAnswerOf, never by an answerer
52
+ }
53
+
54
+ interface InquiryTransport { ask: (inquiry: Inquiry, signal?: AbortSignal) => Promise<InquiryAnswer> }
55
+ interface InquiryConfig { transport?: string; policy: InquiryPolicy }
56
+ ```
57
+
58
+ The pure helpers are the ONE reading of an answer every layer uses — a second reading is a second
59
+ contract: `defaultAnswerFor(inquiry)` (the default, or a decline), `answeredWith(answer)` (the first
60
+ value, else the text, else `null`), `isDeclined(answer)`, `capAnswer(answer, max?)`,
61
+ `stateAnswerOf(answer)`, `renderInquiry(inquiry)` (a one-line label for a note or a run row).
62
+
63
+ ## Three policies, and no fourth
64
+
65
+ `InquiryConfig` travels on `ExecutionState.inquiry` — serializable, and deliberately absent from
66
+ `COLLABORATOR_KEYS`, so a run resumed days later asks through the same channel under the same
67
+ policy.
68
+
69
+ | Policy | `ExecutionService.ask` does |
70
+ |---|---|
71
+ | `Ask` | Hands the question to the seated transport and waits for the answer |
72
+ | `Default` | Returns `defaultAnswerFor(inquiry)` and asks nobody. The caller is expected to RECORD the assumption where a user can read it |
73
+ | `Refuse` | Throws `InquiryDeclined` — nobody may be asked at all |
74
+
75
+ **No configuration means `Default`.** A run that was never given a channel must never block on one:
76
+ that is what makes the primitive safe to reach for from code that also runs unattended (a web
77
+ project pipeline, a scheduled job).
78
+
79
+ ## One ceiling, referenced rather than restated
80
+
81
+ `DEFAULT_INQUIRY_ANSWER_CHARS` (2 000) is the only ceiling on a stored answer. The connector's
82
+ `CONNECT_INQUIRY_MAX_TEXT` equals it, the connector's answer schema caps `text` at it, and
83
+ `capAnswer` enforces it. Three ceilings for one value is how a user's 3 000-character answer is
84
+ accepted on the wire and silently halved further in — so never introduce a local cap; import this
85
+ one.
86
+
87
+ **Only the prose is ever cut.** A `value` is the decision itself — for a `Choice` it must equal one
88
+ of the question's own option values — so a shortened one is not a degraded answer but a different
89
+ answer, matching no option, which `answeredWith` would hand on as what the person chose. An
90
+ over-long `value` is a defect upstream (the connector's schema refuses one rather than shortening
91
+ it): `capAnswer` passes it through whole and raises `truncated` on it.
92
+
93
+ `capAnswer` **reports** the cut (`truncated: true`). A silent truncation is exactly the class of
94
+ failure the primitive exists to prevent.
95
+
96
+ A resumable pipeline **state** stores less: `stateAnswerOf` keeps the decision whole and cuts the
97
+ prose to `INQUIRY_STATE_TEXT_CHARS` (200), because a state is keys, markers and paths — two
98
+ full-size answers would make it prose. The full answer still goes back to whoever asked; long text
99
+ belongs in whatever document the application keeps for it (the converter writes
100
+ `docs/conversion/inquiries.md`, keyed by inquiry id).
101
+
102
+ ## The transport registry, and why an absent channel is fatal
103
+
104
+ ```typescript
105
+ registerInquiryTransport(key, transport) // seat on attach
106
+ releaseInquiryTransport(key) // release when the channel goes away
107
+ hasInquiryTransport(key)
108
+ inquiryTransportFor(key | undefined) // throws InquiryUnavailable — never waits
109
+ ```
110
+
111
+ Module-level and keyed by string, exactly like the delegate-transport and provider-plugin
112
+ registries beside it: a process holds many at once, and an execution names the one its run belongs
113
+ to. It is `inquiryTransportFor`, not `transportFor`, because `@owlmeans/llm` and
114
+ `@owlmeans/llm-delegate` are re-exported into one namespace by `@owlmeans/viable`.
115
+
116
+ **A transport that cannot serve a question THROWS.** A declined answer is a decision; a channel that
117
+ is gone is terminal, and the two must never look alike.
118
+
119
+ `InquiryUnavailable` is registered fatal (`registerFatalError`) **beside the throw**, so no caller
120
+ has to remember: every retry ladder aborts at once instead of spending itself on a channel nobody is
121
+ behind. `InquiryDeclined` is deliberately NOT fatal — the run decides for itself and carries on.
122
+
123
+ ## `ExecutionService.ask` and the one bridge
124
+
125
+ ```typescript
126
+ const answer = await ctx.executions().ask(exec, inquiry) // policy-driven, may throw
127
+ const ask = executionInquiry(service, exec) // (inquiry, signal?) => answer | null
128
+ ```
129
+
130
+ `executionInquiry` is the ONE adapter that maps `InquiryUnavailable → null` and rethrows everything
131
+ else. Wire a pipeline's `options.inquiry.ask` and an agent plugin's channel through it rather than
132
+ catching the error again: a consumer that folded a decline into the same `null` would turn "I will
133
+ not decide" into a run that parks forever, and one that folded them the other way would fail a run
134
+ because a browser tab closed.
135
+
136
+ It asks for the one METHOD it calls (`{ ask }`) and infers the execution type from the execution
137
+ handed to it, so a consumer's own service — `@owlmeans/viable`'s, instantiated with its own
138
+ `ViableExecutionShape` — passes with no type argument, and a facade or a test double stands in
139
+ just as well. Never make an adapter generic over the SHAPE instead: `S` appears in
140
+ `ExecutionService<S>` only through indexed accesses, which is not an inference site, so it silently
141
+ falls back to the bare `ExecutionShape` and refuses every real service contravariantly on `root`.
142
+
143
+ ## The `ask_user` tool
144
+
145
+ `inquiryPlugin({ ask })` (`@owlmeans/agent`, alias `INQUIRY_PLUGIN`, order 45) adds the `ask_user`
146
+ tool and one Context paragraph. Both appear **only when `ask` is wired** — a tool nobody can serve
147
+ is a tool the model tries once and remembers as broken.
148
+
149
+ The body never throws, with exactly one exception: `InquiryUnavailable` is **rethrown**. No channel
150
+ is an answerable situation (`{ error: 'Nobody can answer …' }`); a channel that WAS there and
151
+ vanished is not. That escape only works if the agent passes `fatal: e => isFatalError(e) != null`
152
+ into `safeInvokeTool` — **an agent installing this plugin must**, or the loop spends its whole turn
153
+ budget on a dead channel.
154
+
155
+ ## Parking a pipeline: `Waiting`
156
+
157
+ `PipelineRunContext.ask(inquiry)` has three outcomes, in order:
158
+
159
+ 1. An answer already in the state (a resume) is returned at once — a question is never asked twice.
160
+ 2. A live channel answers: the answer is recorded in the state (`stateAnswerOf(capAnswer(...))`
161
+ under `state[INQUIRY_ANSWERS_KEY][inquiry.id]`) and the FULL answer is returned.
162
+ 3. Nobody is there: the run stops `Waiting` with the inquiry on its row, and the call never returns.
163
+
164
+ **`Inquiry.id` must be stable across re-entries of the same step.** It is the only thing an answer
165
+ is matched by, so derive it from the step and the thing being decided (`${step}:${entity}:tone`) —
166
+ never mint one per call. A fresh id can never match what the state holds, so the run asks again on
167
+ every entry and parks forever, which is the one way to break the asked-once contract from inside.
168
+
169
+ A `Waiting` run is resumable and **not stale** — it has no process, and its heartbeat will not move
170
+ again until somebody answers, so nothing that sweeps stale runs may pick it up. A run with no store
171
+ cannot park at all: it throws `PipelineNotResumableError`, because nothing would be there to resume.
172
+
173
+ Answers are merged **by the runner**, in both `invoke` and `resume` (`resume(runId, { answers })`),
174
+ never by a caller's mapping: the seed overwrites the restored state key by key, so a mapping that
175
+ forwarded the answers map would wipe the child's own recorded answers. The merge applies the same
176
+ `stateAnswerOf(capAnswer(...))` cut the live path does — a resume is how an answer usually arrives,
177
+ so a ceiling enforced on the live path alone is enforced where the least text comes in. Answers are
178
+ also the one state key that ACCUMULATES, so `ctx.ask` re-reads the map at write time: steps with no
179
+ edge between them run in one superstep, and a write built from a pre-await copy loses a sibling's
180
+ answer. When a composed child parks, `asStep` asks the parent's `ctx.ask` and re-invokes the child
181
+ if an answer comes back (bounded); only when the parent's own `ask` parks does the parent park too.
182
+
183
+ ## Rules
184
+
185
+ - Ask only for a decision that is genuinely a person's. Anything discoverable by reading is not a
186
+ question.
187
+ - One question at a time, with a `default` wherever one is defensible — most runs answer themselves
188
+ under policy `Default`, and a question with no default costs them a decline.
189
+ - Never widen a `Choice` past `DEFAULT_INQUIRY_OPTIONS` (12); beyond that it is not a question.
190
+ - Never introduce a second answer ceiling, a second reading of "was this answered", or a second
191
+ mapping of `InquiryUnavailable`.
192
+ - A refusal a user can act on must be phrased for them — a raw `inquiry:declined:<id>` reaching a
193
+ screen is a bug in the consumer, not in this primitive.
194
+
195
+ ## Related
196
+
197
+ - [[llm-common]] — where the contracts live · [[llm]] — the registry and `ExecutionService`
198
+ - `@owlmeans/llm-delegate` (`internal` monorepo, skill `llm-delegate`) — the same registry/fatal
199
+ shape for model calls performed elsewhere
200
+ - [[agent]] — the pipeline runner and the `ask_user` plugin · [[agent-common]] — the run contracts