llmz 0.9.0 → 1.0.2

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 (238) hide show
  1. package/DOCS.md +232 -1794
  2. package/ERRORS.md +101 -0
  3. package/README.md +96 -582
  4. package/dist/chat/chat.d.ts +63 -0
  5. package/dist/chat/component.d.ts +31 -0
  6. package/dist/chat/component.default.d.ts +58 -0
  7. package/dist/chat/response.d.ts +15 -0
  8. package/dist/compiler/compiler.d.ts +2 -7
  9. package/dist/compiler/plugins/dynamic-code.d.ts +7 -0
  10. package/dist/compiler/plugins/replace-comment.d.ts +2 -3
  11. package/dist/compiler/plugins/return-async.d.ts +5 -4
  12. package/dist/compiler/plugins/termination.d.ts +8 -0
  13. package/dist/compiler/plugins/tool-resolution.d.ts +4 -0
  14. package/dist/compiler/plugins/variable-extraction.d.ts +2 -8
  15. package/dist/component-CMFoCHyu.cjs +649 -0
  16. package/dist/component-DncqP4fB.js +614 -0
  17. package/dist/context.d.ts +71 -378
  18. package/dist/custom-client-C-WXvLD9.js +1655 -0
  19. package/dist/custom-client-Cgt9_0r1.cjs +1732 -0
  20. package/dist/custom-client.d.ts +12 -2
  21. package/dist/errors/catalog.d.ts +294 -0
  22. package/dist/errors/hooks.d.ts +2 -0
  23. package/dist/errors-C6ppQUZX.cjs +893 -0
  24. package/dist/errors-Crk5P8jo.js +636 -0
  25. package/dist/errors.d.ts +24 -36
  26. package/dist/execute-CiYvPs5Q.js +2061 -0
  27. package/dist/execute-DTTfacsi.cjs +2062 -0
  28. package/dist/{exit-DUM96DN1.cjs → exit-BOtKWPuT.cjs} +38 -28
  29. package/dist/{exit-CDdxDBRT.js → exit-DMPwCSba.js} +37 -27
  30. package/dist/exit.d.ts +19 -16
  31. package/dist/index.cjs +177 -1237
  32. package/dist/index.d.ts +25 -28
  33. package/dist/index.js +137 -1213
  34. package/dist/index.workerd.js +8974 -11145
  35. package/dist/inspect.d.ts +18 -4
  36. package/dist/inspection.d.ts +40 -0
  37. package/dist/native-0bq5ATZM.cjs +212 -0
  38. package/dist/native-n_718Z-T.js +177 -0
  39. package/dist/objects.d.ts +2 -57
  40. package/dist/prompts/native.d.ts +2 -0
  41. package/dist/prompts/prompt.d.ts +10 -101
  42. package/dist/quickjs-variant-21IO97PF.cjs +1080 -0
  43. package/dist/quickjs-variant-CtuGagsb.js +985 -0
  44. package/dist/result.d.ts +30 -861
  45. package/dist/runtime/execute.d.ts +3 -2
  46. package/dist/runtime/execution-activity.d.ts +11 -0
  47. package/dist/runtime/execution-diagnostics.d.ts +11 -0
  48. package/dist/runtime/execution-report.d.ts +51 -0
  49. package/dist/runtime/fixtures/chat.d.ts +8 -0
  50. package/dist/runtime/fixtures/long-search.d.ts +1 -1
  51. package/dist/runtime/fixtures/native-client.d.ts +33 -0
  52. package/dist/runtime/fixtures/protocol-languages.d.ts +1 -1
  53. package/dist/runtime/forced-inspection.d.ts +13 -0
  54. package/dist/runtime/forced-inspection.test.d.ts +1 -0
  55. package/dist/runtime/generate.d.ts +23 -20
  56. package/dist/runtime/generate.test.d.ts +1 -0
  57. package/dist/runtime/hook-provenance.test.d.ts +1 -0
  58. package/dist/runtime/inspection-hook.test.d.ts +1 -0
  59. package/dist/runtime/inspection-values.d.ts +14 -0
  60. package/dist/runtime/inspection-values.test.d.ts +1 -0
  61. package/dist/runtime/interruption-control-flow.test.d.ts +1 -0
  62. package/dist/runtime/interruption-settings.test.d.ts +1 -0
  63. package/dist/runtime/javascript-api.d.ts +62 -0
  64. package/dist/runtime/javascript-api.test.d.ts +1 -0
  65. package/dist/runtime/native-review.test.d.ts +1 -0
  66. package/dist/runtime/native-tools.d.ts +18 -0
  67. package/dist/runtime/native-tools.test.d.ts +1 -0
  68. package/dist/runtime/object-memory.test.d.ts +1 -0
  69. package/dist/runtime/single-tool.test.d.ts +1 -0
  70. package/dist/runtime/think-signal.test.d.ts +1 -0
  71. package/dist/runtime/token-budget.d.ts +22 -0
  72. package/dist/runtime/token-budget.test.d.ts +1 -0
  73. package/dist/runtime/tool-wrapper.d.ts +6 -5
  74. package/dist/runtime/traces.test.d.ts +1 -0
  75. package/dist/runtime/truncation.test.d.ts +1 -0
  76. package/dist/runtime/types.d.ts +47 -31
  77. package/dist/runtime/variable-recovery.test.d.ts +1 -0
  78. package/dist/runtime/vm-context.d.ts +6 -1
  79. package/dist/runtime/worker-completion.test.d.ts +1 -0
  80. package/dist/runtime-names.d.ts +4 -0
  81. package/dist/schema-CXlnbb3n.cjs +68 -0
  82. package/dist/schema-GE4ISPCR.js +45 -0
  83. package/dist/schema.d.ts +10 -0
  84. package/dist/schema.test.d.ts +1 -0
  85. package/dist/session/compactor.d.ts +51 -0
  86. package/dist/session/compactor.test.d.ts +1 -0
  87. package/dist/session/history.d.ts +36 -0
  88. package/dist/session/history.test.d.ts +1 -0
  89. package/dist/session/json.d.ts +3 -0
  90. package/dist/session/json.test.d.ts +1 -0
  91. package/dist/session/memory-codec.d.ts +9 -0
  92. package/dist/session/memory-codec.test.d.ts +1 -0
  93. package/dist/session/memory-render.d.ts +15 -0
  94. package/dist/session/memory-render.test.d.ts +1 -0
  95. package/dist/session/memory.d.ts +112 -0
  96. package/dist/session/memory.test.d.ts +1 -0
  97. package/dist/session/messages.d.ts +19 -0
  98. package/dist/session/messages.test.d.ts +1 -0
  99. package/dist/session/serialization.d.ts +36 -0
  100. package/dist/session/serialization.test.d.ts +1 -0
  101. package/dist/session/session-memory.test.d.ts +1 -0
  102. package/dist/session/session.d.ts +121 -0
  103. package/dist/session/session.test.d.ts +1 -0
  104. package/dist/session/speaker.test.d.ts +1 -0
  105. package/dist/{transcript.d.ts → session/transcript.d.ts} +4 -8
  106. package/dist/session/transcript.test.d.ts +1 -0
  107. package/dist/{tool-Bv8N4IPj.js → tool-DstssiAs.js} +144 -92
  108. package/dist/{tool-Dy6AJpJH.cjs → tool-_wK9hQOb.cjs} +146 -94
  109. package/dist/tool.d.ts +25 -17
  110. package/dist/truncate-BEKbdo3t.js +79 -0
  111. package/dist/truncate-uomAMGPs.cjs +102 -0
  112. package/dist/truncate.d.ts +24 -0
  113. package/dist/truncate.test.d.ts +1 -0
  114. package/dist/types.d.ts +33 -9
  115. package/dist/{typings-3lsqCbkn.cjs → typings-BA_XxAcY.cjs} +38 -193
  116. package/dist/{typings-CrBa7Tyy.js → typings-BEVDvtuk.js} +38 -175
  117. package/dist/typings-formatter.d.ts +1 -1
  118. package/dist/typings.d.ts +3 -0
  119. package/dist/{uniq-BYQ7GWwb.js → uniq-DfUeSNaX.js} +2 -2
  120. package/dist/{uniq-G872RWmE.cjs → uniq-DiXgysNV.cjs} +6 -18
  121. package/dist/{_baseIteratee-P_firB7b.js → utils-BOFzb8Ok.js} +1134 -16
  122. package/dist/{_baseIteratee-CF4qP2a8.cjs → utils-CTlsXvdj.cjs} +1281 -84
  123. package/dist/vm/drivers/node.d.ts +1 -1
  124. package/dist/vm/drivers/quickjs.d.ts +1 -1
  125. package/dist/vm/errors.d.ts +4 -4
  126. package/dist/vm/index.d.ts +3 -4
  127. package/dist/vm/instrument.d.ts +14 -6
  128. package/dist/vm/member-proxy.d.ts +4 -0
  129. package/dist/vm/member-proxy.test.d.ts +1 -0
  130. package/dist/vm/memory.test.d.ts +1 -0
  131. package/dist/vm/return-semantics.test.d.ts +1 -0
  132. package/dist/vm/types.d.ts +20 -3
  133. package/dist/{vm-BLsHw1da.js → vm-C7SfutHn.js} +1187 -180
  134. package/dist/{vm-D6s1zF6M.cjs → vm-gmekHc0i.cjs} +1221 -207
  135. package/package.json +13 -6
  136. package/dist/_baseForOwn-BfWDTL8L.cjs +0 -53
  137. package/dist/_baseForOwn-CFhWe_W6.js +0 -48
  138. package/dist/_getPrototype-CG7Dxrrz.js +0 -6
  139. package/dist/_getPrototype-DQDlNVVM.cjs +0 -10
  140. package/dist/_overArg-C5Hy7oCi.cjs +0 -196
  141. package/dist/_overArg-CRYbj1NH.js +0 -155
  142. package/dist/chat.d.ts +0 -401
  143. package/dist/common-BlRFtdf2.js +0 -807
  144. package/dist/common-BnvrDnIT.cjs +0 -860
  145. package/dist/compiler/plugins/track-tool-calls.d.ts +0 -33
  146. package/dist/compiler-C-OI9sim.cjs +0 -547
  147. package/dist/compiler-CW1VjQsn.js +0 -473
  148. package/dist/component-C6Agl2ns.cjs +0 -73
  149. package/dist/component-DGG7d_Mz.js +0 -44
  150. package/dist/component.d.ts +0 -88
  151. package/dist/component.default.d.ts +0 -278
  152. package/dist/custom-client-ChuZ63zp.js +0 -2658
  153. package/dist/custom-client-DAsGRnYc.cjs +0 -2729
  154. package/dist/dual-modes-DDkoCp3R.js +0 -3006
  155. package/dist/dual-modes-DPXiuvNe.cjs +0 -3090
  156. package/dist/errors-C962uJdS.cjs +0 -220
  157. package/dist/errors-Cg1G0Awu.js +0 -161
  158. package/dist/example-format.d.ts +0 -8
  159. package/dist/example.d.ts +0 -45
  160. package/dist/execute-BWcdv9so.js +0 -1059
  161. package/dist/execute-DM2-7y86.cjs +0 -1060
  162. package/dist/exit-parser.d.ts +0 -39
  163. package/dist/handlers.d.ts +0 -10
  164. package/dist/hoist.d.ts +0 -2
  165. package/dist/isEmpty-BhJRCqIF.js +0 -52
  166. package/dist/isEmpty-LY533Wmh.cjs +0 -57
  167. package/dist/isPlainObject-CSxXVKCk.cjs +0 -56
  168. package/dist/isPlainObject-D39AzPO2.js +0 -51
  169. package/dist/jsx-CIgdGGVs.js +0 -52
  170. package/dist/jsx-owqxrquU.cjs +0 -75
  171. package/dist/jsx.d.ts +0 -11
  172. package/dist/message-stream/index.d.ts +0 -9
  173. package/dist/message-stream/instructions.d.ts +0 -30
  174. package/dist/message-stream/normalizer.d.ts +0 -21
  175. package/dist/message-stream/parser.d.ts +0 -89
  176. package/dist/message-stream/registry.d.ts +0 -14
  177. package/dist/message-stream/replayable.d.ts +0 -27
  178. package/dist/message-stream/response-parser.d.ts +0 -24
  179. package/dist/message-stream/sanitize.d.ts +0 -10
  180. package/dist/message-stream/stream.d.ts +0 -54
  181. package/dist/message-stream/types.d.ts +0 -97
  182. package/dist/message-stream/validator.d.ts +0 -23
  183. package/dist/prompts/chat-mode/protocol.d.ts +0 -2
  184. package/dist/prompts/chat-mode/system.d.ts +0 -2
  185. package/dist/prompts/chat-mode/system.md.d.ts +0 -2
  186. package/dist/prompts/chat-mode/user.d.ts +0 -2
  187. package/dist/prompts/chat-mode/user.md.d.ts +0 -2
  188. package/dist/prompts/code-examples.d.ts +0 -2
  189. package/dist/prompts/common.d.ts +0 -7
  190. package/dist/prompts/dual-modes.d.ts +0 -2
  191. package/dist/prompts/execution-history.d.ts +0 -6
  192. package/dist/prompts/execution-state.d.ts +0 -3
  193. package/dist/prompts/protocol-basics.d.ts +0 -5
  194. package/dist/prompts/protocol.d.ts +0 -31
  195. package/dist/prompts/worker-mode/protocol.d.ts +0 -2
  196. package/dist/prompts/worker-mode/system.d.ts +0 -2
  197. package/dist/prompts/worker-mode/system.md.d.ts +0 -2
  198. package/dist/prompts/worker-mode/user.d.ts +0 -2
  199. package/dist/prompts/worker-mode/user.md.d.ts +0 -2
  200. package/dist/quickjs-variant-BYhMBpwU.js +0 -45
  201. package/dist/quickjs-variant-CsIwTVFC.cjs +0 -56
  202. package/dist/rolldown-runtime-EAsCxrDo.js +0 -14
  203. package/dist/runtime/fixtures/qwen-protocol-failures.d.ts +0 -29
  204. package/dist/runtime/interpret-result.d.ts +0 -22
  205. package/dist/snapshots.d.ts +0 -249
  206. package/dist/stack-traces-BIE_5Xwf.js +0 -21
  207. package/dist/stack-traces-ntw_qhoy.cjs +0 -26
  208. package/dist/truncator-CLkouTCO.cjs +0 -276
  209. package/dist/truncator-DZqEqnpD.js +0 -253
  210. package/dist/truncator.d.ts +0 -118
  211. package/dist/utils-3ESwP3yg.cjs +0 -1075
  212. package/dist/utils-UBeAFeEI.js +0 -950
  213. /package/dist/{citations.d.ts → chat/citations.d.ts} +0 -0
  214. /package/dist/{citations.test.d.ts → chat/citations.test.d.ts} +0 -0
  215. /package/dist/{component.default.test.d.ts → chat/component.default.test.d.ts} +0 -0
  216. /package/dist/{compiler/plugins/track-tool-calls.test.d.ts → chat/component.test.d.ts} +0 -0
  217. /package/dist/{example.test.d.ts → chat/response.test.d.ts} +0 -0
  218. /package/dist/{exit-parser.test.d.ts → compiler/plugins/dynamic-code.test.d.ts} +0 -0
  219. /package/dist/{handlers.test.d.ts → compiler/plugins/tool-resolution.test.d.ts} +0 -0
  220. /package/dist/{hoist.test.d.ts → errors/catalog.test.d.ts} +0 -0
  221. /package/dist/{message-stream/instructions.test.d.ts → exit.test.d.ts} +0 -0
  222. /package/dist/{message-stream/normalizer.test.d.ts → inspection.test.d.ts} +0 -0
  223. /package/dist/{message-stream/parser.test.d.ts → prompts/native.test.d.ts} +0 -0
  224. /package/dist/{message-stream/response-parser.test.d.ts → result.test.d.ts} +0 -0
  225. /package/dist/{message-stream/stream.test.d.ts → runtime/chat-completion.test.d.ts} +0 -0
  226. /package/dist/{message-stream/validator.test.d.ts → runtime/chat-delivery.test.d.ts} +0 -0
  227. /package/dist/{prompts/adherence.test.d.ts → runtime/chat-last-speaker.test.d.ts} +0 -0
  228. /package/dist/{prompts/catalogue-examples.test.d.ts → runtime/compatibility-regressions.test.d.ts} +0 -0
  229. /package/dist/{prompts/code-examples.test.d.ts → runtime/context-registration.test.d.ts} +0 -0
  230. /package/dist/{prompts/common.test.d.ts → runtime/empty-completion.test.d.ts} +0 -0
  231. /package/dist/{prompts/concrete-patterns.test.d.ts → runtime/error-recovery.test.d.ts} +0 -0
  232. /package/dist/{prompts/long-search.test.d.ts → runtime/errors.test.d.ts} +0 -0
  233. /package/dist/{prompts/protocol-simple.test.d.ts → runtime/execution-activity.test.d.ts} +0 -0
  234. /package/dist/{prompts/protocol.test.d.ts → runtime/execution-diagnostics.test.d.ts} +0 -0
  235. /package/dist/{prompts/sections.test.d.ts → runtime/execution-report.test.d.ts} +0 -0
  236. /package/dist/runtime/{interpret-result.test.d.ts → execution-safety.test.d.ts} +0 -0
  237. /package/dist/{transcript.test.d.ts → runtime/exit-control-flow.test.d.ts} +0 -0
  238. /package/dist/{truncator.test.d.ts → runtime/fixtures/long-search.test.d.ts} +0 -0
package/DOCS.md CHANGED
@@ -1,1923 +1,361 @@
1
- # LLMz Documentation
1
+ # LLMZ API guide
2
2
 
3
- **LLMz: A Revolutionary TypeScript AI Agent Framework**
3
+ [Quickstart](./README.md) · [Sessions](#sessions-and-messages) · [Memory](#execution-and-memory) · [Tools](#tools-and-exits) · [Components](#components) · [Streaming](#streaming-and-delivery) · [Budgets](#token-budgets-and-compaction) · [Inspection](#inspection) · [Errors](./ERRORS.md) · [Hooks](#hooks) · [Migration](#migrating-from-the-previous-api)
4
4
 
5
- _Stop chaining tools. Start generating real code._
5
+ ## The public API
6
6
 
7
- ---
7
+ | API | Responsibility |
8
+ | ----------------------------- | --------------------------------------------------------------------------------- |
9
+ | `execute(props)` | Run model iterations until completion or failure. |
10
+ | `Session` | Own input queues, native conversation history, and exact retained memory. |
11
+ | `Chat` | Configure assistant text and rich component delivery. |
12
+ | `Tool` | Expose a host function with optional input and output schemas. |
13
+ | `Exit` | Define a named completion and its output schema. |
14
+ | `Component` | Define a rich message schema, exact JavaScript method name, and delivery handler. |
15
+ | `ExecutionResult` | Read one run's outcome, provider usage, and diagnostics. |
16
+ | `truncate` | Attach a display budget to a value without changing its data. |
17
+ | `inspect` / `createInspector` | Format bounded previews for host integrations. |
8
18
 
9
- ## Table of Contents
19
+ ## Sessions and messages
10
20
 
11
- 1. [Introduction](#introduction)
12
- 2. [Core Philosophy](#core-philosophy)
13
- 3. [Quick Start](#quick-start)
14
- 4. [Core Concepts](#core-concepts)
15
- 5. [Execution Modes](#execution-modes)
16
- 6. [Tools](#tools)
17
- 7. [Objects and Variables](#objects-and-variables)
18
- 8. [Execution Results](#execution-results)
19
- 9. [Hooks System](#hooks-system)
20
- 10. [Advanced Features](#advanced-features)
21
- 11. [API Reference](#api-reference)
22
- 12. [Examples](#examples)
21
+ Create one `Session` per conversation or worker context. Append input before calling `execute({ session, ... })`. Omit `session` to create a fresh one, then read it from `result.session`.
23
22
 
24
- ---
23
+ ```ts
24
+ import { Session } from 'llmz'
25
25
 
26
- ## Introduction
27
-
28
- LLMz is a revolutionary TypeScript AI agent framework that fundamentally changes how AI agents work. Like other agent frameworks, LLMz calls LLM models in a loop to achieve desired outcomes with access to tools and memory. However, LLMz is **code-first** – meaning it generates and runs TypeScript code in a sandbox rather than using traditional JSON tool calling.
29
-
30
- ### What Makes LLMz Different
31
-
32
- Traditional agent frameworks rely on JSON tool calling, which has significant limitations:
33
-
34
- - **Hard-to-parse JSON schemas** for LLMs
35
- - **Incapable of complex logic** like loops and conditionals
36
- - **Multiple expensive roundtrips** for each tool call
37
- - **Unreliable beyond simple scenarios**
38
-
39
- LLMz leverages the fact that models have been trained extensively on millions of TypeScript codebases, making them incredibly reliable at generating working code. This enables:
40
-
41
- - **Complex logic and multi-tool orchestration** in **one call**
42
- - **Native LLM thinking** via comments and code structure
43
- - **Complete type safety** and predictable schemas
44
- - **Seamless scaling** in production environments
45
-
46
- ### Battle-Tested at Scale
47
-
48
- LLMz operates as an LLM-native TypeScript VM built on top of Zui (Botpress's internal schema library), battle-tested in production powering millions of AI agents worldwide.
49
-
50
- ---
51
-
52
- ## Core Philosophy
53
-
54
- ### Code Generation > Tool Calling
55
-
56
- Traditional tool-calling agents are fundamentally limited by the JSON interface between the LLM and tools. This requires multiple roundtrips for complex tasks and cannot handle conditional logic, loops, or sophisticated data processing.
57
-
58
- LLMz solves this by letting LLMs do what they do best: **generate code**. Since models are trained extensively on code, they can reliably generate TypeScript that:
59
-
60
- - Calls multiple tools in sequence
61
- - Handles conditional logic and error cases
62
- - Processes and transforms data between tool calls
63
- - Implements complex business logic
64
- - Maintains type safety throughout execution
65
-
66
- ### Example: Traditional vs LLMz
67
-
68
- **Traditional Tool Calling:**
69
-
70
- ```
71
- LLM → JSON: {"tool": "getPrice", "params": {"from": "quebec", "to": "new york"}}
72
- System → Response: {"price": 600}
73
- LLM → JSON: {"tool": "checkBudget", "params": {"amount": 600}}
74
- System → Response: {"canAfford": false}
75
- LLM → JSON: {"tool": "notifyUser", "params": {"message": "Price too high"}}
76
- ```
77
-
78
- **LLMz Code Generation:**
79
-
80
- ```typescript
81
- // Check the ticket price and user's budget in one go
82
- const price = await getTicketPrice({ from: 'quebec', to: 'new york' })
83
- const budget = await getUserBudget()
84
-
85
- if (price > budget) {
86
- await notifyUser({ message: `Price $${price} exceeds budget $${budget}` })
87
- return { action: 'budget_exceeded', price, budget }
88
- } else {
89
- const ticketId = await buyTicket({ from: 'quebec', to: 'new york' })
90
- return { action: 'done', result: ticketId }
91
- }
26
+ const session = new Session()
27
+ session.append([
28
+ { role: 'user', content: 'Hello' },
29
+ { role: 'assistant', content: 'How can I help?' },
30
+ {
31
+ role: 'user',
32
+ content: 'What does this receipt say?',
33
+ attachments: [{ type: 'image', url: 'https://example.com/receipt.png', id: 'receipt', alt: 'Store receipt' }],
34
+ },
35
+ ])
92
36
  ```
93
37
 
94
- ---
38
+ `append` accepts a native Cognitive message, a `Transcript.Message`, or an array of either. It validates the whole batch before enqueueing any of it. Identical messages remain distinct inputs. The session copies its inputs and returns copies of its messages.
95
39
 
96
- ## Quick Start
40
+ | Input | Cognitive representation |
41
+ | --------------------------------------- | -------------------------------------------------------------------------- |
42
+ | Native text or multipart message | Preserves content, part order, and opaque JSON provider fields. |
43
+ | User/assistant text | Retains its role and text. |
44
+ | `attachments` | Adds image/audio parts to that message, with adjacent ID/alt descriptions. |
45
+ | `modality: 'voice'` or audio attachment | Labels convenience input as a voice transcript. |
46
+ | `role: 'event'` | User-role data labeled with the event name and a bounded payload preview. |
47
+ | `role: 'summary'` | User-role data labeled as a conversation summary. |
97
48
 
98
- ### Installation
49
+ Native multipart inputs already describe their content; do not combine them with convenience `attachments` or `modality`. Events and summaries do not become system instructions. Pass system instructions through `execute({ instructions })`.
99
50
 
100
- ```bash
101
- npm install llmz @botpress/client
102
- ```
51
+ `session.transcript` returns the retained conversation and queued input, preserving `event` and `summary` roles. `session.messages` exposes processed native messages; `session.pendingMessages` exposes queued native input. All three return detached copies.
103
52
 
104
- ### Basic Example (Worker Mode)
53
+ External events start turns just like user messages. Append them, then execute:
105
54
 
106
- ```typescript
107
- import { execute } from 'llmz'
55
+ ```ts
108
56
  import { Client } from '@botpress/client'
57
+ import { Chat, Session, execute } from 'llmz'
109
58
 
110
- const client = new Client({
111
- botId: process.env.BOTPRESS_BOT_ID!,
112
- token: process.env.BOTPRESS_TOKEN!,
113
- })
59
+ const client = new Client()
60
+ const session = new Session()
61
+ const chat = new Chat({ response: { handler: (text) => console.log(text) } })
114
62
 
115
- const result = await execute({
116
- instructions: 'Calculate the sum of numbers 1 to 100',
117
- client,
63
+ session.append({
64
+ role: 'event',
65
+ name: 'button.clicked',
66
+ payload: { button: 'confirm', orderId: 'order-42' },
118
67
  })
119
-
120
- if (result.isSuccess()) {
121
- console.log('Result:', result.output)
122
- console.log('Generated code:', result.iteration.code)
123
- }
68
+ await execute({ client, session, chat, instructions: 'Respond to order actions.' })
124
69
  ```
125
70
 
126
- ### Basic Example (Chat Mode)
71
+ Appending an event does not start execution automatically. Events arriving during a run stay queued for the next call; repeated clicks remain separate events. Event payloads are data, so validate authorization and business rules in your tool handlers.
127
72
 
128
- ```typescript
129
- import { execute } from 'llmz'
130
- import { Client } from '@botpress/client'
131
- import { CLIChat } from './utils/cli-chat'
73
+ Input cannot inject native tool calls or results. Restore a serialized session to continue existing tool history. The runtime retains assistant calls and their matching results together, including provider continuation metadata. It never appends memory instructions to signed assistant output.
132
74
 
133
- const client = new Client({
134
- botId: process.env.BOTPRESS_BOT_ID!,
135
- token: process.env.BOTPRESS_TOKEN!,
136
- })
75
+ Convenience event payloads receive a 5,000-token preview in native model input. The original payload remains in `session.transcript` and serialized session state until that event is compacted. Ordinary user text is retained in full. Keep authoritative application events in the host when they must outlive conversation compaction.
137
76
 
138
- const chat = new CLIChat()
77
+ Session status is `idle`, `pending`, or `active`. A turn claims queued input once. Input arriving during execution remains queued until that turn completes. Concurrent `execute` calls on one session are rejected.
139
78
 
140
- while (await chat.iterate()) {
141
- await execute({
142
- instructions: 'You are a helpful assistant',
143
- chat,
144
- client,
145
- })
146
- }
147
- ```
148
-
149
- ---
150
-
151
- ## Core Concepts
152
-
153
- ### Execution Loop
154
-
155
- At its core, LLMz exposes a single method (`execute`) that runs in a loop until one of these conditions is met:
156
-
157
- 1. **An Exit is returned** - Agent completes with structured result
158
- 2. **Agent waits for user input** (Chat Mode) - Returns control to user
159
- 3. **Maximum iterations reached** - Safety limit to prevent infinite loops
160
-
161
- The loop automatically handles:
162
-
163
- - Tool calling and result processing
164
- - Thinking about outputs and context
165
- - Error recovery and retry logic
166
- - Variable state persistence across iterations
79
+ `session.messages` is canonical history. `session.pendingMessages` contains unclaimed input. `session.requestMessages()` produces a copy with an ephemeral memory overview; `requestMessages({ memory: false })` omits the overview. Runtime lifecycle methods such as `beginTurn`, `nextIteration`, and `settleIteration` are used by `execute`; applications normally only need `append` and persistence.
167
80
 
168
- ### Generated Code Structure
81
+ ## Execution and memory
169
82
 
170
- Every LLMz code block follows a predictable structure that LLMs can reliably generate:
83
+ The model receives one native tool, `run_javascript`, and declarations for the registered host tools, objects, components, and exits. A response may contain at most one JavaScript call. Its program can compose multiple business operations.
171
84
 
172
- #### Return Statement (Required)
85
+ These examples are JavaScript generated by the model, not host TypeScript:
173
86
 
174
- At minimum, an LLMz response must contain a return statement with an Exit:
175
-
176
- ```typescript
177
- // Chat mode - give turn back to user
178
- return { action: 'listen' }
179
-
180
- // Worker mode - complete with result
181
- return { action: 'done', result: calculatedValue }
87
+ ```js
88
+ const account = await getAccount({ id: 'customer-42' })
89
+ return inspect(account)
182
90
  ```
183
91
 
184
- #### Tool Calls with Logic
185
-
186
- Unlike traditional tool calling, LLMz enables complex logic impossible with JSON:
187
-
188
- ```typescript
189
- // Complex conditional logic and error handling
190
- const price = await getTicketPrice({ from: 'quebec', to: 'new york' })
191
-
192
- if (price > 500) {
193
- throw new Error('Price too high')
194
- } else {
195
- const ticketId = await buyTicket({ from: 'quebec', to: 'new york' })
196
- return { action: 'done', result: ticketId }
197
- }
92
+ ```js
93
+ return exit('done', { accountId: account.id })
198
94
  ```
199
95
 
200
- #### Comments for Planning
201
-
202
- Comments help LLMs think step-by-step and plan ahead:
203
-
204
- ```typescript
205
- // Check user's budget first before proceeding with purchase
206
- const budget = await getUserBudget()
207
-
208
- // Only proceed if we have enough funds
209
- if (budget >= price) {
210
- // Purchase the ticket
211
- const ticket = await buyTicket(ticketDetails)
212
- }
213
- ```
214
-
215
- #### React Components (Chat Mode Only)
216
-
217
- In Chat Mode, agents can yield React components for rich user interaction:
218
-
219
- ```typescript
220
- // Multi-line text support
221
- yield <Text>
222
- Hello, world!
223
- This is a second line.
224
- </Text>
225
-
226
- // Composed/nested components
227
- yield <Message>
228
- <Text>What do you prefer?</Text>
229
- <Button>Cats</Button>
230
- <Button>Dogs</Button>
231
- </Message>
232
-
233
- return { action: 'listen' }
234
- ```
235
-
236
- ### Compilation Pipeline
237
-
238
- LLMz uses a sophisticated Babel-based compilation system to transform generated code:
239
-
240
- 1. **AST Parsing**: TypeScript/JSX code parsed into Abstract Syntax Tree
241
- 2. **Plugin Transformation**: Custom plugins modify the AST for execution
242
- 3. **Code Generation**: Modified AST compiled back to executable JavaScript
243
- 4. **Source Maps**: Generated for debugging and error tracking
244
-
245
- Key transformations include:
246
-
247
- - Tool call instrumentation for monitoring
248
- - Variable extraction and tracking
249
- - JSX component handling
250
- - Line number preservation for stack traces
251
-
252
- ### Virtual Machine Execution
96
+ `inspect(value)` exposes a preview in the next model request. Exact retained values remain available to JavaScript. `exit(name, payload)` validates a completion payload against its registered exit. Normal returned data does not complete a worker.
253
97
 
254
- LLMz supports multiple execution environments:
98
+ Worker prompts require every response to be a `run_javascript` call with empty assistant text, including during recovery and completion. Workers must not emit preambles, progress updates, or acknowledgements such as “Done.” Results are inspected or returned through a registered exit.
255
99
 
256
- - **Production**: Uses `isolated-vm` for security isolation
257
- - **CI/Development**: Falls back to Node.js VM for compatibility
258
- - **Browser**: Uses standard JavaScript execution
100
+ | Value | Lifetime |
101
+ | ----------------- | ---------------------------------------------------------------------------- |
102
+ | Named variables | Remain in session memory across executions and history compaction. |
103
+ | `$return` | Latest available returned result; read-only. |
104
+ | `$iterations` | Retained iteration records, newest first; records and results are read-only. |
105
+ | Object properties | Host-backed values with declared read/write access. |
106
+ | Prompt previews | Derived display text; never the source of stored values. |
259
107
 
260
- The VM provides:
108
+ A record's `hasResult` distinguishes an absent result from a result whose exact value is `undefined`. A failed iteration without a new result preserves the previous `$return`. A new result that cannot be retained clears `$return` rather than exposing stale evidence. Compaction removes discarded iteration results; it does not fall back to an older `$return`.
261
109
 
262
- - Memory isolation and limits
263
- - Execution timeouts
264
- - Secure context separation
265
- - Stack trace sanitization
110
+ Memory accepts finite numbers, booleans, strings, null, undefined, dense arrays, and plain data objects. Its codec preserves `undefined` and negative zero through JSON persistence. Functions, accessors, symbols, cycles, and custom class instances cannot be stored as exact memory values.
266
111
 
267
- ---
112
+ ```ts
113
+ import { Session } from 'llmz'
268
114
 
269
- ## Execution Modes
270
-
271
- LLMz operates in two distinct modes depending on whether a chat interface is provided:
272
-
273
- ### Chat Mode
274
-
275
- **Enabled when**: `chat` parameter is provided to `execute()`
276
-
277
- Chat Mode is designed for interactive conversational agents that need to:
278
-
279
- - Maintain conversation history
280
- - Respond to user messages
281
- - Yield UI components for rich interaction
282
- - Handle turn-taking between agent and user
283
-
284
- Key characteristics:
285
-
286
- - Agent can yield React components to user
287
- - Special `ListenExit` automatically available
288
- - Transcript management for conversation history
289
- - Turn-based execution flow
290
-
291
- ```typescript
292
- const result = await execute({
293
- instructions: 'You are a helpful assistant',
294
- chat: myChatInstance,
295
- tools: [searchTool, calculatorTool],
296
- client,
115
+ const session = new Session({
116
+ variables: { accountId: 'customer-42', attempts: 0 },
117
+ maxBytes: 4 * 1024 * 1024,
297
118
  })
298
-
299
- if (result.is(ListenExit)) {
300
- // Agent is waiting for user input
301
- }
119
+ console.log(session.memory.variables)
120
+ console.log(session.iterations)
302
121
  ```
303
122
 
304
- ### Worker Mode
305
-
306
- **Enabled when**: `chat` parameter is omitted from `execute()`
307
-
308
- Worker Mode is designed for automated execution environments that need to:
123
+ The default memory limit is 16 MiB. It covers encoded named variables, object memory, and retained result records; it is not a byte limit on all conversation messages. Token budgeting controls the request sent to the model. Successful writes may be retained when later writes exceed capacity; reports describe unavailable values. Host side effects are not rolled back.
309
124
 
310
- - Process data and perform computations
311
- - Execute multi-step workflows
312
- - Return structured results
313
- - Run without human interaction
125
+ ## Persistence and recovery
314
126
 
315
- Key characteristics:
127
+ Persist `session.toJSON()` after execution has settled. It rejects serialization while a run holds the session lock or an iteration is pending.
316
128
 
317
- - Focus on computational tasks and data processing
318
- - Uses `DefaultExit` if no custom exits provided
319
- - Sandboxed execution with security isolation
320
- - Automated completion without user interaction
129
+ ```ts
130
+ import { Session } from 'llmz'
321
131
 
322
- ```typescript
323
- const result = await execute({
324
- instructions: 'Process the customer data and generate insights',
325
- tools: [dataProcessorTool, analyticseTool],
326
- exits: [dataProcessedExit],
327
- client,
328
- })
329
-
330
- if (result.is(dataProcessedExit)) {
331
- console.log('Analysis complete:', result.output)
332
- }
132
+ const session = new Session()
133
+ session.append({ role: 'user', content: 'Queued request' })
134
+ const encoded = JSON.stringify(session.toJSON())
135
+ const restored = Session.fromJSON(JSON.parse(encoded))
136
+ console.log(restored.status) // pending
333
137
  ```
334
138
 
335
- ### Mode Comparison
336
-
337
- | Feature | Chat Mode | Worker Mode |
338
- | -------------------- | ------------------- | --------------- |
339
- | User Interaction | ✅ Interactive | ❌ Automated |
340
- | UI Components | ✅ React components | ❌ No UI |
341
- | Conversation History | ✅ Full transcript | ❌ No history |
342
- | Default Exits | `ListenExit` | `DefaultExit` |
343
- | Primary Use Case | Conversational AI | Data processing |
344
- | Execution Pattern | Turn-based | Continuous |
139
+ Restoration checks format versions, chronology, identities, call/result pairing, exact result encodings, and memory capacity. Unknown versions are rejected. Conversation/provider fields must be acyclic JSON data; encode binary values explicitly. This is conversation and memory persistence, not a paused JavaScript stack.
345
140
 
346
- ---
141
+ After a failed execution, the session retains the active input batch and any completed effects/results. Executing it again continues that turn. Newly appended messages remain queued. A successful retry can therefore leave `session.status === "pending"`; call `execute()` again to process that next batch. Its user messages reset the chat response guard when the next turn starts. Applications should inspect failures before retrying operations that produce external effects.
347
142
 
348
- ## Tools
143
+ `result.toJSON()` is a compact outcome summary. `result.diagnostics()` exports run diagnostics. Neither replaces `session.toJSON()`.
349
144
 
350
- Tools are the primary way to extend LLMz agents with external capabilities. Unlike traditional agent frameworks, LLMz tools are called through generated TypeScript code, enabling complex orchestration and error handling.
145
+ Chat requires the assistant to be the last participant who spoke before `exit("listen")` can complete. Successfully delivered text and components count across iterations, executions, and saved sessions. A new user message requires a new response; events, tool results, failed deliveries, and whitespace-only content do not change the last speaker. An empty or whitespace-only model response with `stopReason: "stop"` and no tool calls follows the same listen flow and guard. Silent completion raises a recoverable `MissingChatResponseError` before `onExit`, preserving tool results for the next attempt. Set `options: { requireChatResponse: false }` for intentionally silent handling. Custom task exits and worker mode are unaffected. Older session snapshots without delivery tracking restore with an unknown last speaker and require a response by default.
351
146
 
352
- ### Tool Definition
147
+ ## Tools and exits
353
148
 
354
- Tools are defined using Zui schemas for complete type safety:
149
+ A `Tool` has a name, async handler, optional description, input/output schemas, aliases, metadata, and retry callback. Its original input schema validates and normalizes arguments before the handler runs. Its output schema describes likely returned data for TypeScript and the model: results are never validated, normalized, stripped, or rejected against that schema. Dynamic tools, objects, exits, instructions, and model configuration can be supplied as getters evaluated for each iteration.
355
150
 
356
- ```typescript
357
- import { Tool } from 'llmz'
151
+ ```ts
358
152
  import { z } from '@bpinternal/zui'
153
+ import { Tool, Exit } from 'llmz'
359
154
 
360
- const weatherTool = new Tool({
361
- name: 'getWeather',
362
- description: 'Get current weather for a location',
363
- input: z.object({
364
- location: z.string().describe('City name or coordinates'),
365
- units: z.enum(['celsius', 'fahrenheit']).optional().default('celsius'),
366
- }),
367
- output: z.object({
368
- temperature: z.number(),
369
- conditions: z.string(),
370
- humidity: z.number(),
371
- }),
372
- handler: async ({ location, units }) => {
373
- // Implementation here
374
- return {
375
- temperature: 22,
376
- conditions: 'sunny',
377
- humidity: 65,
378
- }
379
- },
380
- })
381
- ```
382
-
383
- ### Tool Usage in Generated Code
384
-
385
- The LLM generates TypeScript code that calls tools naturally:
386
-
387
- ```typescript
388
- // Simple tool call
389
- const weather = await getWeather({ location: 'New York' })
390
-
391
- // Complex logic with multiple tools
392
- const weather = await getWeather({ location: userLocation })
393
- if (weather.temperature < 0) {
394
- const clothing = await getSuggestions({ type: 'winter', temperature: weather.temperature })
395
- yield <Text>It's {weather.temperature}°C! {clothing.suggestion}</Text>
396
- } else {
397
- yield <Text>Nice weather! {weather.conditions} at {weather.temperature}°C</Text>
398
- }
399
-
400
- return { action: 'listen' }
401
- ```
402
-
403
- ### Advanced Tool Features
404
-
405
- #### Tool Aliases
406
-
407
- Tools can have multiple names for flexible calling:
408
-
409
- ```typescript
410
- const tool = new Tool({
411
- name: 'calculatePrice',
412
- aliases: ['getPrice', 'checkCost'],
413
- // ... rest of definition
155
+ const getStock = new Tool({
156
+ name: 'getStock',
157
+ input: z.object({ sku: z.string() }),
158
+ output: z.number(),
159
+ handler: async ({ sku }) => (sku === 'coffee' ? 20 : 0),
414
160
  })
415
-
416
- // All of these work in generated code:
417
- // await calculatePrice(params)
418
- // await getPrice(params)
419
- // await checkCost(params)
420
- ```
421
-
422
- #### Static Inputs
423
-
424
- Force specific inputs to be always included:
425
-
426
- ```typescript
427
- const tool = new Tool({
428
- name: 'logEvent',
429
- input: z.object({
430
- event: z.string(),
431
- userId: z.string(),
432
- timestamp: z.number(),
433
- }),
434
- staticInputs: {
435
- userId: 'user-123',
436
- timestamp: () => Date.now(), // Dynamic static input
437
- },
438
- handler: async ({ event, userId, timestamp }) => {
439
- // userId and timestamp are automatically provided
440
- },
161
+ const done = new Exit({
162
+ name: 'done',
163
+ description: 'Return available stock.',
164
+ schema: z.object({ available: z.number() }),
441
165
  })
442
166
  ```
443
167
 
444
- #### Tool Wrapping and Cloning
168
+ See the [complete typed error catalogue and recovery contract](./ERRORS.md) for stable codes, import-safe guards, iteration diagnostics, and critical failures.
445
169
 
446
- Clone and modify existing tools:
170
+ LLMz retains the original schemas separately from their model-facing JSON descriptions. Tool inputs, exit payloads, component props, and object-property assignments preserve `.trim()`, `.refine()`, `.superRefine()`, `.preprocess()`, `.transform()`, and pipelines. Rejected refinements produce the corresponding typed input error and feedback for the next iteration. Successful parsing runs effects once; handlers, exit hooks, deliveries, and stored properties receive the parsed value. Cloning tools and exits preserves their validators.
447
171
 
448
- ```typescript
449
- const originalTool = new Tool({/* definition */})
450
-
451
- const wrappedTool = originalTool.clone({
452
- name: 'wrappedVersion',
453
- description: 'Enhanced version with logging',
454
- handler: async (input) => {
455
- console.log('Tool called with:', input)
456
- const result = await originalTool.execute(input)
457
- console.log('Tool returned:', result)
458
- return result
459
- },
460
- })
461
- ```
172
+ Tool input effects may be asynchronous. Exit validation, component rendering, and object-property setters are synchronous: async effects on those surfaces raise `InvalidConfigurationError`. Put asynchronous checks in a tool input schema. Exceptions thrown by schema callbacks are configuration failures; use refinement issues to report correctable input errors.
462
173
 
463
- ### Tool Type Generation
464
-
465
- Use `tool.getTypings()` to see the TypeScript definitions generated for the LLM:
466
-
467
- ```typescript
468
- console.log(weatherTool.getTypings())
469
- // Output:
470
- // /**
471
- // * Get current weather for a location
472
- // */
473
- // declare function getWeather(input: {
474
- // location: string; // City name or coordinates
475
- // units?: "celsius" | "fahrenheit";
476
- // }): Promise<{
477
- // temperature: number;
478
- // conditions: string;
479
- // humidity: number;
480
- // }>;
481
- ```
174
+ For a type-changing tool input, `execute()` accepts `z.input<I>` and the handler receives `z.output<I>`. Exit results and component handlers use the parsed output type. Tool output schemas describe raw data, so their type hint uses `z.input<O>`; output effects never execute. A handler may return data outside that hint. Memory and inspection retain their own supported-value constraints.
482
175
 
483
- ### Best Practices
176
+ Model-facing schemas describe accepted input shapes, not arbitrary JavaScript behavior. Use descriptions to explain custom constraints. Object property schemas describe assignments; host-provided initial values are already-normalized state and are not parsed again when loaded or restored. Plain session variables have no user-supplied validation schema.
484
177
 
485
- 1. **Descriptive Schemas**: Detailed descriptions help LLMs generate better code
486
- 2. **Type Safety**: Use strict Zui schemas for predictable behavior
487
- 3. **Error Handling**: Tools should handle errors gracefully
488
- 4. **Performance**: Keep tool execution fast to avoid timeouts
489
- 5. **Documentation**: Clear descriptions improve code generation quality
178
+ `toJSON()` exports descriptions, not executable refinements or transforms. Persist sessions separately and reattach the original tool, exit, component, and object definitions when resuming. Reconstructing a schema from JSON cannot restore its JavaScript callbacks.
490
179
 
491
- ---
180
+ Use `result.is(done)` to narrow the result and its output type. `result.isError()` exposes an execution failure. Without custom exits, workers receive `DefaultExit`. An explicit empty `exits` array supplies no completion exits. Chat adds `ListenExit`; an accepted plain assistant answer can complete the chat turn.
492
181
 
493
- ## Objects and Variables
182
+ Tools return business data. Generated code sends rich messages through registered `chat.<component>(props)` methods. A tool may throw or return `new ThinkSignal(reason, context)` to require inspection of a successful result. JavaScript receives `context` as the tool return value: assignments, subsequent calls, and awaited parallel tools continue normally. At the end of that iteration, inspection takes precedence over an attempted exit or a later recoverable error; exit hooks do not run. The next model turn receives every signaling tool's name, source line, reason, and result together inside `<forced_inspection>`, and is instructed to reuse those results rather than repeat successful calls. Explicit `inspect()` output is also preserved. Tool-result display budgets and `truncate()` policies still apply. Only the business tool handler may produce a `ThinkSignal`; tool hooks must return their documented input/output overrides. Forced inspection uses the output accepted by `onAfterTool`. If that hook rejects or cancels, the rejected result is withheld from guest memory and inspection.
494
183
 
495
- Objects in LLMz provide namespaced containers for related tools and variables, enabling sophisticated state management and data organization.
184
+ Forced inspection delays completion, not business actions: code after a signaling tool still runs, including other tools and chat component deliveries. It is not an authorization barrier. Cancellation and critical failures still stop execution completely. `ThinkSignal` is not durable pause/resume.
496
185
 
497
- ### Object Definition
186
+ ## Components
498
187
 
499
- Objects group related functionality and provide scoped variables:
188
+ Component names are exact JavaScript identifiers. Object and array schemas are supported, including root effects and pipelines; transformed props must still be an object or array. Aliases and generation metadata are not component options. Text names such as `text` and `message` are reserved for native assistant output.
500
189
 
501
- ```typescript
502
- import { ObjectInstance } from 'llmz'
190
+ ```ts
503
191
  import { z } from '@bpinternal/zui'
504
-
505
- const userObject = new ObjectInstance({
506
- name: 'user',
507
- properties: [
508
- {
509
- name: 'name',
510
- value: 'John Doe',
511
- writable: true,
512
- type: z.string(),
513
- },
514
- {
515
- name: 'age',
516
- value: 30,
517
- writable: false, // Read-only
518
- type: z.number(),
519
- },
520
- {
521
- name: 'preferences',
522
- value: { theme: 'dark', language: 'en' },
523
- writable: true,
524
- type: z.object({
525
- theme: z.enum(['light', 'dark']),
526
- language: z.string(),
527
- }),
528
- },
529
- ],
530
- tools: [
531
- new Tool({
532
- name: 'updateProfile',
533
- input: z.object({ name: z.string() }),
534
- handler: async ({ name }) => {
535
- // This tool is scoped to the user object
536
- return { success: true }
537
- },
538
- }),
539
- ],
540
- })
541
- ```
542
-
543
- ### Variables in Generated Code
544
-
545
- The LLM can read and write object properties in generated code:
546
-
547
- ```typescript
548
- // Reading variables
549
- const userName = user.name // "John Doe"
550
- const userAge = user.age // 30
551
-
552
- // Writing to writable variables
553
- user.name = 'Jane Smith' // ✅ Succeeds
554
- user.preferences = { theme: 'light', language: 'es' } // ✅ Succeeds
555
-
556
- // Attempting to write read-only variables
557
- user.age = 25 // ❌ Throws AssignmentError
558
- ```
559
-
560
- ### Type Safety and Validation
561
-
562
- Variables are validated against their schemas:
563
-
564
- ```typescript
565
- // Valid assignment
566
- user.preferences = { theme: 'dark', language: 'fr' } // ✅
567
-
568
- // Invalid assignment - wrong type
569
- user.preferences = { theme: 'blue', language: 'fr' } // ❌ Throws validation error
570
-
571
- // Invalid assignment - missing required fields
572
- user.preferences = { theme: 'dark' } // ❌ Missing language field
573
- ```
574
-
575
- ### Mutation Tracking
576
-
577
- LLMz automatically tracks changes to object properties:
578
-
579
- ```typescript
580
- // In generated code
581
- user.name = 'Updated Name'
582
- user.preferences.theme = 'light'
583
-
584
- // After execution, mutations are available
585
- console.log(result.iteration.mutations)
586
- // [
587
- // {
588
- // object: 'user',
589
- // property: 'name',
590
- // before: 'John Doe',
591
- // after: 'Updated Name'
592
- // },
593
- // {
594
- // object: 'user',
595
- // property: 'preferences',
596
- // before: { theme: 'dark', language: 'en' },
597
- // after: { theme: 'light', language: 'en' }
598
- // }
599
- // ]
600
- ```
601
-
602
- ### Namespaced Tools
603
-
604
- Tools within objects are called with object namespace:
605
-
606
- ```typescript
607
- // Tool is scoped to the user object
608
- await user.updateProfile({ name: 'New Name' })
609
-
610
- // This automatically updates the user object's properties
611
- // and is tracked as a mutation
612
- ```
613
-
614
- ### Object Sealing and Protection
615
-
616
- Objects are automatically sealed to prevent unauthorized modifications:
617
-
618
- ```typescript
619
- // In generated code - these will throw errors
620
- user.newProperty = 'value' // ❌ Cannot add new properties
621
- delete user.name // ❌ Cannot delete properties
622
-
623
- // Only predefined properties can be modified (if writable)
624
- user.name = 'New Name' // ✅ Allowed if writable: true
625
- ```
626
-
627
- ### Variable Persistence
628
-
629
- Variables persist across iterations and thinking cycles:
630
-
631
- ```typescript
632
- // Iteration 1: Set a variable
633
- user.preferences = { theme: 'dark', language: 'es' }
634
- return { action: 'think' } // Trigger thinking
635
-
636
- // Iteration 2: Variable is still available
637
- const currentTheme = user.preferences.theme // 'dark'
638
- ```
639
-
640
- ### Best Practices
641
-
642
- 1. **Meaningful Names**: Use descriptive object and property names
643
- 2. **Appropriate Scope**: Group related functionality together
644
- 3. **Write Protection**: Mark properties as read-only when appropriate
645
- 4. **Type Safety**: Use strict schemas for predictable behavior
646
- 5. **Mutation Tracking**: Leverage mutation tracking for audit trails
647
-
648
- ---
649
-
650
- ## Execution Results
651
-
652
- Every call to `execute()` returns an `ExecutionResult` that provides type-safe access to the execution outcome. LLMz execution can result in three different types of results.
653
-
654
- ### Result Types
655
-
656
- #### SuccessExecutionResult
657
-
658
- Agent completed successfully with an Exit. Contains the structured data produced by the agent.
659
-
660
- ```typescript
661
- const result = await execute({
662
- instructions: 'Calculate the sum',
663
- client,
664
- })
665
-
666
- if (result.isSuccess()) {
667
- console.log('Output:', result.output)
668
- console.log('Exit used:', result.exit.name)
669
- console.log('Generated code:', result.iteration.code)
670
- }
671
- ```
672
-
673
- #### ErrorExecutionResult
674
-
675
- Execution failed with an unrecoverable error:
676
-
677
- ```typescript
678
- if (result.isError()) {
679
- console.error('Error:', result.error)
680
- console.error('Failed iteration:', result.iteration?.error)
681
-
682
- // Analyze failure progression
683
- result.iterations.forEach((iter, i) => {
684
- console.log(`Iteration ${i + 1}: ${iter.status.type}`)
685
- })
686
- }
687
- ```
688
-
689
- #### PartialExecutionResult
690
-
691
- Execution was interrupted by a SnapshotSignal for pauseable operations:
692
-
693
- ```typescript
694
- if (result.isInterrupted()) {
695
- console.log('Interrupted:', result.signal.message)
696
-
697
- // Save snapshot for later resumption
698
- const serialized = result.snapshot.toJSON()
699
- await database.saveSnapshot(serialized)
700
- }
701
- ```
702
-
703
- ### Type-Safe Exit Checking
704
-
705
- Use `result.is(exit)` for type-safe access to specific exit data:
706
-
707
- ```typescript
708
- const successExit = new Exit({
709
- name: 'success',
710
- schema: z.object({
711
- recordsProcessed: z.number(),
712
- processingTime: z.number(),
713
- }),
714
- })
715
-
716
- const errorExit = new Exit({
717
- name: 'error',
718
- schema: z.object({
719
- errorCode: z.string(),
720
- details: z.string(),
721
- }),
722
- })
723
-
724
- const result = await execute({
725
- instructions: 'Process the data',
726
- exits: [successExit, errorExit],
727
- client,
728
- })
729
-
730
- // Type-safe exit handling with automatic output typing
731
- if (result.is(successExit)) {
732
- // TypeScript knows result.output has the success schema
733
- console.log(`Processed ${result.output.recordsProcessed} records`)
734
- console.log(`Processing took ${result.output.processingTime}ms`)
735
- } else if (result.is(errorExit)) {
736
- // TypeScript knows result.output has the error schema
737
- console.error(`Error ${result.output.errorCode}: ${result.output.details}`)
738
- }
739
- ```
740
-
741
- ### Built-in Exits
742
-
743
- ```typescript
744
- import { ListenExit, DefaultExit, ThinkExit } from 'llmz'
745
-
746
- // Check for built-in exits
747
- if (result.is(ListenExit)) {
748
- console.log('Agent is waiting for user input')
749
- }
750
-
751
- if (result.is(DefaultExit)) {
752
- // DefaultExit has success/failure discriminated union
753
- if (result.output.success) {
754
- console.log('Completed successfully:', result.output.result)
755
- } else {
756
- console.error('Completed with error:', result.output.error)
757
- }
758
- }
759
-
760
- if (result.is(ThinkExit)) {
761
- console.log('Agent requested thinking time')
762
- console.log('Current variables:', result.output.variables)
763
- }
764
- ```
765
-
766
- ### Accessing Execution Details
767
-
768
- #### Iterations and Execution Flow
769
-
770
- ```typescript
771
- // Access the final iteration
772
- const lastIteration = result.iteration
773
- if (lastIteration) {
774
- console.log('Generated code:', lastIteration.code)
775
- console.log('Status:', lastIteration.status.type)
776
- console.log('Duration:', lastIteration.duration)
777
- }
778
-
779
- // Access all iterations to see full execution flow
780
- result.iterations.forEach((iteration, index) => {
781
- console.log(`Iteration ${index + 1}:`)
782
- console.log(' Status:', iteration.status.type)
783
- console.log(' Code length:', iteration.code?.length || 0)
784
- console.log(' Variables:', Object.keys(iteration.variables).length)
785
- })
786
- ```
787
-
788
- #### Variables and Declarations
789
-
790
- ```typescript
791
- // If agent generates: const hello = '1234'
792
- const lastIteration = result.iteration
793
- if (lastIteration) {
794
- console.log(lastIteration.variables.hello) // '1234'
795
-
796
- // Access all variables from the final iteration
797
- Object.entries(lastIteration.variables).forEach(([name, value]) => {
798
- console.log(`Variable ${name}:`, value)
799
- })
800
- }
801
- ```
802
-
803
- #### Tool Calls and Traces
804
-
805
- ```typescript
806
- // Access tool calls from all iterations
807
- const allToolCalls = result.iterations.flatMap((iter) => iter.traces.filter((trace) => trace.type === 'tool_call'))
808
-
809
- console.log('Total tool calls:', allToolCalls.length)
810
-
811
- // Access other trace types
812
- const lastIteration = result.iteration
813
- if (lastIteration) {
814
- const yields = lastIteration.traces.filter((trace) => trace.type === 'yield')
815
- const comments = lastIteration.traces.filter((trace) => trace.type === 'comment')
816
- const propertyAccess = lastIteration.traces.filter((trace) => trace.type === 'property')
817
- }
818
- ```
819
-
820
- #### Context and Metadata
821
-
822
- ```typescript
823
- if (result.isSuccess()) {
824
- // Access original execution parameters
825
- console.log('Instructions:', result.context.instructions)
826
- console.log('Loop limit:', result.context.loop)
827
- console.log('Temperature:', result.context.temperature)
828
- console.log('Model:', result.context.model)
829
-
830
- // Access tools and exits that were available
831
- console.log(
832
- 'Available tools:',
833
- result.context.tools?.map((t) => t.name)
834
- )
835
- console.log(
836
- 'Available exits:',
837
- result.context.exits?.map((e) => e.name)
838
- )
839
- }
840
- ```
841
-
842
- ### Error Analysis
843
-
844
- ```typescript
845
- if (result.isError()) {
846
- console.error('Execution failed:', result.error)
847
-
848
- // Analyze the failure progression
849
- const failedIteration = result.iteration
850
- if (failedIteration) {
851
- switch (failedIteration.status.type) {
852
- case 'execution_error':
853
- console.error('Code execution failed:', failedIteration.status.execution_error.message)
854
- console.error('Stack trace:', failedIteration.status.execution_error.stack)
855
- console.error('Failed code:', failedIteration.code)
856
- break
857
-
858
- case 'generation_error':
859
- console.error('LLM generation failed:', failedIteration.status.generation_error.message)
860
- break
861
-
862
- case 'invalid_code_error':
863
- console.error('Invalid code generated:', failedIteration.status.invalid_code_error.message)
864
- console.error('Invalid code:', failedIteration.code)
865
- break
866
-
867
- case 'aborted':
868
- console.error('Execution aborted:', failedIteration.status.aborted.reason)
869
- break
870
- }
871
- }
872
-
873
- // Review all iterations to understand failure progression
874
- console.log('Iterations before failure:', result.iterations.length)
875
- result.iterations.forEach((iter, i) => {
876
- console.log(`Iteration ${i + 1}: ${iter.status.type}`)
877
- })
878
- }
879
- ```
880
-
881
- ### Snapshot Handling
882
-
883
- Handle interrupted executions with snapshot resumption:
884
-
885
- ```typescript
886
- const result = await execute({
887
- instructions: 'Process large dataset with pauseable operation',
888
- tools: [snapshotCapableTool],
889
- client,
890
- })
891
-
892
- if (result.isInterrupted()) {
893
- console.log('Execution paused:', result.signal.message)
894
-
895
- // Serialize snapshot for persistence
896
- const serialized = result.snapshot.toJSON()
897
- await database.saveSnapshot('execution-123', serialized)
898
-
899
- // Later, resume from snapshot
900
- const snapshot = Snapshot.fromJSON(serialized)
901
- snapshot.resolve({ resumeData: 'Operation completed' })
902
-
903
- const continuation = await execute({
904
- snapshot,
905
- instructions: result.context.instructions,
906
- tools: result.context.tools,
907
- exits: result.context.exits,
908
- client,
909
- })
910
-
911
- if (continuation.isSuccess()) {
912
- console.log('Resumed execution completed:', continuation.output)
913
- }
914
- }
915
- ```
916
-
917
- ---
918
-
919
- ## Hooks System
920
-
921
- LLMz provides a comprehensive hook system that allows you to inject custom logic at various points during execution. Hooks are categorized as either blocking (execution waits) or non-blocking, and either mutation (can modify data) or non-mutation.
922
-
923
- ### Hook Types Overview
924
-
925
- | Hook | Blocking | Mutation | Called When |
926
- | ------------------- | -------- | -------- | -------------------------- |
927
- | `onTrace` | ❌ | ❌ | Each trace generated |
928
- | `onIterationEnd` | ✅ | ❌ | After iteration completion |
929
- | `onExit` | ✅ | ❌ | When exit is reached |
930
- | `onBeforeExecution` | ✅ | ✅ | Before code execution |
931
- | `onBeforeTool` | ✅ | ✅ | Before tool execution |
932
- | `onAfterTool` | ✅ | ✅ | After tool execution |
933
-
934
- ### onTrace (Non-blocking, Non-mutation)
935
-
936
- Called for each trace generated during iteration. Useful for logging, debugging, or monitoring execution progress.
937
-
938
- ```typescript
939
- await execute({
940
- onTrace: ({ trace, iteration }) => {
941
- console.log(`Iteration ${iteration}: ${trace.type}`, trace)
942
-
943
- // Log specific trace types
944
- if (trace.type === 'tool_call') {
945
- console.log(`Tool ${trace.tool_name} called with:`, trace.input)
946
- }
947
- },
948
- // ... other props
949
- })
950
- ```
951
-
952
- **Available Trace Types:**
953
-
954
- - `abort_signal`: Abort signal received
955
- - `comment`: Comment found in generated code
956
- - `llm_call_success`: LLM generation completed successfully
957
- - `property`: Object property accessed or modified
958
- - `think_signal`: ThinkSignal thrown
959
- - `tool_call`: Tool executed
960
- - `yield`: Component yielded in chat mode
961
- - `log`: General logging event
962
-
963
- ### onIterationEnd (Blocking, Non-mutation)
964
-
965
- Called after each iteration ends, regardless of status. Useful for logging, cleanup, or controlling iteration timing.
966
-
967
- ```typescript
968
- await execute({
969
- onIterationEnd: async (iteration, controller) => {
970
- console.log(`Iteration ${iteration.id} ended with status: ${iteration.status.type}`)
971
-
972
- // Add delays, cleanup, or conditional logic
973
- if (iteration.status.type === 'execution_error') {
974
- await logError(iteration.error)
975
-
976
- // Add delay before retry
977
- await new Promise((resolve) => setTimeout(resolve, 1000))
978
- }
979
-
980
- // Can use controller to abort execution if needed
981
- if (shouldAbort(iteration)) {
982
- controller.abort('Custom abort reason')
983
- }
192
+ import { Chat, Component } from 'llmz'
193
+
194
+ const cards = new Component({
195
+ name: 'cards',
196
+ description: 'Show a list of products.',
197
+ props: z.array(z.object({ title: z.string(), price: z.number() })),
198
+ handler: (items, metadata) => {
199
+ console.log(metadata.id, items)
984
200
  },
985
- // ... other props
986
201
  })
202
+ const chat = new Chat({ components: [cards] })
203
+ const rendered = cards.render([{ title: 'Coffee', price: 12 }])
204
+ console.log(rendered.props)
987
205
  ```
988
206
 
989
- ### onExit (Blocking, Non-mutation)
207
+ The model calls `chat.cards(...)`. `render` parses props once and captures an immutable delivery value. `withHandler` binds delivery without changing a reusable definition. `DefaultComponents` provides ready-made rich message definitions; bind handlers before using them for delivery.
990
208
 
991
- Called when an exit is reached. Useful for logging, notifications, or implementing guardrails by throwing errors to prevent exit.
209
+ `Chat.response` accepts `markdown`, `text`, or `speech`, or `{ preset, instructions, handler, onDelta }`. Speech is a prose style for text-to-speech; it does not synthesize audio.
992
210
 
993
- ```typescript
994
- await execute({
995
- onExit: async (result) => {
996
- console.log(`Exiting with: ${result.exit.name}`, result.result)
211
+ ## Streaming and delivery
997
212
 
998
- // Implement guardrails
999
- if (result.exit.name === 'approve_loan' && result.result.amount > 10000) {
1000
- throw new Error('Manager approval required for loans over $10,000')
1001
- }
213
+ `response.onDelta` receives previews. Its normal event has `delta`, accumulated `content`, `id`, and `iterationId`. A restart event has `restart: true`, an iteration ID, attempt number, models, and reason. Retract the prior preview before displaying replacement text.
1002
214
 
1003
- // Send notifications
1004
- await notifyStakeholders(result)
215
+ `response.handler` receives the accepted complete text and its message metadata. Components use their own handlers. Without handlers, native text is still retained in session history, but does not count as delivered speech for `requireChatResponse`.
1005
216
 
1006
- // Log to audit trail
1007
- await auditLog.record({
1008
- action: result.exit.name,
1009
- data: result.result,
1010
- timestamp: Date.now(),
1011
- })
1012
- },
1013
- // ... other props
1014
- })
1015
- ```
217
+ Complete JavaScript calls can execute while streaming continues. If a stream fails after dispatch, the runtime retains completed effects and reports the interruption; it does not replay the program on another model. `options.midStreamFallback` enables restarts before dispatch and requires a preview consumer capable of handling retractions. Normal preview callback failures are ignored; failed retractions stop generation. A final delivery failure can fail execution.
1016
218
 
1017
- ### onBeforeExecution (Blocking, Mutation)
219
+ ## Token budgets and compaction
1018
220
 
1019
- Called after LLM generates code but before execution. Allows code modification and guardrails implementation.
221
+ `options.maxTokens` caps the context window, not output alone. It must be a positive safe integer. The effective limit is the smaller of this cap and every configured model's input limit. A fallback list uses the smallest input and output limits so the same request fits every candidate.
1020
222
 
1021
- ```typescript
1022
- await execute({
1023
- onBeforeExecution: async (iteration, controller) => {
1024
- console.log('Generated code:', iteration.code)
223
+ The runtime reserves output space before fitting input: 10% of the effective limit, with a 256-token floor and 16,000-token ceiling, also bounded by the model's output limit and space for input. A window too small for the request fails before model dispatch.
1025
224
 
1026
- // Code modification
1027
- if (iteration.code?.includes('dangerousOperation')) {
1028
- return {
1029
- code: iteration.code.replace('dangerousOperation', 'safeOperation'),
1030
- }
1031
- }
225
+ Input measurement includes system instructions, message scaffolding, tools, JavaScript arguments, tool receipts, memory previews, and execution-budget guidance. Enforcement uses exact tokenization with the configured local tokenizer. This remains an estimate of the provider's request accounting: model tokenizers and provider framing can differ. Image/audio transport URLs and encoded bytes are excluded from text measurement; provider-specific media token costs are not available locally. Media-shaped business arguments and ordinary strings are counted as text.
1032
226
 
1033
- // Guardrails - throw to prevent execution
1034
- if (iteration.code?.includes('forbidden')) {
1035
- throw new Error('Forbidden operation detected')
1036
- }
227
+ Automatic compaction is enabled by default. At 85% of the available input budget, it aims for 65%, preferring to keep the two most recent iterations. A hard overflow can require retaining fewer iterations. The compactor asks an LLM to summarize the discarded conversation, including decisions, external events, confirmed effects, failures, and pending work. Large histories are summarized in bounded segments; each request reserves output space and includes the previous segment's summary.
1037
228
 
1038
- // Add security checks
1039
- const securityIssues = await scanCodeForSecurity(iteration.code)
1040
- if (securityIssues.length > 0) {
1041
- throw new Error(`Security issues found: ${securityIssues.join(', ')}`)
1042
- }
229
+ A successful compaction inserts a `{ role: 'summary', content }` event at the start of the retained transcript. It replaces earlier compaction summaries, preserving their information in the new summary. Native requests receive the summary as labeled user-role data. The summarizer cannot call tools. Summaries are lossy model output; named JavaScript memory stays exact and is never reconstructed from them.
1043
230
 
1044
- // Log code generation for audit
1045
- await auditCodeGeneration(iteration.code)
231
+ ```ts
232
+ import { Client } from '@botpress/client'
233
+ import { Session } from 'llmz'
234
+
235
+ const client = new Client()
236
+ const session = new Session({
237
+ compaction: {
238
+ triggerRatio: 0.85,
239
+ targetRatio: 0.65,
240
+ keepRecentIterations: 2,
241
+ maxSummaryTokens: 1024,
242
+ model: 'fast',
1046
243
  },
1047
- // ... other props
1048
244
  })
1049
- ```
1050
-
1051
- ### onBeforeTool (Blocking, Mutation)
1052
-
1053
- Called before any tool execution. Allows input modification and tool execution control.
1054
-
1055
- ```typescript
1056
- await execute({
1057
- onBeforeTool: async ({ iteration, tool, input, controller }) => {
1058
- console.log(`Executing tool: ${tool.name}`, input)
1059
-
1060
- // Input modification
1061
- if (tool.name === 'sendEmail') {
1062
- return {
1063
- input: {
1064
- ...input,
1065
- subject: `[Automated] ${input.subject}`, // Add prefix
1066
- from: 'noreply@company.com', // Override sender
1067
- },
1068
- }
1069
- }
1070
-
1071
- // Access control
1072
- if (tool.name === 'deleteFile' && !hasPermission(input.path)) {
1073
- throw new Error('Insufficient permissions to delete file')
1074
- }
1075
245
 
1076
- // Rate limiting
1077
- const rateLimit = await checkRateLimit(tool.name)
1078
- if (rateLimit.exceeded) {
1079
- throw new Error(`Rate limit exceeded for ${tool.name}`)
1080
- }
246
+ // Read-only: returns a summary message without changing the conversation.
247
+ const summary = await session.summarize({ client })
1081
248
 
1082
- // Validation
1083
- await validateToolUsage(tool, input)
1084
- },
1085
- // ... other props
1086
- })
249
+ // Replace older history now, preserving the most recent iteration.
250
+ await session.compact({ client, keepRecentIterations: 1 })
251
+ console.log(summary, session.transcript)
1087
252
  ```
1088
253
 
1089
- ### onAfterTool (Blocking, Mutation)
1090
-
1091
- Called after tool execution. Allows output modification and post-processing.
1092
-
1093
- ```typescript
1094
- await execute({
1095
- onAfterTool: async ({ iteration, tool, input, output, controller }) => {
1096
- console.log(`Tool ${tool.name} completed`, { input, output })
1097
-
1098
- // Output modification
1099
- if (tool.name === 'fetchUserData') {
1100
- return {
1101
- output: {
1102
- ...output,
1103
- // Remove sensitive data before LLM sees it
1104
- ssn: undefined,
1105
- creditCard: undefined,
1106
- // Add metadata
1107
- fetchedAt: Date.now(),
1108
- },
1109
- }
1110
- }
1111
-
1112
- // Result enhancement
1113
- if (tool.name === 'calculatePrice') {
1114
- return {
1115
- output: {
1116
- ...output,
1117
- currency: 'USD',
1118
- timestamp: Date.now(),
1119
- exchangeRate: await getCurrentExchangeRate(),
1120
- },
1121
- }
1122
- }
254
+ Without an explicit compaction model, automatic summaries use the execution model; standalone `summarize` and `compact` use `fast`. Summary generation makes additional model calls. `maxSummaryTokens` is a ceiling; model limits and remaining request space may reduce it. `signal` cancels explicit summarization or compaction.
1123
255
 
1124
- // Logging and caching
1125
- await Promise.all([
1126
- cacheResult(tool.name, input, output),
1127
- logToolExecution(tool.name, input, output),
1128
- updateMetrics(tool.name, Date.now() - tool.startTime),
1129
- ])
1130
- },
1131
- // ... other props
1132
- })
1133
- ```
256
+ Set `compaction: false` to disable automatic summarization; requests that cannot fit then fail without dropping history. A custom `compaction.summarize({ messages, maxTokens, signal })` can return summary text; the same validation and token limit still apply. Serializable controls survive `toJSON()` / `fromJSON()`. Reattach a custom callback with `Session.fromJSON(state, { compaction: { ...controls, summarize } })`.
1134
257
 
1135
- ### Hook Execution Order
258
+ Compaction retains complete call/result groups, current-turn input, pending iterations, queued input, and named memory. Discarded iteration results are removed from `$iterations` and `$return`. Summary failure, cancellation, an oversized summary, or a failing `onBeforeRequest` hook leaves the original history intact. A prepared summary is committed only after the final request fits and its hooks succeed. Explicit `compact` and `summarize` acquire the session lock and cannot overlap execution.
1136
259
 
1137
- For each iteration:
260
+ For deliberate deletion without summarization, `session.prune(retainedIterationIds)` keeps the specified complete iterations. `requestMessages({ retainedIterationIds })` previews that selection. Use these lower-level methods only when discarding history is intentional.
1138
261
 
1139
- 1. **onTrace**: Throughout execution (non-blocking)
1140
- 2. **onBeforeExecution**: After code generation, before execution
1141
- 3. **onBeforeTool**: Before each tool call
1142
- 4. **onAfterTool**: After each tool call
1143
- 5. **onExit**: When exit is reached
1144
- 6. **onIterationEnd**: After iteration completes
262
+ If required instructions, current input, tool definitions, or retained memory previews still exceed the budget, shorten them or use a larger context limit. LLMZ does not silently truncate current user input.
1145
263
 
1146
- ### Advanced Hook Patterns
264
+ `result.tokens` reports provider usage summed over this run. Each iteration's `tokens.context` describes the measured request after compaction and hook replacement; its categories sum to `context.total`. These estimates and provider usage have different purposes.
1147
265
 
1148
- #### Conditional Hook Logic
266
+ ## Inspection
1149
267
 
1150
- ```typescript
1151
- await execute({
1152
- onBeforeTool: async ({ tool, input }) => {
1153
- // Apply different logic based on tool
1154
- switch (tool.name) {
1155
- case 'payment':
1156
- return await handlePaymentValidation(input)
1157
- case 'notification':
1158
- return await handleNotificationThrottling(input)
1159
- default:
1160
- return // No modification
1161
- }
1162
- },
1163
- })
1164
- ```
268
+ Use `truncate` in a tool handler to set a value's display budget while retaining its full data:
1165
269
 
1166
- #### Error Recovery in Hooks
1167
-
1168
- ```typescript
1169
- await execute({
1170
- onExit: async (result) => {
1171
- try {
1172
- await criticalPostProcessing(result)
1173
- } catch (error) {
1174
- // Log error but don't fail the entire execution
1175
- console.error('Post-processing failed:', error)
1176
-
1177
- // Optionally throw to retry the iteration
1178
- if (error.retryable) {
1179
- throw new Error('Retrying due to recoverable error')
1180
- }
1181
- }
1182
- },
1183
- })
1184
- ```
1185
-
1186
- #### Hook State Management
1187
-
1188
- ```typescript
1189
- let executionMetrics = { toolCalls: 0, totalTime: 0 }
1190
-
1191
- await execute({
1192
- onBeforeTool: async ({ tool }) => {
1193
- executionMetrics.toolCalls++
1194
- tool.startTime = Date.now()
1195
- },
1196
-
1197
- onAfterTool: async ({ tool }) => {
1198
- executionMetrics.totalTime += Date.now() - tool.startTime
1199
- },
1200
-
1201
- onIterationEnd: async () => {
1202
- console.log('Execution metrics:', executionMetrics)
1203
- },
270
+ ```ts
271
+ import { z } from '@bpinternal/zui'
272
+ import { Tool, truncate } from 'llmz'
273
+
274
+ const getEvidence = new Tool({
275
+ name: 'getEvidence',
276
+ output: z.string(),
277
+ handler: async () =>
278
+ truncate({
279
+ value: 'First section\nDetailed evidence\nLast section',
280
+ maxTokens: 800,
281
+ preserve: 'both',
282
+ }),
1204
283
  })
1205
284
  ```
1206
285
 
1207
- ### Best Practices
1208
-
1209
- 1. **Error Handling**: Always wrap hook logic in try-catch for production
1210
- 2. **Performance**: Keep hooks lightweight, especially `onTrace`
1211
- 3. **Security**: Use `onBeforeExecution` and `onBeforeTool` for security validation
1212
- 4. **Debugging**: Leverage `onTrace` for comprehensive execution monitoring
1213
- 5. **Guardrails**: Implement business logic validation in `onExit`
1214
- 6. **Data Transformation**: Use `onBeforeTool`/`onAfterTool` for input/output processing
1215
- 7. **Async Operations**: All hooks support async/await for external API calls
1216
- 8. **State Management**: Use closures or external state for cross-hook data sharing
1217
-
1218
- ---
1219
-
1220
- ## Advanced Features
1221
-
1222
- ### Snapshots (Pauseable Execution)
1223
-
1224
- Snapshots allow you to pause and resume LLMz execution, enabling long-running workflows that can be interrupted and continued later.
286
+ `preserve` is `top`, `bottom`, or `both`. Budgets are nonnegative safe integers; zero hides the preview. `options.toolResultMaxTokens` defaults to 2,000 and accepts 0–2,000. An explicit `truncate` policy can raise or lower the inspected result budget. It cannot bypass the overall request limit. Tool-result preview budgets do not cap the entire execution report, which also contains status and activity.
1225
287
 
1226
- #### SnapshotSignal
288
+ Inventories and diagnostic previews keep their own small budgets even if a value carries a larger inspection override. Truncation markers are included in the final measured output. Unicode boundaries, cyclic structures, throwing formatters, and accessor properties are handled without changing retained data.
1227
289
 
1228
- Inside a tool, throw a `SnapshotSignal` to halt execution and create a serializable snapshot:
290
+ Both `return inspect(value)` and forced ThinkSignal inspection use the native inspector. Standalone text is displayed directly, preserving whitespace, code, and literal tags within its preview budget. Report sections add no XML entity escaping or CDATA. Strings nested in objects or arrays use JSON quoting so source indentation, line endings, and literal escapes remain distinct from the preview's formatting.
1229
291
 
1230
- ```typescript
1231
- import { SnapshotSignal, Tool } from 'llmz'
292
+ `onInspect` customizes previews by purpose and identity. Return `undefined` for the default formatter. The hook receives an isolated read-only snapshot; custom text is still bounded. A formatter failure falls back to default inspection.
1232
293
 
1233
- const longRunningTool = new Tool({
1234
- name: 'processLargeDataset',
1235
- input: z.object({ datasetId: z.string() }),
1236
- async handler({ datasetId }) {
1237
- // Start processing
1238
- const dataset = await loadDataset(datasetId)
294
+ ```ts
295
+ import { createInspector, inspect, type ExecutionHooks } from 'llmz'
1239
296
 
1240
- // At any point, pause execution for later resumption
1241
- if (dataset.size > LARGE_THRESHOLD) {
1242
- throw new SnapshotSignal(
1243
- 'Dataset is large, pausing for background processing',
1244
- 'Processing will continue once background job completes'
1245
- )
297
+ const hooks: ExecutionHooks = {
298
+ onInspect: (event) => {
299
+ if (event.purpose === 'variable' && event.identity?.variable === 'apiKey') {
300
+ return '[redacted]'
1246
301
  }
1247
-
1248
- return { processed: true }
302
+ return undefined
1249
303
  },
1250
- })
1251
- ```
1252
-
1253
- #### Snapshot Handling
1254
-
1255
- ```typescript
1256
- const result = await execute({
1257
- instructions: 'Process the uploaded dataset',
1258
- tools: [longRunningTool],
1259
- client,
1260
- })
1261
-
1262
- if (result.isInterrupted()) {
1263
- console.log('Execution paused:', result.signal.message)
1264
-
1265
- // Serialize snapshot for persistence
1266
- const serialized = result.snapshot.toJSON()
1267
- await database.saveSnapshot('job-123', serialized)
1268
-
1269
- // Start background processing
1270
- await backgroundJobQueue.add('process-dataset', {
1271
- snapshotId: 'job-123',
1272
- datasetId: result.signal.toolCall?.input.datasetId,
1273
- })
1274
304
  }
305
+ const inspector = createInspector(hooks.onInspect)
306
+ console.log(inspector({ count: 3 }, { purpose: 'result', maxTokens: 40 }))
307
+ console.log(inspect({ count: 3 }, undefined, { tokens: 40, compact: true }))
1275
308
  ```
1276
309
 
1277
- #### Resuming from Snapshot
1278
-
1279
- ```typescript
1280
- // Later, when background job completes
1281
- const serialized = await database.getSnapshot('job-123')
1282
- const snapshot = Snapshot.fromJSON(serialized)
310
+ A redacted preview is not a security boundary: the underlying value still exists in memory and can be used by JavaScript or tools. Keep secrets out of model-accessible state.
1283
311
 
1284
- // Resolve the snapshot with the result
1285
- snapshot.resolve({
1286
- processed: true,
1287
- recordCount: 1000000,
1288
- processingTime: 3600000,
1289
- })
312
+ ## Hooks
1290
313
 
1291
- // Continue execution from where it left off
1292
- const continuation = await execute({
1293
- snapshot,
1294
- instructions: 'Process the uploaded dataset', // Same as original
1295
- tools: [longRunningTool], // Same tools
1296
- client,
1297
- })
1298
-
1299
- if (continuation.isSuccess()) {
1300
- console.log('Processing completed:', continuation.output)
1301
- }
1302
- ```
314
+ | Hook | Purpose |
315
+ | ------------------------------ | --------------------------------------------------------------------------------------------------- |
316
+ | `onIterationStart` | Observe the iteration before generation. Use getters for dynamic configuration. |
317
+ | `onBeforeRequest` | Return replacement native messages after tentative compaction. The replacement must fit the budget. |
318
+ | `onBeforeExecution` | Optionally return replacement JavaScript code. |
319
+ | `onBeforeTool` / `onAfterTool` | Inspect or replace a business tool's input/output. |
320
+ | `onInspect` | Format bounded previews without changing retained values. |
321
+ | `onTrace` | Observe execution traces. Observer exceptions do not alter completed actions. |
322
+ | `onIterationEnd` | Observe a settled iteration. |
323
+ | `onExit` | Validate or handle a proposed completion. |
1303
324
 
1304
- #### Snapshot Rejection
325
+ ```ts
326
+ import type { ExecutionHooks } from 'llmz'
1305
327
 
1306
- ```typescript
1307
- // If background processing fails
1308
- const snapshot = Snapshot.fromJSON(serialized)
1309
- snapshot.reject(new Error('Background processing failed'))
1310
-
1311
- const continuation = await execute({
1312
- snapshot,
1313
- // ... same parameters
1314
- })
1315
-
1316
- // The agent will receive the error and can handle it
1317
- ```
1318
-
1319
- ### Thinking (Agent Reflection)
1320
-
1321
- The thinking system allows agents to pause and reflect on variables and context before proceeding.
1322
-
1323
- #### ThinkSignal (Tool-Initiated)
1324
-
1325
- Tools can force thinking by throwing a `ThinkSignal`:
1326
-
1327
- ```typescript
1328
- const analysisTool = new Tool({
1329
- name: 'analyzeData',
1330
- input: z.object({ data: z.array(z.number()) }),
1331
- async handler({ data }) {
1332
- const result = performAnalysis(data)
1333
-
1334
- // Force the agent to think about the results before responding
1335
- throw new ThinkSignal(
1336
- 'Analysis complete, consider the implications',
1337
- `Found ${result.anomalies.length} anomalies and ${result.patterns.length} patterns`
1338
- )
1339
- },
1340
- })
1341
- ```
1342
-
1343
- #### Agent-Initiated Thinking
1344
-
1345
- Agents can request thinking time in generated code:
1346
-
1347
- ```typescript
1348
- // In generated code
1349
- const analysisResult = await analyzeData({ data: userInputData })
1350
-
1351
- // Think about the results before responding to user
1352
- return { action: 'think' }
1353
- ```
1354
-
1355
- #### Thinking with Variables
1356
-
1357
- Pass specific variables for reflection:
1358
-
1359
- ```typescript
1360
- // In generated code
1361
- const price = await calculatePrice({ items: cartItems })
1362
- const budget = await getUserBudget()
1363
-
1364
- // Think about pricing vs budget with specific context
1365
- return {
1366
- action: 'think',
1367
- price,
1368
- budget,
1369
- recommendation: price > budget ? 'deny' : 'approve',
1370
- }
1371
- ```
1372
-
1373
- #### Handling Think Results
1374
-
1375
- ```typescript
1376
- const result = await execute({
1377
- instructions: 'Analyze the user data and provide recommendations',
1378
- tools: [analysisTool],
1379
- client,
1380
- })
1381
-
1382
- if (result.is(ThinkExit)) {
1383
- console.log('Agent is thinking about:', result.output.variables)
1384
-
1385
- // Continue execution after thinking
1386
- const continuation = await execute({
1387
- instructions: result.context.instructions,
1388
- tools: result.context.tools,
1389
- // Variables from thinking are automatically preserved
1390
- client,
1391
- })
1392
- }
1393
- ```
1394
-
1395
- ### Citations (RAG Support)
1396
-
1397
- CitationsManager provides standardized source tracking and referencing for RAG (Retrieval-Augmented Generation) systems.
1398
-
1399
- #### Core Concepts
1400
-
1401
- Citations use rare Unicode symbols (`【】`) as markers that are unlikely to appear in natural text. The system supports:
1402
-
1403
- - **Source Registration**: Register any object as a citation source
1404
- - **Tag Generation**: Automatic creation of unique citation tags like `【0】`, `【1】`
1405
- - **Content Processing**: Extract and clean citation tags from text
1406
- - **Multiple Citations**: Support for multi-source citations like `【0,1,3】`
1407
-
1408
- #### Basic Usage
1409
-
1410
- ```typescript
1411
- import { CitationsManager } from 'llmz'
1412
-
1413
- const citations = new CitationsManager()
1414
-
1415
- // Register sources and get citation tags
1416
- const source1 = citations.registerSource({
1417
- file: 'document.pdf',
1418
- page: 5,
1419
- title: 'Company Policy',
1420
- })
1421
-
1422
- const source2 = citations.registerSource({
1423
- url: 'https://example.com/article',
1424
- title: 'Best Practices',
1425
- })
1426
-
1427
- console.log(source1.tag) // "【0】"
1428
- console.log(source2.tag) // "【1】"
1429
-
1430
- // Use tags in content
1431
- const content = `The policy states employees must arrive on time${source1.tag}. However, best practices suggest flexibility${source2.tag}.`
1432
- ```
1433
-
1434
- #### RAG Implementation Example
1435
-
1436
- ```typescript
1437
- const ragTool = new Tool({
1438
- name: 'search',
1439
- description: 'Searches in the knowledge base for relevant information.',
1440
- input: z.string().describe('The query to search in the knowledge base.'),
1441
- async handler(query) {
1442
- // Perform semantic search
1443
- const { passages } = await client.searchFiles({
1444
- query,
1445
- limit: 20,
1446
- contextDepth: 3,
1447
- })
1448
-
1449
- if (!passages.length) {
1450
- throw new ThinkSignal(
1451
- 'No results found',
1452
- 'No results were found in the knowledge base. Try rephrasing your question.'
1453
- )
1454
- }
1455
-
1456
- // Build response with citations
1457
- let message: string[] = ['Here are the search results:']
1458
- let { tag: example } = chat.citations.registerSource({}) // Example citation
1459
-
1460
- // Register each passage as a source
1461
- for (const passage of passages) {
1462
- const { tag } = chat.citations.registerSource({
1463
- file: passage.file.key,
1464
- title: passage.file.tags.title,
1465
- })
1466
-
1467
- message.push(`<${tag} file="${passage.file.key}">`)
1468
- message.push(`**${passage.file.tags.title}**`)
1469
- message.push(passage.content)
1470
- message.push(`</${tag}>`)
1471
- }
1472
-
1473
- // Provide context with citation instructions
1474
- throw new ThinkSignal(
1475
- `Got search results. When answering, you MUST add inline citations (eg: "The price is $10${example} ...")`,
1476
- message.join('\n').trim()
1477
- )
1478
- },
1479
- })
1480
- ```
1481
-
1482
- #### Chat Integration
1483
-
1484
- ```typescript
1485
- class CLIChat extends Chat {
1486
- public citations: CitationsManager = new CitationsManager()
1487
-
1488
- private async sendMessage(input: RenderedComponent) {
1489
- if (input.type === 'Text') {
1490
- let sources: string[] = []
1491
-
1492
- // Extract citations and format them for display
1493
- const { cleaned } = this.citations.extractCitations(input.text, (citation) => {
1494
- let idx = chalk.bgGreenBright.black.bold(` ${sources.length + 1} `)
1495
- sources.push(`${idx}: ${JSON.stringify(citation.source)}`)
1496
- return `${idx}` // Replace 【0】 with [1]
1497
- })
1498
-
1499
- // Display cleaned text and sources
1500
- console.log(`🤖 Agent: ${cleaned}`)
1501
-
1502
- if (sources.length) {
1503
- console.log(chalk.dim('Citations'))
1504
- console.log(chalk.dim('========='))
1505
- console.log(chalk.dim(sources.join('\n')))
1506
- }
1507
- }
1508
- }
1509
- }
1510
- ```
1511
-
1512
- #### Advanced Citation Features
1513
-
1514
- **Multiple Citation Support:**
1515
-
1516
- ```typescript
1517
- // Agent can reference multiple sources in one citation
1518
- const content = 'This fact is supported by multiple studies【0,1,3】'
1519
-
1520
- const { cleaned, citations } = manager.extractCitations(content)
1521
- // citations array contains entries for sources 0, 1, and 3
1522
- ```
1523
-
1524
- **Object Citation Processing:**
1525
-
1526
- ```typescript
1527
- // Remove citations from complex objects
1528
- const dataWithCitations = {
1529
- summary: 'The report shows positive trends【0】',
1530
- details: {
1531
- revenue: 'Increased by 15%【1】',
1532
- costs: 'Reduced by 8%【2】',
328
+ const hooks: ExecutionHooks = {
329
+ onBeforeRequest: ({ messages }) => ({
330
+ messages: [{ role: 'system', content: 'Use concise language.' }, ...messages],
331
+ }),
332
+ onTrace: ({ trace }) => {
333
+ console.debug(trace.type)
1533
334
  },
1534
335
  }
1535
-
1536
- const [cleanData, extractedCitations] = manager.removeCitationsFromObject(dataWithCitations)
1537
- // cleanData has citations removed, extractedCitations contains path + citation info
1538
- ```
1539
-
1540
- **Citation Stripping:**
1541
-
1542
- ```typescript
1543
- // Remove all citation tags from content
1544
- const textWithCitations = 'This statement【0】 has multiple【1,2】 citations.'
1545
- const cleaned = CitationsManager.stripCitationTags(textWithCitations)
1546
- // Result: "This statement has multiple citations."
1547
- ```
1548
-
1549
- ### Dynamic Context
1550
-
1551
- LLMz supports dynamic evaluation of most parameters, allowing context-aware configuration:
1552
-
1553
- ```typescript
1554
- await execute({
1555
- // Dynamic instructions based on context
1556
- instructions: (ctx) => {
1557
- const timeOfDay = new Date().getHours()
1558
- const greeting = timeOfDay < 12 ? 'Good morning' : 'Good afternoon'
1559
- return `${greeting}! You are a helpful assistant with access to ${ctx.tools?.length || 0} tools.`
1560
- },
1561
-
1562
- // Dynamic tools based on user permissions
1563
- tools: async (ctx) => {
1564
- const userPermissions = await getUserPermissions(ctx.userId)
1565
- return allTools.filter((tool) => userPermissions.includes(tool.permission))
1566
- },
1567
-
1568
- // Dynamic objects with current state
1569
- objects: async (ctx) => {
1570
- const userPreferences = await loadUserPreferences(ctx.userId)
1571
- return [
1572
- new ObjectInstance({
1573
- name: 'user',
1574
- properties: [{ name: 'preferences', value: userPreferences, writable: true }],
1575
- }),
1576
- ]
1577
- },
1578
-
1579
- client,
1580
- })
1581
- ```
1582
-
1583
- ---
1584
-
1585
- ### Mid-Stream Model Fallback (midStreamFallback)
1586
-
1587
- By default, LLMz streams responses: message bodies (`■send` blocks) and completed `Chat.handler` sends are dispatched as soon as they are produced, and the ■run block starts executing while the model is still generating the rest of the response. This gives the lowest time-to-first-token but runs code before the response is complete.
1588
-
1589
- Set `options.midStreamFallback: true` to allow Cognitive to restart a failed stream on another model. There is **no buffered delivery and no final delivery queue**: with fallback enabled, delta text and completed `Chat.handler` sends both stream immediately during generation. Only **TOOL/CODE execution** waits for a stream to complete successfully. When Cognitive abandons an attempt, a reset-only delta (`restart: true`) is delivered through the existing `Chat.onMessageDelta`; it invalidates **all** messages of the current iteration — **including messages already committed through `handler`** — so consumers must retract and replace them:
1590
-
1591
- ```typescript
1592
- await execute({
1593
- // ...
1594
- chat: new MyChat({
1595
- onMessageDelta: (delta) => {
1596
- if (delta.restart) {
1597
- // invalidates ALL current-iteration messages, including completed handler
1598
- // sends: retract/replace them before the replacement streams
1599
- return retractIteration(delta.iterationId)
1600
- } else {
1601
- // stream this message's text as it arrives (same iterationId as below)
1602
- updateMessage(delta)
1603
- }
1604
- },
1605
- }),
1606
- options: { midStreamFallback: true },
1607
- })
1608
- ```
1609
-
1610
- `retractIteration`/`updateMessage` are placeholder names for your consumer's message store. **This option is not safe to enable blindly**: a reset invalidates messages that were already delivered through `handler`. If your transport cannot retract or replace an already-sent message — an external irreversible send such as SMS, email, or a third-party webhook — those sends cannot be undone, so keep `midStreamFallback` disabled unless your consumer can retract and replace invalidated messages.
1611
-
1612
- #### MessageDelta contract
1613
-
1614
- `Chat.onMessageDelta` receives a single typed `MessageDelta` discriminated union:
1615
-
1616
- - **Ordinary text chunk** (`restart: false`) — the next piece of one message's body; all fields are required:
1617
- ```typescript
1618
- {
1619
- restart: false,
1620
- iterationId: string, // stable across ALL attempts of this LLM generation iteration
1621
- id: string, // message id — DIFFERENT per attempt (unique when fallback is on)
1622
- component: string,
1623
- props: Record<string, unknown>, // final by the time the body starts streaming
1624
- delta: string, // new chunk of body text
1625
- content: string, // full body text accumulated so far, including this chunk
1626
- }
1627
- ```
1628
- - **Reset-only delta** (`restart: true`) — carries no text; signals the current generation attempt was abandoned and a replacement is being attempted:
1629
- ```typescript
1630
- {
1631
- restart: true,
1632
- iterationId: string, // the iteration whose messages (incl. handler sends) must be retracted/replaced
1633
- attempt: number,
1634
- fromModel: string,
1635
- toModel: string,
1636
- reason: string,
1637
- }
1638
- ```
1639
-
1640
- IDs: `iterationId` is **stable across attempts** — every restart of the same iteration keeps the same `iterationId` — while `id` is **different per attempt**. After a reset, replacement chunks arrive under new `id`s within the same `iterationId`.
1641
-
1642
- Consumer rules:
1643
-
1644
- - On `restart: true`, retract/replace **all** current-iteration messages bearing that `iterationId` — streamed text **and** messages already delivered through `handler`.
1645
- - On an ordinary chunk, update the message for `delta.id` (create it if new, otherwise append).
1646
- - A reset is emitted and awaited **before** replacement output begins, **even if the replacement yields no message at all**. Return/await the retraction promise in your handler. If it throws or rejects, generation fails without delivering replacement output or executing code; ordinary text-preview errors remain best-effort.
1647
- - A reset only invalidates the messages of its own `iterationId`; it **never** invalidates messages from earlier, completed iterations.
1648
- - Only TOOL/CODE execution is gated on a successful stream: generated code and tool calls never run from an abandoned attempt.
1649
-
1650
- Outside of fallback (the default), `onMessageDelta` behavior is unchanged: ordinary chunks carry `iterationId`/`id`/`component`/`props`/`delta`/`content` with a single stable message id, and no restart/reset deltas are produced.
1651
-
1652
- #### Completed-send correlation
1653
-
1654
- `Chat.handler(component, metadata)` receives `{ iterationId: string, id: string }` for **every send**, including empty-body and non-text components, without requiring `onMessageDelta`. For streamed messages, `metadata.id` matches the text delta's `id`; it changes across attempts while `iterationId` stays stable. Existing one-argument handlers continue to work.
1655
-
1656
- Record **all** persisted message IDs created while awaiting that handler under its metadata (one component may produce multiple persisted messages). A restart can then retract everything for the matching iteration, even after a message's stream tracking has completed. Non-streaming sends also receive metadata. Tool-yielded messages get unique per-yield IDs and the runtime iteration ID; standalone `Tool.execute` calls without an iteration use their `callId` as the scope.
1657
-
1658
- #### Migrating consumers
1659
-
1660
- - **No final delivery queue**: messages are not withheld until the stream ends. Deltas and completed `handler` sends arrive during generation; drop any "promote on success" design — treat every delivered message as provisional until the iteration completes without a reset.
1661
- - **Retract, don't just clear previews**: on `restart: true`, retraction must cascade to every current-iteration message, including ones already committed through `handler`. If your UI or transport cannot do that, keep `midStreamFallback` disabled.
1662
- - **Clear on errors and cancellation**: a failed or cancelled generation can leave current-iteration messages on screen, incomplete, or unresettable. Explicitly clear/roll back current-iteration state when execution errors or ends — there is no exactly-once guarantee, and caller retries can duplicate effects.
1663
- - **Irreversible transports**: external sends that cannot be undone (SMS, email, webhooks) must not blindly enable fallback without a reset-capable gateway.
1664
-
1665
- #### Caveats
1666
-
1667
- - **No extra retry loop**: Cognitive owns fallback, bounded by the model chain. LLMz does not retry abandoned attempts itself.
1668
- - **Cancellation**: cancellation still aborts the original generation request as usual; consumers must clear current-iteration state on abort (see above). Cancellation deadlines are not reset, and the existing stream inactivity guard remains active during handoffs.
1669
-
1670
- This option defaults to false and applies only to streaming clients. Non-streaming Cognitive requests already fall back transparently. It can be combined with `maxTimeToFirstToken` and `transcriptionModel`; do not enable it globally underneath LLMz without also enabling LLMz's fallback option and a reset-capable consumer.
1671
-
1672
- Restarts emit `llm_call_restarted` traces with the attempt, source/destination models, and reason. Token timings remain relative to the original request and describe the surviving attempt (including time spent on prior attempts). Usage and cost retain Cognitive's final reported metadata; LLMz does not infer or sum abandoned-attempt billing.
1673
-
1674
- ## API Reference
1675
-
1676
- ### Core Functions
1677
-
1678
- #### execute(props: ExecutionProps): Promise<ExecutionResult>
1679
-
1680
- Main execution function that runs LLMz agents in either Chat Mode or Worker Mode.
1681
-
1682
- **Parameters:**
1683
-
1684
- - `props.client` - Botpress Client or Cognitive Client instance for LLM generation
1685
- - `props.instructions` - System prompt/instructions for the LLM (static string or dynamic function)
1686
- - `props.chat` - Optional Chat instance to enable Chat Mode with user interaction
1687
- - `props.tools` - Array of Tool instances available to the agent (static or dynamic)
1688
- - `props.objects` - Array of ObjectInstance for namespaced tools and variables (static or dynamic)
1689
- - `props.exits` - Array of Exit definitions for structured completion (static or dynamic)
1690
- - `props.snapshot` - Optional Snapshot to resume paused execution
1691
- - `props.signal` - Optional AbortSignal to cancel execution
1692
- - `props.options` - Optional execution options (loop limit, temperature, model, timeout)
1693
- - `props.onTrace` - Optional non-blocking hook for monitoring traces during execution
1694
- - `props.onIterationEnd` - Optional blocking hook called after each iteration
1695
- - `props.onExit` - Optional blocking hook called when an exit is reached
1696
- - `props.onBeforeExecution` - Optional blocking hook to modify code before VM execution
1697
- - `props.onBeforeTool` - Optional blocking hook to modify tool inputs before execution
1698
- - `props.onAfterTool` - Optional blocking hook to modify tool outputs after execution
1699
-
1700
- **Returns:** `Promise<ExecutionResult>` - Result containing success/error/interrupted status with type-safe exit checking
1701
-
1702
- ### Tool Class
1703
-
1704
- #### new Tool(config: ToolConfig)
1705
-
1706
- Creates a new tool definition with type-safe schemas.
1707
-
1708
- **Properties:**
1709
-
1710
- - `name: string` - Tool name used in generated code
1711
- - `description?: string` - Description for LLM understanding
1712
- - `input?: ZuiSchema` - Input validation schema
1713
- - `output?: ZuiSchema` - Output validation schema
1714
- - `handler: (input: any) => Promise<any> | any` - Tool implementation
1715
- - `aliases?: string[]` - Alternative names for the tool
1716
- - `staticInputs?: Record<string, any>` - Force specific input values
1717
-
1718
- **Methods:**
1719
-
1720
- - `execute(input: any, context?: ToolContext): Promise<any>` - Execute the tool
1721
- - `getTypings(): string` - Get TypeScript definitions for LLM
1722
- - `clone(overrides: Partial<ToolConfig>): Tool` - Create a modified copy
1723
-
1724
- ### Exit Class
1725
-
1726
- #### new Exit(config: ExitConfig)
1727
-
1728
- Defines a structured exit point for agent execution.
1729
-
1730
- **Properties:**
1731
-
1732
- - `name: string` - Exit name used in generated code
1733
- - `description?: string` - Description for LLM understanding
1734
- - `schema?: ZuiSchema` - Output validation schema
1735
- - `aliases?: string[]` - Alternative names for the exit
1736
-
1737
- ### ObjectInstance Class
1738
-
1739
- #### new ObjectInstance(config: ObjectConfig)
1740
-
1741
- Creates a namespaced container for tools and variables.
1742
-
1743
- **Properties:**
1744
-
1745
- - `name: string` - Object name used in generated code
1746
- - `properties?: PropertyConfig[]` - Object properties/variables
1747
- - `tools?: Tool[]` - Tools scoped to this object
1748
-
1749
- **PropertyConfig:**
1750
-
1751
- - `name: string` - Property name
1752
- - `value: any` - Initial value
1753
- - `writable: boolean` - Whether property can be modified
1754
- - `type?: ZuiSchema` - Validation schema
1755
-
1756
- ### ExecutionResult Types
1757
-
1758
- #### SuccessExecutionResult
1759
-
1760
- **Properties:**
1761
-
1762
- - `isSuccess(): boolean` - Type guard for success
1763
- - `output: any` - The result data from the exit
1764
- - `exit: Exit` - The exit that was used
1765
- - `iteration: Iteration` - Final iteration details
1766
- - `iterations: Iteration[]` - All iterations
1767
- - `context: Context` - Execution context
1768
- - `is(exit: Exit): boolean` - Type-safe exit checking
1769
-
1770
- #### ErrorExecutionResult
1771
-
1772
- **Properties:**
1773
-
1774
- - `isError(): boolean` - Type guard for error
1775
- - `error: Error | string` - The error that occurred
1776
- - `iteration?: Iteration` - Failed iteration details
1777
- - `iterations: Iteration[]` - All iterations before failure
1778
- - `context: Context` - Execution context
1779
-
1780
- #### PartialExecutionResult
1781
-
1782
- **Properties:**
1783
-
1784
- - `isInterrupted(): boolean` - Type guard for interruption
1785
- - `signal: SnapshotSignal` - The signal that caused interruption
1786
- - `snapshot: Snapshot` - Serializable execution state
1787
- - `iterations: Iteration[]` - All iterations before interruption
1788
- - `context: Context` - Execution context
1789
-
1790
- ### Chat Class
1791
-
1792
- Abstract base class for implementing chat interfaces.
1793
-
1794
- **Abstract Methods:**
1795
-
1796
- - `getTranscript(): Promise<Transcript.Message[]> | Transcript.Message[]` - Get conversation history
1797
- - `getComponents(): Promise<ComponentDefinition[]> | ComponentDefinition[]` - Get available UI components
1798
- - `handler(component: RenderedComponent): Promise<void>` - Handle agent messages
1799
-
1800
- ### CitationsManager Class
1801
-
1802
- Manages source citations for RAG systems.
1803
-
1804
- **Methods:**
1805
-
1806
- - `registerSource(source: any): { tag: string, id: number }` - Register a source and get citation tag
1807
- - `extractCitations(text: string, replacer?: (citation) => string): { cleaned: string, citations: Citation[] }` - Extract and process citations
1808
- - `removeCitationsFromObject(obj: any): [cleanedObj: any, citations: Citation[]]` - Remove citations from objects
1809
- - `static stripCitationTags(text: string): string` - Remove all citation tags
1810
-
1811
- ### Snapshot Class
1812
-
1813
- Manages pauseable execution state.
1814
-
1815
- **Methods:**
1816
-
1817
- - `toJSON(): string` - Serialize snapshot
1818
- - `static fromJSON(json: string): Snapshot` - Deserialize snapshot
1819
- - `resolve(data: any): void` - Resume with success
1820
- - `reject(error: Error): void` - Resume with error
1821
-
1822
- ### Built-in Exits
1823
-
1824
- - `ListenExit` - Automatically available in Chat Mode for user interaction
1825
- - `DefaultExit` - Default exit for Worker Mode with success/failure discrimination
1826
- - `ThinkExit` - Used when agent requests thinking time
1827
-
1828
- ### Signals
1829
-
1830
- - `SnapshotSignal` - Thrown to pause execution for later resumption
1831
- - `ThinkSignal` - Thrown to request agent reflection time
1832
- - `LoopExceededError` - Thrown when maximum iterations reached
1833
-
1834
- ### Environment Variables
1835
-
1836
- - `VM_DRIVER: 'isolated-vm' | 'node'` - Choose VM execution environment
1837
- - `CI: boolean` - Automatically detected, affects VM driver selection
1838
-
1839
- ---
1840
-
1841
- ## Examples
1842
-
1843
- The LLMz repository includes 20 comprehensive examples demonstrating different patterns and capabilities:
1844
-
1845
- ### Chat Examples (Interactive Patterns)
1846
-
1847
- 1. **01_chat_basic** - Basic conversational agent setup
1848
- 2. **02_chat_exits** - Custom exits for structured conversations
1849
- 3. **03_chat_conditional_tool** - Conditional tool usage based on context
1850
- 4. **04_chat_small_models** - Optimizations for smaller language models
1851
- 5. **05_chat_web_search** - Integration with web search capabilities
1852
- 6. **06_chat_confirm_tool** - User confirmation patterns for sensitive operations
1853
- 7. **07_chat_guardrails** - Safety mechanisms and content filtering
1854
- 8. **08_chat_multi_agent** - Multi-agent orchestration and delegation
1855
- 9. **09_chat_variables** - Object variables and state management
1856
- 10. **10_chat_components** - Rich UI components and interactive elements
1857
-
1858
- ### Worker Examples (Automated Patterns)
1859
-
1860
- 11. **11_worker_minimal** - Simplest worker mode execution
1861
- 12. **12_worker_fs** - File system operations and data processing
1862
- 13. **13_worker_sandbox** - Security isolation and sandboxing
1863
- 14. **14_worker_snapshot** - Pauseable execution and resumption
1864
- 15. **15_worker_stacktraces** - Error handling and debugging
1865
- 16. **16_worker_tool_chaining** - Complex multi-tool workflows
1866
- 17. **17_worker_error_recovery** - Graceful error recovery patterns
1867
- 18. **18_worker_security** - Security best practices and validation
1868
- 19. **19_worker_wrap_tool** - Tool modification and enhancement
1869
- 20. **20_chat_rag** - Retrieval-Augmented Generation with citations
1870
-
1871
- ### Running Examples
1872
-
1873
- ```bash
1874
- # Install dependencies
1875
- pnpm install
1876
-
1877
- # Set up environment variables
1878
- cp .env.example .env
1879
- # Edit .env with your Botpress credentials
1880
-
1881
- # Run a specific example
1882
- pnpm start 01_chat_basic
1883
- pnpm start chat_basic
1884
- pnpm start 01
1885
-
1886
- # List all available examples
1887
- pnpm start
1888
336
  ```
1889
337
 
1890
- ### Example Environment Setup
1891
-
1892
- Create a `.env` file in the examples directory:
1893
-
1894
- ```env
1895
- BOTPRESS_BOT_ID=your_bot_id_here
1896
- BOTPRESS_TOKEN=your_token_here
1897
- ```
1898
-
1899
- ### Key Learning Paths
1900
-
1901
- **Getting Started:**
1902
-
1903
- - Start with `01_chat_basic` and `11_worker_minimal`
1904
- - Understand the difference between Chat and Worker modes
1905
- - Learn basic tool integration patterns
338
+ Request messages are copies. Hooks should preserve native call/result pairing and opaque provider fields. `onBeforeRequest` changes the model request, not canonical session history. Blocking hook failures can fail the iteration; observation hooks have the behavior documented by their types.
1906
339
 
1907
- **Intermediate Concepts:**
340
+ ## Runtime configuration
1908
341
 
1909
- - Explore `09_chat_variables` for state management
1910
- - Study `16_worker_tool_chaining` for complex workflows
1911
- - Review `14_worker_snapshot` for pauseable execution
342
+ `options.loop` bounds model iterations; `options.timeout` bounds JavaScript execution. Supply `signal` to cancel a run. Cancellation preserves completed effects and any values captured before interruption.
1912
343
 
1913
- **Advanced Patterns:**
344
+ `model` can be a model ID, an ordered fallback list, or a dynamic getter. `options.maxTimeToFirstToken` and `midStreamFallback` apply to streaming clients. `options.transcriptionModel` selects transcription for audio on models without native audio support.
1914
345
 
1915
- - Examine `08_chat_multi_agent` for orchestration
1916
- - Learn from `20_chat_rag` for knowledge integration
1917
- - Study `18_worker_security` for production deployment
346
+ On platforms that prohibit runtime WASM compilation, configure a compatible QuickJS variant with `configureQuickJS` and a precompiled tokenizer with `configureTokenizer` before execution. [Examples](./examples) show host integration patterns.
1918
347
 
1919
- Each example includes detailed comments explaining the concepts and implementation patterns, making them excellent learning resources for understanding LLMz capabilities.
348
+ ## Migrating from the previous API
1920
349
 
1921
- ---
350
+ | Previous API | Current replacement |
351
+ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
352
+ | `Chat.transcript` or `execute.messages` | `session.append(...)`, then `execute({ session, ... })`. |
353
+ | `Chat.handler`, `onMessageDelta` | `response.handler`, `response.onDelta`; rich messages use component handlers. |
354
+ | Snapshot signals, snapshot results, `execute.snapshot` | Persist settled sessions. Durable paused-program resumption is no longer supported. |
355
+ | Persisting `ExecutionResult` as conversation state | `session.toJSON()` and `Session.fromJSON(...)`. |
356
+ | `Example`, `execute.examples`, response examples | Put guidance in instructions, or supply request messages with `onBeforeRequest`. |
357
+ | Component aliases, generated method normalization, generation metadata | Exact component names with object or array props. |
358
+ | `utils.wrapContent`, legacy truncation helpers | `truncate({ value, maxTokens, preserve })`. |
359
+ | Returning iteration mutations from `onIterationStart` | Dynamic configuration getters or `onBeforeRequest`. |
1922
360
 
1923
- _This documentation covers the complete LLMz framework. For the latest updates and community contributions, visit the [LLMz repository](https://github.com/botpress/llmz)._
361
+ Stored state has explicit format versions. Migration from older serialized state is not automatic. Rebuild supported session inputs from your application's authoritative conversation records when upgrading.