@owlmeans/llm 0.1.18-rc.3 → 0.1.18-rc.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agent-meta/manifest.json +9 -2
- package/agent-meta/skills/inquiry/SKILL.md +200 -0
- package/agent-meta/skills/llm/SKILL.md +248 -117
- package/agent-meta/skills/llm-prompt-caching/SKILL.md +37 -3
- package/build/consts.d.ts +4 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +4 -0
- package/build/consts.js.map +1 -1
- package/build/execution/service.d.ts.map +1 -1
- package/build/execution/service.js +60 -11
- package/build/execution/service.js.map +1 -1
- package/build/execution/types.d.ts +91 -13
- package/build/execution/types.d.ts.map +1 -1
- package/build/execution/utils.d.ts.map +1 -1
- package/build/execution/utils.js +15 -9
- package/build/execution/utils.js.map +1 -1
- package/build/helpers/retry.d.ts +14 -0
- package/build/helpers/retry.d.ts.map +1 -1
- package/build/helpers/retry.js +14 -0
- package/build/helpers/retry.js.map +1 -1
- package/build/helpers/spectate.d.ts.map +1 -1
- package/build/helpers/spectate.js +12 -5
- package/build/helpers/spectate.js.map +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -2
- package/build/index.js.map +1 -1
- package/build/inquiry/bridge.d.ts +22 -0
- package/build/inquiry/bridge.d.ts.map +1 -0
- package/build/inquiry/bridge.js +29 -0
- package/build/inquiry/bridge.js.map +1 -0
- package/build/inquiry/errors.d.ts +29 -0
- package/build/inquiry/errors.d.ts.map +1 -0
- package/build/inquiry/errors.js +40 -0
- package/build/inquiry/errors.js.map +1 -0
- package/build/inquiry/index.d.ts +4 -0
- package/build/inquiry/index.d.ts.map +1 -0
- package/build/inquiry/index.js +4 -0
- package/build/inquiry/index.js.map +1 -0
- package/build/inquiry/transport.d.ts +12 -0
- package/build/inquiry/transport.d.ts.map +1 -0
- package/build/inquiry/transport.js +35 -0
- package/build/inquiry/transport.js.map +1 -0
- package/build/model.d.ts +1 -1
- package/build/model.d.ts.map +1 -1
- package/build/model.js +200 -141
- package/build/model.js.map +1 -1
- package/build/plugins/anthropic.d.ts +40 -0
- package/build/plugins/anthropic.d.ts.map +1 -1
- package/build/plugins/anthropic.js +79 -6
- package/build/plugins/anthropic.js.map +1 -1
- package/build/plugins/openai.d.ts +12 -0
- package/build/plugins/openai.d.ts.map +1 -1
- package/build/plugins/openai.js +29 -3
- package/build/plugins/openai.js.map +1 -1
- package/build/plugins/types.d.ts +7 -0
- package/build/plugins/types.d.ts.map +1 -1
- package/build/prompt/service.d.ts.map +1 -1
- package/build/prompt/service.js +11 -0
- package/build/prompt/service.js.map +1 -1
- package/build/prompt/types.d.ts +20 -0
- package/build/prompt/types.d.ts.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +42 -7
- package/build/service.js.map +1 -1
- package/build/types.d.ts +83 -1
- package/build/types.d.ts.map +1 -1
- package/build/utils/config.d.ts +11 -0
- package/build/utils/config.d.ts.map +1 -1
- package/build/utils/config.js +21 -1
- package/build/utils/config.js.map +1 -1
- package/build/utils/null-report.d.ts.map +1 -1
- package/build/utils/null-report.js +9 -1
- package/build/utils/null-report.js.map +1 -1
- package/package.json +13 -13
- package/src/consts.ts +4 -0
- package/src/execution/service.ts +62 -11
- package/src/execution/types.ts +99 -15
- package/src/execution/utils.ts +16 -9
- package/src/helpers/retry.ts +16 -0
- package/src/helpers/spectate.ts +14 -5
- package/src/index.ts +5 -2
- package/src/inquiry/bridge.ts +33 -0
- package/src/inquiry/errors.ts +46 -0
- package/src/inquiry/index.ts +3 -0
- package/src/inquiry/transport.ts +42 -0
- package/src/model.ts +113 -37
- package/src/plugins/anthropic.ts +89 -6
- package/src/plugins/openai.ts +33 -3
- package/src/plugins/types.ts +8 -0
- package/src/prompt/service.ts +12 -0
- package/src/prompt/types.ts +20 -0
- package/src/service.ts +53 -7
- package/src/types.ts +84 -1
- package/src/utils/config.ts +23 -1
- package/src/utils/null-report.ts +9 -1
- package/tests/context.ts +3 -0
- package/tests/execution.spec.ts +148 -13
- package/tests/helpers.spec.ts +4 -1
- package/tests/inquiry.spec.ts +200 -0
- package/tests/plugins.spec.ts +211 -0
- 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.30 @owlmeans/llm-common@^0.1.18-rc.29
|
|
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.30
|
|
188
188
|
```
|
|
189
189
|
|
|
190
190
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/llm",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.30",
|
|
5
|
+
"generatedAt": "2026-09-19T13:42:56.247Z",
|
|
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
|