@adonis-agora/agent 0.31.0 → 0.33.1

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/CHANGELOG.md +795 -0
  2. package/dist/assets/spa/assets/index-DjIMQOxi.js +9 -0
  3. package/dist/assets/spa/index.html +1 -1
  4. package/dist/providers/agent_provider.d.ts.map +1 -1
  5. package/dist/providers/agent_provider.js +140 -2
  6. package/dist/providers/agent_provider.js.map +1 -1
  7. package/dist/src/agent-deps-factory.d.ts +16 -2
  8. package/dist/src/agent-deps-factory.d.ts.map +1 -1
  9. package/dist/src/agent-deps-factory.js +8 -0
  10. package/dist/src/agent-deps-factory.js.map +1 -1
  11. package/dist/src/agent-deps.d.ts +21 -1
  12. package/dist/src/agent-deps.d.ts.map +1 -1
  13. package/dist/src/agent-deps.js.map +1 -1
  14. package/dist/src/agent-loop.d.ts +228 -9
  15. package/dist/src/agent-loop.d.ts.map +1 -1
  16. package/dist/src/agent-loop.js +1360 -209
  17. package/dist/src/agent-loop.js.map +1 -1
  18. package/dist/src/agent-service.d.ts +27 -0
  19. package/dist/src/agent-service.d.ts.map +1 -1
  20. package/dist/src/agent-service.js +29 -0
  21. package/dist/src/agent-service.js.map +1 -1
  22. package/dist/src/ai-sdk/ai-sdk-model.d.ts.map +1 -1
  23. package/dist/src/ai-sdk/ai-sdk-model.js +21 -1
  24. package/dist/src/ai-sdk/ai-sdk-model.js.map +1 -1
  25. package/dist/src/client/sse.d.ts +32 -2
  26. package/dist/src/client/sse.d.ts.map +1 -1
  27. package/dist/src/client/sse.js +45 -2
  28. package/dist/src/client/sse.js.map +1 -1
  29. package/dist/src/define_config.d.ts +41 -7
  30. package/dist/src/define_config.d.ts.map +1 -1
  31. package/dist/src/define_config.js.map +1 -1
  32. package/dist/src/durable/agent-run-workflow.d.ts +10 -11
  33. package/dist/src/durable/agent-run-workflow.d.ts.map +1 -1
  34. package/dist/src/durable/agent-run-workflow.js +49 -21
  35. package/dist/src/durable/agent-run-workflow.js.map +1 -1
  36. package/dist/src/durable/durable-agent-runner.d.ts +3 -2
  37. package/dist/src/durable/durable-agent-runner.d.ts.map +1 -1
  38. package/dist/src/durable/durable-agent-runner.js +2 -2
  39. package/dist/src/durable/durable-agent-runner.js.map +1 -1
  40. package/dist/src/elicitation.d.ts +207 -0
  41. package/dist/src/elicitation.d.ts.map +1 -0
  42. package/dist/src/elicitation.js +315 -0
  43. package/dist/src/elicitation.js.map +1 -0
  44. package/dist/src/evals/approval-prior.d.ts +58 -0
  45. package/dist/src/evals/approval-prior.d.ts.map +1 -0
  46. package/dist/src/evals/approval-prior.js +82 -0
  47. package/dist/src/evals/approval-prior.js.map +1 -0
  48. package/dist/src/evals/evaluate.d.ts +51 -0
  49. package/dist/src/evals/evaluate.d.ts.map +1 -0
  50. package/dist/src/evals/evaluate.js +76 -0
  51. package/dist/src/evals/evaluate.js.map +1 -0
  52. package/dist/src/evals/governance-sample-source.d.ts +23 -0
  53. package/dist/src/evals/governance-sample-source.d.ts.map +1 -0
  54. package/dist/src/evals/governance-sample-source.js +93 -0
  55. package/dist/src/evals/governance-sample-source.js.map +1 -0
  56. package/dist/src/evals/in-memory-score-store.d.ts +16 -0
  57. package/dist/src/evals/in-memory-score-store.d.ts.map +1 -0
  58. package/dist/src/evals/in-memory-score-store.js +57 -0
  59. package/dist/src/evals/in-memory-score-store.js.map +1 -0
  60. package/dist/src/evals/index.d.ts +15 -0
  61. package/dist/src/evals/index.d.ts.map +1 -0
  62. package/dist/src/evals/index.js +14 -0
  63. package/dist/src/evals/index.js.map +1 -0
  64. package/dist/src/evals/judge.d.ts +30 -0
  65. package/dist/src/evals/judge.d.ts.map +1 -0
  66. package/dist/src/evals/judge.js +53 -0
  67. package/dist/src/evals/judge.js.map +1 -0
  68. package/dist/src/evals/live-scoring.d.ts +42 -0
  69. package/dist/src/evals/live-scoring.d.ts.map +1 -0
  70. package/dist/src/evals/live-scoring.js +103 -0
  71. package/dist/src/evals/live-scoring.js.map +1 -0
  72. package/dist/src/evals/sample-source.d.ts +40 -0
  73. package/dist/src/evals/sample-source.d.ts.map +1 -0
  74. package/dist/src/evals/sample-source.js +33 -0
  75. package/dist/src/evals/sample-source.js.map +1 -0
  76. package/dist/src/evals/score-store.d.ts +54 -0
  77. package/dist/src/evals/score-store.d.ts.map +1 -0
  78. package/dist/src/evals/score-store.js +2 -0
  79. package/dist/src/evals/score-store.js.map +1 -0
  80. package/dist/src/evals/scorers/answer-relevancy.scorer.d.ts +28 -0
  81. package/dist/src/evals/scorers/answer-relevancy.scorer.d.ts.map +1 -0
  82. package/dist/src/evals/scorers/answer-relevancy.scorer.js +42 -0
  83. package/dist/src/evals/scorers/answer-relevancy.scorer.js.map +1 -0
  84. package/dist/src/evals/scorers/approval-outcome.scorer.d.ts +22 -0
  85. package/dist/src/evals/scorers/approval-outcome.scorer.d.ts.map +1 -0
  86. package/dist/src/evals/scorers/approval-outcome.scorer.js +60 -0
  87. package/dist/src/evals/scorers/approval-outcome.scorer.js.map +1 -0
  88. package/dist/src/evals/scorers/approval-risk.scorer.d.ts +28 -0
  89. package/dist/src/evals/scorers/approval-risk.scorer.d.ts.map +1 -0
  90. package/dist/src/evals/scorers/approval-risk.scorer.js +54 -0
  91. package/dist/src/evals/scorers/approval-risk.scorer.js.map +1 -0
  92. package/dist/src/evals/scorers/run-completion.scorer.d.ts +18 -0
  93. package/dist/src/evals/scorers/run-completion.scorer.d.ts.map +1 -0
  94. package/dist/src/evals/scorers/run-completion.scorer.js +55 -0
  95. package/dist/src/evals/scorers/run-completion.scorer.js.map +1 -0
  96. package/dist/src/evals/summarize.d.ts +44 -0
  97. package/dist/src/evals/summarize.d.ts.map +1 -0
  98. package/dist/src/evals/summarize.js +87 -0
  99. package/dist/src/evals/summarize.js.map +1 -0
  100. package/dist/src/evals/types.d.ts +81 -0
  101. package/dist/src/evals/types.d.ts.map +1 -0
  102. package/dist/src/evals/types.js +15 -0
  103. package/dist/src/evals/types.js.map +1 -0
  104. package/dist/src/history-window.d.ts +50 -11
  105. package/dist/src/history-window.d.ts.map +1 -1
  106. package/dist/src/history-window.js +84 -12
  107. package/dist/src/history-window.js.map +1 -1
  108. package/dist/src/index.d.ts +9 -2
  109. package/dist/src/index.d.ts.map +1 -1
  110. package/dist/src/index.js +9 -2
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/mcp/server.d.ts +8 -0
  113. package/dist/src/mcp/server.d.ts.map +1 -1
  114. package/dist/src/mcp/server.js +27 -1
  115. package/dist/src/mcp/server.js.map +1 -1
  116. package/dist/src/mcp-client/importer.d.ts +75 -0
  117. package/dist/src/mcp-client/importer.d.ts.map +1 -0
  118. package/dist/src/mcp-client/importer.js +161 -0
  119. package/dist/src/mcp-client/importer.js.map +1 -0
  120. package/dist/src/mcp-client/index.d.ts +9 -0
  121. package/dist/src/mcp-client/index.d.ts.map +1 -0
  122. package/dist/src/mcp-client/index.js +8 -0
  123. package/dist/src/mcp-client/index.js.map +1 -0
  124. package/dist/src/mcp-client/options.d.ts +99 -0
  125. package/dist/src/mcp-client/options.d.ts.map +1 -0
  126. package/dist/src/mcp-client/options.js +2 -0
  127. package/dist/src/mcp-client/options.js.map +1 -0
  128. package/dist/src/mcp-client/tool-input.d.ts +39 -0
  129. package/dist/src/mcp-client/tool-input.d.ts.map +1 -0
  130. package/dist/src/mcp-client/tool-input.js +61 -0
  131. package/dist/src/mcp-client/tool-input.js.map +1 -0
  132. package/dist/src/mcp-client/tool-kind.d.ts +37 -0
  133. package/dist/src/mcp-client/tool-kind.d.ts.map +1 -0
  134. package/dist/src/mcp-client/tool-kind.js +20 -0
  135. package/dist/src/mcp-client/tool-kind.js.map +1 -0
  136. package/dist/src/mcp-client/tool-name.d.ts +22 -0
  137. package/dist/src/mcp-client/tool-name.d.ts.map +1 -0
  138. package/dist/src/mcp-client/tool-name.js +35 -0
  139. package/dist/src/mcp-client/tool-name.js.map +1 -0
  140. package/dist/src/mcp-client/tool-source.d.ts +75 -0
  141. package/dist/src/mcp-client/tool-source.d.ts.map +1 -0
  142. package/dist/src/mcp-client/tool-source.js +244 -0
  143. package/dist/src/mcp-client/tool-source.js.map +1 -0
  144. package/dist/src/mcp-client/transient.d.ts +28 -0
  145. package/dist/src/mcp-client/transient.d.ts.map +1 -0
  146. package/dist/src/mcp-client/transient.js +126 -0
  147. package/dist/src/mcp-client/transient.js.map +1 -0
  148. package/dist/src/mcp-client/unsafe-regex.d.ts +23 -0
  149. package/dist/src/mcp-client/unsafe-regex.d.ts.map +1 -0
  150. package/dist/src/mcp-client/unsafe-regex.js +194 -0
  151. package/dist/src/mcp-client/unsafe-regex.js.map +1 -0
  152. package/dist/src/memory.d.ts +448 -0
  153. package/dist/src/memory.d.ts.map +1 -0
  154. package/dist/src/memory.js +338 -0
  155. package/dist/src/memory.js.map +1 -0
  156. package/dist/src/processors.d.ts +134 -0
  157. package/dist/src/processors.d.ts.map +1 -0
  158. package/dist/src/processors.js +215 -0
  159. package/dist/src/processors.js.map +1 -0
  160. package/dist/src/rag/index.d.ts +1 -1
  161. package/dist/src/rag/index.d.ts.map +1 -1
  162. package/dist/src/rag/index.js +1 -1
  163. package/dist/src/rag/index.js.map +1 -1
  164. package/dist/src/rag/pg-vector-store.d.ts +41 -0
  165. package/dist/src/rag/pg-vector-store.d.ts.map +1 -1
  166. package/dist/src/rag/pg-vector-store.js +93 -15
  167. package/dist/src/rag/pg-vector-store.js.map +1 -1
  168. package/dist/src/replay-integrity.d.ts +17 -0
  169. package/dist/src/replay-integrity.d.ts.map +1 -0
  170. package/dist/src/replay-integrity.js +19 -0
  171. package/dist/src/replay-integrity.js.map +1 -0
  172. package/dist/src/runners/inline-agent-runner.d.ts +33 -7
  173. package/dist/src/runners/inline-agent-runner.d.ts.map +1 -1
  174. package/dist/src/runners/inline-agent-runner.js +113 -27
  175. package/dist/src/runners/inline-agent-runner.js.map +1 -1
  176. package/dist/src/skills.d.ts +291 -0
  177. package/dist/src/skills.d.ts.map +1 -0
  178. package/dist/src/skills.js +0 -0
  179. package/dist/src/skills.js.map +1 -0
  180. package/dist/src/spi/agent-runner.d.ts +20 -6
  181. package/dist/src/spi/agent-runner.d.ts.map +1 -1
  182. package/dist/src/spi/agent-store.d.ts +53 -0
  183. package/dist/src/spi/agent-store.d.ts.map +1 -1
  184. package/dist/src/spi/governance-queries.d.ts +5 -0
  185. package/dist/src/spi/governance-queries.d.ts.map +1 -1
  186. package/dist/src/spi/history-window.d.ts +64 -10
  187. package/dist/src/spi/history-window.d.ts.map +1 -1
  188. package/dist/src/spi/model-provider.d.ts +18 -0
  189. package/dist/src/spi/model-provider.d.ts.map +1 -1
  190. package/dist/src/spi/processors.d.ts +161 -0
  191. package/dist/src/spi/processors.d.ts.map +1 -0
  192. package/dist/src/spi/processors.js +57 -0
  193. package/dist/src/spi/processors.js.map +1 -0
  194. package/dist/src/spi/token-stream-sink.d.ts +43 -0
  195. package/dist/src/spi/token-stream-sink.d.ts.map +1 -1
  196. package/dist/src/spi/token-stream-sink.js +17 -1
  197. package/dist/src/spi/token-stream-sink.js.map +1 -1
  198. package/dist/src/sse.d.ts +5 -2
  199. package/dist/src/sse.d.ts.map +1 -1
  200. package/dist/src/sse.js +11 -2
  201. package/dist/src/sse.js.map +1 -1
  202. package/dist/src/stores/lucid-governance-queries.d.ts.map +1 -1
  203. package/dist/src/stores/lucid-governance-queries.js +3 -0
  204. package/dist/src/stores/lucid-governance-queries.js.map +1 -1
  205. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  206. package/dist/src/stores/lucid-schema.js +15 -0
  207. package/dist/src/stores/lucid-schema.js.map +1 -1
  208. package/dist/src/stores/lucid.d.ts +11 -3
  209. package/dist/src/stores/lucid.d.ts.map +1 -1
  210. package/dist/src/stores/lucid.js +65 -0
  211. package/dist/src/stores/lucid.js.map +1 -1
  212. package/dist/src/structured-output.d.ts +57 -0
  213. package/dist/src/structured-output.d.ts.map +1 -0
  214. package/dist/src/structured-output.js +80 -0
  215. package/dist/src/structured-output.js.map +1 -0
  216. package/dist/src/testing/fake-model-provider.d.ts +8 -4
  217. package/dist/src/testing/fake-model-provider.d.ts.map +1 -1
  218. package/dist/src/testing/fake-model-provider.js +6 -9
  219. package/dist/src/testing/fake-model-provider.js.map +1 -1
  220. package/dist/src/testing/in-memory-governance-queries.d.ts.map +1 -1
  221. package/dist/src/testing/in-memory-governance-queries.js +1 -0
  222. package/dist/src/testing/in-memory-governance-queries.js.map +1 -1
  223. package/dist/src/testing/in-memory-store.d.ts +4 -3
  224. package/dist/src/testing/in-memory-store.d.ts.map +1 -1
  225. package/dist/src/testing/in-memory-store.js +13 -7
  226. package/dist/src/testing/in-memory-store.js.map +1 -1
  227. package/dist/src/testing/index.d.ts +1 -1
  228. package/dist/src/testing/index.d.ts.map +1 -1
  229. package/dist/src/testing/index.js.map +1 -1
  230. package/dist/src/tool-registry.d.ts +9 -0
  231. package/dist/src/tool-registry.d.ts.map +1 -1
  232. package/dist/src/tool-registry.js +11 -0
  233. package/dist/src/tool-registry.js.map +1 -1
  234. package/dist/src/types.d.ts +70 -2
  235. package/dist/src/types.d.ts.map +1 -1
  236. package/package.json +38 -24
  237. package/skills/agent-personas-agents/SKILL.md +16 -12
  238. package/dist/assets/spa/assets/index-3Q2m5_Ax.js +0 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,800 @@
1
1
  # @adonis-agora/agent
2
2
 
3
+ ## 0.33.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#138](https://github.com/DavideCarvalho/adonis-agora-agent/pull/138) [`615b56f`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/615b56f5a2a09de2fa165448dbdb85a8890760a2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - A memory provider written as a class keeps its receiver, so `remember` can actually write
8
+
9
+ `writeMemory` read the method off the config and called it detached:
10
+
11
+ ```ts
12
+ const write = config.provider.write;
13
+ …
14
+ const record = await write({ … });
15
+ ```
16
+
17
+ A provider is normally a class — an Adonis service with its model or repository injected — and a detached method has lost its receiver, so the first `this.` inside it throws a `TypeError`. The loop reports that as a failed tool call, the model narrates the failure, and nothing is ever written down. Reads were unaffected: `list` and `forget` are called through the object.
18
+
19
+ `offerMemories` detached `search` the same way, which breaks the same host on the RECALL path — and that one runs on every turn, so it breaks before anything is ever written. Both are bound now.
20
+
21
+ Every provider in this repo's own tests is an object literal of arrow functions, which needs no receiver — which is exactly why it went unseen. The regression test is a provider written the way a host writes one, with both `search` and `write` reading its storage off `this`.
22
+
23
+ - [#139](https://github.com/DavideCarvalho/adonis-agora-agent/pull/139) [`6a6ff1f`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/6a6ff1f0369ced0eddca3940c2f1ad73081c75da) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - A declined action reads as a decision, not as a malfunction
24
+
25
+ When a person declines an action tool, the loop handed the model a tool result whose error text was the single word `rejected`. That names no actor and is indistinguishable from a tool that threw, so the answer that followed diagnosed the refusal — "the key may not exist", "there may be permission restrictions" — and offered to retry the same action, asking the person to say no twice.
26
+
27
+ - `ToolResult` gains `denied?: true`. It is set instead of a failure and read by everything that has to tell the two apart; `error` still carries what the model is told, because that is the channel a model reads an outcome on.
28
+ - That text now says who decided, that nothing ran, and what not to do next: not an error, no guessing at causes, no retry and no reaching for another way to do the same thing. A reason given when declining is included.
29
+
30
+ Ported from the NestJS sibling, where the bad narration was seen in production.
31
+
32
+ - [#144](https://github.com/DavideCarvalho/adonis-agora-agent/pull/144) [`f22bd40`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/f22bd4094b95705ed285fa9b84f9b17df9cf59e4) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `PgVectorStore` strips the NUL byte (0x00) before it reaches Postgres, on writes and on reads
33
+
34
+ Postgres rejects the NUL byte in `text`/`jsonb` values outright (`invalid byte sequence for encoding "UTF8": 0x00`), but text extracted from PDFs sometimes carries one and upstream sources like Qdrant accept it fine — so a chunk that ingested cleanly elsewhere could fail `upsert` here with no way to write it. `upsert` (id, text, source, metadata — including nested metadata values, array items, and object keys) and `updateMetadata` (the patch, values and keys, plus the `documentId` lookup) now run every string through a new exported `stripNulBytes` helper first, so a stray NUL byte is dropped and the rest of the text is written unchanged. The same stripping now also applies on read: `remove`'s `documentId`, and the metadata filter (`search`, `listDocuments`, `listDocumentIds`, `removeWhere`) — keeping every lookup consistent with the already-stripped stored data.
35
+
36
+ `stripNulBytes` also fixes two edge cases in how it walks a value: only plain objects are recursed into (checked via `Object.getPrototypeOf`), so a `Date`, class instance, `Buffer`, etc. inside metadata passes through unchanged instead of being flattened into `{}`; and the result is built with `Object.fromEntries` rather than assignment onto a fresh `{}`, so a metadata key literally named `__proto__` round-trips as a real own property instead of silently hitting the inherited prototype accessor and being dropped. The same `__proto__` drop was still reachable through two downstream accumulators that built their own object with plain assignment — `buildMetadataWhere`'s scalar filter object and `updateMetadata`'s merge-patch object — both now accumulate `[key, value]` pairs and build with `Object.fromEntries` too, so a `__proto__`-named filter or patch key survives all the way to the binding.
37
+
38
+ - [#140](https://github.com/DavideCarvalho/adonis-agora-agent/pull/140) [`53016f7`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/53016f7505598e4685209ae526a402e770a3f54e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - A tool call's kind travels with the call, so the approval gate does not depend on which process replays the turn
39
+
40
+ `claimToolCall` settles a call's kind inside `persist:toolcall` and journals it from there, which makes every replay agree. It does not make the first writer right: a resumed run is handed to whichever instance takes the lease, and an instance that never declared the tool reads `undefined` from its own registry, journals `read` for an action tool, and executes it with nobody's approval.
41
+
42
+ The kind is now stamped where the tool was OFFERED — inside the `llm:<i>` checkpoint, by the process that built the definition list the model chose from — and `claimToolCall` writes that value rather than asking its own registry. A call that arrives unstamped still falls back to the local lookup, so a journal written before kinds travelled replays unchanged.
43
+
44
+ No checkpoint name, position or count changes.
45
+
46
+ ## 0.33.0
47
+
48
+ ### Minor Changes
49
+
50
+ - [#129](https://github.com/DavideCarvalho/adonis-agora-agent/pull/129) [`0de5ab8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0de5ab8f98095c8281147efdbf92c996e8a5a603) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `@adonis-agora/agent/mcp-client` — tools de um servidor MCP que você não controla.
51
+
52
+ A direção contrária ao servidor MCP que este pacote já tinha: em vez de expor as tools DESTE
53
+ deployment para um cliente externo, importa as tools de um servidor MCP externo para o
54
+ `ToolRegistry` deste deployment. Elas entram no MESMO registry que a descoberta de `@aiTool`
55
+ alimenta, então passam por todos os gates que uma tool escrita à mão passa — a `RolesPolicy`, o
56
+ allow-list de persona/agente, e a validação de input.
57
+
58
+ ```ts
59
+ // config/agent.ts
60
+ mcpServers: [{ name: 'github', transport: { type: 'stdio', command: 'mcp-github' } }]
61
+ ```
62
+
63
+ **Uma tool importada é `action` por padrão**, ou seja, com HITL: ela foi escrita fora deste código e
64
+ os efeitos dela não são visíveis daqui. Alargar isso é uma decisão que o host toma em voz alta —
65
+ `kind: 'read'` (só para um servidor que você audita), `kind: 'trust-annotations'` (acreditar no
66
+ `readOnlyHint`, que é afirmado justamente por quem tem os efeitos que ele descreve, então é uma
67
+ declaração de confiança e não uma verificação — e uma tool que declara `readOnlyHint` E
68
+ `destructiveHint` está se descrevendo de forma incoerente e continua travada), ou um predicado por
69
+ tool.
70
+
71
+ **Um nome pertence a quem o reivindicou primeiro.** O registry é indexado por nome e nada mais, então
72
+ uma importação sem namespace deixaria o `search` de um servidor remoto substituir o `search` do
73
+ próprio app — uma troca invisível de todo lugar, porque o modelo continua chamando `search` e o
74
+ `search` agora chega no servidor de outra pessoa. Os nomes importados são prefixados com o nome do
75
+ servidor por padrão, e um segundo reivindicante é RECUSADO com um aviso nomeando os dois. Vale entre
76
+ servidores também, e a resposta é estável: a listagem roda em paralelo porque os servidores são
77
+ independentes, mas o registro reexecuta na ORDEM DE CONFIGURAÇÃO, então um nome disputado vai para o
78
+ servidor configurado primeiro por mais rápido que o outro responda.
79
+
80
+ **Duas coisas que um schema remoto pode fazer com este processo**, as duas terminando com a tool
81
+ DESCARTADA em vez de importada com um schema permissivo — a alternativa deixaria o modelo mandar
82
+ argumentos arbitrários para a tool remota com a aparência de uma chamada validada. Um schema que não
83
+ compila. E um `pattern` que backtracka exponencialmente: todo validador do SDK do MCP compila
84
+ `pattern` para um `RegExp` nativo, e `(a+)+$` contra 27 `a`s e um final que não casa leva ~1s, 30
85
+ ~8s, 40 mais do que qualquer um vai esperar — de forma síncrona, na única thread que o processo tem.
86
+ O pattern é escrito pelo servidor remoto e a string é escrita pelo modelo, cuja indução a descrição
87
+ da mesma tool pode fornecer, então a coisa toda cabe dentro de uma definição de tool.
88
+
89
+ **A conexão não é estado durável.** Um servidor remoto pode reiniciar, ser redeployado ou cortar uma
90
+ conexão ociosa, e o primeiro sintoma é uma tool call que falha. Então uma chamada que falha de forma
91
+ transiente descarta o client, e o retry reconecta na próxima tentativa. O retry é o
92
+ `invokeWithTransientRetry` que o loop já usa, no lugar e não como novo checkpoint. Uma tool que
93
+ respondeu `isError` falhou por mérito próprio e NUNCA é retentada: a mensagem dela é texto que o
94
+ servidor remoto escreveu, e casar isso contra marcadores de transiência deixaria a prosa de uma tool
95
+ decidir se este lado retenta.
96
+
97
+ Um servidor fora do ar custa as tools dele e nada mais — o modelo simplesmente nunca é oferecido a
98
+ elas — a menos que ele declare `required: true`. E `refresh(name)` é o caminho de volta para um
99
+ servidor que estava fora no boot.
100
+
101
+ Duas coisas do porte de referência ficaram de fora porque o SPI de tools daqui não tem as costuras:
102
+ `ToolSpec.enabled` (um servidor dizer se as tools dele existem neste deployment) e
103
+ `ToolHandler.canUse` (um gate por ator). As duas são capacidades do SPI de tools, não do cliente MCP.
104
+
105
+ - [#129](https://github.com/DavideCarvalho/adonis-agora-agent/pull/129) [`0de5ab8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0de5ab8f98095c8281147efdbf92c996e8a5a603) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Um ciclo de delegação é detectado como ciclo — e nada antes disso parava um handoff mútuo.
106
+
107
+ `delegatesTo` é um grafo, e o modelo não o vê. Num handoff mútuo (alpha entrega pra beta, beta entrega
108
+ de volta pra alpha) cada agente faz uma chamada razoável e a recursão é da fiação. O
109
+ `delegationDepth` que o runner durável já carregava tinha um comentário dizendo "carregado para um
110
+ guard futuro" — ou seja, **não existia guard algum**: o runner inline nem contava, e a única coisa que
111
+ terminava uma cadeia cíclica era o `maxSteps` de cada turno somado ao acaso.
112
+
113
+ Entra `AgentRunInput.delegationPath`, a cadeia de nomes de agente que chegou até este run, com os DOIS
114
+ runners acrescentando o próprio agente para cada filho que começam. O loop compara o alvo da
115
+ delegação contra essa cadeia mais o próprio agente: uma repetição É o ciclo, nomeada na recusa com
116
+ quantas vezes aquele agente já esteve nela.
117
+
118
+ ```
119
+ delegation cycle: alpha → beta → alpha — alpha 2 times on one chain
120
+ ```
121
+
122
+ `maxAgentAppearances` (padrão 1) conta APARIÇÕES, então 2 admite exatamente um retorno deliberado — o
123
+ que um supervisor que de fato devolve trabalho precisa. `maxDelegationDepth` (padrão 5) entra como
124
+ backstop para uma cadeia que é longa sem repetir, e é reportado só quando nada é circular. Os dois são
125
+ declarados por agente, ao lado do `maxSteps`.
126
+
127
+ **Por que não bastava contar.** Uma contagem só sabe dizer que a cadeia é LONGA. Ela não sabe dizer
128
+ que está andando em círculo, e confundir as duas coisas errava os dois casos: um handoff mútuo
129
+ queimava o teto inteiro em turnos de agente antes de reportar uma profundidade que o leitor ainda
130
+ tinha que interpretar, e uma cadeia legítima de seis agentes DISTINTOS era recusada por parecer um
131
+ ciclo que não era. A cadeia de nomes custa exatamente o que o contador custava para carregar.
132
+
133
+ A recusa é resolvida dentro do mesmo checkpoint que assenta o kind da call (`persist:toolcall:<id>`),
134
+ então o veredito é o que um replay relê — e não algo que cada processo re-deriva a partir de config
135
+ local.
136
+
137
+ Uma diferença deliberada em relação ao porte de referência: a ancestralidade inclui o agente do
138
+ PRÓPRIO run. Sem isso, um handoff mútuo de verdade é recusado com `alpha → alpha`, uma aresta que
139
+ nenhum deployment declara, porque o salto que fechou o círculo fica de fora da mensagem. Com isso,
140
+ uma auto-delegação também passa a ser ciclo na hora, em vez de um salto depois.
141
+
142
+ - [#129](https://github.com/DavideCarvalho/adonis-agora-agent/pull/129) [`0de5ab8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0de5ab8f98095c8281147efdbf92c996e8a5a603) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Um run filho grava qual run o delegou.
143
+
144
+ A aresta pai→filho de uma delegação existia no journal do runtime durável e em nenhum outro lugar.
145
+ Toda superfície de confiabilidade e de custo — e todas elas leem LINHAS de run — via o gasto de uma
146
+ delegação como um turno órfão: ninguém conseguia somar o que um turno custou de verdade, porque a
147
+ parte que ele pediu a outro agente aparecia como um run que ninguém começou. E o journal durável não
148
+ é joinable com a tabela de runs, então não havia como recompor a árvore depois.
149
+
150
+ Entra `RecordRunStartInput.parentRunId` e `AgentRunInput.parentRunId`, preenchido pelos DOIS runners
151
+ (o inline em `runNested`, o durável no `ctx.child`), persistido como `agent_run.parent_run_id` pelo
152
+ `LucidAgentStore` e pelo store em memória, e devolvido como `RunSummaryRow.parentRunId` —
153
+ `null` para um turno que uma pessoa começou.
154
+
155
+ `createAgentTables` repara a coluna aditivamente num banco que já tem `agent_run`: um
156
+ `CREATE TABLE IF NOT EXISTS` não alcança coluna nova em tabela que já existe, e o `ALTER` entra pelo
157
+ mesmo caminho que já conserta as colunas `run_id`, com o reparo reportado por nome.
158
+
159
+ O teste que pega a classe de bug que isto é: a fixture é tipada `Required<RecordRunStartInput>`, então
160
+ um campo novo no input FALHA A COMPILAR até que a linha de cada adapter saiba nomeá-lo. E o
161
+ threading — a metade que pode estar morta sem falhar nada, porque um store com a coluna e um runner
162
+ que nunca a preenche é indistinguível de um deployment em que ninguém delega — é coberto por uma
163
+ delegação de verdade pelo `InlineAgentRunner`, perguntando ao read-model qual turno pagou pelo filho.
164
+
165
+ - [#129](https://github.com/DavideCarvalho/adonis-agora-agent/pull/129) [`0de5ab8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0de5ab8f98095c8281147efdbf92c996e8a5a603) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Um turno lê a JANELA que ele vai mandar, não a transcrição da thread.
166
+
167
+ Um turno precisa das últimas mensagens, do título, e de saber se a thread já foi respondida alguma
168
+ vez. O `getThread` entregava a transcrição inteira — toda linha de mensagem, todo anexo e toda saída
169
+ de tool que a thread já gravou — e o `load:thread` então journalava o que carregou. Numa thread de 50
170
+ turnos em que cada turno rodou uma tool de 50 KB isso é ~2,6 MB lidos e gravados para mandar quatro
171
+ mensagens, 99% deles saída de tool que nenhuma chamada ao modelo ia ver. E o preço é pago de novo em
172
+ todo replay, porque o payload do checkpoint é relido e reparseado por todo processo que retoma o run.
173
+
174
+ Entra `ThreadTurnReader.loadThreadForTurn({ threadId, messageLimit })` no SPI, implementado pelo
175
+ `LucidAgentStore`: as `messageLimit` linhas mais novas, com o limite sendo o do BANCO
176
+ (`order by created_at desc limit ?`), projetadas nas colunas que um turno de modelo lê — `usage`,
177
+ `follow_ups` e `run_id` ficam na tabela.
178
+
179
+ **O probe é estrutural**, então um store que não oferece a janela continua respondendo pelo
180
+ `getThread`, com o mesmo limite aplicado em processo. E ele mora DENTRO do `load:thread`, não em
181
+ volta: não acrescenta posição nenhuma, e o payload gravado é idêntico nos dois caminhos — a escolha
182
+ de store de um deployment não pode decidir os checkpoints de um run. Nenhum marcador de patch,
183
+ nenhuma posição mexida.
184
+
185
+ **Dois tetos de propósito não nomeiam limite.** Um que RESUME, porque o `summarize` recebe o que o
186
+ `select` DESCARTOU: uma leitura limitada ao que o `select` guarda não descarta nada, e o turno
187
+ dobraria um resumo vazio dentro de um prompt que está sem as mensagens que ele substitui — calado.
188
+ E um teto que não é contagem de linhas, porque de um orçamento de tokens não sai contagem alguma: uma
189
+ mensagem pode custar quatro tokens ou quarenta mil. `HistoryWindow.maxMessages` é o campo que declara
190
+ o limite, e declarar é uma PROMESSA sobre o `select` — que ele guarda no máximo essa quantidade, e que
191
+ são as mais NOVAS.
192
+
193
+ **`hasAssistantMessage` é respondido sobre a thread INTEIRA, nunca sobre a página.** Ele decide um
194
+ intake `thread-start`, e uma janela que por acaso só tem as últimas perguntas do usuário pertence a
195
+ uma conversa que já foi respondida — lido da página, um thread longo se reapresentaria a cada turno.
196
+
197
+ - [#129](https://github.com/DavideCarvalho/adonis-agora-agent/pull/129) [`0de5ab8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0de5ab8f98095c8281147efdbf92c996e8a5a603) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Uma aprovação humana vale UM efeito, e uma rejeição registrada foi alguém que a fez.
198
+
199
+ Quatro defeitos no mesmo eixo: o que a biblioteca grava como decisão de uma pessoa, e quantas vezes
200
+ um efeito remoto acontece por causa dela. Os dois primeiros **fabricam ou duplicam** uma decisão
201
+ humana, que é a classe de falha que nada na frente denuncia.
202
+
203
+ ## Um retry não re-emite uma ação aprovada
204
+
205
+ `McpToolSource.callTool` envolvia TODA chamada em `invokeWithTransientRetry`. Um timeout, uma conexão
206
+ derrubada, um reset no meio do voo, um 500/502/503 — cada um deles é também exatamente como um tool
207
+ remoto que **rodou** aparece quando a resposta se perde. Re-emitir em cima disso gasta uma única
208
+ aprovação humana em dois efeitos remotos, e nada deste lado fica sabendo: os efeitos de um tool
209
+ importado acontecem na máquina de outra pessoa, que é justamente o motivo de ele ser `action` por
210
+ padrão.
211
+
212
+ Agora o classificador depende do `kind`:
213
+
214
+ | `kind` | Tenta de novo em |
215
+ | --- | --- |
216
+ | `read` | `isTransientMcpError` — conexão derrubada, timeout, status HTTP retentável, erro de socket |
217
+ | `action` | `isPreExecutionMcpError` — só falhas que **provam** que nada rodou: conexão recusada, host nunca resolvido ou alcançado, ou nenhum transporte para enviar |
218
+
219
+ Passar o próprio `classify` assume esse juízo para todo `kind`, `action` incluído. `isPreExecutionMcpError`
220
+ é exportado de `@adonis-agora/agent/mcp-client`.
221
+
222
+ ## Respostas não se tornam uma rejeição
223
+
224
+ Um conjunto de perguntas estaciona como uma call `action` em `pending_approval` — é o que o põe na
225
+ caixa de aprovações que o deployment já tem. O custo é que um canal carrega duas formas, então um
226
+ cliente consegue endereçar `POST /agent/tool-call/answer` a uma call que na verdade espera um
227
+ approve/reject. `ElicitationReply` não carrega `approved`, e `!undefined` é `true`: a linha virava
228
+ `rejected`, uma decisão que o operador nunca tomou e que nada depois distingue de uma que ele tomou.
229
+
230
+ A redução vale em UM sentido só. Um sim/não pode ser lido como "confirmou as respostas pré-marcadas";
231
+ um conjunto de respostas não diz nada sobre se o trabalho proposto deve seguir. Então é **recusado**,
232
+ nunca registrado:
233
+
234
+ - o runner inline sabe em qual espera o run está parado e responde `409` (`HumanReplyMismatchError`);
235
+ - sob o runner durável um sinal não carrega essa pista, então o loop descarta a resposta e o run
236
+ continua parado na aprovação que sempre esperou. Esperar de novo gasta outra posição, e um replay
237
+ se alinha porque a resposta descartada está ela mesma no journal na posição em que chegou.
238
+
239
+ De qualquer um dos lados a linha segue `pending_approval`, e a aprovação de verdade ainda a assenta.
240
+
241
+ ## Um run delegado pode ser respondido
242
+
243
+ Um sub-agente estaciona numa espera real — `tool:<childRunId>:<callId>` — e a linha pendente que ele
244
+ escreve carrega esse runId, então a caixa de aprovações sempre soube respondê-la. O que faltava não
245
+ era o caminho de volta: era o **id**. Quem olha assiste ao stream de TOPO, porque é o único stream a
246
+ que alguém assina; o filho encaminha os frames pra lá (é para isso que `sinkRunId` existe), e um
247
+ formulário sem run id é um formulário que quem olha vê e não consegue responder.
248
+
249
+ Então o id passa a viajar NO frame. Os dois frames de trabalho estacionado o carregam:
250
+
251
+ | Evento SSE | Payload | Responde com |
252
+ | --- | --- | --- |
253
+ | `event: approval` | `{runId, id, toolName, input}` | `POST /agent/tool-call/approve` \| `/reject` |
254
+ | `event: elicitation` | `{runId, id, request}` | `POST /agent/tool-call/answer` \| `/skip` |
255
+
256
+ `event: approval` é novo: uma `action` estacionada não tinha frame nenhum, só a linha
257
+ `pending_approval`. É escrito de DENTRO do checkpoint `persist:toolcall:<callId>`, então não gasta
258
+ posição própria e um replay — que devolve aquele checkpoint memoizado — nunca re-posta um formulário
259
+ para uma decisão já tomada. E `runId` é o run ESTACIONADO, não o stream em que o frame chegou: ler o
260
+ run do frame `meta` sinalizaria o ancestral e deixaria o filho suspenso. `decodeFrame` no cliente
261
+ aprendeu os dois eventos — antes ele descartava `elicitation` inteiro — e descarta um frame sem
262
+ `runId` em vez de entregar um que não dá para acionar.
263
+
264
+ O runner inline passa a espelhar isso: o loop aninhado escreve no sink do ancestral via
265
+ `childSinkWriter` e estaciona em `${childRunId}:${toolCallId}`, em vez de auto-declinar. Um
266
+ comportamento, nos dois runners — e um sub-agente cujas aprovações ninguém vai responder **fica
267
+ pendurado**, exatamente como um agente de topo. Se um job delega, mantenha tools de aprovação fora do
268
+ sub-agente (`tools:` na definição dele).
269
+
270
+ Os docs afirmavam o auto-declínio em três lugares (`docs/programmatic-api.mdx`,
271
+ `docs/concepts/agent-loop.mdx`, `docs/authoring/personas-and-agents.mdx`) — era essa afirmação que
272
+ estava errada, não o comportamento do durável. Nenhum marcador `patched` é gasto: a forma do journal
273
+ do filho não muda.
274
+
275
+ ## Um refresh de MCP remove, não só acrescenta
276
+
277
+ `McpToolImporter.refresh` só somava. Um servidor que remove ou renomeia um tool deixava o spec e o
278
+ handler antigos registrados, e o modelo seguia recebendo a oferta de um tool cuja próxima chamada
279
+ falha remotamente — segundos depois, na máquina de outra pessoa.
280
+
281
+ `refresh()` passa a ser a resposta ATUAL do servidor a `tools/list`: o que desapareceu é desregistrado
282
+ via o novo `ToolRegistry.unregister(name)`, então chamar aquilo é um `ToolNotFoundError` aqui. Só
283
+ nomes que este importer registrou para AQUELE servidor entram — nunca os do app, nunca de outro
284
+ servidor — e um nome liberado é reivindicável pelo próximo servidor em ordem de configuração no mesmo
285
+ refresh. Um servidor que não pôde ser **alcançado** não poda nada: seus tools são desconhecidos, não
286
+ desaparecidos, e uma oscilação de rede não é motivo para retirar do modelo um tool que funciona.
287
+
288
+ `refresh(name)` para um nome que nenhum servidor aberto casa avisa e retorna `0`, em vez de deixar um
289
+ `0` que se lê igual a um servidor que respondeu sem tools.
290
+
291
+ ### Patch Changes
292
+
293
+ - [`0af6194`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/0af6194cd4f0d53652938fa0d408f59b212eff5e) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O docblock de `AgentRunner` descreve a fiação deste pacote, não a da referência Nest.
294
+
295
+ Três nomes que não existem aqui: `AGENT_RUNNER`, que não é chave de binding nenhuma — o
296
+ `agent_provider` constrói o runner direto e registra só o `AgentService`; `@dudousxd/nestjs-durable`,
297
+ sendo que o peer durável deste pacote é `@adonis-agora/durable`; e o decorator `@Workflow`, que esta
298
+ porta não tem — o turno durável é `class AgentRunWorkflow extends BaseWorkflow`, registrada por
299
+ `registerWorkflowClass`.
300
+
301
+ ## 0.32.0
302
+
303
+ ### Minor Changes
304
+
305
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `@adonis-agora/agent/evals` — nota de qualidade sobre os runs que já estão gravados.
306
+
307
+ A biblioteca já sabia dizer quanto um turno custou e se ele terminou; não sabia dizer se ele prestou.
308
+ Entra um SPI `Scorer` (um run gravado entra, um `0..1` mais a frase que o justifica sai), um runner em
309
+ lote retomável, um `ScoreStore` para os vereditos, e os agregadores puros (`summarizeByScorer` /
310
+ `summarizeByAgent` / `bucketScoreTrend` / `worstScoredRuns`) que espelham a aritmética do read-model de
311
+ governança — assim um painel e um gate de CI nunca discordam sobre se a qualidade mexeu.
312
+
313
+ **Offline por construção.** A leitura é do que o agente já persistiu — a linha do run, a transcrição, as
314
+ tool calls e os desfechos — através do `AgentGovernanceQueries` e de mais nada, então funciona igual nos
315
+ stores Lucid e em memória e **não acrescenta nenhuma tabela de leitura**. Nada roda dentro de um turno:
316
+ um juiz inline dobraria a latência e a conta de toda mensagem que um usuário manda. `runEvaluation` pula
317
+ um par `(run, scorer)` que o store já cobriu, então um backfill interrompido e reiniciado com a mesma
318
+ query não cobra nada de novo.
319
+
320
+ **Quatro scorers embutidos, um por coisa que esta biblioteca de fato sabe:**
321
+
322
+ - `RunCompletionScorer` (`rule`) — o turno entregou? Um run que assenta `completed` sem ter respondido
323
+ nada tira 0, que é exatamente o que a taxa de sucesso da governança chama de sucesso; uma resposta
324
+ escrita com uma tool falhando tira 0,5; um run `cancelled` tira 0 com o motivo dizendo isso.
325
+ - `ApprovalOutcomeScorer` (`rule`) — **toda rejeição de HITL é um rótulo negativo de qualidade que um
326
+ humano produziu de graça.** A biblioteca já para uma tool `action` e pergunta a uma pessoa se aquilo
327
+ deve rodar; essa resposta fica gravada na tool call e é a única verdade de referência do sistema que
328
+ ninguém precisou pagar para coletar. A nota é a fração das actions DECIDIDAS do run que um humano
329
+ aprovou. Uma action aprovada que depois explodiu conta como aprovada — a pessoa disse sim.
330
+ - `ApprovalRiskScorer` (`statistical`) — o mesmo corpus virado previsão: taxa de aprovação por tool
331
+ suavizada por um Beta(1,1), então uma tool inédita fica exatamente em 0,5 e um 1-de-1 rejeitado nunca
332
+ lê como certeza. O run vale pela action MAIS arriscada que propôs, não pela média, para que uma caixa
333
+ de aprovações seja drenada da pior para a melhor.
334
+ - `AnswerRelevancyScorer` (`model`) — LLM como juiz sobre qualquer `ModelProvider`, com `discardingSink()`
335
+ e `parseJudgeVerdict()` exportados para escrever o seu.
336
+
337
+ Um scorer devolve `null` — e não `1` — para um run sobre o qual não tem o que dizer. A maioria dos runs é
338
+ só leitura e não carrega veredito humano nenhum; contar esses como perfeitos enterraria os que carregam
339
+ sob uma média de ~1. Um scorer que LANÇA é coletado como falha daquele run e o lote segue: um juiz que
340
+ respondeu em prosa é uma avaliação quebrada, e gravar isso como 0 poria a culpa no agente.
341
+
342
+ **Duas coisas ficaram diferentes do porte de referência, e as duas por causa deste read-model.** O
343
+ `runDetail` daqui já devolve as mensagens carimbadas com o `run_id` do próprio run, então
344
+ `GovernanceRunSampleSource` lê pergunta e resposta direto dele — sem juntar a transcrição da thread, sem
345
+ heurística de janela de tempo, e sem depender do `AgentStore`. E como não existe um feed de tool calls
346
+ paginado por tipo, o prior de aprovação vem de `priorFromRuns` (dobra as actions dos runs que o lote já
347
+ carregou, custo zero de leitura) ou de `loadApprovalPrior` sobre o feed de atividade recente, limitado
348
+ por `limit`.
349
+
350
+ **Scoring ao vivo é opt-in e não consegue derrubar um turno.** `attachLiveScoring` assina
351
+ `agora:agent:run.finished` e pontua DEPOIS que o run assentou e o stream dele fechou, numa promise
352
+ destacada, com assinante protegido e toda falha roteada para `onError`. Não existe caminho de código de
353
+ um scorer de volta para dentro de um turno. Ainda custa trabalho de verdade por run, então o lote segue
354
+ sendo o default e `sampleRate` alivia a carga de qualquer coisa que cobre.
355
+
356
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `HistoryWindow` ganha orçamento de tokens e um caminho de sumarização embutido.
357
+
358
+ `SlidingWindowHistory` só sabia contar mensagens, e resumir era explicitamente problema do consumidor.
359
+ Agora ela aceita `maxMessages`, `maxTokens` ou os dois (vence quem corta mais), com `estimate` para
360
+ trocar a heurística de ~4 caracteres por token por um tokenizer de verdade, e `summarize` para dobrar
361
+ o que a janela deixou de fora numa mensagem `system` inicial. `summarizeWithModel(model)` é o
362
+ sumarizador embutido: uma chamada extra, não-streamada (escreve num sink que descarta, então os
363
+ tokens do resumo nunca chegam ao stream do usuário), registrada como uma linha de uso `summary` — a
364
+ cota soma o ledger inteiro sem filtrar propósito, então limitar o custo de contexto não vira gasto
365
+ que ninguém contabiliza. `estimateMessageTokens` e `DEFAULT_HISTORY_SUMMARY_INSTRUCTION` são
366
+ exportados para compor a sua própria.
367
+
368
+ O seam mudou de forma, e a razão é determinismo. `apply(messages, ctx) => ModelMessage[]`, rodado
369
+ inteiro dentro de um checkpoint `history:window`, virou duas metades que rodam em lugares
370
+ deliberadamente diferentes:
371
+
372
+ - `select(messages, ctx) => { keep, drop }` é PURA e roda FORA de qualquer checkpoint. Seguro porque
373
+ a entrada dela já É um checkpoint (o resultado cacheado de `load:thread`), então um replay chega ao
374
+ mesmo corte sem gastar posição nenhuma — é isso que permite existir uma janela sem mexer em uma
375
+ única posição do loop.
376
+ - `summarize(dropped, ctx)` chama um modelo, então roda DENTRO de `history:summarize`: um run
377
+ retomado lê de volta o resumo que a tentativa suspensa produziu, em vez de gerar outro (e de pagar
378
+ duas vezes por ele).
379
+
380
+ Para quem não configura janela nenhuma, o loop grava exatamente os mesmos checkpoints de sempre: o
381
+ bloco inteiro é pulado. Para quem configura, um run já em voo continua replayando contra o
382
+ `history:window` que o histórico dele guarda — a decisão de qual forma seguir sai do journal
383
+ (`ctx.patched('agent:history-select')`), nunca da versão do código do processo que está replayando.
384
+
385
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O servidor MCP deixava de fora os dois portões que o loop aplica.
386
+
387
+ `tools/call` chamava `registry.invoke` direto, e `invoke` não conhece nem a `allowedTools` deste
388
+ servidor nem o `kind` da tool. Três consequências, a primeira séria:
389
+
390
+ - **Uma tool `action` executava sem aprovação humana.** No loop ela é HITL-gated: alguém aprova
391
+ antes de rodar. Via MCP não há humano nenhum, e o handler rodava assim mesmo. Como o default de
392
+ `roles` é ADMIN-only, a política de papéis era o único gate entre um chamador remoto e qualquer
393
+ efeito colateral registrado.
394
+ - **Tools servidas pelo loop** (`agent`, `ask`, `skill`, `memory`) apareciam na listagem. Uma tool de
395
+ handoff é registrada com handler-stub porque quem delega é o `AgentLoop` — chamá-la responderia
396
+ `{}` e não delegaria a ninguém.
397
+ - **A `allowedTools` só valia na listagem.** Quem adivinhasse um nome alcançava uma tool que a
398
+ implantação tirou da superfície de propósito.
399
+
400
+ Agora `isExposable` decide igual na listagem e na chamada: `action` fora por default, com
401
+ `actions: 'execute'` como opt-in nomeado — uma implantação dizendo que aceita um ator não aprovado
402
+ rodando toda `action` que os papéis dele alcançam. Kinds servidos pelo loop nunca são expostos, nem
403
+ sob o opt-in. E a allow-list é re-checada na chamada.
404
+
405
+ `createMcpServer` não tinha teste nenhum, que é como isso passou. Tem agora, contra um `Client` MCP
406
+ real, e cada portão foi revertido para ver o buraco reabrir.
407
+
408
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Memória de trabalho: o que o assistente concluiu sobre uma pessoa e sobre a organização dela,
409
+ atravessando turnos e threads.
410
+
411
+ Retrieval responde "o que os documentos dizem"; memória responde "o que eu decidi sobre você". Uma
412
+ passagem é conteúdo que alguém escreveu e pode corrigir na fonte, e vai citada. Uma memória não tem
413
+ fonte para consertar: é inferência do próprio agente. Por isso todo registro carrega um
414
+ `MemoryOrigin`, por isso o bloco diz ao modelo que aquilo pode estar errado, e por isso `forget` é
415
+ OBRIGATÓRIO no provider enquanto `write` é opcional — um deployment pode razoavelmente popular
416
+ memória pelo próprio pipeline e não dar tool de escrita ao agente; nenhum pode razoavelmente guardar
417
+ conclusões sobre uma pessoa que a pessoa não consiga apagar.
418
+
419
+ **Os mesmos tokens de escopo e o MESMO `ScopeResolver` das skills**, para um deployment ter UMA
420
+ resposta a "quais escopos este ator tem". Onde memória difere de skill de verdade, a diferença fica:
421
+ a entrada carrega o VALOR derrotado e não só o escopo dele (um agente que soubesse apenas que existe
422
+ um valor mais amplo não consegue dizer ao usuário qual é a diferença — só escolher, em silêncio, que
423
+ é exatamente o que isto previne), e existe superfície HTTP de leitura e delete: `GET <path>/memories`
424
+ e `DELETE <path>/memories/:id`. O autor de uma skill sabe que ela existe; o sujeito de uma memória
425
+ não sabe. A leitura ignora `maxMemories` de propósito — aquele teto é orçamento de UM turno, e
426
+ aplicá-lo ali significaria que alguém não consegue ver, e portanto apagar, uma crença que o
427
+ assistente está a uma escrita de usar de novo.
428
+
429
+ **Seleção por relevância (`MemoryProvider.search`).** O orçamento do prompt é limitado; o store não
430
+ é. Escolher o bloco por ESCOPO mata os escopos mais amplos primeiro: a vigésima anotação de uma
431
+ pessoa acaba com toda chance que os fatos da organização dela tinham — em silêncio, atrás de um
432
+ `omitted` diferente de zero. O escopo continua sendo filtro duro que corta ANTES do ranking, e deixa
433
+ de ser o ranking. O host é dono do índice (omita `search` e todo turno é a leitura escopada de
434
+ sempre, sem mudança nenhuma); a lib é dona de resolução, precedência, orçamento e journal. A busca
435
+ roda DENTRO de `memory:digest`, então todo replay lê de volta a seleção que a primeira tentativa fez.
436
+
437
+ **`pinned` é categórico, não prioridade numérica.** Um número infla, não carrega significado
438
+ revisável e compete com relevância, que já é uma ordenação contínua. Categoria vira orçamento: o
439
+ pinned sai de `maxMemories` primeiro, e o que transborda é reportado como `pinnedOmitted` — porque
440
+ essa omissão é erro de configuração, não o orçamento fazendo o trabalho dele. Um agente não consegue
441
+ pinar as próprias escritas: `StoreMemoryInput` não tem esse campo, a mesma imposição por forma que
442
+ mantém `scope` fora da tool `remember`.
443
+
444
+ **O bloco é enquadrado por `origin.author`, em duas seções.** Uma memória de escopo amplo
445
+ normalmente foi PUBLICADA por um administrador, não concluída pelo agente. Um enquadramento único
446
+ sobre o bloco inteiro mandava o modelo tratar uma decisão organizacional como palpite próprio e
447
+ "preferir o que o usuário diz agora" a respeito dela — o que entrega a qualquer usuário um override
448
+ da política da empresa por simples afirmação. O que uma pessoa afirmou é instrução; o que o agente
449
+ concluiu é hipótese.
450
+
451
+ A tool `remember` não é registrada, gasta os checkpoints de um `read` e é autorizada contra os
452
+ escopos que `memory:digest` gravou — nunca contra uma resolução nova. A escrita acontece DENTRO do
453
+ `tool:<callId>`, o que a torna idempotente sob replay e põe no journal o que o modelo foi informado
454
+ sobre ela. Um turno sem `memory` configurado tem sequência de checkpoints idêntica à de um que nunca
455
+ teve a opção.
456
+
457
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `StoredMessage` passa a carregar o `runId` (e a `persona`) que a mensagem foi gravada com.
458
+
459
+ `AppendMessageInput.runId` já existia e os dois stores já gravavam a coluna `run_id`, mas nada
460
+ devolvia o valor: `getThread` não trazia o campo e o store em memória guardava a correlação num mapa
461
+ lateral `messageId → runId`. Ou seja, quem lê uma thread só conseguia adivinhar a que turno cada
462
+ mensagem pertence comparando timestamps contra o `startedAt` do run — e essa comparação quebra no
463
+ momento em que um turno é regerado: a regeneração trunca a resposta substituída e responde de novo à
464
+ mensagem de usuário SOBREVIVENTE, sem acrescentar uma nova, então andar para a frente no tempo
465
+ entrega ao run antigo o texto da substituição.
466
+
467
+ Junto veio uma auditoria do contrato inteiro: **tudo que `appendMessage` aceita tem que voltar em
468
+ `getThread`**. Um campo que o adapter aceita e nunca devolve é invisível até alguém reabrir a thread e
469
+ não achar mais o anexo — nada falha, nada é logado. `attachments` já fazia o round-trip nos dois
470
+ stores daqui; `persona` não fazia em nenhum dos dois, e agora faz. O teste novo é tipado
471
+ `Required<Omit<AppendMessageInput, …>>`, então um campo opcional novo no input não compila até ser
472
+ listado ali.
473
+
474
+ Nenhuma mudança de schema: as colunas `run_id` e `persona` de `agent_message` já existiam.
475
+
476
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Deixar o agente fazer uma pergunta estruturada ao usuário — e esperar.
477
+
478
+ A única forma de um run parar por causa de uma pessoa era `awaitApproval`: um sim/não sobre uma tool
479
+ call já proposta, no meio do trabalho. A direção contrária não existia — coletar o ESCOPO, antes do
480
+ trabalho, enquanto mudar de rumo ainda é barato. Agora duas superfícies fazem isso, e foram construídas
481
+ para serem indistinguíveis lá na frente.
482
+
483
+ **Um intake configurado.** `AgentLoopDeps.intake` declara as perguntas; o turno passa por elas antes da
484
+ primeira chamada de modelo. Como as perguntas são AUTORADAS, o intake não custa chamada de modelo
485
+ nenhuma e não grava linha de uso — e `questions.length` é conhecido antes do formulário aparecer, que é
486
+ a única forma honesta de um cliente renderizar "Pergunta 1 de 3" em vez de descobrir uma quarta no meio
487
+ do caminho. `when: 'thread-start'` (default) pergunta uma vez por thread; `'every-turn'`, antes de cada
488
+ turno.
489
+
490
+ **Um `ask` chamável pelo modelo.** `ask: true` oferece ao modelo uma tool embutida `ask` para o caso que
491
+ um intake não consegue antecipar. O input schema dela EXIGE um `defaults` já escolhido em toda pergunta:
492
+ "eu já escolhi o que eu escolheria, então confirmar basta" é a afirmação em que essa superfície inteira
493
+ se apoia, e um schema é o único lugar onde isso vira obrigatório em vez de aspiração. Um conjunto de
494
+ perguntas malformado volta como falha de tool comum carregando as issues de validação, então o modelo
495
+ conserta o próprio erro em vez de derrubar o run ou estacionar uma pessoa.
496
+
497
+ **Uma forma só, um caminho de retomada só.** As duas gravam UMA linha de tool call pendente chamada
498
+ `ask` (`toolType: 'action'`, `status: 'pending_approval'`, então ela aparece na caixa de aprovações que
499
+ já existe), as duas emitem o mesmo frame novo de stream `elicitation`, e as duas estacionam no mesmo
500
+ sinal `tool:<runId>:<callId>` em que uma aprovação HITL já espera. `POST /agent/tool-call/answer` e
501
+ `/skip` espelham `approve`/`reject`, com a mesma checagem de dono. `AgentLoopHooks` ganha um
502
+ `awaitAnswers` opcional; um host que só implementou `awaitApproval` ainda conclui uma elicitation, lendo
503
+ approve como "confirmou as respostas pré-escolhidas" e reject como "pulou".
504
+
505
+ **Uma pergunta omitida assume o próprio default**, resolvido no servidor contra o request que o run já
506
+ tem em mãos e não no cliente — então "só apertou enter" e "escolheu exatamente os defaults" persistem
507
+ igual, e um cliente que nunca renderizou os defaults não consegue submeter em branco. Um array PRESENTE e
508
+ vazio é um "nenhuma dessas" explícito e não cai no default. A linha assentada grava `defaulted: string[]`,
509
+ então um auditor ainda enxerga quais perguntas um humano tocou. **Pular não é confirmar:** cai nos mesmos
510
+ valores e persiste como `rejected` em vez de `executed`, porque seguir com uma suposição que a pessoa se
511
+ recusou a confirmar é um fato diferente de seguir com uma que ela escolheu. Ninguém responder estaciona o
512
+ run indefinidamente, exatamente como uma aprovação — não existe timeout de intake, porque um timeout que
513
+ aplicasse os defaults fabricaria consentimento a partir do silêncio.
514
+
515
+ `ToolKind` ganha um quarto membro, `'ask'`. Nenhum `ToolSpec` o carrega: `ask` nunca é registrada, não tem
516
+ handler, e é oferecida ao modelo direto da config do módulo — então o branch que decide se uma call
517
+ estaciona numa pessoa nunca pode ser resolvido por um lookup de registry local do processo. Como nos
518
+ outros kinds, o valor é resolvido DENTRO do checkpoint `persist:toolcall:<callId>` que já existia e é
519
+ lido de volta dali em todo replay.
520
+
521
+ **Checkpoints.** Um intake gasta uma posição para o veredito (`intake:ask`) mais uma nos turnos em que
522
+ pergunta (`intake:answers`); um `ask` reusa os nomes do caminho de aprovação e acrescenta um
523
+ (`stream:elicitation:<id>`). O veredito do intake é DEVOLVIDO por `intake:ask` em vez de recalculado,
524
+ porque quando uma retomada replaya o turno a primeira tentativa já anexou a mensagem do próprio intake na
525
+ thread — recalcular "esta thread já foi perguntada?" responderia não na ida e sim na volta, e poria a
526
+ chamada de modelo onde o histórico guarda a espera. Nenhum marcador `patched` é gasto por nenhuma das
527
+ duas: um intake só é alcançável por config nova e um `ask` só por um kind gravado que nenhum run
528
+ existente registrou. Não declarar nenhuma das duas e a sequência de checkpoints do turno fica idêntica.
529
+
530
+ **Uma diferença estrutural em relação ao porte de referência:** este store não tem
531
+ `setMessageToolResults`, então as respostas assentadas ficam na linha da tool call — que é onde este repo
532
+ já guarda todo resultado de tool — em vez de também serem anexadas na mensagem. A transcrição que o modelo
533
+ lê carrega o round-trip completo de qualquer jeito. `StreamFrame` ganhou uma variante tipada
534
+ (`{ t: 'elicitation', id, request }`) e `frameToSse` a serializa como `event: elicitation`; o envelope de
535
+ um frame de texto continua byte-idêntico.
536
+
537
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Processadores de entrada e de saída — um seam de cada lado da chamada de modelo, com custo proporcional.
538
+
539
+ O `PromptBuilder` conseguia acrescentar ao system prompt e nada conseguia olhar a resposta. Para uma
540
+ aplicação que roda SQL gerado sobre dados sensíveis isso é um buraco no controle, não uma conveniência
541
+ faltando: o único lugar onde a saída do modelo ainda pode ser barrada é entre o provider e o leitor, e
542
+ esse lugar não existia.
543
+
544
+ `AgentLoopDeps.inputProcessors` / `outputProcessors` criam os dois. Um `InputProcessor` reescreve
545
+ `{ system, messages }` antes de TODA chamada de modelo do turno — toda chamada, não uma por run, porque
546
+ a transcrição cresce entre os passos e um redator que só viu o prompt de abertura deixaria passar o que
547
+ um resultado de tool trouxe de volta. Um `OutputProcessor` vê a resposta de cada passo e devolve `pass`,
548
+ `replace` (uma redação é uma substituição) ou `reject`, que encerra o run com `OutputRejectedError` em vez
549
+ de uma resposta. Os dois são globais do módulo e valem para todo agente e persona: um controle do qual
550
+ uma persona pode sair não é um controle.
551
+
552
+ **Não são um segundo `HistoryWindow`.** Seleção — quais mensagens da thread entram no turno — continua
553
+ sendo do `historyWindow`, que é puro e roda FORA de qualquer checkpoint. Processadores transformam o que a
554
+ seleção produziu e rodam DENTRO de um, então podem chamar um modelo. A transcrição canônica do loop não é
555
+ tocada: uma redação é o que sai do processo, nunca a memória da thread sobre o que foi dito — e dois
556
+ passos de um mesmo turno nunca compõem a reescrita um do outro.
557
+
558
+ **Registrar um processador de saída tira a chamada de modelo do sink ao vivo, e o preço disso é
559
+ proporcional ao que a cadeia declara.** Um gate que precisa ler a resposta inteira não pode rodar depois
560
+ que ela já chegou ao leitor, então o turno escreve num buffer e o loop solta o conteúdo — como UM frame
561
+ `text` — quando a cadeia passa. Isso é a resposta certa para uma passada de moderação e caro demais para um
562
+ redator de regex que não precisa da resposta toda:
563
+
564
+ ```ts
565
+ const redactEmails: OutputProcessor = {
566
+ name: 'redact-emails',
567
+ incremental: { lookbackChars: 320 },
568
+ process: (answer) => ({ action: 'replace', text: answer.text.replace(EMAIL, '[email]') }),
569
+ }
570
+ ```
571
+
572
+ **Não declarar continua significando resposta inteira**, e uma cadeia só é incremental quando TODO membro
573
+ declara. Quem escreveu `process` contra o texto completo nunca é rebaixado porque um vizinho aderiu.
574
+ Declarar `incremental` é uma promessa sobre todo prefixo: a cadeia vê o PREFIXO que cresce (nunca cada
575
+ frame novo), então sempre recebe texto bem formado; fora dos últimos `lookbackChars` caracteres da própria
576
+ saída, um `replace` não muda mais conforme o prefixo cresce; e uma recusa promete ser decidível a partir de
577
+ um prefixo. `lookbackChars` é por processador (default 64) e o gate usa o maior da cadeia.
578
+
579
+ A passada de resposta inteira continua AUTORITATIVA para o stream e para o store — a liberação incremental
580
+ só adianta o prefixo. O gate depois confere que a resposta assentada `startsWith` o que já foi liberado e
581
+ levanta `ProcessorFailedError` se não for, de modo que a concordância entre o que foi transmitido e o que
582
+ foi gravado é estrutural, e uma janela curta demais para um padrão falha alto em vez de transmitir
583
+ justamente o texto que ela existia para redigir.
584
+
585
+ **Determinismo.** `process:input:<step>` e `process:output:<step>` só existem quando configurados, então
586
+ quem não registra nada grava exatamente os mesmos checkpoints de sempre. O buffer, o prefixo já liberado e
587
+ uma recusa vinda de prefixo viajam no CHECKPOINT `llm:<step>`, não numa variável local: um run que suspende
588
+ entre a chamada de modelo e o gate retoma num processo que nunca viu aquele stream, e a liberação é
589
+ calculada a partir do `releasedText` gravado — então a retomada emite só a cauda que ainda deve, em vez de
590
+ despejar a mesma resposta uma segunda vez, mesmo que a cadeia tenha sido redeclarada no meio. Uma recusa só
591
+ é levantada DEPOIS de `persist:usage:<step>` e `quota:bump:<step>`: aqueles tokens foram gastos de verdade, e
592
+ um gate que escondesse o próprio custo deixaria uma cadeia mal calibrada queimar um orçamento invisivelmente.
593
+
594
+ **Uma diferença estrutural em relação ao porte de referência:** o sink daqui carrega `StreamFrame` tipado,
595
+ não bytes, então não há frame que o gate não consiga classificar — e o buffer viaja num checkpoint como
596
+ está. Em compensação, as tool calls de um passo só são reportadas quando `runTurn` RETORNA, então no caminho
597
+ incremental `ModelAnswer.toolCalls` fica vazio até a passada autoritativa, que sempre as vê.
598
+
599
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Restringir a resposta de um turno a um schema.
600
+
601
+ Toda resposta que esta biblioteca produzia era texto livre, então a única forma de tirar um valor
602
+ tipado de um turno era declarar uma TOOL cujo trabalho inteiro era receber esse valor.
603
+
604
+ `AgentLoopDeps.outputSchema` aceita qualquer [Standard Schema](https://standardschema.dev) (Zod,
605
+ Valibot, ArkType). O valor validado volta como `object` no resultado do run, tipado quando o loop é
606
+ chamado direto (`runAgentLoop<T>`), e é gravado na mensagem do assistente como uma tool call sintética
607
+ `structured_output` — o mesmo dispositivo que o retrieval em modo inject já usa, então ele persiste e
608
+ renderiza sem nenhum store ganhar coluna. É declarado no AGENTE e não por requisição porque um schema é
609
+ um objeto vivo e `AgentRunInput` atravessa uma fronteira JSON a caminho de um workflow durable.
610
+
611
+ **Como compõe com tool calling: como uma passada de formatação separada, sempre.** O turno roda a
612
+ iteração modelo→tools exatamente como rodaria sem schema; quando um passo volta sem tool calls, uma
613
+ chamada extra não-streamada (`structured:<step>:<attempt>`, `tools: []`, `outputSchema` setado)
614
+ reescreve aquela resposta no formato do schema. A maioria dos providers não serve response format e
615
+ tool set no mesmo request. Pular a passada para um agente que por acaso não tem tools seria mais barato
616
+ e deliberadamente NÃO é feito: essa decisão leria o registry de tools de qualquer processo que estivesse
617
+ replayando, que é exatamente como um run retomado acaba pedindo uma posição de checkpoint que o
618
+ histórico dele não tem. Então a passada é incondicional, e custa uma chamada de modelo por turno,
619
+ faturada na própria linha de uso `structured_output`.
620
+
621
+ **A passada é uma tradução, então ela recebe o que uma tradução precisa:** a pergunta e a resposta que
622
+ sobreviveu ao gate de saída — nunca a transcrição inteira do turno, que seria o prompt do turno de novo
623
+ e sem desconto nenhum (a passada troca o bloco `system` pela instrução do schema, e o bloco `system` é o
624
+ prefixo do cache). A pergunta sai do prompt PROCESSADO, não de `AgentRunInput.userText`: esta é uma
625
+ segunda saída do modelo e tem que ficar atrás da mesma cadeia de `inputProcessors` que o turno
626
+ transmitido ficou. `outputFromTranscript` devolve o comportamento antigo para o agente cuja resposta
627
+ genuinamente não pode ser reescrita a partir das próprias palavras.
628
+
629
+ **Uma resposta que falha o schema é um desfecho DEFINIDO.** Até `outputRepairAttempts` chamadas a mais
630
+ (default 1) repetem o pedido com as issues de validação da tentativa anterior anexadas; depois disso o
631
+ run falha com `StructuredOutputError` carregando as issues, o texto que as violou e a contagem de
632
+ tentativas. Limitado porque um modelo que não consegue satisfazer um schema normalmente também não
633
+ consegue na quarta tentativa, e toda tentativa é cobrada. `outputRepairAttempts: 0` falha na primeira
634
+ resposta inválida.
635
+
636
+ `ModelTurnArgs` ganha `outputSchema` e `ModelTurnResult` ganha `object`. O adaptador do AI SDK mapeia o
637
+ schema em `output: Output.object(...)` do `streamText` para o provider restringir a geração, e devolve o
638
+ valor que ele mesmo parseou — mas o loop valida de qualquer jeito. "O provider disse que bate" não é a
639
+ mesma afirmação que "bate", e um provider que ignorou o schema tem que falhar onde a falha é reparável,
640
+ não lá na frente. Um adaptador que não consegue restringir a geração continua funcionando: o loop lê o
641
+ JSON do texto da resposta, cercas e prosa de abertura incluídas.
642
+
643
+ `UsagePurpose` ganha `'structured_output'`; os dois stores já gravam `purpose` como texto, então não há
644
+ mudança de schema. Quem não declara `outputSchema` não vê checkpoint novo, chamada extra nem mudança
645
+ nenhuma na sequência de checkpoints do loop.
646
+
647
+ **Uma diferença estrutural em relação ao porte de referência:** o loop daqui devolve os resultados de
648
+ tool ao modelo como uma mensagem `user` VAZIA carregando `toolResults`, então "a última mensagem do
649
+ usuário" não serve para achar a pergunta — a passada receberia uma pergunta em branco em todo turno que
650
+ chamou uma tool. `restatementPrompt` procura a última mensagem de usuário que de fato diz alguma coisa.
651
+
652
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O resultado de uma tool passa a chegar em quem reabre a thread.
653
+
654
+ O loop escrevia a saída de cada tool só na tabela de tool calls. Quem lê uma thread pareia uma call
655
+ com o resultado dela pela MENSAGEM em que a call foi feita — então toda tool de todo turno já
656
+ encerrado aparecia como uma tool ainda rodando, para sempre, nos dois stores. Nada falhava, nada era
657
+ logado: a transcrição simplesmente mentia sobre o estado de um turno que terminou.
658
+
659
+ `AgentStore.setMessageToolResults(messageId, results)` anexa os resultados já liquidados à mensagem
660
+ que fez as calls, substituindo o que ela tinha. É **obrigatório** na SPI, não opcional: um store que
661
+ silenciosamente não implementasse isso renderizaria um turno terminado como um turno eternamente em
662
+ voo, e um método faltando tem que quebrar a compilação em vez de quebrar a tela. `LucidAgentStore` e
663
+ `InMemoryAgentStore` implementam os dois.
664
+
665
+ Uma escrita, um comportamento, os dois adapters — não duas implementações que por acaso concordam.
666
+
667
+ O loop escreve num checkpoint próprio (`persist:toolresults:<step>`), depois da última tool do turno.
668
+ Isso é uma posição nova no journal, então ela é guardada por `ctx.patched('agent:message-tool-results')`:
669
+ um run que suspendeu no meio de um turno sob a forma anterior não tem espaço entre o último
670
+ `persist:toolexec` e o `llm:` seguinte, lê o marcador como ausente e continua replayando a forma que o
671
+ histórico dele guarda. Todo valor escrito ali já vem de um checkpoint acima, então um replay grava a
672
+ mesma lista.
673
+
674
+ O intake configurado ganha o mesmo tratamento dentro de `intake:answers`: o checkpoint `intake:ask`
675
+ agora devolve o id da mensagem que perguntou, e a resposta liquidada (ou o skip) pousa nela. Um
676
+ checkpoint que gravou apenas SE o intake rodou não traz id, e só nesse caso a linha de tool call
677
+ continua sendo o único registro.
678
+
679
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Retrieval em modo inject e a resposta estruturada passam a ser entregues como tool calls comuns.
680
+
681
+ As duas já eram gravadas como tool calls sintéticas, mas só como LINHA na tabela de tool calls: a
682
+ call não estava na mensagem e o resultado não estava em lugar nenhum dela. Como um leitor de thread
683
+ pareia call e resultado pela mensagem, nenhuma das duas aparecia para nenhum cliente — nem ao vivo,
684
+ nem ao reabrir a conversa. Citações de um retrieval injetado e o valor validado de um `outputSchema`
685
+ existiam no banco e não existiam na tela.
686
+
687
+ Agora as duas sobem na mensagem do assistente a que pertencem, com a call em `toolCalls` e o
688
+ resultado em `toolResults`, exatamente como uma tool `read` que o modelo tivesse pedido. Um cliente
689
+ que já renderiza tool call renderiza essas duas sem mudança nenhuma.
690
+
691
+ O id de cada uma sai do RUN (`retrieve-<runId>`, `structured-<runId>`) e não da mensagem: a mensagem
692
+ ainda não existe quando o par é montado, e as duas precisam estar no append — uma call anexada
693
+ depois renderiza como tool ainda rodando, que é o problema que isto resolve. Os checkpoints
694
+ (`persist:retrieval:<messageId>`, `persist:structured:<messageId>`) mantêm nome e posição, e todo
695
+ valor gravado ali já vem de um checkpoint anterior do mesmo turno, então um replay remonta o mesmo
696
+ par em vez de cunhar um novo.
697
+
698
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Skills: procedimentos autorais que o modelo busca quando a tarefa pede, escopados por tokens que o
699
+ host define.
700
+
701
+ Uma skill NÃO é um agente. Um agente é QUEM responde — prompt, tools, janela de histórico, schema de
702
+ saída. Uma skill é COMO uma tarefa específica é feita, e qualquer agente pode puxar uma. Por isso uma
703
+ skill não carrega modelo, nem lista de tools, nem schema: no instante em que carregasse, as duas
704
+ seriam a mesma coisa com nomes diferentes e o consumidor teria que escolher entre elas por razões que
705
+ ninguém saberia enunciar.
706
+
707
+ **O escopo é um token OPACO, nunca um enum.** `actor:u1`, `tenant:base-7`, `global`, ou o
708
+ `sector:logistics` do próprio host. Um `ScopeResolver` fornecido pelo host diz quais se aplicam, do
709
+ mais específico para o mais amplo, e essa ORDEM é a precedência. Um enum aqui faria de cada eixo novo
710
+ (setor, esquadrão, base, turno) uma migração numa biblioteca que não tem por que saber que eles
711
+ existem; um token é uma string que o host cunha sozinho. Esta biblioteca é dona do contrato (o que um
712
+ escopo significa, como a precedência funciona, o que é journalado); o host é dono das linhas.
713
+
714
+ **O que custa ao prompt: uma linha por skill.** O bloco `<skills>` carrega nome, escopo e descrição —
715
+ nunca o corpo. O CORPO chega como resultado de tool, na transcrição, onde o `HistoryWindow` já
716
+ governa. Então skills não viram um quarto competidor pelo bloco `system`, e um deployment com
717
+ cinquenta skills paga cinquenta linhas mais os corpos que o turno de fato pediu para ler.
718
+
719
+ **Os dois valores saem do journal.** Os escopos resolvidos e o catálogo que sobreviveu à precedência
720
+ são UM checkpoint (`skills:catalog`); o corpo carregado sai de dentro do `tool:<callId>`. Um replay
721
+ que relesse o provider comporia um prompt DIFERENTE a partir de uma skill editada no meio, numa
722
+ posição de transcrição que o histórico já guarda. O catálogo journalado também é a fronteira de
723
+ autorização: um nome que o turno não foi oferecido é recusado ali, então um modelo que inventa um
724
+ nome não alcança um corpo por um provider que serviria de bom grado.
725
+
726
+ A tool `skill` não é registrada — não tem handler, exatamente como `ask` — e gasta os checkpoints de
727
+ um `read`, exatamente: `persist:toolcall`, `tool:<id>`, `persist:toolexec`/`persist:toolfail`. Um
728
+ turno sem `skills` configurado tem sequência de checkpoints idêntica à de um que nunca teve a opção.
729
+
730
+ `withAskTool` virou `withBuiltInTools({ tools, ask, skills })`, que é onde as duas built-ins entram na
731
+ lista do turno.
732
+
733
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - As tool calls `read` de um mesmo turno passam a rodar em paralelo.
734
+
735
+ Um modelo rotineiramente pede várias tools de uma vez, e o loop executava uma depois da outra — duas
736
+ leituras independentes de três segundos custavam seis. Agora elas se sobrepõem, e o turno custa a
737
+ chamada mais lenta em vez da soma.
738
+
739
+ O que fez disso um problema de determinismo, e não um `Promise.all`, é que o corpo do loop é
740
+ replayado pelo engine durable, que distribui posições de checkpoint por um contador monotônico
741
+ conforme o corpo roda. Intercalar blocos inteiros por chamada ordenaria essas posições por quem
742
+ terminasse primeiro, o que difere entre um run e seu replay.
743
+
744
+ Então só as INVOCAÇÕES se sobrepõem. Os `persist:toolcall` antes delas e os
745
+ `persist:toolexec`/`persist:toolfail` depois continuam estritamente sequenciais, em ordem de
746
+ chamada, e as invocações são todas lançadas no mesmo tick — `ctx.localStep` pega sua posição na
747
+ CHAMADA, antes do primeiro `await`, então o bloco fica fixado em ordem de chamada independentemente
748
+ de como as tools terminem. Um turno só é elegível quando o kind journalado de TODA chamada é `read`:
749
+ uma `action` suspende numa aprovação humana (paralelismo não compra nada, e reservar uma posição de
750
+ invocação para uma chamada que pode ser REJEITADA gasta uma posição que o branch rejeitado nunca
751
+ preenche), e uma delegação `agent` é `ctx.child`, cuja forma paralela é o `ctx.all` do próprio
752
+ runtime.
753
+
754
+ Dois `AgentLoopHooks` opcionais novos:
755
+
756
+ - `parallel(tasks)` — roda as tasks concorrentemente e resolve quando TODAS assentaram, resultados em
757
+ ordem de entrada. Ausente, o loop segue sequencial, que é a resposta honesta para um runner que
758
+ atribui posições em qualquer outro momento que não a chamada. `settleAll` é a implementação que os
759
+ dois runners embarcados passam. Esperar todas é carga estrutural: um runner durable desenrola um
760
+ turno lançando, e uma irmã abandonada no meio do próprio step é uma tool que ninguém roda.
761
+ - `patched(id)` — o portão de versão do runner (`ctx.patched`). O batching move os `persist:toolcall`
762
+ para antes da primeira execução, então um run que suspendeu no meio de um turno sob a forma antiga
763
+ continua replayando contra ela.
764
+
765
+ Um turno com menos de duas chamadas, ou de um runner que não optou por isso, grava exatamente a
766
+ sequência de checkpoints que sempre gravou.
767
+
768
+ Uma falha de controle de fluxo do runner (`hooks.isControlFlowError`) também deixou de virar
769
+ `persist:toolfail`: como uma recusa de integridade de replay, ela sobe intacta, antes de qualquer
770
+ persistência.
771
+
772
+ ### Patch Changes
773
+
774
+ - [#117](https://github.com/DavideCarvalho/adonis-agora-agent/pull/117) [`3ba0ea8`](https://github.com/DavideCarvalho/adonis-agora-agent/commit/3ba0ea8c2cfc289af8ce38d70a452177c3b93eb8) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - O `kind` de uma tool call passa a ser resolvido dentro do checkpoint `persist:toolcall`, não no corpo do loop.
775
+
776
+ O kind decide o fluxo da call — uma `action` suspende o run num sinal de aprovação
777
+ (`tool:<runId>:<callId>`), uma `agent` delega, o resto grava um step — e era lido de
778
+ `deps.registry` no corpo do workflow, ou seja, o branch dependia do registry do processo que por
779
+ acaso rodasse aquele corpo. Um processo cujo registry não tem a tool (um módulo que nunca a
780
+ declarou, uma superfície que não monta tools, uma instância ainda subindo) lia `undefined`, caía
781
+ no default `'read'` e pedia um checkpoint `tool:` onde o histórico guardava o sinal de aprovação —
782
+ recusa de não-determinismo no resume, e uma action gated rodando sem a aprovação de ninguém.
783
+
784
+ A resolução — e os gates de delegação que dependem dela — agora acontece dentro do step
785
+ `persist:toolcall:<callId>` e é retornada dele, então o replay lê o kind gravado em vez de
786
+ perguntar ao próprio registry. Mesmo nome de step na mesma posição do journal, então runs em voo
787
+ continuam replayando; um checkpoint anterior ao valor retornado não traz nada, e só nesse caso o
788
+ registry local volta a ser consultado.
789
+
790
+ Recusas de integridade de replay agora sobem intactas pelo loop e pelo workflow `agora.agent.run`.
791
+ Os dois `catch` reagiam a uma delas escrevendo MAIS checkpoints — um `persist:toolfail`, um
792
+ `persist:run:fail` — e num journal que já divergiu cada um deles pede uma posição que o histórico
793
+ não tem, então a tentativa de recuperação levantava a própria recusa e era ESSA que aparecia: uma
794
+ mensagem apontando o seq errado e nomeando checkpoints do caminho de recuperação, não os dois que
795
+ de fato discordaram. O workflow ainda fecha o stream, para o subscriber não ficar pendurado num run
796
+ que o engine está prestes a falhar. `isReplayIntegrityError` é exportado do pacote.
797
+
3
798
  ## 0.31.0
4
799
 
5
800
  ### Minor Changes