@adonis-agora/agent 0.31.0 → 0.33.0

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