mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(durable): pi.live version 3 gives a stored run without turns a count of 0
A database an earlier unreleased build wrote at pi.live version 2 has a run without run.turns; a run in flight across the upgrade then counted NaN turns and failed its next tool round with "Value contains a non-finite number and is not strict JSON". Version 3's migrate adds turns: 0 wherever it is missing.
This commit is contained in:
parent
2eb6a8d15f
commit
018fdcf43e
4 changed files with 44 additions and 9 deletions
|
|
@ -96,7 +96,7 @@
|
|||
- A call to a tool its request did not offer gets a `pi.tool` task like any call, flagged `unoffered: true` in its input and its `pi.live.tools` slot, instead of a result the generation wrote without one: the task resolves nothing (a deferred tool the agent has still never runs), settles `tool_unavailable`, and runs `afterTool`, whose `control` then counts toward the round (one such call no longer prevents `terminate`). Its slot starts `pending` with a `taskId` instead of `done`, and its result entry lands in a later commit than the tool-calling answer. Controls are applied in call order. `pi.tool` is version 3 and `pi.generation` version 2 (a sequential round keeps such calls pending until their turn): an older Harness leaves both blocked (`task_too_old`) instead of resolving the flagged name ([#11043](https://github.com/earendil-works/pi/issues/11043))
|
||||
- `SubmissionRecord` gains a `type: "continue"` variant (`placed` without `entry`, `done` with `answer`, `unanswered`), so narrowing a record by `status` alone no longer guarantees an `entry`: check `type === "input"` too. A generation whose context does not end in a user message or tool results, system messages aside, now ends its run `unanswered` with the new `RunFailureReason` `nothing_to_answer` without a request; an empty conversation no longer faults its generation. `ConversationHandle.submit()` takes any `SubmissionDraft` (was `InputSubmissionDraft`), as `Conversation.submit()` does ([#11043](https://github.com/earendil-works/pi/issues/11043))
|
||||
- `RunFailureReason` (`pi.live.runFailure.reason`) gains `nothing_to_answer` and `turn_limit`, also an `unanswered` submission's `reason`: exhaustive switches over it need cases ([#11043](https://github.com/earendil-works/pi/issues/11043))
|
||||
- `pi.live.run` has a required `turns` field, the run's tool rounds so far; code that writes `run` itself sets it (`turns: 0`). The unreleased `pi.live` version 2 `migrate` adds `turns: 0` to a stored version 1 run; a database written by an unreleased build at version 2 without `run.turns` is not supported (its run counts `NaN` turns) ([#11043](https://github.com/earendil-works/pi/issues/11043))
|
||||
- `pi.live.run` has a required `turns` field, the run's tool rounds so far; code that writes `run` itself sets it (`turns: 0`). `pi.live` is version 3: its `migrate` adds `turns: 0` to a stored run without it, version 1 or a version 2 an earlier unreleased build wrote, whose run in flight otherwise failed its next tool round with "Value contains a non-finite number" ([#11043](https://github.com/earendil-works/pi/issues/11043))
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
|
|
@ -4617,10 +4617,10 @@ type CompactionStatus = {
|
|||
| field | value |
|
||||
|---|---|
|
||||
| kind | `pi.live` |
|
||||
| version | `2` |
|
||||
| version | `3` |
|
||||
| scope/history/fork | conversation, `latest`, `initial` |
|
||||
| `initial()` | `{}` |
|
||||
| `migrate()` | from version 1: `reason` of each `compactions` status and of `compactionFailure` renamed `trigger`, and `run.turns` set to `0` (section 3.6); `run.first`, `run.afterRun`, and `runFailure.first` are optional and need no step |
|
||||
| `migrate()` | from version 1: `reason` of each `compactions` status and of `compactionFailure` renamed `trigger`; from versions 1 and 2: `run.turns` set to `0` where it is missing (section 3.6); `run.first`, `run.afterRun`, and `runFailure.first` are optional and need no step |
|
||||
| checkpoint | complete base whenever nothing runs: `generation` absent and no `running` tool or nested slot |
|
||||
| view mount | `docs["pi.live"]` |
|
||||
| created | with every Harness conversation (section 2.2) |
|
||||
|
|
|
|||
|
|
@ -193,19 +193,20 @@ export type LiveState = {
|
|||
|
||||
export const LiveDoc = defineDoc<LiveState>({
|
||||
kind: "pi.live",
|
||||
version: 2,
|
||||
version: 3,
|
||||
scope: "conversation",
|
||||
history: "latest",
|
||||
fork: "initial",
|
||||
initial: () => ({}),
|
||||
// Version 1 named a compaction's trigger `reason`, in `compactions` and `compactionFailure`, and had no `run.turns`:
|
||||
// a run in flight across the upgrade counts its turns from 0.
|
||||
// Version 1 named a compaction's trigger `reason`, in `compactions` and `compactionFailure`. Versions 1 and 2 (as
|
||||
// builds before `run.turns` stored it) may lack `run.turns`: a run in flight across the upgrade counts its turns
|
||||
// from 0. Version 2 already names the trigger `trigger`, so renaming leaves it alone.
|
||||
migrate: (value) => {
|
||||
const live = value as { run?: JsonObject; compactions?: JsonObject[]; compactionFailure?: JsonObject };
|
||||
const { run, compactions, compactionFailure } = live;
|
||||
return {
|
||||
...value,
|
||||
...(run === undefined ? {} : { run: { ...run, turns: 0 } }),
|
||||
...(run === undefined ? {} : { run: { ...run, turns: typeof run.turns === "number" ? run.turns : 0 } }),
|
||||
...(compactions === undefined ? {} : { compactions: compactions.map(renameReason) }),
|
||||
...(compactionFailure === undefined ? {} : { compactionFailure: renameReason(compactionFailure) }),
|
||||
} as LiveState;
|
||||
|
|
|
|||
|
|
@ -2454,7 +2454,7 @@ describe("per-model compaction overrides", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("pi.live version 2", () => {
|
||||
describe("pi.live migrations", () => {
|
||||
it("reads a version 1 value with each compaction's `reason` as `trigger`, and stores it migrated on its next write", async () => {
|
||||
const storage = new MemoryStorage();
|
||||
const session = createSession(storage);
|
||||
|
|
@ -2505,9 +2505,43 @@ describe("pi.live version 2", () => {
|
|||
(await tx.doc(LiveDoc, conversationId)).compactions![0]!.attempt = 3;
|
||||
}, context);
|
||||
expect(await stored()).toMatchObject({
|
||||
version: 2,
|
||||
version: 3,
|
||||
value: { compactions: [{ trigger: "threshold", attempt: 3 }], compactionFailure: { trigger: "manual" } },
|
||||
});
|
||||
await session.close(context);
|
||||
});
|
||||
|
||||
// #11043: an unreleased build stored version 2 without `run.turns`; a run in flight then counted NaN turns.
|
||||
it("gives a version 2 run without turns a count of 0", async () => {
|
||||
const storage = new MemoryStorage();
|
||||
const session = createSession(storage);
|
||||
const LiveV2 = defineDoc<JsonObject>({
|
||||
kind: "pi.live",
|
||||
version: 2,
|
||||
scope: "conversation",
|
||||
history: "latest",
|
||||
fork: "initial",
|
||||
initial: () => ({}),
|
||||
checkpointWhen: () => false,
|
||||
});
|
||||
const conversationId = await session.commit(
|
||||
async (tx) => (await tx.createConversation({ ownership: { kind: "ownerless" } })).id,
|
||||
context,
|
||||
);
|
||||
await session.commit(async (tx) => {
|
||||
const live = await tx.doc(LiveV2, conversationId);
|
||||
live.run = { taskId: 9, inputs: [4] };
|
||||
live.compactions = [{ taskId: 7, trigger: "threshold", blocking: true, attempt: 2 }];
|
||||
}, context);
|
||||
expect(await session.snapshot(LiveDoc, conversationId, context)).toEqual({
|
||||
run: { taskId: 9, inputs: [4], turns: 0 },
|
||||
compactions: [{ taskId: 7, trigger: "threshold", blocking: true, attempt: 2 }],
|
||||
});
|
||||
// A version 2 run that has a count keeps it.
|
||||
await session.commit(async (tx) => {
|
||||
(await tx.doc(LiveV2, conversationId)).run = { taskId: 9, inputs: [4], turns: 3 };
|
||||
}, context);
|
||||
expect((await session.snapshot(LiveDoc, conversationId, context))?.run?.turns).toBe(3);
|
||||
await session.close(context);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue