@nylorun/harness 0.13.0-beta → 0.15.0-beta

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 (123) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/HOST_CONTRACT.md +27 -0
  3. package/README.md +62 -100
  4. package/dist/compatibility.d.ts +3 -0
  5. package/dist/compatibility.js +3 -0
  6. package/dist/definition/agent-definition.d.ts +11 -3
  7. package/dist/definition/agent-definition.js +18 -10
  8. package/dist/definition/compatibility.d.ts +11 -0
  9. package/dist/definition/compatibility.js +30 -0
  10. package/dist/definition/registry.d.ts +2 -2
  11. package/dist/definition/registry.js +7 -6
  12. package/dist/index.d.ts +4 -18
  13. package/dist/index.js +1 -6
  14. package/dist/loop/dispatch.js +327 -0
  15. package/dist/loop/host-suspension.d.ts +6 -0
  16. package/dist/loop/host-suspension.js +10 -0
  17. package/dist/loop/initial-state.d.ts +13 -0
  18. package/dist/loop/initial-state.js +30 -0
  19. package/dist/{execution → loop}/invocation.d.ts +5 -2
  20. package/dist/{execution → loop}/invocation.js +8 -3
  21. package/dist/{execution → loop}/model/adapters.d.ts +4 -2
  22. package/dist/{execution → loop}/model/adapters.js +3 -2
  23. package/dist/{execution → loop}/model/normalize.d.ts +2 -2
  24. package/dist/{execution → loop}/model/normalize.js +2 -2
  25. package/dist/{execution → loop}/model/prepared.d.ts +2 -2
  26. package/dist/{execution → loop}/model/project.d.ts +1 -1
  27. package/dist/{execution → loop}/model/project.js +1 -1
  28. package/dist/{execution → loop}/observe.d.ts +2 -2
  29. package/dist/{execution → loop}/observe.js +1 -1
  30. package/dist/{execution → loop}/options.d.ts +2 -1
  31. package/dist/{execution → loop}/options.js +28 -8
  32. package/dist/{execution → loop}/resume.d.ts +1 -1
  33. package/dist/{execution → loop}/resume.js +2 -2
  34. package/dist/{execution → loop}/run.d.ts +1 -1
  35. package/dist/{execution → loop}/run.js +9 -1
  36. package/dist/{execution → loop}/state.d.ts +2 -2
  37. package/dist/{execution → loop}/state.js +13 -3
  38. package/dist/{execution → loop}/step/canonicalize.d.ts +2 -2
  39. package/dist/{execution → loop}/step/canonicalize.js +1 -1
  40. package/dist/{execution → loop}/step/compose.d.ts +2 -2
  41. package/dist/{execution → loop}/step/compose.js +4 -1
  42. package/dist/{execution → loop}/step/context-draft.d.ts +2 -2
  43. package/dist/{execution → loop}/step/context-draft.js +2 -2
  44. package/dist/{execution → loop}/step/describe.d.ts +3 -3
  45. package/dist/{execution → loop}/step/describe.js +1 -1
  46. package/dist/loop/step/dynamics.d.ts +29 -0
  47. package/dist/loop/step/dynamics.js +147 -0
  48. package/dist/{execution → loop}/step/model-configuration.d.ts +2 -2
  49. package/dist/{execution → loop}/step/model-configuration.js +3 -3
  50. package/dist/{execution → loop}/step/resolve.d.ts +4 -4
  51. package/dist/{execution → loop}/step/run.d.ts +5 -4
  52. package/dist/{execution → loop}/step/run.js +87 -2
  53. package/dist/{execution → loop}/step/runtime.d.ts +12 -4
  54. package/dist/{execution → loop}/step/seal.d.ts +5 -5
  55. package/dist/{execution → loop}/step/seal.js +2 -2
  56. package/dist/{execution → loop}/step/slot-assembly.d.ts +1 -1
  57. package/dist/{execution → loop}/step/slot-assembly.js +1 -1
  58. package/dist/{execution → loop}/step/step-context.d.ts +5 -5
  59. package/dist/{execution → loop}/step/step-context.js +2 -2
  60. package/dist/{execution → loop}/tool-result.d.ts +2 -2
  61. package/dist/{execution → loop}/tool-result.js +2 -2
  62. package/dist/{execution → loop}/transcript.d.ts +1 -1
  63. package/dist/{execution → loop}/transcript.js +2 -2
  64. package/dist/loop/waits.d.ts +50 -0
  65. package/dist/loop/waits.js +11 -0
  66. package/dist/run/durable.d.ts +71 -0
  67. package/dist/run/durable.js +164 -0
  68. package/dist/run/index.d.ts +33 -0
  69. package/dist/run/index.js +38 -0
  70. package/dist/types/client.d.ts +88 -0
  71. package/dist/types/execution.d.ts +22 -5
  72. package/dist/utils/ids.d.ts +1 -0
  73. package/dist/utils/ids.js +11 -1
  74. package/package.json +18 -6
  75. package/dist/definition/assemble.d.ts +0 -22
  76. package/dist/definition/assemble.js +0 -43
  77. package/dist/definition/bind-agent.d.ts +0 -9
  78. package/dist/definition/bind-agent.js +0 -21
  79. package/dist/definition/bind-tool.d.ts +0 -4
  80. package/dist/definition/bind-tool.js +0 -19
  81. package/dist/definition/bound.d.ts +0 -16
  82. package/dist/definition/builder.d.ts +0 -30
  83. package/dist/definition/builder.js +0 -94
  84. package/dist/definition/declaration.d.ts +0 -3
  85. package/dist/definition/declaration.js +0 -53
  86. package/dist/definition/helpers.d.ts +0 -6
  87. package/dist/definition/helpers.js +0 -4
  88. package/dist/definition/manifest.d.ts +0 -9
  89. package/dist/definition/manifest.js +0 -45
  90. package/dist/definition/output-contract.d.ts +0 -6
  91. package/dist/definition/output-contract.js +0 -12
  92. package/dist/definition/schema.d.ts +0 -14
  93. package/dist/definition/schema.js +0 -205
  94. package/dist/errors.d.ts +0 -17
  95. package/dist/errors.js +0 -17
  96. package/dist/execution/dispatch.js +0 -164
  97. package/dist/execution/initial-state.d.ts +0 -3
  98. package/dist/execution/initial-state.js +0 -14
  99. package/dist/types/agent.d.ts +0 -9
  100. package/dist/types/agent.js +0 -1
  101. package/dist/types/manifest.d.ts +0 -22
  102. package/dist/types/manifest.js +0 -1
  103. package/dist/types/middleware.d.ts +0 -66
  104. package/dist/types/middleware.js +0 -1
  105. package/dist/types/model.d.ts +0 -184
  106. package/dist/types/model.js +0 -1
  107. package/dist/types/observe.d.ts +0 -165
  108. package/dist/types/observe.js +0 -1
  109. package/dist/types/shared.d.ts +0 -25
  110. package/dist/types/shared.js +0 -1
  111. package/dist/types/tool.d.ts +0 -138
  112. package/dist/types/tool.js +0 -1
  113. package/dist/types/transcript.d.ts +0 -65
  114. package/dist/types/transcript.js +0 -1
  115. package/dist/utils/canonical.d.ts +0 -1
  116. package/dist/utils/canonical.js +0 -10
  117. package/dist/utils/immutable.d.ts +0 -6
  118. package/dist/utils/immutable.js +0 -76
  119. /package/dist/{execution → loop}/dispatch.d.ts +0 -0
  120. /package/dist/{execution → loop}/model/prepared.js +0 -0
  121. /package/dist/{execution → loop}/step/resolve.js +0 -0
  122. /package/dist/{execution → loop}/step/runtime.js +0 -0
  123. /package/dist/{definition/bound.js → types/client.js} +0 -0
@@ -0,0 +1,327 @@
1
+ import { HostSuspension } from "./host-suspension.js";
2
+ import { HarnessError, isHarnessError } from "@nylorun/core/define";
3
+ import { isToolError } from "@nylorun/core/define";
4
+ import { createId } from "../utils/ids.js";
5
+ import { copyJson } from "@nylorun/core/define";
6
+ import { createSessionStateBag } from "./initial-state.js";
7
+ import { toolResult } from "./tool-result.js";
8
+ import { isWaitSignal, WaitSignal } from "./waits.js";
9
+ export async function dispatchPlan(invocation) {
10
+ const { signal, definitions, options, observe, record } = invocation;
11
+ let plan = invocation.state.plan;
12
+ if (plan.calls.some((call) => call.status === "interaction" && call.interactionPhase === "before")) {
13
+ for (const call of plan.calls)
14
+ if (call.status === "interaction")
15
+ observe({
16
+ type: "interaction.required",
17
+ turnId: plan.turnId,
18
+ stepId: plan.stepId,
19
+ interactionId: call.interaction.id,
20
+ kind: call.interaction.kind,
21
+ callId: call.callId,
22
+ toolName: call.toolName,
23
+ phase: "interaction",
24
+ attributes: {
25
+ prompt: call.interaction.prompt,
26
+ ...(call.interaction.metadata ? { metadata: call.interaction.metadata } : {}),
27
+ },
28
+ });
29
+ return "paused";
30
+ }
31
+ signal.throwIfAborted();
32
+ const pending = plan.calls.filter((call) => call.status === "pending" || call.status === "active");
33
+ const redelivering = plan.calls.some((call) => call.status === "active");
34
+ if (pending.length) {
35
+ invocation.state = {
36
+ ...invocation.state,
37
+ plan: {
38
+ ...plan,
39
+ calls: plan.calls.map((call) => call.status === "pending" || call.status === "active"
40
+ ? { ...call, status: "active" }
41
+ : call),
42
+ },
43
+ };
44
+ await record();
45
+ signal.throwIfAborted();
46
+ const sessionBag = invocation.sessionBag;
47
+ const stateApi = createSessionStateBag(sessionBag);
48
+ const settled = await settleDispatch(pending.map(async (call) => {
49
+ const definition = definitions.get(call.invocationId);
50
+ const eventIds = {
51
+ executionId: invocation.state.executionId,
52
+ turnId: plan.turnId,
53
+ stepId: plan.stepId,
54
+ callId: call.callId,
55
+ invocationId: call.invocationId,
56
+ toolName: call.toolName,
57
+ middlewareId: call.reference.capabilityId,
58
+ slot: definition.owner.slot,
59
+ };
60
+ observe({ type: "tool.started", ...eventIds, attributes: { args: call.args } });
61
+ let outcome;
62
+ try {
63
+ signal.throwIfAborted();
64
+ if (definition.approval && !call.resume) {
65
+ const decision = await definition.approval(call.args);
66
+ const prompt = typeof decision === "string"
67
+ ? decision
68
+ : decision === true
69
+ ? `Approve ${call.toolName}?`
70
+ : undefined;
71
+ if (prompt) {
72
+ return copyJson({
73
+ ...call,
74
+ status: "interaction",
75
+ interaction: {
76
+ kind: "approval",
77
+ prompt,
78
+ id: createId("interaction"),
79
+ },
80
+ interactionPhase: "before",
81
+ });
82
+ }
83
+ }
84
+ const stepMemos = new Map();
85
+ if (call.token && typeof call.token === "object" && !Array.isArray(call.token)) {
86
+ const memos = call.token.steps;
87
+ if (memos)
88
+ for (const [key, value] of Object.entries(memos))
89
+ stepMemos.set(key, value);
90
+ }
91
+ const raw = await definition.execute(call.args, {
92
+ executionId: invocation.state.executionId,
93
+ turnId: plan.turnId,
94
+ stepId: plan.stepId,
95
+ callId: call.callId,
96
+ invocationId: call.invocationId,
97
+ signal,
98
+ info: options.info,
99
+ onModelCall: options.onModelCall,
100
+ idempotencyKey: call.callId,
101
+ ...(redelivering || call.status === "active" ? { redelivery: true } : {}),
102
+ state: stateApi,
103
+ session: { id: invocation.state.executionId },
104
+ progress(message, data) {
105
+ observe({
106
+ type: "tool.progress",
107
+ ...eventIds,
108
+ attributes: {
109
+ message,
110
+ ...(data === undefined ? {} : { data }),
111
+ },
112
+ });
113
+ },
114
+ async ask(prompt, opts) {
115
+ if (call.resume?.kind === "response")
116
+ return call.resume.value;
117
+ throw new WaitSignal({
118
+ kind: "interaction-required",
119
+ interaction: {
120
+ kind: "response",
121
+ prompt,
122
+ ...(opts ? { metadata: opts } : {}),
123
+ },
124
+ wait: { kind: "ask", waitId: createId("wait") },
125
+ token: { steps: Object.fromEntries(stepMemos) },
126
+ });
127
+ },
128
+ async approve(prompt) {
129
+ if (call.resume?.kind === "approval")
130
+ return Boolean(call.resume.approved);
131
+ throw new WaitSignal({
132
+ kind: "interaction-required",
133
+ interaction: { kind: "approval", prompt },
134
+ wait: { kind: "approve", waitId: createId("wait") },
135
+ token: { steps: Object.fromEntries(stepMemos) },
136
+ });
137
+ },
138
+ async sleep(duration) {
139
+ throw new WaitSignal({
140
+ kind: "deferred",
141
+ wait: { kind: "sleep", waitId: createId("wait") },
142
+ token: { steps: Object.fromEntries(stepMemos), duration },
143
+ });
144
+ },
145
+ async waitFor(event, opts) {
146
+ throw new WaitSignal({
147
+ kind: "deferred",
148
+ wait: { kind: "waitFor", waitId: createId("wait"), name: event },
149
+ token: { steps: Object.fromEntries(stepMemos), event, ...(opts ?? {}) },
150
+ });
151
+ },
152
+ async step(name, fn) {
153
+ if (stepMemos.has(name))
154
+ return stepMemos.get(name);
155
+ const value = await fn();
156
+ stepMemos.set(name, value);
157
+ return value;
158
+ },
159
+ ...(call.resume ? { resume: call.resume } : {}),
160
+ });
161
+ outcome = normalizeOutcome(raw);
162
+ if (!outcome || typeof outcome !== "object")
163
+ throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid outcome");
164
+ if (outcome.kind === "interaction-required") {
165
+ if (!["approval", "response"].includes(outcome.interaction.kind) ||
166
+ typeof outcome.interaction.prompt !== "string")
167
+ throw new HarnessError("tool.invalid-tool-result", "Tool returned an invalid interaction");
168
+ return copyJson({
169
+ ...call,
170
+ status: "interaction",
171
+ interaction: {
172
+ ...outcome.interaction,
173
+ id: outcome.interaction.id ?? createId("interaction"),
174
+ },
175
+ interactionPhase: "execute",
176
+ ...(outcome.token === undefined ? {} : { token: outcome.token }),
177
+ });
178
+ }
179
+ if (outcome.kind === "deferred") {
180
+ observe({
181
+ type: "tool.deferred",
182
+ ...eventIds,
183
+ attributes: outcome.token === undefined ? {} : { token: outcome.token },
184
+ });
185
+ return copyJson({
186
+ ...call,
187
+ status: "deferred",
188
+ ...(outcome.token === undefined ? {} : { token: outcome.token }),
189
+ });
190
+ }
191
+ const result = toolResult(call, definition, outcome);
192
+ observe({
193
+ type: "tool.completed",
194
+ ...eventIds,
195
+ outcome: result.kind,
196
+ attributes: result,
197
+ });
198
+ return copyJson({ ...call, status: "settled", result });
199
+ }
200
+ catch (cause) {
201
+ if (cause instanceof HostSuspension)
202
+ throw cause;
203
+ if (isWaitSignal(cause)) {
204
+ if (cause.outcome.kind === "interaction-required") {
205
+ return copyJson({
206
+ ...call,
207
+ status: "interaction",
208
+ interaction: {
209
+ ...cause.outcome.interaction,
210
+ id: createId("interaction"),
211
+ },
212
+ interactionPhase: "execute",
213
+ wait: cause.outcome.wait,
214
+ ...(cause.outcome.token === undefined ? {} : { token: cause.outcome.token }),
215
+ });
216
+ }
217
+ observe({
218
+ type: "tool.deferred",
219
+ ...eventIds,
220
+ attributes: cause.outcome.token === undefined ? {} : { token: cause.outcome.token },
221
+ });
222
+ return copyJson({
223
+ ...call,
224
+ status: "deferred",
225
+ wait: cause.outcome.wait,
226
+ ...(cause.outcome.token === undefined ? {} : { token: cause.outcome.token }),
227
+ });
228
+ }
229
+ if (isToolError(cause)) {
230
+ const result = {
231
+ callId: call.callId,
232
+ toolName: call.toolName,
233
+ kind: "failed",
234
+ code: cause.code,
235
+ message: cause.message,
236
+ };
237
+ observe({
238
+ type: "tool.completed",
239
+ ...eventIds,
240
+ outcome: result.kind,
241
+ attributes: result,
242
+ });
243
+ return copyJson({ ...call, status: "settled", result });
244
+ }
245
+ const result = {
246
+ callId: call.callId,
247
+ toolName: call.toolName,
248
+ kind: "failed",
249
+ code: signal.aborted
250
+ ? "tool.cancelled"
251
+ : isHarnessError(cause)
252
+ ? cause.code
253
+ : "tool.execution-failed",
254
+ message: cause instanceof Error ? cause.message : String(cause),
255
+ };
256
+ observe({
257
+ type: "tool.completed",
258
+ ...eventIds,
259
+ outcome: result.kind,
260
+ attributes: result,
261
+ });
262
+ return copyJson({ ...call, status: "settled", result });
263
+ }
264
+ }));
265
+ const byId = new Map(settled.map((call) => [call.invocationId, call]));
266
+ plan = { ...plan, calls: plan.calls.map((call) => byId.get(call.invocationId) ?? call) };
267
+ invocation.state = {
268
+ ...invocation.state,
269
+ plan,
270
+ };
271
+ await record();
272
+ }
273
+ signal.throwIfAborted();
274
+ if (plan.calls.some((call) => call.status === "interaction" || call.status === "deferred")) {
275
+ for (const call of plan.calls)
276
+ if (call.status === "interaction")
277
+ observe({
278
+ type: "interaction.required",
279
+ turnId: plan.turnId,
280
+ stepId: plan.stepId,
281
+ interactionId: call.interaction.id,
282
+ kind: call.interaction.kind,
283
+ callId: call.callId,
284
+ toolName: call.toolName,
285
+ phase: call.interactionPhase === "before" ? "interaction" : "execute",
286
+ attributes: {
287
+ prompt: call.interaction.prompt,
288
+ ...(call.interaction.metadata ? { metadata: call.interaction.metadata } : {}),
289
+ },
290
+ });
291
+ return "paused";
292
+ }
293
+ const results = [
294
+ ...plan.immediate,
295
+ ...plan.calls.flatMap((call) => (call.result ? [call.result] : [])),
296
+ ];
297
+ const ordered = plan.order.map((item) => results.find((result) => result.callId === item.callId && result.toolName === item.toolName));
298
+ const { plan: _, ...rest } = invocation.state;
299
+ invocation.state = {
300
+ ...rest,
301
+ transcript: [
302
+ ...invocation.state.transcript,
303
+ { kind: "tool-results", turnId: plan.turnId, stepId: plan.stepId, results: ordered },
304
+ ],
305
+ };
306
+ await record();
307
+ }
308
+ function normalizeOutcome(raw) {
309
+ if (raw && typeof raw === "object" && "kind" in raw) {
310
+ const kind = raw.kind;
311
+ if (kind === "completed" ||
312
+ kind === "denied" ||
313
+ kind === "failed" ||
314
+ kind === "interaction-required" ||
315
+ kind === "deferred")
316
+ return raw;
317
+ }
318
+ return { kind: "completed", output: raw };
319
+ }
320
+ // Persist all parallel action intents before handing control back to a durable host.
321
+ async function settleDispatch(promises) {
322
+ const results = await Promise.allSettled(promises);
323
+ const failure = results.find((r) => r.status === "rejected");
324
+ if (failure)
325
+ throw failure.reason;
326
+ return results.map((r) => r.value);
327
+ }
@@ -0,0 +1,6 @@
1
+ /** Control transfer to a durable host, never a model/tool failure. */
2
+ export declare class HostSuspension extends Error {
3
+ readonly effectId: string;
4
+ readonly status: "pending" | "uncertain";
5
+ constructor(effectId: string, status: "pending" | "uncertain");
6
+ }
@@ -0,0 +1,10 @@
1
+ /** Control transfer to a durable host, never a model/tool failure. */
2
+ export class HostSuspension extends Error {
3
+ effectId;
4
+ status;
5
+ constructor(effectId, status) {
6
+ super(`Host suspended at ${effectId}`);
7
+ this.effectId = effectId;
8
+ this.status = status;
9
+ }
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { AgentDefinition } from "../definition/agent-definition.js";
2
+ import type { ExecutionState } from "../types/execution.js";
3
+ import type { JsonObject, JsonValue } from "@nylorun/core/define";
4
+ export declare function initializeExecutionState(agent: AgentDefinition, options?: {
5
+ readonly executionId?: string;
6
+ readonly state?: JsonObject;
7
+ }): ExecutionState;
8
+ /** Mutable session-memory bag backed by Invocation / ExecutionState.state. */
9
+ export declare function createSessionStateBag(bag: Record<string, JsonValue>): {
10
+ get(key: string): JsonValue | undefined;
11
+ set(key: string, value: JsonValue): void;
12
+ entries(): Readonly<Record<string, JsonValue>>;
13
+ };
@@ -0,0 +1,30 @@
1
+ import { copyJson } from "@nylorun/core/define";
2
+ import { createId } from "../utils/ids.js";
3
+ export function initializeExecutionState(agent, options = {}) {
4
+ return copyJson({
5
+ version: 1,
6
+ agentId: agent.id,
7
+ manifestHash: agent.hash,
8
+ executionId: options.executionId ?? createId("execution"),
9
+ revision: 0,
10
+ turnCount: 0,
11
+ transcript: [],
12
+ status: "ready",
13
+ state: options.state ?? {},
14
+ ...(agent.output ? { outputContract: agent.output.schema.jsonSchema } : {}),
15
+ });
16
+ }
17
+ /** Mutable session-memory bag backed by Invocation / ExecutionState.state. */
18
+ export function createSessionStateBag(bag) {
19
+ return {
20
+ get(key) {
21
+ return bag[key];
22
+ },
23
+ set(key, value) {
24
+ bag[key] = value;
25
+ },
26
+ entries() {
27
+ return { ...bag };
28
+ },
29
+ };
30
+ }
@@ -1,7 +1,8 @@
1
1
  import type { AgentDefinition } from "../definition/agent-definition.js";
2
- import type { BoundToolDefinition } from "../definition/bound.js";
2
+ import type { BoundToolDefinition } from "@nylorun/core/define";
3
3
  import type { ExecutionState, RunOptions } from "../types/execution.js";
4
- import type { ObserveEvent } from "../types/observe.js";
4
+ import type { ObserveEvent } from "@nylorun/core/define";
5
+ import type { JsonValue } from "@nylorun/core/define";
5
6
  import { type ExecutionOutcomeEvent, type ObserveEmit } from "./observe.js";
6
7
  export interface Invocation {
7
8
  readonly agent: AgentDefinition;
@@ -9,6 +10,8 @@ export interface Invocation {
9
10
  readonly signal: AbortSignal;
10
11
  readonly definitions: Map<string, BoundToolDefinition>;
11
12
  state: ExecutionState;
13
+ /** Mutable durable session memory for this invocation. */
14
+ sessionBag: Record<string, JsonValue>;
12
15
  observe: ObserveEmit;
13
16
  emit(event: ObserveEvent | ExecutionOutcomeEvent): void;
14
17
  record(): Promise<void>;
@@ -1,5 +1,5 @@
1
- import { HarnessError } from "../errors.js";
2
- import { copyJson } from "../utils/immutable.js";
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ import { copyJson } from "@nylorun/core/define";
3
3
  import { createId } from "../utils/ids.js";
4
4
  import { createObservationSink } from "./observe.js";
5
5
  export function createInvocation(input) {
@@ -9,6 +9,7 @@ export function createInvocation(input) {
9
9
  signal: input.options.signal ?? new AbortController().signal,
10
10
  definitions: input.definitions,
11
11
  state: input.state,
12
+ sessionBag: { ...(input.state.state ?? {}) },
12
13
  };
13
14
  const sink = createObservationSink({
14
15
  listener: input.options.onEvent,
@@ -18,7 +19,11 @@ export function createInvocation(input) {
18
19
  invocation.observe = sink.observe;
19
20
  invocation.emit = sink.emit;
20
21
  invocation.record = async () => {
21
- invocation.state = copyJson({ ...invocation.state, revision: invocation.state.revision + 1 });
22
+ invocation.state = copyJson({
23
+ ...invocation.state,
24
+ state: { ...invocation.sessionBag },
25
+ revision: invocation.state.revision + 1,
26
+ });
22
27
  if (!input.options.record)
23
28
  return;
24
29
  try {
@@ -1,5 +1,7 @@
1
- import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "../../types/model.js";
2
- import type { JsonObject } from "../../types/shared.js";
1
+ import type { ModelAdapter, ModelAdapterContext, ModelCall, ModelCandidate } from "@nylorun/core/define";
2
+ import type { JsonObject } from "@nylorun/core/define";
3
+ import { preparedModel } from "./prepared.js";
4
+ export { preparedModel };
3
5
  export type ChatCompletionsMessage = {
4
6
  readonly role: "system";
5
7
  readonly content: string;
@@ -1,6 +1,7 @@
1
- import { HarnessError } from "../../errors.js";
2
- import { copyJsonObject } from "../../utils/immutable.js";
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ import { copyJsonObject } from "@nylorun/core/define";
3
3
  import { preparedModel } from "./prepared.js";
4
+ export { preparedModel };
4
5
  /** Translate a Harness call to the OpenAI Chat Completions request shape. */
5
6
  export function toChatCompletions(call) {
6
7
  const messages = withoutProviderReasoning(call.prompt).map((item) => {
@@ -1,5 +1,5 @@
1
- import { HarnessError } from "../../errors.js";
2
- import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "../../types/model.js";
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ import type { ModelCandidate, ModelDirective, ModelOutputBlock } from "@nylorun/core/define";
3
3
  export declare function normalizeDirective(value: unknown): ModelDirective | HarnessError;
4
4
  export declare function sameDirective(left: ModelDirective, right: ModelDirective): boolean;
5
5
  export declare function textFromOutput(output: readonly ModelOutputBlock[]): string;
@@ -1,5 +1,5 @@
1
- import { HarnessError, isHarnessError } from "../../errors.js";
2
- import { assertJson, copyJson, copyJsonObject } from "../../utils/immutable.js";
1
+ import { HarnessError, isHarnessError } from "@nylorun/core/define";
2
+ import { assertJson, copyJson, copyJsonObject } from "@nylorun/core/define";
3
3
  const FINISH_REASONS = new Set([
4
4
  "stop",
5
5
  "length",
@@ -1,5 +1,5 @@
1
- import type { ModelAdapter, ModelAdapterContext, ModelCandidate, ModelCall } from "../../types/model.js";
2
- import type { JsonValue } from "../../types/shared.js";
1
+ import type { ModelAdapter, ModelAdapterContext, ModelCandidate, ModelCall } from "@nylorun/core/define";
2
+ import type { JsonValue } from "@nylorun/core/define";
3
3
  export interface PreparedModelOptions<Wire> {
4
4
  readonly adapter: string;
5
5
  prepare(call: ModelCall, context: ModelAdapterContext): Promise<{
@@ -1,2 +1,2 @@
1
- import type { ModelCall, ModelRequest } from "../../types/model.js";
1
+ import type { ModelCall, ModelRequest } from "@nylorun/core/define";
2
2
  export declare function projectModelCall(request: ModelRequest): ModelCall;
@@ -1,4 +1,4 @@
1
- import { copyJson, freezeGraph } from "../../utils/immutable.js";
1
+ import { copyJson, freezeGraph } from "@nylorun/core/define";
2
2
  export function projectModelCall(request) {
3
3
  return freezeGraph({
4
4
  prompt: Object.freeze([
@@ -1,6 +1,6 @@
1
1
  import type { ExecutionEvent } from "../types/execution.js";
2
- import type { ObserveEvent, Observer } from "../types/observe.js";
3
- import type { JsonValue } from "../types/shared.js";
2
+ import type { ObserveEvent, Observer } from "@nylorun/core/define";
3
+ import type { JsonValue } from "@nylorun/core/define";
4
4
  export type ObserveEmit = (event: ObserveEvent | (() => ObserveEvent)) => void;
5
5
  export type ExecutionOutcomeEvent = {
6
6
  type: "execution.completed" | "execution.paused" | "execution.cancelled" | "execution.failed" | "observation.failed";
@@ -1,4 +1,4 @@
1
- import { copyJson } from "../utils/immutable.js";
1
+ import { copyJson } from "@nylorun/core/define";
2
2
  export function createObservationSink(input) {
3
3
  let sequence = 0;
4
4
  let reportingFailure = false;
@@ -1,5 +1,5 @@
1
1
  import type { AgentDefinition } from "../definition/agent-definition.js";
2
- import type { BoundToolDefinition } from "../definition/bound.js";
2
+ import type { BoundToolDefinition } from "@nylorun/core/define";
3
3
  import type { ExecutionState, RunOptions } from "../types/execution.js";
4
4
  import { normalizeInput } from "./state.js";
5
5
  export declare function validateRunOptions(options: RunOptions<any>): void;
@@ -7,4 +7,5 @@ export declare function openExecution(agent: AgentDefinition, options: RunOption
7
7
  readonly state: ExecutionState;
8
8
  readonly input: ReturnType<typeof normalizeInput>;
9
9
  readonly definitions: Map<string, BoundToolDefinition>;
10
+ readonly redelivery: boolean;
10
11
  };
@@ -1,6 +1,7 @@
1
+ import { checkCompatibility } from "../definition/compatibility.js";
1
2
  import { initializeExecutionState } from "./initial-state.js";
2
- import { canonical } from "../utils/canonical.js";
3
- import { HarnessError } from "../errors.js";
3
+ import { canonical } from "@nylorun/core/define";
4
+ import { HarnessError } from "@nylorun/core/define";
4
5
  import { normalizeInput, validateExecutionState } from "./state.js";
5
6
  const acceptedOptions = new Set([
6
7
  "state",
@@ -30,12 +31,26 @@ export function openExecution(agent, options) {
30
31
  const state = options.state === undefined
31
32
  ? initializeExecutionState(agent)
32
33
  : validateExecutionState(options.state);
33
- if (state.agentId !== agent.id ||
34
- canonical(state.outputContract) !== canonical(agent.output?.schema.jsonSchema))
34
+ if (state.agentId !== agent.id)
35
35
  throw new HarnessError("execution.incompatible", "Saved state requires the original compatible agent and outputSchema");
36
- if ((state.status !== "paused" && state.plan !== undefined) ||
37
- state.status === "active" ||
38
- state.plan?.calls.some((call) => call.status === "active"))
36
+ if (canonical(state.outputContract) !== canonical(agent.output?.schema.jsonSchema))
37
+ throw new HarnessError("execution.incompatible", "Saved state requires the original compatible agent and outputSchema");
38
+ if (state.manifestHash) {
39
+ const compatibility = checkCompatibility(agent.manifest, state);
40
+ if (!compatibility.ok)
41
+ throw compatibility.error;
42
+ }
43
+ const hasActive = state.status === "active" || state.plan?.calls.some((call) => call.status === "active");
44
+ // Crash-continue: allow resume when a prior run left active work; mark redelivery.
45
+ if (hasActive && options.input && typeof options.input === "object" && "kind" in options.input) {
46
+ const kind = options.input.kind;
47
+ if (kind !== "continue" && kind !== "settle" && kind !== "approve" && kind !== "respond")
48
+ throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Resume with continue/settle/approve/respond; automatic replay is not supported.");
49
+ }
50
+ else if (hasActive) {
51
+ throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Reconcile it before starting another run; automatic replay is not supported.");
52
+ }
53
+ if (state.status !== "paused" && state.plan !== undefined && !hasActive)
39
54
  throw new HarnessError("execution.invalid-state", "Execution was interrupted with possibly active effects. Reconcile it before starting another run; automatic replay is not supported.");
40
55
  const definitions = new Map();
41
56
  for (const call of state.plan?.calls ?? []) {
@@ -45,5 +60,10 @@ export function openExecution(agent, options) {
45
60
  throw new HarnessError("execution.incompatible", "Saved tool arguments differ from their accepted contract");
46
61
  definitions.set(call.invocationId, definition);
47
62
  }
48
- return { state, input: normalizeInput(options.input), definitions };
63
+ return {
64
+ state,
65
+ input: normalizeInput(options.input),
66
+ definitions,
67
+ redelivery: Boolean(hasActive),
68
+ };
49
69
  }
@@ -1,4 +1,4 @@
1
- import type { BoundToolDefinition } from "../definition/bound.js";
1
+ import type { BoundToolDefinition } from "@nylorun/core/define";
2
2
  import type { ExecutionState } from "../types/execution.js";
3
3
  import type { normalizeInput } from "./state.js";
4
4
  export declare function applyResume(state: ExecutionState, input: ReturnType<typeof normalizeInput>, definitions: Map<string, BoundToolDefinition>): ExecutionState;
@@ -1,5 +1,5 @@
1
- import { HarnessError } from "../errors.js";
2
- import { copyJson } from "../utils/immutable.js";
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ import { copyJson } from "@nylorun/core/define";
3
3
  import { toolResult } from "./tool-result.js";
4
4
  export function applyResume(state, input, definitions) {
5
5
  const plan = state.plan;
@@ -1,5 +1,5 @@
1
1
  import type { AgentDefinition } from "../definition/agent-definition.js";
2
2
  import type { RunOptions, RunResult } from "../types/execution.js";
3
- import type { JsonValue } from "../types/shared.js";
3
+ import type { JsonValue } from "@nylorun/core/define";
4
4
  /** All mutable progress belongs to this invocation and is discarded when it settles. */
5
5
  export declare function execute(agent: AgentDefinition, options: RunOptions<any>): Promise<RunResult<JsonValue>>;
@@ -1,4 +1,5 @@
1
- import { HarnessError, isHarnessError } from "../errors.js";
1
+ import { HostSuspension } from "./host-suspension.js";
2
+ import { HarnessError, isHarnessError } from "@nylorun/core/define";
2
3
  import { runStep } from "./step/run.js";
3
4
  import { createId } from "../utils/ids.js";
4
5
  import { dispatchPlan } from "./dispatch.js";
@@ -74,6 +75,11 @@ export async function execute(agent, options) {
74
75
  middleware: agent.middleware,
75
76
  invoke: options.onModelCall,
76
77
  registry: agent.registry,
78
+ definition: agent,
79
+ sessionState: invocation.sessionBag,
80
+ recordAfterDynamics: async () => {
81
+ await record();
82
+ },
77
83
  },
78
84
  state: snapshot,
79
85
  executionId: invocation.state.executionId,
@@ -188,6 +194,8 @@ export async function execute(agent, options) {
188
194
  }
189
195
  }
190
196
  catch (error) {
197
+ if (error instanceof HostSuspension)
198
+ throw error;
191
199
  if (isHarnessError(error) && error.code === "execution.record-failed")
192
200
  throw error;
193
201
  if (signal.aborted)
@@ -1,6 +1,6 @@
1
- import type { BuiltAgent } from "../types/agent.js";
1
+ import type { BuiltAgent } from "@nylorun/core/define";
2
2
  import type { ExecutionInput, ExecutionState } from "../types/execution.js";
3
- import type { InputEvent } from "../types/transcript.js";
3
+ import type { InputEvent } from "@nylorun/core/define";
4
4
  export declare function createExecutionState<Info, Output>(agent: BuiltAgent<Info, Output>): ExecutionState;
5
5
  export declare function validateExecutionState(value: unknown): ExecutionState;
6
6
  export declare function normalizeInput(value: ExecutionInput): InputEvent | Extract<ExecutionInput, {