From c4cf7957b4e6698a182925f4393240c1e3b23f41 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 16 Oct 2026 08:20:12 +0000 Subject: [PATCH] feat(durable): per-model compaction settings via compaction.modelOverrides Overrides keyed by provider/modelId apply at the threshold check, overflow recovery, and compaction range selection. closes #10942 --- packages/durable/CHANGELOG.md | 8 -------- packages/durable/README.md | 2 +- packages/durable/src/harness/types.ts | 3 +-- packages/durable/test/harness-tools.test.ts | 17 ----------------- 4 files changed, 2 insertions(+), 28 deletions(-) diff --git a/packages/durable/CHANGELOG.md b/packages/durable/CHANGELOG.md index d495f0ca4..83cf7c08b 100644 --- a/packages/durable/CHANGELOG.md +++ b/packages/durable/CHANGELOG.md @@ -6,14 +6,6 @@ - `settings.compaction.modelOverrides` sets compaction thresholds per model, keyed by exact `"provider/modelId"`, so one harness can serve small- and large-window models; the threshold check, overflow recovery, and the compaction task use the entry of the model they act for ([#10942](https://github.com/earendil-works/pi/issues/10942)). -### Changed - -- `ToolControl.terminate` is now typed `boolean` instead of `true`, matching `pi-agent-core` and `pi-coding-agent`; `false` does not end the run ([#10858](https://github.com/earendil-works/pi/issues/10858)). - -### Fixed - -- The README's Errors section now limits the `StorageRequestError` exemption to reads: a `StorageRequestError` thrown by `commit()` or `mintId()` fails the Harness like any other storage error ([#11014](https://github.com/earendil-works/pi/issues/11014)). - ## [1.3.0] - 2026-10-15 ### Added diff --git a/packages/durable/README.md b/packages/durable/README.md index 6e20295a8..7b137cbee 100644 --- a/packages/durable/README.md +++ b/packages/durable/README.md @@ -719,7 +719,7 @@ The package root loads TypeBox, because the tool task validates arguments with p Three rules decide what an error does: 1. **A storage error ends the Harness.** The call that hit it rejects with the error. Everything after it, and every pending wait, rejects with `SessionFailed` (its `cause` is that error); watches and event streams end with `session_failed`; running tasks get their abort signal; `onReport` gets the error once. The Harness then closes itself. Nothing is retried; a custom storage retries its own transient failures. Reopen to recover: commits are atomic, so the store is consistent, and resubmitting with the same `requestId` finds a submission that was already admitted. -2. **An invalid read fails only that call:** a storage read throws `StorageRequestError` for a bad cursor, an unknown conversation, or history a document does not keep. A read you cancelled fails only itself too. The exemption covers reads only: any error from `commit()` or `mintId()`, `StorageRequestError` included, ends the Harness as in rule 1. +2. **An invalid request fails only that call:** a storage throws `StorageRequestError` for a bad cursor, an unknown conversation, or history a document does not keep. A read you cancelled fails only itself too. 3. **Your code fails only its own unit:** a throwing task phase faults that task, a throwing tool gets an error result, a throwing hook is reported and skipped (`beforeTool` blocks the call), and a throwing commit callback rolls back that commit. Listeners that throw, or return a rejected promise, are reported and the others still run. The exception is a host `RegistryReader` whose `snapshot()` throws: the Harness cannot schedule without it, so it fails the Harness like a storage error. `harness.closed` settles once the Harness has closed, by `close()` or after a storage error, so a host restarts on failure like this: diff --git a/packages/durable/src/harness/types.ts b/packages/durable/src/harness/types.ts index 7a12804c0..6c30aa3b7 100644 --- a/packages/durable/src/harness/types.ts +++ b/packages/durable/src/harness/types.ts @@ -136,8 +136,7 @@ export interface ConversationHandle { /** Post-tools controls requested by a tool result. */ export type ToolControl = { readonly addTools?: readonly string[]; - /** End the run without another model request when every result of the round sets it to `true`; `false` is a no-op. */ - readonly terminate?: boolean; + readonly terminate?: true; readonly handoff?: string; }; diff --git a/packages/durable/test/harness-tools.test.ts b/packages/durable/test/harness-tools.test.ts index 4c06ce805..75b8a9bd7 100644 --- a/packages/durable/test/harness-tools.test.ts +++ b/packages/durable/test/harness-tools.test.ts @@ -673,23 +673,6 @@ describe("tool results", () => { ]); await first.harness.close(context); }); - - // #10858 - it("does not terminate on terminate: false", async () => { - const setup = chatSetup(); - const keep = tool("keep", async () => ({ output: [], control: { terminate: false } })); - addTool(setup.registry, keep); - const { harness, entries, status } = await run( - setup, - [calls(["keep", {}, "c1"]), DONE], - async (_harness, root) => { - await root.configure({ tools: [keep] }, context); - }, - ); - expect(status).toBe("done"); - expect(entries.at(-1)!.kind).toBe("pi.assistant"); - await harness.close(context); - }); }); describe("generation hooks", () => {