@earendil-works/pi-durable 1.3.0 → 1.5.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 (257) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +287 -82
  3. package/dist/codemode/codemode.d.ts +4 -4
  4. package/dist/codemode/codemode.d.ts.map +1 -1
  5. package/dist/codemode/codemode.js +21 -16
  6. package/dist/codemode/codemode.js.map +1 -1
  7. package/dist/codemode/models.d.ts.map +1 -1
  8. package/dist/codemode/models.js +6 -4
  9. package/dist/codemode/models.js.map +1 -1
  10. package/dist/documents.d.ts +1 -1
  11. package/dist/documents.d.ts.map +1 -1
  12. package/dist/documents.js.map +1 -1
  13. package/dist/env/index.d.ts +12 -10
  14. package/dist/env/index.d.ts.map +1 -1
  15. package/dist/env/index.js.map +1 -1
  16. package/dist/env/node-watch.d.ts +3 -3
  17. package/dist/env/node-watch.d.ts.map +1 -1
  18. package/dist/env/node-watch.js +18 -16
  19. package/dist/env/node-watch.js.map +1 -1
  20. package/dist/env/node.d.ts +2 -2
  21. package/dist/env/node.d.ts.map +1 -1
  22. package/dist/env/node.js +12 -4
  23. package/dist/env/node.js.map +1 -1
  24. package/dist/errors.d.ts +18 -2
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +26 -3
  27. package/dist/errors.js.map +1 -1
  28. package/dist/harness/agent.d.ts +13 -2
  29. package/dist/harness/agent.d.ts.map +1 -1
  30. package/dist/harness/agent.js +48 -6
  31. package/dist/harness/agent.js.map +1 -1
  32. package/dist/harness/compaction.d.ts +3 -1
  33. package/dist/harness/compaction.d.ts.map +1 -1
  34. package/dist/harness/compaction.js +38 -6
  35. package/dist/harness/compaction.js.map +1 -1
  36. package/dist/harness/context.d.ts +23 -7
  37. package/dist/harness/context.d.ts.map +1 -1
  38. package/dist/harness/context.js +71 -25
  39. package/dist/harness/context.js.map +1 -1
  40. package/dist/harness/define.d.ts +7 -3
  41. package/dist/harness/define.d.ts.map +1 -1
  42. package/dist/harness/define.js +4 -0
  43. package/dist/harness/define.js.map +1 -1
  44. package/dist/harness/generation.d.ts +38 -5
  45. package/dist/harness/generation.d.ts.map +1 -1
  46. package/dist/harness/generation.js +497 -108
  47. package/dist/harness/generation.js.map +1 -1
  48. package/dist/harness/harness.d.ts +7 -2
  49. package/dist/harness/harness.d.ts.map +1 -1
  50. package/dist/harness/harness.js +147 -57
  51. package/dist/harness/harness.js.map +1 -1
  52. package/dist/harness/inbox.d.ts +7 -3
  53. package/dist/harness/inbox.d.ts.map +1 -1
  54. package/dist/harness/inbox.js +7 -4
  55. package/dist/harness/inbox.js.map +1 -1
  56. package/dist/harness/json.d.ts +20 -4
  57. package/dist/harness/json.d.ts.map +1 -1
  58. package/dist/harness/json.js +23 -5
  59. package/dist/harness/json.js.map +1 -1
  60. package/dist/harness/live.d.ts +122 -16
  61. package/dist/harness/live.d.ts.map +1 -1
  62. package/dist/harness/live.js +93 -12
  63. package/dist/harness/live.js.map +1 -1
  64. package/dist/harness/partial-changes.d.ts +54 -0
  65. package/dist/harness/partial-changes.d.ts.map +1 -0
  66. package/dist/harness/partial-changes.js +162 -0
  67. package/dist/harness/partial-changes.js.map +1 -0
  68. package/dist/harness/prompt.d.ts +1 -1
  69. package/dist/harness/prompt.d.ts.map +1 -1
  70. package/dist/harness/prompt.js +1 -1
  71. package/dist/harness/prompt.js.map +1 -1
  72. package/dist/harness/registry-reader.d.ts +11 -0
  73. package/dist/harness/registry-reader.d.ts.map +1 -0
  74. package/dist/harness/registry-reader.js +25 -0
  75. package/dist/harness/registry-reader.js.map +1 -0
  76. package/dist/harness/scheduler.d.ts +86 -14
  77. package/dist/harness/scheduler.d.ts.map +1 -1
  78. package/dist/harness/scheduler.js +1459 -369
  79. package/dist/harness/scheduler.js.map +1 -1
  80. package/dist/harness/submissions.d.ts +23 -11
  81. package/dist/harness/submissions.d.ts.map +1 -1
  82. package/dist/harness/submissions.js +104 -36
  83. package/dist/harness/submissions.js.map +1 -1
  84. package/dist/harness/task-graph.d.ts +28 -8
  85. package/dist/harness/task-graph.d.ts.map +1 -1
  86. package/dist/harness/task-graph.js +85 -36
  87. package/dist/harness/task-graph.js.map +1 -1
  88. package/dist/harness/telemetry.d.ts +307 -0
  89. package/dist/harness/telemetry.d.ts.map +1 -0
  90. package/dist/harness/telemetry.js +136 -0
  91. package/dist/harness/telemetry.js.map +1 -0
  92. package/dist/harness/tool.d.ts +2 -0
  93. package/dist/harness/tool.d.ts.map +1 -1
  94. package/dist/harness/tool.js +223 -61
  95. package/dist/harness/tool.js.map +1 -1
  96. package/dist/harness/transcript.d.ts +72 -0
  97. package/dist/harness/transcript.d.ts.map +1 -0
  98. package/dist/harness/transcript.js +264 -0
  99. package/dist/harness/transcript.js.map +1 -0
  100. package/dist/harness/types.d.ts +302 -48
  101. package/dist/harness/types.d.ts.map +1 -1
  102. package/dist/harness/types.js.map +1 -1
  103. package/dist/harness/util.d.ts +7 -1
  104. package/dist/harness/util.d.ts.map +1 -1
  105. package/dist/harness/util.js +13 -0
  106. package/dist/harness/util.js.map +1 -1
  107. package/dist/harness/view.d.ts +43 -22
  108. package/dist/harness/view.d.ts.map +1 -1
  109. package/dist/harness/view.js +130 -71
  110. package/dist/harness/view.js.map +1 -1
  111. package/dist/index.d.ts +13 -9
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +7 -5
  114. package/dist/index.js.map +1 -1
  115. package/dist/search.d.ts +19 -3
  116. package/dist/search.d.ts.map +1 -1
  117. package/dist/search.js +107 -44
  118. package/dist/search.js.map +1 -1
  119. package/dist/session/cached-storage.d.ts +71 -0
  120. package/dist/session/cached-storage.d.ts.map +1 -0
  121. package/dist/session/cached-storage.js +269 -0
  122. package/dist/session/cached-storage.js.map +1 -0
  123. package/dist/session/conversation-tails.d.ts +33 -0
  124. package/dist/session/conversation-tails.d.ts.map +1 -0
  125. package/dist/session/conversation-tails.js +101 -0
  126. package/dist/session/conversation-tails.js.map +1 -0
  127. package/dist/session/keys.d.ts +68 -0
  128. package/dist/session/keys.d.ts.map +1 -0
  129. package/dist/session/keys.js +161 -0
  130. package/dist/session/keys.js.map +1 -0
  131. package/dist/session/session.d.ts +181 -29
  132. package/dist/session/session.d.ts.map +1 -1
  133. package/dist/session/session.js +1073 -183
  134. package/dist/session/session.js.map +1 -1
  135. package/dist/session/transaction.d.ts +86 -4
  136. package/dist/session/transaction.d.ts.map +1 -1
  137. package/dist/session/transaction.js +250 -38
  138. package/dist/session/transaction.js.map +1 -1
  139. package/dist/session/writer.d.ts +74 -0
  140. package/dist/session/writer.d.ts.map +1 -0
  141. package/dist/session/writer.js +294 -0
  142. package/dist/session/writer.js.map +1 -0
  143. package/dist/storage/indexeddb.d.ts +68 -0
  144. package/dist/storage/indexeddb.d.ts.map +1 -0
  145. package/dist/storage/indexeddb.js +881 -0
  146. package/dist/storage/indexeddb.js.map +1 -0
  147. package/dist/storage/jsonl/storage.d.ts +30 -2
  148. package/dist/storage/jsonl/storage.d.ts.map +1 -1
  149. package/dist/storage/jsonl/storage.js +88 -24
  150. package/dist/storage/jsonl/storage.js.map +1 -1
  151. package/dist/storage/memory.d.ts +7 -2
  152. package/dist/storage/memory.d.ts.map +1 -1
  153. package/dist/storage/memory.js +29 -3
  154. package/dist/storage/memory.js.map +1 -1
  155. package/dist/storage/sqlite/cloudflare.d.ts +2 -51
  156. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
  157. package/dist/storage/sqlite/cloudflare.js +2 -133
  158. package/dist/storage/sqlite/cloudflare.js.map +1 -1
  159. package/dist/storage/sqlite/database.d.ts +1 -36
  160. package/dist/storage/sqlite/database.d.ts.map +1 -1
  161. package/dist/storage/sqlite/database.js.map +1 -1
  162. package/dist/storage/sqlite/index.d.ts +1 -1
  163. package/dist/storage/sqlite/index.d.ts.map +1 -1
  164. package/dist/storage/sqlite/index.js.map +1 -1
  165. package/dist/storage/sqlite/migrations.d.ts +2 -5
  166. package/dist/storage/sqlite/migrations.d.ts.map +1 -1
  167. package/dist/storage/sqlite/migrations.js +28 -28
  168. package/dist/storage/sqlite/migrations.js.map +1 -1
  169. package/dist/storage/sqlite/node.d.ts +2 -37
  170. package/dist/storage/sqlite/node.d.ts.map +1 -1
  171. package/dist/storage/sqlite/node.js +3 -174
  172. package/dist/storage/sqlite/node.js.map +1 -1
  173. package/dist/storage/sqlite/schema.d.ts +1 -3
  174. package/dist/storage/sqlite/schema.d.ts.map +1 -1
  175. package/dist/storage/sqlite/schema.js +2 -9
  176. package/dist/storage/sqlite/schema.js.map +1 -1
  177. package/dist/storage/sqlite/storage.d.ts +15 -3
  178. package/dist/storage/sqlite/storage.d.ts.map +1 -1
  179. package/dist/storage/sqlite/storage.js +110 -36
  180. package/dist/storage/sqlite/storage.js.map +1 -1
  181. package/dist/storage/store/commit.d.ts +33 -0
  182. package/dist/storage/store/commit.d.ts.map +1 -0
  183. package/dist/storage/store/commit.js +286 -0
  184. package/dist/storage/store/commit.js.map +1 -0
  185. package/dist/storage/store/documents.d.ts +43 -0
  186. package/dist/storage/store/documents.d.ts.map +1 -0
  187. package/dist/storage/store/documents.js +102 -0
  188. package/dist/storage/store/documents.js.map +1 -0
  189. package/dist/storage/store/index.d.ts +2 -0
  190. package/dist/storage/store/index.d.ts.map +1 -0
  191. package/dist/storage/store/index.js +2 -0
  192. package/dist/storage/store/index.js.map +1 -0
  193. package/dist/storage/store/queries.d.ts +49 -0
  194. package/dist/storage/store/queries.d.ts.map +1 -0
  195. package/dist/storage/store/queries.js +93 -0
  196. package/dist/storage/store/queries.js.map +1 -0
  197. package/dist/storage/store/storage.d.ts +59 -0
  198. package/dist/storage/store/storage.d.ts.map +1 -0
  199. package/dist/storage/store/storage.js +438 -0
  200. package/dist/storage/store/storage.js.map +1 -0
  201. package/dist/storage/store/tables.d.ts +54 -0
  202. package/dist/storage/store/tables.d.ts.map +1 -0
  203. package/dist/storage/store/tables.js +108 -0
  204. package/dist/storage/store/tables.js.map +1 -0
  205. package/dist/testing/commit-writes.d.ts +5 -0
  206. package/dist/testing/commit-writes.d.ts.map +1 -0
  207. package/dist/testing/commit-writes.js +12 -0
  208. package/dist/testing/commit-writes.js.map +1 -0
  209. package/dist/testing/env-conformance.js +7 -7
  210. package/dist/testing/env-conformance.js.map +1 -1
  211. package/dist/testing/index.d.ts +1 -0
  212. package/dist/testing/index.d.ts.map +1 -1
  213. package/dist/testing/index.js +1 -0
  214. package/dist/testing/index.js.map +1 -1
  215. package/dist/testing/storage-benchmark.d.ts.map +1 -1
  216. package/dist/testing/storage-benchmark.js +18 -17
  217. package/dist/testing/storage-benchmark.js.map +1 -1
  218. package/dist/testing/storage-conformance.d.ts.map +1 -1
  219. package/dist/testing/storage-conformance.js +369 -67
  220. package/dist/testing/storage-conformance.js.map +1 -1
  221. package/dist/timers.d.ts +21 -0
  222. package/dist/timers.d.ts.map +1 -0
  223. package/dist/timers.js +72 -0
  224. package/dist/timers.js.map +1 -0
  225. package/dist/tool-search/tool-search.d.ts +6 -12
  226. package/dist/tool-search/tool-search.d.ts.map +1 -1
  227. package/dist/tool-search/tool-search.js +7 -7
  228. package/dist/tool-search/tool-search.js.map +1 -1
  229. package/dist/tools/bash.d.ts +12 -12
  230. package/dist/tools/bash.d.ts.map +1 -1
  231. package/dist/tools/bash.js +16 -15
  232. package/dist/tools/bash.js.map +1 -1
  233. package/dist/tools/edit.d.ts +7 -7
  234. package/dist/tools/edit.d.ts.map +1 -1
  235. package/dist/tools/edit.js +9 -9
  236. package/dist/tools/edit.js.map +1 -1
  237. package/dist/tools/index.d.ts +32 -32
  238. package/dist/tools/read.d.ts +6 -6
  239. package/dist/tools/read.d.ts.map +1 -1
  240. package/dist/tools/read.js +19 -8
  241. package/dist/tools/read.js.map +1 -1
  242. package/dist/tools/write.d.ts +4 -4
  243. package/dist/tools/write.d.ts.map +1 -1
  244. package/dist/tools/write.js +5 -5
  245. package/dist/tools/write.js.map +1 -1
  246. package/dist/types.d.ts +331 -64
  247. package/dist/types.d.ts.map +1 -1
  248. package/dist/types.js.map +1 -1
  249. package/package.json +27 -10
  250. package/dist/harness/events.d.ts +0 -186
  251. package/dist/harness/events.d.ts.map +0 -1
  252. package/dist/harness/events.js +0 -373
  253. package/dist/harness/events.js.map +0 -1
  254. package/dist/session/observation.d.ts +0 -42
  255. package/dist/session/observation.d.ts.map +0 -1
  256. package/dist/session/observation.js +0 -281
  257. package/dist/session/observation.js.map +0 -1
@@ -1,10 +1,12 @@
1
- import type { AttachedReplicatedState, Context, JsonValue } from "@earendil-works/chord";
2
- import type { AssistantMessage, CacheRetention, Message, Models, ModelThinkingLevel, Static, Tool, ToolCall, ToolResultMessage, Transport, TSchema, Usage, UserMessage } from "@earendil-works/pi-ai";
1
+ import type { Context, JsonValue, WatchHandle } from "@earendil-works/chord";
2
+ import type { Api, AssistantMessage, CacheRetention, ClientIdentity, Message, Model, Models, ModelThinkingLevel, Tool, ToolCall, ToolResultMessage, Transport, Usage, UserMessage } from "@earendil-works/pi-ai";
3
+ import type { Static } from "@earendil-works/pi-schema";
4
+ import type { TelemetryContext } from "@earendil-works/pi-telemetry";
3
5
  import type { ExecutionEnv, ShellOutputSkip, ShellOutputWindow } from "../env/index.ts";
4
- import type { ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Page, Session, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx, WatchHandle } from "../types.ts";
6
+ import type { CommitOptions, ConversationId, ConversationOwnership, ConversationRecord, Cursor, DocumentObserver, DocumentReader, Entry, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Observation, Observer, Page, Report, Session, SetTimer, SubmissionId, SubmissionRecord, Task, TaskId, TaskOptions, TaskRecord, TaskState, Tx, TypedEntry } from "../types.ts";
5
7
  import type { TaskGraph, TaskGraphWatch } from "./task-graph.ts";
6
8
  import type { UsageState } from "./usage.ts";
7
- import type { ConversationView } from "./view.ts";
9
+ import type { ConversationView, ConversationWindow } from "./view.ts";
8
10
  /** Provider and model ID resolved through pi-ai `Models`. */
9
11
  export type ModelRef = {
10
12
  readonly provider: string;
@@ -19,13 +21,23 @@ export type InputEntry = {
19
21
  readonly kind: string;
20
22
  readonly data?: JsonValue;
21
23
  };
22
- /** Host submission: user input that may start a run, or a passive entry write. */
24
+ /**
25
+ * Host submission: user input that may start a run, a passive entry write, or a continue: a run without a new user
26
+ * message, for an idle conversation whose context the model still owes an answer (it ends in a user message or tool
27
+ * results, for example after a run that ended without an answer).
28
+ */
23
29
  export type SubmissionDraft = {
24
30
  readonly requestId?: string;
25
31
  } & ({
26
32
  readonly type: "input";
27
33
  readonly content: UserInput;
28
34
  readonly whenBusy?: "steer" | "followUp" | "reject";
35
+ /**
36
+ * `reject`: an idle conversation (no run) rejects the input with `ConversationIdle` and writes nothing, so the input
37
+ * never starts a run of its own. With `whenBusy: "steer"` it steers the current run or is refused; a run that
38
+ * answered and waits for its `afterRun` tasks is still busy, and such an input queues and starts the next run.
39
+ */
40
+ readonly whenIdle?: "reject";
29
41
  /** Place the input as this entry; default `pi.user` without data. The kind must not start with `pi.`. */
30
42
  readonly entry?: InputEntry;
31
43
  } | {
@@ -33,6 +45,20 @@ export type SubmissionDraft = {
33
45
  readonly entry: EntryDraft;
34
46
  readonly content?: never;
35
47
  readonly whenBusy?: never;
48
+ readonly whenIdle?: never;
49
+ } | {
50
+ /**
51
+ * Start a run on the context as it is, without a user message. Only when idle: a busy conversation rejects it with
52
+ * `ConversationBusy`, and it never queues. Items kept in `pi.inbox` are placed first and join the run. A context
53
+ * with nothing to answer (empty, or ending in an answer, also a text answer cut off at its output limit) ends the
54
+ * run without a request, `unanswered` with `nothing_to_answer`. A cut-off answer with tool calls is continued:
55
+ * its calls never ran, and the context ends in their error results (`missing_result`).
56
+ */
57
+ readonly type: "continue";
58
+ readonly content?: never;
59
+ readonly entry?: never;
60
+ readonly whenBusy?: never;
61
+ readonly whenIdle?: never;
36
62
  });
37
63
  export type InputSubmissionDraft = Extract<SubmissionDraft, {
38
64
  readonly type: "input";
@@ -76,12 +102,16 @@ export type ConversationAbortOptions = {
76
102
  /** Hook handler map declared by a task definition. */
77
103
  export type HooksOf<K> = K extends Task<infer _I, infer _S, infer _R, infer H> ? H : never;
78
104
  /**
79
- * Invocation-bound conversation operations for tasks and tools. Rejects after the invocation ends; passive entries are
80
- * written with ordinary transaction writes instead.
105
+ * Invocation-bound conversation operations for tasks and tools. Rejects after the invocation ends. `submit` takes any
106
+ * submission, in both forms of `Conversation.submit()`: a subagent's conversation is an ordinary one, so a tool may also
107
+ * continue it, and a task may admit a submission in the same commit as its own writes (`runtime.commit()`); entries
108
+ * the admission appends there carry the task's `byTaskId`. The transaction form also rejects once the invocation is
109
+ * aborted.
81
110
  */
82
111
  export interface ConversationHandle {
83
112
  readonly id: ConversationId;
84
- submit(submission: InputSubmissionDraft, context: Context): Promise<Submission>;
113
+ submit(submission: SubmissionDraft, context: Context): Promise<Submission>;
114
+ submit(tx: Tx, submission: SubmissionDraft): Promise<SubmissionId>;
85
115
  /** `Conversation.abort()`: withdraw queued inputs, abort the ordinary ownership scope, and wait until it is idle. */
86
116
  abort(context: Context, options?: ConversationAbortOptions): Promise<void>;
87
117
  /** Resolve when the conversation's ordinary ownership scope has no live non-background task. */
@@ -90,7 +120,12 @@ export interface ConversationHandle {
90
120
  /** Post-tools controls requested by a tool result. */
91
121
  export type ToolControl = {
92
122
  readonly addTools?: readonly string[];
93
- readonly terminate?: true;
123
+ /** End the run without another model request when every result of the round sets it to `true`; `false` is a no-op. */
124
+ readonly terminate?: boolean;
125
+ /**
126
+ * Start a new context with this text as its user message, as `reset(handoff)` writes, and continue the run there.
127
+ * The last handoff in call order wins.
128
+ */
94
129
  readonly handoff?: string;
95
130
  };
96
131
  /** Remark about a call for the model and the UI, such as truncation or a spill path; never part of the tool's data. */
@@ -158,10 +193,12 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
158
193
  readonly registry: RegistrySnapshot;
159
194
  /** The calling conversation's agent, as the tool task's phase resolved it. */
160
195
  agent(context: Context): Promise<Agent>;
161
- /** `HarnessOptions.models`: the catalog, credentials, and request transforms generation uses. */
196
+ /** The current `HarnessOptions.models`, read at each access: the catalog, credentials, and request transforms. */
162
197
  readonly models: Models;
163
198
  /** Built by `HarnessOptions.env` for this call; `undefined` without an environment. */
164
199
  readonly env: ExecutionEnv | undefined;
200
+ /** The Harness clock (`HarnessOptions.now`), for times a rerun after a restart compares against. */
201
+ now(): number;
165
202
  /**
166
203
  * Append running output; it becomes the result's `output` when the result omits it. `skipped` counts output
167
204
  * omitted before the chunk, as reported by an environment given `outputWindow` (`ShellOutputInfo.skipped`).
@@ -185,7 +222,8 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
185
222
  diagnostic(diagnostic: ToolDiagnostic): void;
186
223
  /** Replace running details; the last value becomes the result details when the result omits `details`. */
187
224
  details(value: TDetails, context: Context): Promise<void>;
188
- commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
225
+ /** A commit of this call's task; `options` may defer storing it (`CommitPersistence`). */
226
+ commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context, options?: CommitOptions): Promise<T>;
189
227
  memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
190
228
  memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
191
229
  /**
@@ -231,11 +269,18 @@ export interface ToolExecutionApi<TDetails extends JsonValue = JsonValue> extend
231
269
  readonly progress?: boolean;
232
270
  }): Promise<NestedToolExecutionResult>;
233
271
  }
272
+ /**
273
+ * The arguments type of a tool with parameter schema `TParameters`: pi-schema's `Static<TParameters>` (the builders'
274
+ * type, TypeBox's for the common TypeBox kinds, else `unknown`), and `any` for `ToolRegistration<any>`.
275
+ */
276
+ export type ToolArgs<TParameters> = IsAny<TParameters> extends true ? any : Static<TParameters>;
277
+ type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
234
278
  /**
235
279
  * Executable tool registered in a registry. Only pi-ai `Tool` fields enter the transcript. `args` are typed by
236
- * `parameters`, which the Harness validates them against before `execute()`; `defineTool()` infers both generics.
280
+ * `parameters` (any JSON Schema object; see {@link ToolArgs}), which the Harness validates them against before
281
+ * `execute()`; `defineTool()` infers both generics.
237
282
  */
238
- export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails extends JsonValue = JsonValue> = Tool<TParameters> & {
283
+ export type ToolRegistration<TParameters extends object = object, TDetails extends JsonValue = JsonValue> = Tool<TParameters> & {
239
284
  /** Whether an interrupted execution may rerun on recovery. Default `unsafe`. */
240
285
  readonly replay?: "safe" | "unsafe";
241
286
  /** Default: the settings' `toolExecution`. One sequential call makes its whole round sequential. */
@@ -245,12 +290,12 @@ export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails ext
245
290
  * pure and must not mutate `args`: it runs again when a call is retried before its intent is recorded. Its result is
246
291
  * still validated against `parameters`.
247
292
  */
248
- prepareArguments?(args: unknown): Static<TParameters>;
293
+ prepareArguments?(args: unknown): ToolArgs<TParameters>;
249
294
  /**
250
295
  * Schema of `structuredOutput`, which programs that call the tool receive. Omitted: they receive the result's bounded
251
296
  * output, success or error, as `NestedToolExecutionResult.structuredOutput` describes.
252
297
  */
253
- readonly structuredOutputSchema?: TSchema;
298
+ readonly structuredOutputSchema?: object;
254
299
  /**
255
300
  * Who may call the tool: the model, other tools through `executeTool()`, or both (default). A code mode tool is
256
301
  * `["model"]`, so scripts cannot start scripts; tools behind code mode, such as MCP tools, can be `["tools"]`, so the
@@ -267,7 +312,7 @@ export type ToolRegistration<TParameters extends TSchema = TSchema, TDetails ext
267
312
  readonly maxLines?: number;
268
313
  readonly retain?: "head" | "tail";
269
314
  };
270
- execute(args: Static<TParameters>, api: ToolExecutionApi<TDetails>, context: Context): Promise<ToolExecutionResult<TDetails>>;
315
+ execute(args: ToolArgs<TParameters>, api: ToolExecutionApi<TDetails>, context: Context): Promise<ToolExecutionResult<TDetails>>;
271
316
  };
272
317
  /** Input to system prompt section rendering for one request preparation. */
273
318
  export type PromptInput<Tool extends ToolRegistration = ToolRegistration> = {
@@ -373,6 +418,11 @@ export type AgentState = {
373
418
  instructions?: string;
374
419
  /** Directory within the environment's file system, passed to `HarnessOptions.env`. */
375
420
  cwd?: string;
421
+ /**
422
+ * Tool rounds one run takes at most (`pi.live.run.turns`): the round that reaches it runs its tools, then the run ends
423
+ * `unanswered` with `turn_limit`. An integer, `1` or more; unset, no limit. Task-owned children and forks inherit it.
424
+ */
425
+ maxTurns?: number;
376
426
  };
377
427
  /** A change to `pi.agent`: a given field replaces the stored one, `null` clears it, `undefined` changes nothing. */
378
428
  export type AgentChange = {
@@ -392,6 +442,8 @@ export type AgentChange = {
392
442
  } | null;
393
443
  readonly instructions?: string | null;
394
444
  readonly cwd?: string | null;
445
+ /** `configure()` throws a `RangeError` unless it is an integer `1` or more (or `null`). */
446
+ readonly maxTurns?: number | null;
395
447
  };
396
448
  /** A conversation's agent resolved against a registry snapshot and the settings. */
397
449
  export type Agent<Tool extends ToolRegistration = ToolRegistration> = {
@@ -408,6 +460,11 @@ export type Agent<Tool extends ToolRegistration = ToolRegistration> = {
408
460
  readonly sections: readonly PromptSection<Tool>[];
409
461
  readonly instructions?: string;
410
462
  readonly cwd?: string;
463
+ /**
464
+ * Tool rounds one run takes at most (`pi.live.run.turns`): the round that reaches it runs its tools, then the run ends
465
+ * `unanswered` with `turn_limit`. An integer, `1` or more; unset, no limit. Task-owned children and forks inherit it.
466
+ */
467
+ readonly maxTurns?: number;
411
468
  };
412
469
  /**
413
470
  * Runs inside the creating commit, after the creation hook and the `agent` change. The conversation creation is already
@@ -453,14 +510,28 @@ export type CompactionPolicy = {
453
510
  backgroundTokens: number;
454
511
  };
455
512
  /**
456
- * How often running progress is committed. Each progress commit is a storage write; a host whose storage is remote can
457
- * commit less often, so live answers and tool output appear in larger steps.
513
+ * Harness-wide compaction settings. `modelOverrides` is keyed by exact `"provider/modelId"`; each field it sets
514
+ * replaces the harness-wide one for that model, so small- and large-window models can share a harness.
515
+ */
516
+ export type CompactionSettings = Partial<CompactionPolicy> & {
517
+ readonly modelOverrides?: Readonly<Record<string, Partial<CompactionPolicy>>>;
518
+ };
519
+ /**
520
+ * How often running progress is committed, and how long it may wait in memory before it is stored. Progress commits are
521
+ * deferred (`CommitPersistence`): watches see each at once, and Storage gets them together, at the run's next phase
522
+ * boundary or after `persistWithinMs`. A host can commit less often, so live answers and tool output appear in larger steps.
458
523
  */
459
524
  export type ProgressPolicy = {
460
525
  /** Minimum pause between commits of the answer being generated. */
461
526
  partialIntervalMs: number;
462
527
  /** Minimum pause between commits of running tool output; large commits also pause in proportion to their size. */
463
528
  outputIntervalMs: number;
529
+ /**
530
+ * How long these commits, and other deferred commits without their own `withinMs`, may stay in memory before they are
531
+ * stored (`CommitPersistence`); a crash loses at most about this much of an answer or of tool output. Default 1,000;
532
+ * `Infinity` stores them only at the next commit that reads or writes their document and at close.
533
+ */
534
+ persistWithinMs: number;
464
535
  };
465
536
  /** Why a compaction runs: `compact()`, a threshold in generation preparation, or a context overflow. */
466
537
  export type CompactionReason = "manual" | "threshold" | "overflow";
@@ -478,7 +549,7 @@ export type HarnessSettings = {
478
549
  readonly extensions?: readonly Extension[];
479
550
  readonly stream?: ConversationStreamOptions;
480
551
  readonly retry?: Partial<ConversationRetryPolicy>;
481
- readonly compaction?: Partial<CompactionPolicy>;
552
+ readonly compaction?: CompactionSettings;
482
553
  readonly progress?: Partial<ProgressPolicy>;
483
554
  readonly toolExecution?: ToolExecutionMode;
484
555
  readonly steeringMode?: QueueMode;
@@ -489,25 +560,49 @@ export type HarnessSettings = {
489
560
  */
490
561
  readonly contextRetentionMs?: number;
491
562
  /**
492
- * How long a loaded document that no watch or document state observes stays in memory after its last read or write;
563
+ * How long a loaded document that no watch observes stays in memory after its last read or write;
493
564
  * the next access loads it from Storage. Observed documents always stay. Default ten minutes; `0` drops one right
494
565
  * after use; `Infinity` keeps them. Anything else that is not a number of milliseconds means the default.
495
566
  */
496
567
  readonly documentRetentionMs?: number;
568
+ /**
569
+ * What a run that fails (`model_error`, `no_model`, `interrupted`, `restart_limit`, `nothing_to_answer`,
570
+ * `turn_limit`) does with the inputs queued behind it: `keep` leaves them queued until the next input or queued write, `continue` starts the
571
+ * next run with them, as an answer would, and `withdraw` settles them `unanswered` with reason `run_failed`. Default
572
+ * `keep`.
573
+ */
574
+ readonly afterFailedRun?: AfterFailedRun;
575
+ /**
576
+ * How many times in a row a task may be cut off by a crash, eviction, or reset (a close does not count) without
577
+ * changing its state before it ends instead of running again: a tool call with an error result, a compaction failed
578
+ * into `pi.live.compactionFailure`, a run with `pi.live.runFailure` reason `restart_limit`, any other task `faulted`
579
+ * with detail `{ reason: "restart_limit" }`. A generation's request or poll the host died during is a failed attempt
580
+ * under `retry` first; the limit bounds it only when charging that attempt keeps being cut off. Default 5;
581
+ * `Infinity` never ends a task for it.
582
+ */
583
+ readonly restartLimit?: number;
497
584
  };
585
+ /**
586
+ * What a failed run does with the inputs queued behind it (`HarnessSettings.afterFailedRun`): a run that ends with
587
+ * `model_error`, `no_model`, `interrupted`, `restart_limit`, `nothing_to_answer`, or `turn_limit`.
588
+ */
589
+ export type AfterFailedRun = "keep" | "continue" | "withdraw";
498
590
  /** Resolved settings: every field over its built-in default, object fields merged. */
499
591
  export type Settings = {
500
592
  /** Absent: every installed extension, in install order. */
501
593
  readonly extensions?: readonly Extension[];
502
594
  readonly stream: ConversationStreamOptions;
503
595
  readonly retry: ConversationRetryPolicy;
504
- readonly compaction: CompactionPolicy;
596
+ /** Harness-wide policy over the defaults; per-model overrides apply where the model is known. */
597
+ readonly compaction: CompactionPolicy & Pick<CompactionSettings, "modelOverrides">;
505
598
  readonly progress: ProgressPolicy;
506
599
  readonly toolExecution: ToolExecutionMode;
507
600
  readonly steeringMode: QueueMode;
508
601
  readonly followUpMode: QueueMode;
509
602
  readonly contextRetentionMs: number;
510
603
  readonly documentRetentionMs: number;
604
+ readonly afterFailedRun: AfterFailedRun;
605
+ readonly restartLimit: number;
511
606
  };
512
607
  /** What `HarnessOptions.env` builds an environment for. */
513
608
  export type EnvTarget = {
@@ -517,10 +612,23 @@ export type EnvTarget = {
517
612
  readonly read: DocumentReader;
518
613
  };
519
614
  export type HarnessOptions<Tool extends ToolRegistration = ToolRegistration> = {
520
- /** pi-ai model access used by generation. */
521
- readonly models: Models;
615
+ /** Host-owned adapter for content-free admission/phase telemetry; propagation and metrics are optional. */
616
+ readonly telemetry?: TelemetryContext;
617
+ /**
618
+ * pi-ai model access used by generation, compaction, hooks and tools: a `Models`, or a function returning the current
619
+ * one, called at each use, so a host can replace its model configuration without reopening the Harness.
620
+ */
621
+ readonly models: Models | (() => Models);
622
+ /**
623
+ * How model requests identify the app to providers, merged field by field over `DEFAULT_HARNESS_IDENTITY`
624
+ * (`{ attribution: PI_ATTRIBUTION }`, provider attribution to pi). It applies to every request through the Harness's
625
+ * models, including hooks and tools, and overrides the identity the `Models` was created with; a request's own
626
+ * `identity` overrides it. Pass `{ attribution: null }` to send no attribution headers.
627
+ */
628
+ readonly identity?: ClientIdentity;
522
629
  readonly registry: RegistryReader<Tool>;
523
- readonly settings?: HarnessSettings;
630
+ /** Run settings, or a function returning the current ones; read at each use either way. */
631
+ readonly settings?: HarnessSettings | (() => HarnessSettings | undefined);
524
632
  /** Builds a conversation's environment at each use. Never called on the Session line; may be async. */
525
633
  readonly env?: (target: EnvTarget, context: Context) => ExecutionEnv | undefined | Promise<ExecutionEnv | undefined>;
526
634
  /**
@@ -529,12 +637,55 @@ export type HarnessOptions<Tool extends ToolRegistration = ToolRegistration> = {
529
637
  * Table reads throw `ReadAfterWrite`, as in `init`; a throw fails the creating commit.
530
638
  */
531
639
  readonly conversationCreated?: (tx: Tx, conversation: ConversationRecord) => void | Promise<void>;
640
+ /**
641
+ * The Harness clock, epoch milliseconds; default `Date.now`. Sleeping tasks store deadlines on it, so every process
642
+ * that opens the same storage must use the same clock.
643
+ */
532
644
  readonly now?: () => number;
533
645
  /**
534
- * Receives failures that do not fail the calling operation, such as a throwing hook or listener, and the error that
535
- * fails the Session, once. Its own throw or rejection is dropped.
646
+ * Arms the timers of the Session, the scheduler, and its tasks: pacing progress commits, freeing idle memory
647
+ * (`background`), and, without `onWake`, resuming sleeping tasks; and the timers of `TaskRuntime.setTimer()`. The
648
+ * Node.js environment, file watcher, and image worker keep their own timers. Default `setTimeout`, with background
649
+ * timers unreferenced, or declined in Cloudflare Workers and where they cannot be unreferenced. Tests can pass fake
650
+ * timers; a host can keep timers off a process that should hibernate.
651
+ */
652
+ readonly setTimer?: SetTimer;
653
+ /**
654
+ * Wakes the process for sleeping tasks instead of a `setTimer` timer. Without it, while scheduling runs, a timer that
655
+ * keeps the process alive resumes sleeping tasks when they are due. With it, the Harness arms no timer for them: it
656
+ * calls `onWake` after open and whenever `Harness.wake()` changes, and the host arms its own wakeup, such as a
657
+ * Durable Object alarm, and calls `resume()` when it fires. Called in a microtask the Session line never waits for,
658
+ * after the commit that changed the value was adopted, so a host arms a recovery wakeup before calls that can start
659
+ * work. It may call `wake()` and `resume()`, and must not block. A throw is reported, and the value is offered again
660
+ * at the next change.
536
661
  */
537
- readonly onReport?: (error: unknown) => void;
662
+ readonly onWake?: (wake: Wake) => void;
663
+ /**
664
+ * Receives failures that do not fail the calling operation, such as a throwing hook or listener, a task that faulted,
665
+ * and the error that fails the Session, once: one `Report`, the `error` and what happened (`kind` and the fields of
666
+ * its `ReportInfo`). A host can tell a failed Session (`storage_failed`, often a platform reset) from a faulted task
667
+ * (`task_faulted`). Its own throw or rejection is dropped.
668
+ */
669
+ readonly onReport?: (report: Report) => void;
670
+ /**
671
+ * Open for reading only, as a viewer of a session that another process drives, for example a synced copy on a device
672
+ * that only shows it. Open recovers nothing: a `running` task stays as committed. Every commit with writes rejects
673
+ * with `ReadOnlySession` before it reaches Storage, and so does `resume()` and every call that enables scheduling;
674
+ * the Harness stays usable for reads and watches. `wake()` is always inactive, and `onWake` is never called. Reopen
675
+ * without it to write.
676
+ */
677
+ readonly readOnly?: boolean;
678
+ };
679
+ /**
680
+ * When a Session next needs a running process (`Harness.wake()`). `active`: some task can run now or is running, or a
681
+ * reconcile pass such as open's recovery has not landed yet, so a process that dies must be woken again soon, for
682
+ * example by a recovery alarm. `at`: the earliest sleep not yet due,
683
+ * when there is one. Neither: nothing needs a process until new work is submitted. Tasks blocked on a missing or
684
+ * unmigratable definition, `completing` tasks, and waits on live tasks do not count; the work they wait for does.
685
+ */
686
+ export type Wake = {
687
+ readonly active: boolean;
688
+ readonly at?: number;
538
689
  };
539
690
  /** Live task and what the scheduler would do with it under the current registry. */
540
691
  export type TaskInspection = {
@@ -544,7 +695,7 @@ export type TaskInspection = {
544
695
  }
545
696
  /** The next scheduling pass reserves it; `migrates` when its definition is newer and has `migrate`. */
546
697
  | {
547
- readonly kind: "ready";
698
+ readonly kind: "pending";
548
699
  readonly migrates: boolean;
549
700
  }
550
701
  /**
@@ -555,11 +706,16 @@ export type TaskInspection = {
555
706
  readonly kind: "waiting";
556
707
  readonly on: readonly TaskId[];
557
708
  }
709
+ /** Sleeps until the Harness clock reaches `until`. */
710
+ | {
711
+ readonly kind: "sleeping";
712
+ readonly until: number;
713
+ }
558
714
  /** Outcome held until its ordinary owned work drains. */
559
715
  | {
560
716
  readonly kind: "completing";
561
717
  }
562
- /** No registered definition can take it; aborting it settles it as `orphaned`. */
718
+ /** Cannot run here: no registered definition can take it (aborting it settles it as `orphaned`). */
563
719
  | {
564
720
  readonly kind: "blocked";
565
721
  readonly reason: "missing_task" | "task_too_old" | "migration_failed";
@@ -592,12 +748,25 @@ export interface Conversation {
592
748
  /** `configure()` in its own commit. */
593
749
  configure(change: AgentChange, context: Context): Promise<void>;
594
750
  /**
595
- * Durably admit user input or a passive entry write. A busy conversation, or one with queued items, queues input and
596
- * writes that affect model context in `pi.inbox`; `whenBusy: "reject"` rejects input with `ConversationBusy` instead
597
- * and writes nothing. A write of only `kind` and `data` (no `model`, `head`, or `edits`) is appended at once,
598
- * even mid-run, as it changes nothing the model sees.
751
+ * Durably admit user input, a passive entry write, or a continue. A busy conversation, or one with queued items,
752
+ * queues input and writes that affect model context in `pi.inbox`; `whenBusy: "reject"` rejects input with
753
+ * `ConversationBusy` instead and writes nothing. An idle conversation rejects `whenIdle: "reject"` input with
754
+ * `ConversationIdle` and writes nothing. A write of only `kind` and `data` (no `model`, `head`, or `edits`)
755
+ * is appended at once, even mid-run, as it changes nothing the model sees. A continue starts a run on the context as
756
+ * it is, after placing queued items; a busy conversation rejects it with `ConversationBusy`, as it never queues.
599
757
  */
600
758
  submit(submission: SubmissionDraft, context: Context): Promise<Submission>;
759
+ /**
760
+ * Admit a submission inside the caller's commit `tx` (`Harness.commit()`, `Conversation.commit()`, a task's
761
+ * `runtime.commit()`), with the same rules, and return its ID: the admission is stored with that commit, or not at all
762
+ * when the callback throws later or the commit fails. Call it before the commit's first table write (`appendEntry()`,
763
+ * `createTask()`, `createSubmission()`, another admission): otherwise it throws `ReadAfterWrite`, on every path.
764
+ * Documents may be read and written before and after it. A rejection (`ConversationBusy`, `ConversationIdle`) throws
765
+ * before any write, so the callback may catch it. Do not call `Submission` or other Harness reads inside the callback:
766
+ * they wait for the commit holding the line. Its `pi.durable.submit` span covers the admission only, so its `result`
767
+ * is provisional. Throws for a transaction of another Session.
768
+ */
769
+ submit(tx: Tx, submission: SubmissionDraft): Promise<SubmissionId>;
601
770
  /**
602
771
  * Admit a write of a `pi.reset` entry that starts a new context, carrying `handoff` as a user message when given.
603
772
  * Resolves after admission; while busy, it is placed at the next boundary.
@@ -608,8 +777,8 @@ export interface Conversation {
608
777
  * summary through a write submission: at once when idle, otherwise at the next boundary (spec §8.7).
609
778
  */
610
779
  compact(instructions: string | undefined, context: Context): Promise<TaskId<CompactionResult>>;
611
- /** Session commit whose `tx.createTask()` defaults to this conversation. */
612
- commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context): Promise<T>;
780
+ /** Session commit whose `tx.createTask()` defaults to this conversation; `options` as for `Session.commit()`. */
781
+ commit<T>(change: (tx: Tx) => T | Promise<T>, context: Context, options?: CommitOptions): Promise<T>;
613
782
  /**
614
783
  * Committed raw active transcript and model context. With `at`, the context as of that visible entry: the same view
615
784
  * `fork(at)` would start with, without creating a conversation.
@@ -630,20 +799,30 @@ export interface Conversation {
630
799
  * owned, transitively, by its non-background tasks.
631
800
  */
632
801
  waitForIdle(context: Context): Promise<void>;
633
- /** The structural view (spec §9.3) as a disposable read-only Chord state. */
634
- viewState(context: Context): Promise<AttachedReplicatedState<ConversationView>>;
635
- /** The structural view as a serialized exact-frame watch with bounded pending frames. */
636
- watch(context: Context): Promise<ConversationWatch>;
802
+ /**
803
+ * The structural view (spec §9.3) as a serialized watch; commits made while its listener runs arrive folded. Its
804
+ * `entries` are a window over the transcript, as `window` says: the newest 50 by default, then appends, dropping the
805
+ * oldest past 100. Built on the same mount as `Harness.observeConversation()`.
806
+ */
807
+ watch(context: Context, window?: ConversationWindow): Promise<ConversationWatch>;
637
808
  }
638
809
  export type ConversationWatch = WatchHandle<ConversationView>;
639
810
  /** Durable agent harness over one Session. */
640
811
  export interface Harness extends Session {
641
812
  /**
642
- * Enable task scheduling. Idempotent; throws after close. Calls that ask for progress enable it too:
643
- * `Conversation.submit()`, `Conversation.compact()`, `Conversation.abort()`, `Submission.wait()`, `waitForTask()`,
644
- * `Harness.waitForIdle()`, and `Conversation.waitForIdle()`. Read-only viewers never do.
813
+ * Enable task scheduling. Idempotent; throws after close, and `ReadOnlySession` on a read-only Harness. Calls that
814
+ * ask for progress enable it too: `Conversation.submit()`, `Conversation.compact()`, `Conversation.abort()`,
815
+ * `Submission.wait()`, `waitForTask()`, `Harness.waitForIdle()`, and `Conversation.waitForIdle()`. Read-only viewers
816
+ * never do.
645
817
  */
646
818
  resume(): void;
819
+ /**
820
+ * When this Session next needs a running process, from the scheduler's memory: `active` while some task can run now
821
+ * or is running, `at` for the earliest sleep. Answers also while scheduling is paused. A host that wakes the
822
+ * process itself, such as a Durable Object alarm, arms from it and calls `resume()` when woken; see
823
+ * `HarnessOptions.onWake`.
824
+ */
825
+ wake(): Wake;
647
826
  /** Return the reserved root conversation, creating it with `agent` and `init` in one commit when absent. */
648
827
  root(context: Context, options?: {
649
828
  readonly agent?: AgentChange;
@@ -669,19 +848,42 @@ export interface Harness extends Session {
669
848
  waitForIdle(context: Context): Promise<void>;
670
849
  /** Session total: every conversation's `pi.usage` summed. */
671
850
  usage(context: Context): Promise<UsageState>;
672
- /** Every live task with its owner edge, status, and owned conversations (spec §9.5), as a disposable Chord state. */
673
- taskGraph(context: Context): Promise<AttachedReplicatedState<TaskGraph>>;
674
- /** The task graph as a serialized exact-frame watch with bounded pending frames. */
851
+ /**
852
+ * Every live task with its owner edge, status, and owned conversations (spec §9.4), as a serialized watch; commits
853
+ * made while its listener runs arrive folded.
854
+ */
675
855
  watchTaskGraph(context: Context): Promise<TaskGraphWatch>;
856
+ /**
857
+ * Attach a synchronous `Observer` to a conversation's structural view (spec §9.3), atomically on the Session line,
858
+ * for adapters: `advance` gets each next revision with its operations as memory adopts the commit, inside Storage's
859
+ * `adopt` for a stored one and before it is stored for a deferred one, so it only records it. `window` as for
860
+ * `Conversation.watch()`; each observer has its own. `context` governs only the attachment.
861
+ */
862
+ observeConversation(id: ConversationId, observer: Observer<ConversationView>, context: Context, window?: ConversationWindow): Promise<Observation<ConversationView>>;
863
+ /**
864
+ * Attach a synchronous `Observer` to the task graph (spec §9.4), as `observeConversation()` does to a view. A
865
+ * `blocked` reason that changes without a commit (a registry change, a failed migration) advances it on the Session
866
+ * line outside any commit and `adopt`, with the background context.
867
+ */
868
+ observeTaskGraph(observer: Observer<TaskGraph>, context: Context): Promise<Observation<TaskGraph>>;
676
869
  }
677
- /** What a hook may use: committed reads and the asking task's memos, which hooks and the task share. */
870
+ /**
871
+ * What a hook may use: committed reads and the asking task's memos, which hooks and the task share. Hooks run off the
872
+ * Session line, so reads see what is committed when they run; a hook rerun after a crash may see more.
873
+ */
678
874
  export interface HookApi extends DocumentReader {
679
875
  readonly taskId: TaskId;
680
876
  readonly conversationId: ConversationId;
681
- /** `HarnessOptions.models`. */
877
+ /** The current `HarnessOptions.models`, read at each access. */
682
878
  readonly models: Models;
683
879
  memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;
684
880
  memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;
881
+ /** Committed entry visible from the task's conversation, as `TaskRuntime.entry()`. */
882
+ entry(id: EntryId, context: Context): Promise<EntryRecord | undefined>;
883
+ /** Undefined when the entry is absent, not visible, or has another kind. */
884
+ entry<D extends JsonValue>(token: Entry<D>, id: EntryId, context: Context): Promise<TypedEntry<D> | undefined>;
885
+ /** Committed raw history of the task's conversation, as `TaskRuntime.entries()`. */
886
+ entries(query: Omit<EntryQuery, "conversationId">, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
685
887
  }
686
888
  export type HookResult<T> = T | undefined | Promise<T | undefined>;
687
889
  /** Hooks of the built-in generation task. */
@@ -692,6 +894,17 @@ export interface GenerationHooks {
692
894
  }, api: HookApi, context: Context): HookResult<{
693
895
  readonly messages: readonly Message[];
694
896
  }>;
897
+ /**
898
+ * After pi-ai builds the provider request body, before it is sent; the result replaces the body for that request only.
899
+ * Runs again whenever pi-ai rebuilds the body, such as a retry within one attempt. The body is in the provider API's
900
+ * own format (`request.model.api`), so a hook checks the API before changing it.
901
+ */
902
+ beforeProviderRequest(request: {
903
+ readonly model: Model<Api>;
904
+ readonly payload: unknown;
905
+ }, api: HookApi, context: Context): HookResult<{
906
+ readonly payload: unknown;
907
+ }>;
695
908
  /** Every terminal provider message, before classification. */
696
909
  afterResponse(message: AssistantMessage, api: HookApi, context: Context): void | Promise<void>;
697
910
  /** A final answer; the first `continue` appends a user message and continues the run. */
@@ -700,7 +913,36 @@ export interface GenerationHooks {
700
913
  }>;
701
914
  /** After every tool of the round is terminal; `results` are the round's result entries in call order. */
702
915
  afterTools(assistant: EntryId, results: readonly EntryId[], api: HookApi, context: Context): void | Promise<void>;
916
+ /**
917
+ * After a run answered, its answer committed and its inputs settled `done`: a final answer, or a `terminate` round;
918
+ * not when an `onYield` continuation is taken, and never after a failure or an abort. The requested tasks of every
919
+ * hook are created together as children of the generation, which keeps the run, and the conversation busy, until
920
+ * they are terminal; then queued items are placed and the run ends (spec §8.3). A crash before they are created asks
921
+ * again; once created, never again for this run.
922
+ */
923
+ afterRun(run: RunSummary, api: HookApi, context: Context): HookResult<{
924
+ readonly tasks: readonly RunEndTask[];
925
+ }>;
703
926
  }
927
+ /** How a run ended, for `afterRun` hooks. Read the run's history with `api.entries({ minEntryId: first, maxEntryId: last })`. */
928
+ export type RunSummary = {
929
+ /** A final answer, or a tool round whose every call asked to `terminate`. */
930
+ readonly reason: "answer" | "terminate";
931
+ /**
932
+ * The run's first entry (`LiveRun.first`): the first one the boundary that started it placed, for a continue the failed
933
+ * run's it continues, otherwise its first tool-calling answer; the answer itself for a run that has none of these.
934
+ */
935
+ readonly first: EntryId;
936
+ /** The answer, or the terminating round's tool-calling answer, whose results follow it. */
937
+ readonly answer: EntryId;
938
+ /** The run's last entry: the answer; for a `terminate` round its newest result, or the reset of its handoff. */
939
+ readonly last: EntryId;
940
+ };
941
+ /** A task an `afterRun` hook asks for, built by `runEndTask()`. */
942
+ export type RunEndTask = {
943
+ readonly task: AnyTask;
944
+ readonly input: JsonValue;
945
+ };
704
946
  /** A call as tool hooks see it; `parent` is set for a nested call, one a running tool made through `executeTool()`. */
705
947
  export type ToolHookCall = ToolCall & {
706
948
  readonly parent?: {
@@ -715,7 +957,18 @@ export interface ToolHooks {
715
957
  readonly arguments?: JsonObject;
716
958
  readonly block?: string;
717
959
  }>;
718
- /** After execution, before the result is committed; replaces the result. */
960
+ /**
961
+ * Before the result is committed; replaces the result. Runs on every result the tool task writes: an executed
962
+ * call's, and the error results the Harness writes itself, told apart by their diagnostic code: a tool that does not
963
+ * resolve (`tool_unavailable`), invalid arguments (`invalid_arguments`, with `call.arguments` as the model sent
964
+ * them), a block (`blocked`), and, rebuilt from the call's partial output, an interruption (`interrupted`), the
965
+ * restart limit (`restart_limit`), and an abort (`aborted`, `abandoned`). Once the task was cut off more than
966
+ * `settings.restartLimit` times the hooks no longer run, since they may be what cuts it off, nor in the last attempt
967
+ * the scheduler gives an abort handler (restarts at the limit minus one). A result's `structuredOutput` is dropped
968
+ * when the Harness wrote it (a nested call a hook turns into a success gets the bounded output as a schema-less tool's
969
+ * does), and its `control` applies only to a call that completes (not to an interrupted, cut-off, or aborted one).
970
+ * An abort's hooks run inside the abort handler, which cannot be aborted: a slow hook delays `Conversation.abort()`.
971
+ */
719
972
  afterTool(call: ToolHookCall, result: ToolExecutionResult, api: HookApi, context: Context): HookResult<ToolExecutionResult>;
720
973
  }
721
974
  /** Hooks of the built-in compaction task. */
@@ -737,4 +990,5 @@ export interface CompactionHooks {
737
990
  readonly summary: string;
738
991
  }>;
739
992
  }
993
+ export {};
740
994
  //# sourceMappingURL=types.d.ts.map