@adia-ai/agent 0.8.29 → 0.8.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +384 -11
  3. package/agent.d.ts +44 -3
  4. package/agent.d.ts.map +1 -1
  5. package/agent.js +172 -8
  6. package/agent.js.map +1 -1
  7. package/events.d.ts +46 -2
  8. package/events.d.ts.map +1 -1
  9. package/events.js +30 -1
  10. package/events.js.map +1 -1
  11. package/guardrail.d.ts +68 -0
  12. package/guardrail.d.ts.map +1 -0
  13. package/guardrail.js +79 -0
  14. package/guardrail.js.map +1 -0
  15. package/index.d.ts +15 -7
  16. package/index.d.ts.map +1 -1
  17. package/index.js +7 -3
  18. package/index.js.map +1 -1
  19. package/integrations.d.ts +19 -2
  20. package/integrations.d.ts.map +1 -1
  21. package/integrations.js +6 -1
  22. package/integrations.js.map +1 -1
  23. package/loop.d.ts +19 -8
  24. package/loop.d.ts.map +1 -1
  25. package/loop.js +176 -9
  26. package/loop.js.map +1 -1
  27. package/mcp-transport.d.ts +128 -0
  28. package/mcp-transport.d.ts.map +1 -0
  29. package/mcp-transport.js +361 -0
  30. package/mcp-transport.js.map +1 -0
  31. package/mcp.d.ts +120 -0
  32. package/mcp.d.ts.map +1 -0
  33. package/mcp.js +264 -0
  34. package/mcp.js.map +1 -0
  35. package/package.json +1 -1
  36. package/resource.d.ts +28 -3
  37. package/resource.d.ts.map +1 -1
  38. package/resource.js +43 -5
  39. package/resource.js.map +1 -1
  40. package/session.d.ts +18 -0
  41. package/session.d.ts.map +1 -1
  42. package/session.js +40 -0
  43. package/session.js.map +1 -1
  44. package/stub.d.ts +8 -0
  45. package/stub.d.ts.map +1 -1
  46. package/stub.js +13 -0
  47. package/stub.js.map +1 -1
  48. package/tools.d.ts +6 -0
  49. package/tools.d.ts.map +1 -1
  50. package/tools.js.map +1 -1
  51. package/trace.d.ts +50 -0
  52. package/trace.d.ts.map +1 -0
  53. package/trace.js +60 -0
  54. package/trace.js.map +1 -0
  55. package/workflow.d.ts +113 -11
  56. package/workflow.d.ts.map +1 -1
  57. package/workflow.js +93 -15
  58. package/workflow.js.map +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.30] — 2026-08-07
4
+
5
+ ### Added
6
+ - **MCP-client mode — an MCP server as a tool/resource source (gh#666).** `createAgent({ integrations: [mcpServer({ url, headers })] })` connects over streamable-HTTP, lists the server's tools, and registers each one through the tier-2 integration registry (`register` fail-fast on a name collision, `buildToolDispatch` as the one ToolDef builder). From there they are ordinary `ToolDef`s: the guardrail chain sees the calls, the tracer records the executions, `checkInput` runs at the boundary, failures come back as `isError` tool_results. Additive throughout — an agent declaring no `integrations` produces the same request, events, and session as before (the byte-identity baseline is untouched).
7
+ - **Lazy, memoized connection.** `createAgent` stays synchronous; the handshake happens on the first turn, or eagerly via the new `agent.connect()`, which reports `{ url, serverInfo, protocolVersion, tools, resources }` per server. `agent.close()` ends the sessions. A failed connect is not cached — the next turn retries.
8
+ - **Failure semantics.** Unreachable server, non-2xx, an unspeakable protocol version, a failed `tools/list`, a tool-name collision, or more tools than `maxTools` all **throw `McpConnectionError`** (infrastructure faults the model cannot work around); a failed `tools/call`, a mid-session disconnect, or a server-reported `isError` reaches the model as an `isError` tool_result and the loop continues. There is no path that silently yields an empty tool list. `maxTools` (default `MAX_INTEGRATIONS`) throws instead of dropping the overflow the way `resolveIntegrations`' cap does, which is why MCP registration does not route through `resolveIntegrations` (it has neither a persona list nor an env key).
9
+ - **Resources map onto the existing two modes.** Every server resource is `tool` mode by default (`read_<name>`); `attach: [name|uri]` promotes the ones the app wants inlined every turn. **gh#671 is ruled here: no third `'internal'` resource mode.** A mode declares who invokes and when, and engine-internal retrieval names no mediated party — its dependency is already `services` and its visibility already `ctx.trace` in the `workflow` phase. Recorded in the README.
10
+ - **No new runtime dependency.** The streamable-HTTP client is hand-rolled (~200 lines, `fetch`-only, browser-safe): initialize + `notifications/initialized` + `tools/list` + `tools/call` + `resources/list` + `resources/read`, session-header echo, protocol-version negotiation, cursor pagination, and JSON *or* SSE response bodies. Verified against the real in-repo `packages/a2ui/mcp` SDK server over HTTP (30 tools, 4 resources, tool call and resource read). **stdio is unsupported** — it needs a child process, so it is Node-only by construction. Browser use needs a CORS-enabled server or the host's existing proxy; this package ships no proxy, and `fetchImpl` is the injection seam.
11
+ - **Per-request deadline, default 60s** (`McpTransportOptions.timeoutMs`, `DEFAULT_MCP_TIMEOUT_MS`; `0` disables). It covers the fetch AND the wait for the answer on an SSE body, so a server that accepts a POST and then says nothing fails instead of hanging. 60s rather than a handshake-sized number because an MCP `tools/call` may run an LLM server-side (the in-repo a2ui server's `generate_ui` does). A connect timeout throws; a call timeout reaches the model as an `isError` tool_result.
12
+ - **Abort plumbed end to end** — `SendOpts.signal` reaches an in-flight `tools/call` / `resources/read`, so a user cancellation cancels the server request rather than abandoning it. The signal of the turn that triggers a connect cancels that handshake only; it never stays attached to the resulting connection.
13
+ - **Failures leave no session open** — a source that fails after its handshake closes its own session, a failing second source closes the first one's, and `close()` during an in-flight connect wins (the late arrival closes what it opened and rejects rather than reinstalling itself). `close()` does not spend the agent: a later `send()` reconnects.
14
+ - **New trace phase `mcp`** (`connect` / `connect:failed`) on `TRACE_PHASES`; contributed tools still execute under `tool`.
15
+ - **New test fixture** `mcp-fixture.mjs` — an in-process MCP server behind a `fetch`-shaped function, covering both response body shapes.
16
+ - **Attach-mode resources are contained, not trusted (behavior change).** `attachLayers` now escapes the two structural sequences (`<resource`, `</resource`) in a resource BODY and the attribute specials in its `name` / `description`. A body containing `</resource>` previously closed its own envelope, and everything after it read as prompt — a file, a database row, or an MCP server could write instructions into the system prompt that way. Only those sequences are escaped: attached bodies are catalog JSON, component markup and docs, and mangling every `<` would degrade the data the model reads every turn for a property those two sequences already give. Applies to every attach resource, hand-declared or MCP. A prompt only changes if it contained one of those sequences.
17
+ - **`ResourceDef.get` receives `(input, ctx)`** — `ctx.signal` is the turn's cancellation, so a resource read can be aborted. Additive: a one-argument `get` is unaffected.
18
+ - **`IntegrationManifest.validation: 'local' | 'remote'`** (default `'local'`, behavior unchanged). `'remote'` says the executor is the authoritative validator, so registration accepts the schema as written instead of enforcing `checkInput`'s allowlist — an MCP server's schema reaches the model verbatim rather than being trimmed to a locally-enforceable subset that describes the tool less accurately.
19
+ - **W2 hardening — guardrails, tracing, session memory, partial tool input (gh#665).** All additive; a config that declares none of them produces the same requests, the same events, and the same session as before.
20
+ - **Guardrails** (`defineGuardrail`, `AgentConfig.guardrails`) — the deterministic layer between the model and the world, declared once at `createAgent`. `toolCall` guardrails allow / rewrite / deny a call before it executes (a deny feeds the model an `isError` tool_result naming the guardrail; the loop continues); `output` guardrails allow / rewrite / deny the model's completed text for **the round that ends the turn** — text from a round that also called tools is intermediate and passes through unguarded (README records the caveat) — where a deny ends the turn with no assistant text and the synthetic `stopReason: 'guardrail_denied'`. A rewrite or denial is followed by a `{ text: '', snapshot }` event: a UI folding text events by appending must treat an empty delta as a replace from `snapshot`. They chain in declaration order — a rewrite feeds the next, the first deny short-circuits. `onToolCall` is now exactly one guardrail of the `toolCall` kind, unchanged in behavior and wording, running ahead of the declared chain. New render-only `guardrail` event.
21
+ - **Tracing** (`AgentConfig.trace`, `createTracer`, `consoleSink`, `TRACE_PHASES`) — structured records for prompt assembly, each provider call and response, each tool execution, each workflow step verdict, each guardrail intervention, and the turn's token usage. Two consumers of one record: a render-only `trace` event on the stream and a pluggable sink (`true` → console). Off by default; a throwing sink warns instead of failing the turn. No exporter protocol, no dependency.
22
+ - **Session memory** (`Session.memory`, `remember()`, `ToolContext.remember`) — a per-session JSON-safe key/value slot written ONLY through `reduce()` via the new `memory` event (a null value deletes a key). A tool asks for a write through `ctx.remember(patch)`; the loop yields the event after that tool's result, and drains anything still queued before every `done` — so a final-round tool's write, or a guardrail's (guardrails receive the same context), is emitted rather than silently dropped. Non-JSON-safe patches throw at the write, naming the path (`assertJsonSafe`). Cross-session persistence stays a seam: memory serializes with the Session, storage remains the caller's.
23
+ - **Partial tool-input deltas** — the new `@adia-ai/llm` `tool_use_delta` chunk surfaces as a render-only `tool_input_delta` event (`id`, `name`, `partial`, `snapshot`). The snapshot is raw, incomplete JSON; nothing executes off it, and the complete `tool_use` chunk is unchanged. `scriptClient` gained `ScriptedTurn.toolUseDeltas` to script the path.
24
+
25
+ ### Changed
26
+ - **Workflow contract repairs — the five W1 findings (gh#665).** Additive except where noted:
27
+ - **`until` may return a VERDICT** — `{ accepted, reason, detail, data }`. Only an object with a boolean `accepted` is read as a verdict; **every other return is read for its truthiness**, so a pre-verdict predicate (`r => r.messages.length`, `r => r.id`) keeps meaning what it always meant rather than being treated as a malformed verdict and silently flipping to rejected. The reason lands on the attempt, so consumers stop recomputing why a step was rejected.
28
+ - **`WorkflowAttempt` gains `round`, and optional `reason` / `detail` / `data` / `gateRejected`.** `round` is always present — a consumer doing an exact deep-equal on an attempt object sees one new key.
29
+ - **Workflow-level exit gate** — `defineWorkflow(name, steps, { exitGate })`, the check every accepted result passes through, declared once instead of restated per step. A **terminal step (no `until`) is not exit-gated**: the floor of a ladder is the answer of last resort. Declare `until: () => true` on the last step to gate it like the others.
30
+ - **Same-step retry** — `WorkflowStep.retry = { maxRounds }` re-runs a step against its own rejection verdict before falling through. `ctx.round` (1-based) and `ctx.feedback` (the previous verdict) reach `run`; each round is its own `attempts` entry. An exit-gate rejection consumes a round like any other and arrives on the next round's `ctx.feedback` carrying `gateRejected: true` (the marker is on the Verdict, not only on the attempt).
31
+ - **Services channel** — `WorkflowContext.services` is what a run may USE (llmAdapter, clients, stores), separate from `input`, which is what it is ABOUT. Declared on `AgentConfig.services` and/or per run; per-run wins. `input` is unchanged and not deprecated for consumer-owned shapes.
32
+ - **`agent.run`'s fourth argument may be an options object** (`{ onEvent, services, signal }`) as well as the v0 bare `onEvent` function.
33
+
34
+ ### Changed (v0 → v1, previously recorded)
35
+ - **No longer experimental — the A2UI pipeline migration validated the contracts (gh#664).** `packages/a2ui/compose` now declares its engine escalation ladder as a `defineWorkflow` (`strategies/cascade.js`, reachable as the reserved engine name `auto`), and the A2UI MCP surface declares its corpus reads as `mode:'tool'` resources and `refine_ui` as a `defineTool`. Three contracts changed under that pressure, all additive:
36
+ - **Workflow predicates may be async, and `until` receives the context.** `until?: (result, ctx) => boolean | Promise<boolean>`, `when?: (ctx) => boolean | Promise<boolean>`. A real acceptance gate for a generated surface reads catalog JSON off disk; a sync-only predicate forced every consumer to pre-resolve its gate outside the workflow, which is where cascades get duplicated.
37
+ - **`WorkflowResult.attempts`** — `{ step, accepted, result }` per step that ran, in order. The escalation trace was previously unrecoverable from the result: consumers could see who won but not who was tried or why the ladder climbed.
38
+ - **`ResourceDef.inputSchema`** (tool-mode only; throws on an attach-mode resource) and a `get` that may return a non-string, JSON-serialized on the way out. Without the schema a tool-mode resource was a zero-argument read, which cannot express `search_chunks(query, kind, limit)`.
39
+
40
+ ### Maintenance
41
+ - **`src/` touched in this release window** (14 file(s), e.g. `src/agent.ts`) — carried by the entries above.
42
+
3
43
  ## [0.8.29] — 2026-08-06
4
44
 
5
45
  ### Maintenance
package/README.md CHANGED
@@ -6,8 +6,22 @@ Composable chat-agent harness. Assemble an agent from four declared parts —
6
6
  serializable `Session`, and the one shared event reducer; it renders nothing
7
7
  (pair it with `web-modules/chat` or any UI).
8
8
 
9
- > **Experimental** until the A2UI pipeline migration validates the contracts
10
- > (tracked in [gh#579](https://github.com/adiahealth/gen-ui-kit/issues/579)).
9
+ The contracts are validated by a real consumer: the A2UI generation pipeline
10
+ runs on them ([gh#664](https://github.com/adiahealth/gen-ui-kit/issues/664))
11
+ — its engine escalation ladder is a workflow
12
+ (`packages/a2ui/compose/strategies/cascade.js`), its corpus reads are
13
+ tool-mode resources, and its refiner is a defined tool. What that migration
14
+ could not express became this package's hardening pass
15
+ ([gh#665](https://github.com/adiahealth/gen-ui-kit/issues/665)): workflow
16
+ verdicts, exit gates, same-step retry, a services channel, guardrails,
17
+ tracing, session memory, and partial tool-input deltas.
18
+
19
+ Deferred, with reason: **`@genui/adia-producer`'s bridge keeps its own loop.**
20
+ The retry seam covers per-tier round budgets, but the bridge also owns a
21
+ turn-wide `maxProviderCallsPerTurn` budget spanning steps, a genui `TurnTrace`,
22
+ and `OrchestrationExhaustedError` semantics that come from `@genui/producer`
23
+ — none of which this package models, and modelling them here would import
24
+ another package's orchestration vocabulary into a general harness.
11
25
 
12
26
  ## Install
13
27
 
@@ -40,13 +54,17 @@ const agent = createAgent({
40
54
  workflows: [
41
55
  defineWorkflow('generate', [
42
56
  { name: 'zettel', run: zettel, until: r => r.strategy === 'composition-match' },
43
- { name: 'free-form', run: freeForm, until: r => r.ok },
57
+ { name: 'free-form', run: freeForm, until: async r => r.ok && await passesGate(r) },
44
58
  { name: 'monolithic', run: monolithic }, // terminal — always accepted
45
59
  ]),
46
60
  ],
47
61
  resources: [
48
62
  defineResource({ name: 'visit-summary', mode: 'attach', get: fetchSummary }), // app-controlled
49
- defineResource({ name: 'care-plan', mode: 'tool', get: readPlan }), // model-controlled
63
+ defineResource({ // model-controlled
64
+ name: 'care-plan', mode: 'tool',
65
+ inputSchema: { type: 'object', properties: { section: { type: 'string' } } },
66
+ get: ({ section }) => readPlan(section), // non-string returns are JSON-serialized
67
+ }),
50
68
  ],
51
69
  maxToolRounds: 8,
52
70
  });
@@ -64,9 +82,101 @@ await agent.run(session, 'generate', { intent });
64
82
  | Part | Declared with | What the harness does |
65
83
  |---|---|---|
66
84
  | Prompt | `promptLayer(name, text, {cache})` | Static layers first, `cache_control` on the last cached one (Anthropic block array; joined string elsewhere) |
67
- | Tools | `defineTool({name, inputSchema, execute\|endpoint})` | JSON-schema check at the call boundary, then the execute-and-feed-back loop (bounded by `maxToolRounds`; gate calls via `onToolCall`) |
68
- | Workflows | `defineWorkflow(name, steps)` | Sequential cascade — `until` accepts, absent `until` is terminal, `when` skips |
69
- | Resources | `defineResource({name, mode, get})` | `attach` → dynamic prompt layer each turn; `tool` → a read-only `read_<name>` tool |
85
+ | Tools | `defineTool({name, inputSchema, execute\|endpoint})` | JSON-schema check at the call boundary, then the execute-and-feed-back loop (bounded by `maxToolRounds`) |
86
+ | Workflows | `defineWorkflow(name, steps, {exitGate})` | Sequential cascade — `until` accepts, absent `until` is terminal, `when` skips, `retry` re-runs the same step |
87
+ | Resources | `defineResource({name, mode, inputSchema, get})` | `attach` → dynamic prompt layer each turn; `tool` → a read-only `read_<name>` tool taking `inputSchema`'s arguments |
88
+
89
+ **The attach envelope contains what it wraps.** An attach-mode resource
90
+ inlines somebody else's bytes into the system prompt, so a body containing
91
+ `</resource>` would otherwise close its own envelope and everything after it
92
+ would read as prompt — a file, a database row, or an MCP server could write
93
+ instructions into the system prompt that way. `attachLayers` escapes the two
94
+ structural sequences (`<resource` and `</resource`) in the body and the
95
+ attribute specials in `name` / `description`. Only those: attached bodies here
96
+ are catalog JSON, component markup and docs, and mangling every `<` in them to
97
+ close one hole would degrade the data the model reads on every turn for a
98
+ containment property those two sequences already give. Everything else arrives
99
+ byte-for-byte. This applies to every attach resource, hand-declared or MCP.
100
+
101
+ A resource's `get` receives `(input, ctx)` — `ctx.signal` is the turn's
102
+ cancellation. Existing one-argument `get`s are unaffected.
103
+
104
+ Three cross-cutting layers ride on top: **guardrails** (veto/rewrite),
105
+ **tracing** (structured records of what the harness did), and **session
106
+ memory** (a JSON-safe slot on the Session). Tools and resources may also
107
+ arrive from **an MCP server** instead of being hand-declared — see
108
+ [MCP-client mode](#mcp-client-mode).
109
+
110
+ ### Workflows in detail
111
+
112
+ `until` and `when` may both be async, and `until` also receives the context
113
+ (`until(result, ctx)`) — the acceptance gate for a generated artifact
114
+ usually has to read something. The result carries the whole run:
115
+
116
+ ```js
117
+ const { result, step, accepted, attempts } = await agent.run(session, 'generate', { intent });
118
+ // attempts: [{ step:'zettel', accepted:false, reason:'no-emission', round:1, result:… },
119
+ // { step:'free-form', accepted:true, round:1, result:… }]
120
+ ```
121
+
122
+ `attempts` lists every step round that RAN (a `when`-skipped step doesn't
123
+ appear), in order, with its verdict — that list is the escalation trace a UI
124
+ renders. `accepted` is false only when the last step also rejected; give the
125
+ ladder a terminal step (no `until`) if you want a guaranteed answer.
126
+
127
+ **Verdicts.** `until` may return a verdict object —
128
+ `{ accepted, reason, detail, data }` — or **any other value, read for its
129
+ truthiness**, which is what a pre-verdict predicate (`r => r.messages.length`)
130
+ already did. Only an object with a boolean `accepted` is treated as a verdict.
131
+ The reason lands on the attempt, so a consumer renders why the ladder climbed
132
+ instead of recomputing it:
133
+
134
+ ```js
135
+ until: (r) => r.messages.length
136
+ ? { accepted: true }
137
+ : { accepted: false, reason: 'no-emission', detail: 'engine emitted nothing' },
138
+ ```
139
+
140
+ **Exit gate.** The check every step's result must pass is declared once on
141
+ the workflow instead of restated in every `until`:
142
+
143
+ ```js
144
+ defineWorkflow('generate', steps, {
145
+ exitGate: async (result, ctx) => await validatesAgainstCatalog(result, ctx.input),
146
+ });
147
+ ```
148
+
149
+ It runs after a step's own `until` accepted, and a rejection falls the ladder
150
+ through exactly like an `until` rejection (the attempt carries
151
+ `gateRejected: true` and `reason: 'gate-rejected'`). A **terminal step — one
152
+ with no `until` — is not exit-gated**: the floor of a ladder is the answer of
153
+ last resort. Give the last step `until: () => true` if you want it gated too.
154
+
155
+ **Same-step retry.** A step may retry against its own rejection before
156
+ falling through:
157
+
158
+ ```js
159
+ { name: 'repair', retry: { maxRounds: 3 },
160
+ run: (ctx) => generate(ctx.input, ctx.feedback), // ctx.round is 1-based
161
+ until: (r) => r.valid ? { accepted: true } : { accepted: false, reason: 'invalid', data: r.errors } }
162
+ ```
163
+
164
+ `ctx.feedback` is the previous round's verdict; each round is its own entry
165
+ in `attempts`. `maxRounds` counts total runs (2 = one retry). A workflow
166
+ **exit-gate** rejection consumes a round exactly like an `until` rejection,
167
+ and reaches the next round as `ctx.feedback` carrying `gateRejected: true` —
168
+ so a repair round can tell the gate's objection from the step's own.
169
+
170
+ **Services.** `ctx.input` is what the run is ABOUT; `ctx.services` is what it
171
+ may USE. Declare them on the agent, per run, or both (per-run wins):
172
+
173
+ ```js
174
+ createAgent({ …, services: { llmAdapter, store } });
175
+ await agent.run(session, 'generate', { intent }, { services: { llmAdapter: turnAdapter } });
176
+ ```
177
+
178
+ Services used to ride `input`, and still may — nothing breaks — but a new
179
+ consumer should keep `input` the subject and put dependencies here.
70
180
 
71
181
  ## Events
72
182
 
@@ -77,17 +187,30 @@ can react to — the loop throws for nothing tool-shaped.
77
187
  ```
78
188
  { type:'message', role:'user', text }
79
189
  { type:'text', text, snapshot } · { type:'thinking', text }
190
+ { type:'tool_input_delta', id, name, partial, snapshot }
80
191
  { type:'tool_use', id, name, input } · { type:'tool_result', id, name, output, isError? }
81
192
  { type:'step', workflow, step, data? }
82
193
  { type:'progress', stage } · { type:'surface', surfaceId, line }
194
+ { type:'guardrail', name, target, action, reason?, toolName?, toolUseId? }
195
+ { type:'trace', record } · { type:'memory', patch }
83
196
  { type:'done', text, usage, stopReason } · { type:'error', error }
84
197
  ```
85
198
 
86
- `stopReason` passes through raw from the provider; the loop adds exactly one
87
- synthetic value, `max_tool_rounds`, when the bound is hit.
199
+ `stopReason` passes through raw from the provider; the loop adds exactly two
200
+ synthetic values `max_tool_rounds` when the bound is hit, and
201
+ `guardrail_denied` when an output guardrail vetoed the turn.
202
+
203
+ `thinking` / `step` / `error` / `progress` / `surface` / `tool_input_delta` /
204
+ `guardrail` / `trace` are render-only — `reduce()` folds them into no session
205
+ change. `memory` is the one new event that DOES change the session.
88
206
 
89
- `thinking` / `step` / `error` / `progress` / `surface` are render-only —
90
- `reduce()` folds them into no session change.
207
+ ### Partial tool input
208
+
209
+ While a provider streams a tool call's arguments, each fragment surfaces as
210
+ `tool_input_delta` (Anthropic `input_json_delta`, OpenAI partial argument
211
+ strings; Gemini streams complete calls and emits none). `snapshot` is the raw
212
+ JSON so far — **incomplete and unparseable until the matching `tool_use`
213
+ arrives**. Render progress from it; execute only on `tool_use`.
91
214
 
92
215
  ### Progress events (CHAT-HARNESS law 3)
93
216
 
@@ -144,6 +267,250 @@ loop as `agent.send()`. A silent-apply message yields nothing and never
144
267
  touches the provider — the caller already applied it directly (e.g. to a
145
268
  surface's data-model store).
146
269
 
270
+ ## MCP-client mode
271
+
272
+ Point the agent at an [MCP](https://modelcontextprotocol.io) server and its
273
+ tools and resources join the declared ones. Nothing downstream knows the
274
+ difference: the calls go through the guardrail chain, the tracer records the
275
+ executions, `checkInput` runs at the boundary, a failure comes back as an
276
+ `isError` tool_result. MCP is a *source* of tools, not a second kind of tool.
277
+
278
+ ```js
279
+ import { createAgent, mcpServer } from '@adia-ai/agent';
280
+
281
+ const agent = createAgent({
282
+ llm: { model: 'claude-sonnet-4-6', proxyUrl: '/api/chat' },
283
+ integrations: [
284
+ mcpServer({
285
+ url: 'https://mcp.example.com/mcp',
286
+ headers: { authorization: `Bearer ${token}` }, // v1 auth: headers only
287
+ prefix: 'wiki_', // namespace its tool names
288
+ tools: ['search', 'fetch'], // allowlist; absent = everything it lists
289
+ maxTools: 24, // loud cap, default MAX_INTEGRATIONS (16)
290
+ attach: ['style-guide'], // resources to inline every turn
291
+ timeoutMs: 60_000, // per request; 0 disables (default 60s)
292
+ }),
293
+ ],
294
+ });
295
+
296
+ const report = await agent.connect(); // optional — otherwise the first turn does it
297
+ // [{ url, serverInfo, protocolVersion, tools: [...], resources: [{name, uri, mode}] }]
298
+ await agent.close(); // ends the MCP session(s)
299
+ ```
300
+
301
+ **Connection is lazy and memoized.** `createAgent` is synchronous and a
302
+ constructor that reaches the network is a constructor that can fail, so the
303
+ handshake happens on the first turn — or eagerly on `agent.connect()`, which
304
+ returns what each server offered. `agent.tools` lists the declared set until
305
+ the sources connect, and everything afterwards. A failed connect is not
306
+ cached as a verdict: the next turn tries again.
307
+
308
+ **Failure is loud where it must be and model-visible where it can be.**
309
+
310
+ | When | What happens |
311
+ |---|---|
312
+ | Server unreachable, non-2xx, unspeakable protocol version, `tools/list` fails | `McpConnectionError` **thrown** out of `connect()` / `send()`. An infrastructure fault the model cannot work around — the same class as a missing `@adia-ai/llm`, never a silent empty tool list |
313
+ | Two sources (or a source and a declared tool) contribute the same tool name | `McpConnectionError` at connect — set a `prefix` |
314
+ | More tools listed than `maxTools` | `McpConnectionError` naming the count. `resolveIntegrations`' cap silently drops the overflow; this one refuses to serve two thirds of a server without saying so |
315
+ | A `tools/call` fails, times out, is aborted, or the server goes away mid-session | `isError` tool_result (`McpCallError`'s message). The model sees it and the loop continues |
316
+ | The server answers `isError: true` | Same — the error text reaches the model as an error, never as prose that reads like an answer |
317
+ | An **attach-mode** resource's `resources/read` fails mid-session | `McpCallError` **thrown out of `send()`** during prompt assembly. There is no turn to degrade into: the prompt the model was going to see is the thing that could not be built |
318
+
319
+ **Nothing waits forever.** Every round trip carries `timeoutMs` (default
320
+ `DEFAULT_MCP_TIMEOUT_MS`, 60 s), covering both the fetch and the wait for the
321
+ answer to arrive on an SSE body — a server that accepts a POST and then says
322
+ nothing is a real failure mode and is otherwise indistinguishable from a hang.
323
+ The default has to clear the slowest legitimate answer, and for MCP that is a
324
+ tool that thinks (the in-repo a2ui server runs an LLM inside `tools/call`), so
325
+ it is generous rather than handshake-sized; raise it per source for a slower
326
+ server, or set `0` if you own the clock through `signal`. A timeout during
327
+ connect throws; a timeout during a call reaches the model like any other call
328
+ failure.
329
+
330
+ **Abort is plumbed end to end.** `agent.send(session, text, { signal })` puts
331
+ that signal on the turn's `ctx`, and an MCP `tools/call` or `resources/read`
332
+ carries it to the wire — a user who cancels mid-answer cancels the server call
333
+ instead of leaving it running and ignored. Aborting the turn that happens to
334
+ be doing the connect cancels the handshake too; the signal does *not* stay
335
+ attached to the resulting connection, so a later turn is unaffected.
336
+
337
+ **Failures leave nothing open.** If a source fails after its handshake
338
+ succeeded, its session is closed before the error propagates; if the second of
339
+ two sources fails, the first one's session is closed too. `close()` during an
340
+ in-flight connect wins — the late arrival closes what it opened and rejects
341
+ with "the agent was closed while connecting" rather than quietly reinstalling
342
+ itself. `close()` ends the sessions but does not spend the agent: a later
343
+ `send()` reconnects.
344
+
345
+ **Schemas pass through verbatim.** A server's JSON Schema reaches the model
346
+ unmodified, including keywords `checkInput` cannot enforce. The server is the
347
+ authoritative validator (its rejection returns as an error result), so a
348
+ faithful copy of a contract someone else owns beats a locally-enforceable
349
+ subset that describes the tool less accurately. The manifest states this with
350
+ the new `IntegrationManifest.validation: 'remote'` rather than leaving it
351
+ implied; `'local'` (the default, and every hand-declared integration) keeps
352
+ the strict registration allowlist, because there the only validator is us.
353
+
354
+ **Transport is streamable-HTTP, hand-rolled, no new dependency.**
355
+ `@modelcontextprotocol/sdk` brings a server framework, stdio/websocket
356
+ transports, an auth stack and zod for a client subset that is six JSON-RPC
357
+ methods over one POST endpoint; this package stays dependency-free apart from
358
+ `@adia-ai/llm`. The SDK still belongs in `packages/a2ui/mcp`, which *serves*.
359
+ **stdio is not supported** — it needs a child process, so it is Node-only by
360
+ construction and no in-repo consumer wants it; a `mcpServerStdio()` Node entry
361
+ point is the shape to add if one does.
362
+
363
+ **In a browser, the server must be reachable by `fetch`** — meaning it sends
364
+ CORS headers for your origin, or you put it behind the same proxy you already
365
+ route `@adia-ai/llm` through (`proxyUrl`) and point `url` at that path. This
366
+ package deliberately ships no proxy: a server-side pass-through is the host
367
+ app's, and forwarding `mcp-session-id` and `mcp-protocol-version` both ways is
368
+ all it has to do. `fetchImpl` is the injection seam (tests use it for the
369
+ in-process server; a host can use it to route through its own client). The
370
+ in-repo `packages/a2ui/mcp` server sends no CORS headers today, so reaching
371
+ it from a page means proxying it; from Node it works directly.
372
+
373
+ ### MCP resources, and the gh#671 ruling
374
+
375
+ A server's resources map onto the two existing modes and **no new one**:
376
+
377
+ - **`tool` (default)** — the harness cannot know which of a remote server's
378
+ resources belong in every prompt, and attaching all of them grows the
379
+ system prompt without bound. So each becomes a `read_<name>` tool.
380
+ - **`attach`** — the APP promotes specific resources by name or URI
381
+ (`attach: ['style-guide']`), and those are fetched and inlined every turn.
382
+
383
+ That is exactly MCP's own application-controlled vs. model-controlled
384
+ distinction, expressed with the modes the harness already has.
385
+
386
+ [gh#671](https://github.com/adiahealth/gen-ui-kit/issues/671) asked whether
387
+ engine-internal retrieval (zettel and free-form reading corpus content on
388
+ their own schedule inside a workflow step's `run`) needs a third mode —
389
+ `'internal'`. **Ruled no, and W3 did not need one either.** A resource mode
390
+ is a declaration of *who invokes it and when*, and both existing modes name a
391
+ party the harness mediates: `attach` = the harness fetches, `tool` = the model
392
+ asks. `'internal'` would name "engine code, whenever it likes", which is not
393
+ a mediation — it is a function call. Declaring it would put into the resource
394
+ contract a thing the model can never call and the harness never fetches,
395
+ buying nothing but a second way to spell `await`. The two things the issue
396
+ actually wanted are already contracts: the dependency is declared through
397
+ **`services`** (W2), and the invocation is visible through **`ctx.trace`** in
398
+ the `workflow` phase. Server-pushed MCP resources do not need it either —
399
+ every read is either app-scheduled or model-asked.
400
+
401
+ **Everything a server returns is DATA.** A tool description, a resource body,
402
+ an error string — a description that reads like an instruction is still a
403
+ description. It lands in the tool spec or inside a `<resource>` envelope and
404
+ decides nothing about what the harness does. Guardrails are how you constrain
405
+ what a remote tool may actually do.
406
+
407
+ ## Guardrails
408
+
409
+ The deterministic layer between the model and the world — code that runs
410
+ outside the model's context and cannot be talked past. Declared once at
411
+ `createAgent`, two attach points, both optional:
412
+
413
+ ```js
414
+ import { defineGuardrail } from '@adia-ai/agent';
415
+
416
+ createAgent({
417
+ llm: { … },
418
+ guardrails: [
419
+ defineGuardrail({
420
+ name: 'scope-to-tenant',
421
+ // Before the call executes: allow (or return nothing), rewrite, deny.
422
+ toolCall: (call, ctx) => ({ action: 'rewrite', input: { ...call.input, tenantId } }),
423
+ }),
424
+ defineGuardrail({
425
+ name: 'redact',
426
+ // On the model's completed text for the round.
427
+ output: (text) => ({ action: 'rewrite', text: text.replace(SSN, '[redacted]') }),
428
+ }),
429
+ ],
430
+ });
431
+ ```
432
+
433
+ Guardrails run in declaration order and chain: a rewrite feeds the next one,
434
+ the first `deny` short-circuits.
435
+
436
+ - **Tool-call deny** → the model gets an `isError` tool_result naming the
437
+ guardrail and its reason, and the loop continues. Nothing executes.
438
+ - **Output deny** → the turn ends with no assistant text and
439
+ `stopReason: 'guardrail_denied'`; the reason rides the `guardrail` event.
440
+ - **Output rewrite** → the rewritten text is what `done` carries and what the
441
+ transcript keeps. A final `{ text: '', snapshot: <rewritten> }` event
442
+ follows, and a denial sends `{ text: '', snapshot: '' }`.
443
+ **A UI that folds `text` events by APPENDING the delta must treat an empty
444
+ delta as a replace from `snapshot`, or it will keep showing the original
445
+ while the transcript says otherwise** — `web-modules/chat`'s
446
+ `wireAgentEvents` does exactly this via `chatShell.setStreamedText()`.
447
+
448
+ Two scope limits worth knowing before you rely on the output chain:
449
+
450
+ - **Output guardrails see the text of the round that ENDS the turn.** Text
451
+ from a round that also called tools is intermediate reasoning the model
452
+ supersedes, and denying it has no coherent answer for the tool calls
453
+ emitted beside it — so it passes through unguarded and reaches the
454
+ transcript. Put the check on `toolCall` if what matters is what the model
455
+ is about to DO.
456
+ - **A guardrail may `ctx.remember(patch)`** — it receives the same context a
457
+ tool does. The write is drained as a `memory` event before `done`, never
458
+ dropped.
459
+
460
+ `onToolCall` is now exactly one guardrail of the `toolCall` kind: it still
461
+ works, still denies with its original wording, and runs ahead of the declared
462
+ chain.
463
+
464
+ A guardrail decides from the call and the harness's own context. Text
465
+ produced by a model, a tool, or a resource is DATA — a guardrail that lets
466
+ such text talk it into allowing something is not a guardrail.
467
+
468
+ ## Tracing
469
+
470
+ Off by default. `trace: true` uses the console sink; a function IS the sink.
471
+ Every record is also a `trace` event on the stream:
472
+
473
+ ```js
474
+ const agent = createAgent({ llm: { … }, trace: (record) => myLogger.debug(record) });
475
+ // { phase, name, at, sessionId?, data? }
476
+ ```
477
+
478
+ `phase` is the closed vocabulary (`TRACE_PHASES`, guarded by
479
+ `isTracePhase()`): `prompt` (assembly: layer count, cached blocks, chars),
480
+ `request` / `response` (per provider call: round, model, tool count, stop
481
+ reason, ms), `tool` (per execution: name, id, ms, isError), `workflow`
482
+ (`step:start` / `step:verdict` with the verdict reason), `guardrail` (each
483
+ non-allow decision), `usage` (the turn's token totals), `mcp` (`connect` /
484
+ `connect:failed` per source — a tool a server contributed then executes
485
+ under `tool` like any other). A sink that throws
486
+ warns and the turn continues. There is no exporter protocol and no
487
+ dependency — an OpenTelemetry bridge is a caller-side function.
488
+
489
+ ## Session memory
490
+
491
+ Per-session, JSON-safe, and written ONLY through `reduce()` — the same
492
+ single-writer rule the messages obey:
493
+
494
+ ```js
495
+ import { remember } from '@adia-ai/agent';
496
+
497
+ session = reduce(session, remember({ patientId: 'p_7', tone: 'brief' }));
498
+ session.memory; // { patientId: 'p_7', tone: 'brief' }
499
+ session = reduce(session, remember({ tone: null })); // null deletes the key
500
+ ```
501
+
502
+ A tool writes by ASKING: `ctx.remember(patch)` queues the patch, and the loop
503
+ yields it as a `memory` event right after that tool's result — and drains
504
+ anything still queued before `done`, so a final-round tool's write (or a
505
+ guardrail's, which shares the context) is never dropped. The asker never
506
+ touches state.
507
+
508
+ A patch that would not survive `JSON.stringify` → `JSON.parse` unchanged (a
509
+ Date, a function, `undefined`, a class instance) throws at the write, naming
510
+ the path. Memory rides the Session through `toJSON`/`fromJSON`; **where that
511
+ JSON is stored stays the caller's** — cross-session persistence is a seam
512
+ this package deliberately does not implement.
513
+
147
514
  ## Byte-identity baseline (CHAT-HARNESS law 2)
148
515
 
149
516
  Every axis on `AgentConfig` is optional, and absence must produce a
@@ -170,3 +537,9 @@ like any other pinned contract.
170
537
  `scriptClient(turns)` is a deterministic `LLMClient`: script `[{text, toolUse}]`
171
538
  turns and drive the whole loop keylessly. It also records every `ChatOpts` it
172
539
  was called with (`client.calls`) so tests can assert what reached the wire.
540
+
541
+ `mcp-fixture.mjs`'s `createTestMcpServer({ tools, resources, … })` is the
542
+ same idea for MCP: an in-process server behind a `fetch`-shaped function
543
+ (`fetchImpl`), speaking real JSON-RPC with session headers, cursor
544
+ pagination, `isError` results, and either a JSON or an SSE response body —
545
+ no network, no ports, no SDK.
package/agent.d.ts CHANGED
@@ -15,7 +15,10 @@ import { type ResourceDef } from './resource.js';
15
15
  import { type Session } from './session.js';
16
16
  import type { AgentEvent } from './events.js';
17
17
  import { type ClientMessage } from './frame.js';
18
+ import { type Guardrail } from './guardrail.js';
19
+ import { type McpServerReport, type McpSource } from './mcp.js';
18
20
  import type { ToolContext, ToolDef } from './tools.js';
21
+ import { type TraceConfig } from './trace.js';
19
22
  import { type Workflow, type WorkflowResult } from './workflow.js';
20
23
  export interface AgentConfig {
21
24
  /** Client defaults (model, proxyUrl, apiKey…) handed to createClient(),
@@ -31,12 +34,38 @@ export interface AgentConfig {
31
34
  workflows?: Array<Workflow<never, never>> | Workflow[];
32
35
  resources?: ResourceDef[];
33
36
  maxToolRounds?: number;
34
- /** Gate every tool call; false denies it (isError feedback, loop continues). */
37
+ /** Gate every tool call; false denies it (isError feedback, loop continues).
38
+ * Superseded by `guardrails` — this is now adapted into exactly one
39
+ * guardrail of the `toolCall` kind and runs FIRST, ahead of the declared
40
+ * chain. Kept because it is the v0 seam consumers already call. */
35
41
  onToolCall?: (call: ToolUse, ctx: ToolContext) => boolean | Promise<boolean>;
42
+ /** The deterministic layer between the model and the world: veto or
43
+ * rewrite tool calls and model output (see guardrail.ts). */
44
+ guardrails?: Guardrail[];
45
+ /** Structured tracing: `true` → the console sink, a function → that sink.
46
+ * Absent → no trace events, no console output, no request difference. */
47
+ trace?: TraceConfig;
48
+ /** Dependencies every workflow run may USE (llmAdapter, clients, stores).
49
+ * Reaches steps as `ctx.services`, merged with per-run services. */
50
+ services?: Record<string, unknown>;
51
+ /** External sources whose tools and resources join the declared ones —
52
+ * today, MCP servers (`mcpServer({ url })`). Connected LAZILY on the
53
+ * first turn and memoized, because `createAgent` is synchronous and a
54
+ * constructor that reaches the network is a constructor that can fail.
55
+ * Call `agent.connect()` to do it eagerly and inspect the result. */
56
+ integrations?: McpSource[];
36
57
  }
37
58
  export interface SendOpts {
38
59
  signal?: AbortSignal;
39
60
  }
61
+ /** `agent.run`'s options — the fourth argument may still be a bare
62
+ * `onEvent` function (the v0 shape). */
63
+ export interface RunOpts {
64
+ onEvent?: (event: AgentEvent) => void;
65
+ /** Per-run dependencies, merged over `AgentConfig.services`. */
66
+ services?: Record<string, unknown>;
67
+ signal?: AbortSignal;
68
+ }
40
69
  export interface Agent {
41
70
  createSession(id?: string): Session;
42
71
  send(session: Session, text: string, opts?: SendOpts): AsyncGenerator<AgentEvent>;
@@ -45,9 +74,21 @@ export interface Agent {
45
74
  * A silent-apply message (shouldRunTurn ⇒ false) yields nothing and
46
75
  * never touches the loop — the caller already applied it directly. */
47
76
  sendClientMessage(session: Session, msg: ClientMessage, opts?: SendOpts): AsyncGenerator<AgentEvent>;
48
- run<TResult = unknown>(session: Session, workflowName: string, input?: unknown, onEvent?: (event: AgentEvent) => void): Promise<WorkflowResult<TResult>>;
49
- /** The assembled tool set (declared + tool-mode resources) inspectable. */
77
+ run<TResult = unknown>(session: Session, workflowName: string, input?: unknown, opts?: RunOpts | ((event: AgentEvent) => void)): Promise<WorkflowResult<TResult>>;
78
+ /** The assembled tool set declared tools, tool-mode resources, and (once
79
+ * the sources have connected) everything MCP contributed. Before the
80
+ * first turn or an explicit `connect()`, it is the declared set. */
50
81
  tools: ToolDef[];
82
+ /** Connect the declared `integrations` now and report what each server
83
+ * offered. Idempotent and memoized: the turn loop awaits the same
84
+ * promise. Rejects with `McpConnectionError` when a server is
85
+ * unreachable, mis-speaks the protocol, or collides with a declared tool
86
+ * name — never resolves to a quietly empty tool list. */
87
+ connect(): Promise<McpServerReport[]>;
88
+ /** Close every MCP session (best effort). A Node process that exits
89
+ * without it leaves sessions the server times out on its own; a browser
90
+ * page unload is the same. */
91
+ close(): Promise<void>;
51
92
  }
52
93
  export declare function createAgent(config: AgentConfig): Agent;
53
94
  //# sourceMappingURL=agent.d.ts.map
package/agent.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAYjE,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAe,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAIhF,MAAM,WAAW,WAAW;IAC1B;;8EAE0E;IAC1E,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC;IACvD,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAClF;;;2EAGuE;IACvE,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IACrG,GAAG,CAAC,OAAO,GAAG,OAAO,EACnB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GACpC,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpC,6EAA6E;IAC7E,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAgFtD"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"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"}