@ny4n/durable 1.2.0 → 1.3.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 (67) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +41 -5
  3. package/dist/codemode/codemode.d.ts +132 -0
  4. package/dist/codemode/codemode.d.ts.map +1 -0
  5. package/dist/codemode/codemode.js +791 -0
  6. package/dist/codemode/codemode.js.map +1 -0
  7. package/dist/codemode/describe.d.ts +38 -0
  8. package/dist/codemode/describe.d.ts.map +1 -0
  9. package/dist/codemode/describe.js +117 -0
  10. package/dist/codemode/describe.js.map +1 -0
  11. package/dist/codemode/index.d.ts +3 -0
  12. package/dist/codemode/index.d.ts.map +1 -0
  13. package/dist/codemode/index.js +3 -0
  14. package/dist/codemode/index.js.map +1 -0
  15. package/dist/codemode/output.d.ts +39 -0
  16. package/dist/codemode/output.d.ts.map +1 -0
  17. package/dist/codemode/output.js +160 -0
  18. package/dist/codemode/output.js.map +1 -0
  19. package/dist/harness/agent.d.ts +4 -2
  20. package/dist/harness/agent.d.ts.map +1 -1
  21. package/dist/harness/agent.js +56 -26
  22. package/dist/harness/agent.js.map +1 -1
  23. package/dist/harness/compaction.d.ts +6 -1
  24. package/dist/harness/compaction.d.ts.map +1 -1
  25. package/dist/harness/compaction.js +10 -7
  26. package/dist/harness/compaction.js.map +1 -1
  27. package/dist/harness/events.d.ts +8 -2
  28. package/dist/harness/events.d.ts.map +1 -1
  29. package/dist/harness/events.js +4 -1
  30. package/dist/harness/events.js.map +1 -1
  31. package/dist/harness/generation.d.ts +2 -0
  32. package/dist/harness/generation.d.ts.map +1 -1
  33. package/dist/harness/generation.js +25 -4
  34. package/dist/harness/generation.js.map +1 -1
  35. package/dist/harness/inbox.d.ts +16 -4
  36. package/dist/harness/inbox.d.ts.map +1 -1
  37. package/dist/harness/inbox.js +26 -3
  38. package/dist/harness/inbox.js.map +1 -1
  39. package/dist/harness/live.d.ts +15 -1
  40. package/dist/harness/live.d.ts.map +1 -1
  41. package/dist/harness/live.js +10 -2
  42. package/dist/harness/live.js.map +1 -1
  43. package/dist/harness/scheduler.d.ts.map +1 -1
  44. package/dist/harness/scheduler.js +72 -10
  45. package/dist/harness/scheduler.js.map +1 -1
  46. package/dist/harness/submissions.d.ts +7 -4
  47. package/dist/harness/submissions.d.ts.map +1 -1
  48. package/dist/harness/submissions.js +29 -10
  49. package/dist/harness/submissions.js.map +1 -1
  50. package/dist/harness/tool.d.ts.map +1 -1
  51. package/dist/harness/tool.js +13 -1
  52. package/dist/harness/tool.js.map +1 -1
  53. package/dist/harness/types.d.ts +38 -11
  54. package/dist/harness/types.d.ts.map +1 -1
  55. package/dist/harness/types.js.map +1 -1
  56. package/dist/index.d.ts +2 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/tool-search/index.d.ts +2 -0
  60. package/dist/tool-search/index.d.ts.map +1 -0
  61. package/dist/tool-search/index.js +2 -0
  62. package/dist/tool-search/index.js.map +1 -0
  63. package/dist/tool-search/tool-search.d.ts +18 -0
  64. package/dist/tool-search/tool-search.d.ts.map +1 -0
  65. package/dist/tool-search/tool-search.js +62 -0
  66. package/dist/tool-search/tool-search.js.map +1 -0
  67. package/package.json +16 -3
@@ -1 +1 @@
1
- {"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, Usage } from \"@ny4n/ai/contracts\";\nimport type { Draft, JsonRepresentation, JsonValue } from \"@ny4n/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { Transaction } from \"../session/transaction.ts\";\nimport type { EntryId, JsonObject, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from \"../types.ts\";\nimport { convertPartial } from \"./assistant.ts\";\nimport type { SchedulerOutcome } from \"./scheduler.ts\";\nimport type { CompactionReason, CompactionResult, ToolDiagnostic } from \"./types.ts\";\n\n/** What a running tool call publishes, shared by model-issued and nested calls. */\ntype SlotProgress = {\n\tcallId: string;\n\tname: string;\n\tstatus: \"pending\" | \"running\" | \"done\";\n\t/** Retained running output and what the bounds dropped. */\n\toutput?: string;\n\tdroppedBytes?: number;\n\tdroppedLines?: number;\n\t/** Last `details()` value. */\n\tdetails?: JsonValue;\n\t/** Diagnostics recorded through `api.diagnostic()`. */\n\tdiagnostics?: ToolDiagnostic[];\n};\n\n/** Presentation of one tool call of the current round. */\nexport type ToolSlot = SlotProgress & {\n\t/**\n\t * Absent for a call not started yet (sequential round) and for a call its request did not offer, which starts `done`\n\t * with the `entry` generation wrote.\n\t */\n\ttaskId?: TaskId;\n\t/** Result entry once done; absent when the tool task faulted or was orphaned. */\n\tentry?: EntryId;\n};\n\n/**\n * Presentation of one nested call: a call a running tool made through `executeTool()`. `callId` is\n * `<parentCallId>/<key>`, the call's ID in tool events and unique because keys contain no `/`. The Harness relates\n * slots by task ID: `taskId` is the call's tool task, `parentTaskId` the calling one, model-issued or nested.\n */\nexport type NestedToolSlot = SlotProgress & {\n\tparentCallId: string;\n\tparentTaskId: TaskId;\n\ttaskId: TaskId;\n\t/**\n\t * What the call was made with, replaced by the arguments it runs with, after repair, `beforeTool`, and coercion,\n\t * once it starts executing. A model-issued call's arguments are in its assistant entry instead.\n\t */\n\targuments: JsonObject;\n\t/** Once done: how the call ended, for a status line. The result itself is in the caller's `NestedCallDoc`. */\n\tsummary?: NestedToolSummary;\n};\n\n/** How a nested call ended: an error flag, its execution time, its spend, and up to 500 characters of error text. */\nexport type NestedToolSummary = {\n\tisError: boolean;\n\tdurationMs?: number;\n\tusage?: JsonRepresentation<Usage>;\n\terror?: string;\n};\n\n/** Presentation of one live compaction task (spec §8.7). */\nexport type CompactionStatus = {\n\ttaskId: TaskId<CompactionResult>;\n\treason: CompactionReason;\n\t/** Whether a generation waits for it: a compaction the generation owns. */\n\tblocking: boolean;\n\tattempt: number;\n\t/** Durable backoff before the next summarization attempt. */\n\tretry?: { at: number; error: string };\n};\n\n/** Built-in live conversation state: run control and presentation of the current generation and tool round. */\nexport type LiveState = {\n\t/** Run control: the task that settles the run's inputs, and those inputs; present exactly while busy. */\n\trun?: { taskId: TaskId; inputs: SubmissionId[] };\n\t/** Presentation of the current generation attempt. */\n\tgeneration?: {\n\t\tattempt: number;\n\t\t/** Committed throttled partial of the in-flight response. */\n\t\tmessage?: JsonRepresentation<AssistantMessage>;\n\t\t/** Durable backoff before the next attempt. */\n\t\tretry?: { at: number; error: string };\n\t\t/** Provider-side deferred response being polled. */\n\t\tdeferred?: { pollAt: number };\n\t};\n\t/** The current tool round in call order, from the tool-calling answer until the generation's `tools` phase ends it. */\n\ttools?: ToolSlot[];\n\t/**\n\t * Nested calls of running tool calls, in creation order, so a parent precedes its children and task IDs ascend. A\n\t * call's nested slots are removed when the call settles.\n\t */\n\tnestedTools?: NestedToolSlot[];\n\t/** Live compaction tasks in task ID order; absent when none. */\n\tcompactions?: CompactionStatus[];\n};\n\nexport const LiveDoc = defineDoc<LiveState>({\n\tkind: \"nyan.live\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({}),\n\t// REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds\n\t// while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain\n\t// spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while\n\t// running, so every base is small; a nested call's result lives in its caller's task documents, never here. Do not\n\t// add a delta-count bound; the tool output benchmark checks this rule.\n\tcheckpointWhen: (value) =>\n\t\tvalue.generation === undefined &&\n\t\t!(value.tools ?? []).some((slot) => slot.status === \"running\") &&\n\t\t!(value.nestedTools ?? []).some((slot) => slot.status === \"running\"),\n});\n\n/** Built-in task kinds that can own `nyan.live.run`. */\nconst RUN_TASK_KINDS: ReadonlySet<string> = new Set([\"nyan.generation\"]);\nconst TOOL_TASK_KIND = \"nyan.tool\";\nconst COMPACTION_TASK_KIND = \"nyan.compaction\";\n\n/**\n * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation`, `tools`, and\n * `nestedTools`, whose presentation belongs to the ending run.\n */\nexport function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, settlement: SubmissionSettlement): void {\n\tif (live.run?.taskId === taskId) {\n\t\tfor (const id of live.run.inputs) tx.settleSubmission(id, settlement);\n\t\tdelete live.run;\n\t}\n\tdelete live.generation;\n\tdelete live.tools;\n\tdelete live.nestedTools;\n}\n\n/** Add the status of a compaction task created in this commit; statuses stay in task ID order. */\nexport function addCompactionStatus(live: Draft<LiveState>, status: CompactionStatus): void {\n\tlive.compactions ??= [];\n\tlive.compactions.push(status);\n}\n\n/** The status of compaction task `taskId`, if listed. */\nexport function compactionStatus(live: Draft<LiveState>, taskId: TaskId): Draft<CompactionStatus> | undefined {\n\treturn live.compactions?.find((status) => status.taskId === taskId);\n}\n\n/** Remove the status of compaction task `taskId`, and the list once empty. */\nexport function removeCompactionStatus(live: Draft<LiveState>, taskId: TaskId): void {\n\tconst statuses = live.compactions;\n\tif (statuses === undefined) return;\n\tconst index = statuses.findIndex((status) => status.taskId === taskId);\n\tif (index >= 0) statuses.splice(index, 1);\n\tif (statuses.length === 0) delete live.compactions;\n}\n\n/**\n * The slot of tool task `taskId`, model-issued or nested, if `nyan.live` still lists it. Nested slots are found by binary\n * search: a tool may make thousands of nested calls, each of which looks up its slot in several commits, and every\n * element a search reads costs a node in the draft's change tracker. Their task IDs ascend: each slot is appended in\n * the commit that creates its task, task IDs ascend in commit order, and removal keeps the order.\n */\nexport function toolSlot(live: Draft<LiveState>, taskId: TaskId): Draft<ToolSlot | NestedToolSlot> | undefined {\n\tconst slot = live.tools?.find((each) => each.taskId === taskId);\n\tif (slot !== undefined || live.nestedTools === undefined) return slot;\n\tconst nested = live.nestedTools;\n\tlet low = 0;\n\tlet high = nested.length - 1;\n\twhile (low <= high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tconst candidate = nested[middle]!;\n\t\tif (candidate.taskId === taskId) return candidate;\n\t\tif (candidate.taskId < taskId) low = middle + 1;\n\t\telse high = middle - 1;\n\t}\n\treturn undefined;\n}\n\n/**\n * Mark a slot done and clear its running output; the caller sets a model-issued call's result `entry`, or a nested\n * call's `summary`. Neither is set when the task faulted or was orphaned.\n */\nexport function finishSlot(slot: Draft<ToolSlot | NestedToolSlot>): void {\n\tslot.status = \"done\";\n\tclearProgress(slot);\n}\n\n/** Remove the nested slots below tool task `taskId`, transitively; the list holds parents before their children. */\nexport function removeNestedSlots(live: Draft<LiveState>, taskId: TaskId): void {\n\tconst nested = live.nestedTools;\n\tif (nested === undefined) return;\n\tconst removed = new Set<TaskId>([taskId]);\n\tfor (const slot of nested) if (removed.has(slot.parentTaskId)) removed.add(slot.taskId);\n\tfor (let index = nested.length - 1; index >= 0; index--) {\n\t\tif (removed.has(nested[index]!.parentTaskId)) nested.splice(index, 1);\n\t}\n\tif (nested.length === 0) delete live.nestedTools;\n}\n\n/** Remove what a tool published while running; its result entry or a rerun replaces it. */\nexport function clearProgress(slot: Draft<SlotProgress>): void {\n\tdelete slot.output;\n\tdelete slot.droppedBytes;\n\tdelete slot.droppedLines;\n\tdelete slot.details;\n\tdelete slot.diagnostics;\n}\n\n/**\n * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). A run task ends its\n * run; a tool task's slot is marked done without an entry, and context derivation synthesizes the missing result; a\n * compaction task's status is removed.\n * Ignores other kinds so it never creates `nyan.live` elsewhere. The scheduler calls this without knowing task kinds;\n * the Harness passes it in (spec §5.4).\n * REMINDER: a committed generation partial becomes an aborted assistant entry here, exactly as in the generation abort\n * handler, so the transcript keeps what the model produced and `nyan.usage` counts its spend. The scheduler's commit has\n * no task scope, so that entry has no `byTaskId`. Faults come from task bugs\n * or malformed provider data (a non-JSON value in a response), or a commit the Storage rejected without effect; an\n * uncertain storage failure poisons the Session instead and writes no outcome.\n */\nexport async function settleSchedulerOutcome(\n\ttx: Transaction,\n\trecord: TaskRecord<JsonValue, JsonValue, JsonValue>,\n\toutcome: SchedulerOutcome,\n): Promise<void> {\n\tif (record.kind === TOOL_TASK_KIND) {\n\t\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\t\tconst slot = toolSlot(live, record.id);\n\t\tif (slot !== undefined) finishSlot(slot);\n\t\tremoveNestedSlots(live, record.id);\n\t\treturn;\n\t}\n\tif (record.kind === COMPACTION_TASK_KIND) {\n\t\tremoveCompactionStatus(await tx.doc(LiveDoc, record.conversationId), record.id);\n\t\treturn;\n\t}\n\tif (!RUN_TASK_KINDS.has(record.kind)) return;\n\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\tif (live.run?.taskId !== record.id) return;\n\tawait convertPartial(tx, live, record.conversationId);\n\tendRun(\n\t\ttx,\n\t\tlive,\n\t\trecord.id,\n\t\toutcome.status === \"faulted\"\n\t\t\t? { status: \"unanswered\", reason: \"faulted\", detail: outcome.error.message }\n\t\t\t: { status: \"unanswered\", reason: outcome.reason },\n\t);\n}\n"]}
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/harness/live.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage, Usage } from \"@ny4n/ai/contracts\";\nimport type { Draft, JsonRepresentation, JsonValue } from \"@ny4n/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { Transaction } from \"../session/transaction.ts\";\nimport type { EntryId, JsonObject, SubmissionId, SubmissionSettlement, TaskId, TaskRecord, Tx } from \"../types.ts\";\nimport { convertPartial } from \"./assistant.ts\";\nimport type { SchedulerOutcome } from \"./scheduler.ts\";\nimport type { CompactionReason, CompactionResult, ToolDiagnostic } from \"./types.ts\";\n\n/** What a running tool call publishes, shared by model-issued and nested calls. */\ntype SlotProgress = {\n\tcallId: string;\n\tname: string;\n\tstatus: \"pending\" | \"running\" | \"done\";\n\t/** Retained running output and what the bounds dropped. */\n\toutput?: string;\n\tdroppedBytes?: number;\n\tdroppedLines?: number;\n\t/** Last `details()` value. */\n\tdetails?: JsonValue;\n\t/** Diagnostics recorded through `api.diagnostic()`. */\n\tdiagnostics?: ToolDiagnostic[];\n};\n\n/** Presentation of one tool call of the current round. */\nexport type ToolSlot = SlotProgress & {\n\t/**\n\t * Absent for a call not started yet (sequential round) and for a call its request did not offer, which starts `done`\n\t * with the `entry` generation wrote.\n\t */\n\ttaskId?: TaskId;\n\t/** Result entry once done; absent when the tool task faulted or was orphaned. */\n\tentry?: EntryId;\n};\n\n/**\n * Presentation of one nested call: a call a running tool made through `executeTool()`. `callId` is\n * `<parentCallId>/<key>`, the call's ID in tool events and unique because keys contain no `/`. The Harness relates\n * slots by task ID: `taskId` is the call's tool task, `parentTaskId` the calling one, model-issued or nested.\n */\nexport type NestedToolSlot = SlotProgress & {\n\tparentCallId: string;\n\tparentTaskId: TaskId;\n\ttaskId: TaskId;\n\t/**\n\t * What the call was made with, replaced by the arguments it runs with, after repair, `beforeTool`, and coercion,\n\t * once it starts executing. A model-issued call's arguments are in its assistant entry instead.\n\t */\n\targuments: JsonObject;\n\t/** Once done: how the call ended, for a status line. The result itself is in the caller's `NestedCallDoc`. */\n\tsummary?: NestedToolSummary;\n};\n\n/** How a nested call ended: an error flag, its execution time, its spend, and up to 500 characters of error text. */\nexport type NestedToolSummary = {\n\tisError: boolean;\n\tdurationMs?: number;\n\tusage?: JsonRepresentation<Usage>;\n\terror?: string;\n};\n\n/** Presentation of one live compaction task (spec §8.7). */\nexport type CompactionStatus = {\n\ttaskId: TaskId<CompactionResult>;\n\treason: CompactionReason;\n\t/** Whether a generation waits for it: a compaction the generation owns. */\n\tblocking: boolean;\n\tattempt: number;\n\t/** Durable backoff before the next summarization attempt. */\n\tretry?: { at: number; error: string };\n};\n\n/** The newest compaction that failed since a compaction last produced a summary (spec §8.7). */\nexport type CompactionFailure = {\n\ttaskId: TaskId<CompactionResult>;\n\treason: CompactionReason;\n\t/** The failed or faulted outcome's error message, or why the scheduler orphaned the task. */\n\terror: string;\n};\n\n/** Built-in live conversation state: run control and presentation of the current generation and tool round. */\nexport type LiveState = {\n\t/** Run control: the task that settles the run's inputs, and those inputs; present exactly while busy. */\n\trun?: { taskId: TaskId; inputs: SubmissionId[] };\n\t/** Presentation of the current generation attempt. */\n\tgeneration?: {\n\t\tattempt: number;\n\t\t/** Committed throttled partial of the in-flight response. */\n\t\tmessage?: JsonRepresentation<AssistantMessage>;\n\t\t/** Durable backoff before the next attempt. */\n\t\tretry?: { at: number; error: string };\n\t\t/** Provider-side deferred response being polled. */\n\t\tdeferred?: { pollAt: number };\n\t};\n\t/** The current tool round in call order, from the tool-calling answer until the generation's `tools` phase ends it. */\n\ttools?: ToolSlot[];\n\t/**\n\t * Nested calls of running tool calls, in creation order, so a parent precedes its children and task IDs ascend. A\n\t * call's nested slots are removed when the call settles.\n\t */\n\tnestedTools?: NestedToolSlot[];\n\t/** Live compaction tasks in task ID order; absent when none. */\n\tcompactions?: CompactionStatus[];\n\t/**\n\t * The newest compaction failure, so a client can show it after the task ended. A compaction that produces a summary\n\t * removes it; one that is aborted or finds nothing to compact leaves it.\n\t */\n\tcompactionFailure?: CompactionFailure;\n};\n\nexport const LiveDoc = defineDoc<LiveState>({\n\tkind: \"nyan.live\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({}),\n\t// REMINDER: a complete base whenever nothing runs (spec §8.2): no generation and no running tool slot. That holds\n\t// while idle, in the commit handing a generation over to its tool round, and between tools, so the delta chain\n\t// spans at most one generation or the overlapping execution of one round's tools. A slot holds output only while\n\t// running, so every base is small; a nested call's result lives in its caller's task documents, never here. Do not\n\t// add a delta-count bound; the tool output benchmark checks this rule.\n\tcheckpointWhen: (value) =>\n\t\tvalue.generation === undefined &&\n\t\t!(value.tools ?? []).some((slot) => slot.status === \"running\") &&\n\t\t!(value.nestedTools ?? []).some((slot) => slot.status === \"running\"),\n});\n\n/** Built-in task kinds that can own `nyan.live.run`. */\nconst RUN_TASK_KINDS: ReadonlySet<string> = new Set([\"nyan.generation\"]);\nconst TOOL_TASK_KIND = \"nyan.tool\";\nconst COMPACTION_TASK_KIND = \"nyan.compaction\";\n\n/**\n * End the run owned by `taskId`: settle each of its inputs and remove `run`. Always removes `generation`, `tools`, and\n * `nestedTools`, whose presentation belongs to the ending run.\n */\nexport function endRun(tx: Tx, live: Draft<LiveState>, taskId: TaskId, settlement: SubmissionSettlement): void {\n\tif (live.run?.taskId === taskId) {\n\t\tfor (const id of live.run.inputs) tx.settleSubmission(id, settlement);\n\t\tdelete live.run;\n\t}\n\tdelete live.generation;\n\tdelete live.tools;\n\tdelete live.nestedTools;\n}\n\n/** Add the status of a compaction task created in this commit; statuses stay in task ID order. */\nexport function addCompactionStatus(live: Draft<LiveState>, status: CompactionStatus): void {\n\tlive.compactions ??= [];\n\tlive.compactions.push(status);\n}\n\n/** The status of compaction task `taskId`, if listed. */\nexport function compactionStatus(live: Draft<LiveState>, taskId: TaskId): Draft<CompactionStatus> | undefined {\n\treturn live.compactions?.find((status) => status.taskId === taskId);\n}\n\n/** Remove the status of compaction task `taskId`, and the list once empty. */\nexport function removeCompactionStatus(live: Draft<LiveState>, taskId: TaskId): void {\n\tconst statuses = live.compactions;\n\tif (statuses === undefined) return;\n\tconst index = statuses.findIndex((status) => status.taskId === taskId);\n\tif (index >= 0) statuses.splice(index, 1);\n\tif (statuses.length === 0) delete live.compactions;\n}\n\n/** Remove the status of compaction task `taskId` and record that it failed with `error`. */\nexport function failCompactionStatus(live: Draft<LiveState>, taskId: TaskId, error: string): void {\n\tconst status = compactionStatus(live, taskId);\n\tif (status !== undefined) live.compactionFailure = { taskId: status.taskId, reason: status.reason, error };\n\tremoveCompactionStatus(live, taskId);\n}\n\n/**\n * The slot of tool task `taskId`, model-issued or nested, if `nyan.live` still lists it. Nested slots are found by binary\n * search: a tool may make thousands of nested calls, each of which looks up its slot in several commits, and every\n * element a search reads costs a node in the draft's change tracker. Their task IDs ascend: each slot is appended in\n * the commit that creates its task, task IDs ascend in commit order, and removal keeps the order.\n */\nexport function toolSlot(live: Draft<LiveState>, taskId: TaskId): Draft<ToolSlot | NestedToolSlot> | undefined {\n\tconst slot = live.tools?.find((each) => each.taskId === taskId);\n\tif (slot !== undefined || live.nestedTools === undefined) return slot;\n\tconst nested = live.nestedTools;\n\tlet low = 0;\n\tlet high = nested.length - 1;\n\twhile (low <= high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tconst candidate = nested[middle]!;\n\t\tif (candidate.taskId === taskId) return candidate;\n\t\tif (candidate.taskId < taskId) low = middle + 1;\n\t\telse high = middle - 1;\n\t}\n\treturn undefined;\n}\n\n/**\n * Mark a slot done and clear its running output; the caller sets a model-issued call's result `entry`, or a nested\n * call's `summary`. Neither is set when the task faulted or was orphaned.\n */\nexport function finishSlot(slot: Draft<ToolSlot | NestedToolSlot>): void {\n\tslot.status = \"done\";\n\tclearProgress(slot);\n}\n\n/** Remove the nested slots below tool task `taskId`, transitively; the list holds parents before their children. */\nexport function removeNestedSlots(live: Draft<LiveState>, taskId: TaskId): void {\n\tconst nested = live.nestedTools;\n\tif (nested === undefined) return;\n\tconst removed = new Set<TaskId>([taskId]);\n\tfor (const slot of nested) if (removed.has(slot.parentTaskId)) removed.add(slot.taskId);\n\tfor (let index = nested.length - 1; index >= 0; index--) {\n\t\tif (removed.has(nested[index]!.parentTaskId)) nested.splice(index, 1);\n\t}\n\tif (nested.length === 0) delete live.nestedTools;\n}\n\n/** Remove what a tool published while running; its result entry or a rerun replaces it. */\nexport function clearProgress(slot: Draft<SlotProgress>): void {\n\tdelete slot.output;\n\tdelete slot.droppedBytes;\n\tdelete slot.droppedLines;\n\tdelete slot.details;\n\tdelete slot.diagnostics;\n}\n\n/**\n * Harness cleanup for a terminal outcome the scheduler writes itself (`faulted` or `orphaned`). A run task ends its\n * run; a tool task's slot is marked done without an entry, and context derivation synthesizes the missing result; a\n * compaction task's status is removed and its failure recorded.\n * Ignores other kinds so it never creates `nyan.live` elsewhere. The scheduler calls this without knowing task kinds;\n * the Harness passes it in (spec §5.4).\n * REMINDER: a committed generation partial becomes an aborted assistant entry here, exactly as in the generation abort\n * handler, so the transcript keeps what the model produced and `nyan.usage` counts its spend. The scheduler's commit has\n * no task scope, so that entry has no `byTaskId`. Faults come from task bugs\n * or malformed provider data (a non-JSON value in a response), or a commit the Storage rejected without effect; an\n * uncertain storage failure poisons the Session instead and writes no outcome.\n */\nexport async function settleSchedulerOutcome(\n\ttx: Transaction,\n\trecord: TaskRecord<JsonValue, JsonValue, JsonValue>,\n\toutcome: SchedulerOutcome,\n): Promise<void> {\n\tif (record.kind === TOOL_TASK_KIND) {\n\t\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\t\tconst slot = toolSlot(live, record.id);\n\t\tif (slot !== undefined) finishSlot(slot);\n\t\tremoveNestedSlots(live, record.id);\n\t\treturn;\n\t}\n\tif (record.kind === COMPACTION_TASK_KIND) {\n\t\tconst error = outcome.status === \"faulted\" ? outcome.error.message : outcome.reason;\n\t\tfailCompactionStatus(await tx.doc(LiveDoc, record.conversationId), record.id, error);\n\t\treturn;\n\t}\n\tif (!RUN_TASK_KINDS.has(record.kind)) return;\n\tconst live = await tx.doc(LiveDoc, record.conversationId);\n\tif (live.run?.taskId !== record.id) return;\n\tawait convertPartial(tx, live, record.conversationId);\n\tendRun(\n\t\ttx,\n\t\tlive,\n\t\trecord.id,\n\t\toutcome.status === \"faulted\"\n\t\t\t? { status: \"unanswered\", reason: \"faulted\", detail: outcome.error.message }\n\t\t\t: { status: \"unanswered\", reason: outcome.reason },\n\t);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/harness/scheduler.ts"],"names":[],"mappings":"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"}
@@ -1366,30 +1366,92 @@ export class TaskScheduler {
1366
1366
  tx.setTask(withState(current, next));
1367
1367
  }
1368
1368
  /**
1369
- * A wait names existing tasks other than the waiter and its owners, which could never finish first; `failFast` only
1370
- * tasks the waiter owns. An abort handler cannot wait.
1369
+ * A wait names existing tasks, and `failFast` only tasks the waiter owns. It may not close a cycle in the wait-for
1370
+ * graph, whose edges run from each task to what it waits on and to its live ordinary owned work (spec §5.5); waiting
1371
+ * on itself or on an owner holding it are the shortest such cycles. An abort handler cannot wait.
1372
+ *
1373
+ * Invariant: the wait-for graph is acyclic. Only a wait adds an edge between existing tasks, as a new task has only
1374
+ * incoming edges, so checking each wait maintains it.
1371
1375
  */
1372
1376
  async #validateWait(tx, invocation, current, on, policy) {
1373
1377
  if (invocation.mode === "abort")
1374
1378
  throw new Error(`Abort handler of task ${current.id} cannot wait`);
1375
1379
  const overlay = overlayOf(tx);
1376
- await this.#loadChain(parentOf(current));
1377
- const owners = new Set();
1378
- for (const step of this.#above(parentOf(current)))
1379
- if ("task" in step)
1380
- owners.add(step.task);
1380
+ const targets = [];
1381
1381
  for (const id of on) {
1382
- if (id === current.id || owners.has(id)) {
1383
- throw new Error(`Task ${current.id} cannot wait on itself or its owner ${id}`);
1384
- }
1385
1382
  const member = overlay.tasks.get(id) ?? this.#live.get(id) ?? (await this.#storage.task(id, this.#context));
1386
1383
  if (member === undefined)
1387
1384
  throw new Error(`Task ${id} does not exist`);
1388
1385
  if (policy === "failFast" && member.owner !== current.id) {
1389
1386
  throw new Error(`Task ${current.id} can wait failFast only on tasks it owns; ${id} is not one`);
1390
1387
  }
1388
+ // A live child already holds the waiter (task-owned tasks are never background): waiting on it adds no edge. A
1389
+ // terminal task has no edges.
1390
+ if (member.state.status !== "terminal" && member.owner !== current.id)
1391
+ targets.push(id);
1392
+ }
1393
+ if (targets.length === 0)
1394
+ return;
1395
+ const cycle = await this.#waitCycle(current.id, targets, overlay);
1396
+ if (cycle !== undefined) {
1397
+ throw new Error(`Task ${current.id} cannot wait on ${cycle[0]}: ${[current.id, ...cycle].join(" -> ")} is a wait cycle`);
1391
1398
  }
1392
1399
  }
1400
+ /**
1401
+ * The path from a member of `on` back to `task` in the wait-for graph, if the wait would close a cycle (spec §5.1).
1402
+ * An edge X -> Y means X cannot settle before Y: Y is in the `on` of waiting X that is not abort-marked, or Y is live
1403
+ * ordinary owned work of X,
1404
+ * which holds X as `completing`. Each task is visited once and each subtree walked once, so a check is bounded by the
1405
+ * live work it reaches.
1406
+ */
1407
+ async #waitCycle(task, on, overlay) {
1408
+ // Owned work is found by walks down, which need the chains of every live task loaded.
1409
+ await this.#loadScopes(false);
1410
+ const from = new Map();
1411
+ const walked = new Set();
1412
+ const pending = [];
1413
+ const reach = (id, via) => {
1414
+ if (from.has(id))
1415
+ return;
1416
+ from.set(id, via);
1417
+ pending.push(id);
1418
+ };
1419
+ const path = (end) => {
1420
+ const steps = [];
1421
+ for (let at = end; at !== undefined; at = from.get(at))
1422
+ steps.unshift(at);
1423
+ return steps;
1424
+ };
1425
+ for (const id of on)
1426
+ reach(id, undefined);
1427
+ for (let id = pending.pop(); id !== undefined; id = pending.pop()) {
1428
+ if (id === task)
1429
+ return path(id);
1430
+ const record = overlay.tasks.get(id) ?? this.#live.get(id);
1431
+ if (record === undefined || record.state.status === "terminal")
1432
+ continue;
1433
+ // An abort-marked task waits only for its owned work, as abort runs bottom-up (`#waitingOn`).
1434
+ if (record.state.status === "waiting" && !record.abortRequested) {
1435
+ for (const member of record.state.on)
1436
+ reach(member, id);
1437
+ }
1438
+ if (walked.has(id))
1439
+ continue;
1440
+ walked.add(id);
1441
+ // Below a walked task the walk does not go again: its own walk covered what lies below.
1442
+ const found = [];
1443
+ const walk = this.#walkDown(id, { overlay, enter: (below) => !walked.has(below) });
1444
+ for (const { id: below, node, live } of walk) {
1445
+ if (live === undefined || node.background)
1446
+ continue;
1447
+ found.push(below);
1448
+ reach(below, id);
1449
+ }
1450
+ for (const below of found)
1451
+ walked.add(below);
1452
+ }
1453
+ return undefined;
1454
+ }
1393
1455
  /** End an invocation: its runtime operations reject from now on, its signal aborts, its watches stop, and its task is free. */
1394
1456
  #end(invocation) {
1395
1457
  if (invocation.ended)