@nylorun/harness 0.12.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 (137) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/HOST_CONTRACT.md +27 -0
  3. package/README.md +86 -5
  4. package/dist/compatibility.d.ts +3 -0
  5. package/dist/compatibility.js +3 -0
  6. package/dist/definition/agent-definition.d.ts +19 -0
  7. package/dist/definition/agent-definition.js +20 -0
  8. package/dist/definition/compatibility.d.ts +11 -0
  9. package/dist/definition/compatibility.js +30 -0
  10. package/dist/definition/registry.d.ts +10 -0
  11. package/dist/definition/registry.js +49 -0
  12. package/dist/index.d.ts +4 -15
  13. package/dist/index.js +1 -6
  14. package/dist/loop/dispatch.d.ts +2 -0
  15. package/dist/loop/dispatch.js +327 -0
  16. package/dist/loop/host-suspension.d.ts +6 -0
  17. package/dist/loop/host-suspension.js +10 -0
  18. package/dist/loop/initial-state.d.ts +13 -0
  19. package/dist/loop/initial-state.js +30 -0
  20. package/dist/loop/invocation.d.ts +24 -0
  21. package/dist/loop/invocation.js +37 -0
  22. package/dist/{model → loop/model}/adapters.d.ts +4 -2
  23. package/dist/{model → loop/model}/adapters.js +3 -2
  24. package/dist/{model → loop/model}/normalize.d.ts +2 -2
  25. package/dist/{model → loop/model}/normalize.js +2 -2
  26. package/dist/{model → loop/model}/prepared.d.ts +3 -3
  27. package/dist/{step → loop/model}/project.d.ts +1 -1
  28. package/dist/{step → loop/model}/project.js +2 -10
  29. package/dist/loop/observe.d.ts +17 -0
  30. package/dist/loop/observe.js +53 -0
  31. package/dist/loop/options.d.ts +11 -0
  32. package/dist/loop/options.js +69 -0
  33. package/dist/loop/resume.d.ts +4 -0
  34. package/dist/loop/resume.js +52 -0
  35. package/dist/loop/run.d.ts +5 -0
  36. package/dist/loop/run.js +264 -0
  37. package/dist/loop/state.d.ts +8 -0
  38. package/dist/loop/state.js +218 -0
  39. package/dist/{step → loop/step}/canonicalize.d.ts +2 -2
  40. package/dist/{step → loop/step}/canonicalize.js +2 -2
  41. package/dist/{step → loop/step}/compose.d.ts +3 -2
  42. package/dist/{step → loop/step}/compose.js +8 -5
  43. package/dist/loop/step/context-draft.d.ts +8 -0
  44. package/dist/{step → loop/step}/context-draft.js +5 -22
  45. package/dist/loop/step/describe.d.ts +9 -0
  46. package/dist/loop/step/describe.js +22 -0
  47. package/dist/loop/step/dynamics.d.ts +29 -0
  48. package/dist/loop/step/dynamics.js +147 -0
  49. package/dist/{step → loop/step}/model-configuration.d.ts +4 -3
  50. package/dist/{step → loop/step}/model-configuration.js +3 -3
  51. package/dist/loop/step/resolve.d.ts +11 -0
  52. package/dist/{step → loop/step}/resolve.js +5 -3
  53. package/dist/loop/step/run.d.ts +30 -0
  54. package/dist/{step → loop/step}/run.js +104 -53
  55. package/dist/loop/step/runtime.d.ts +40 -0
  56. package/dist/{step → loop/step}/seal.d.ts +13 -8
  57. package/dist/{step → loop/step}/seal.js +8 -6
  58. package/dist/{step → loop/step}/slot-assembly.d.ts +4 -4
  59. package/dist/{step → loop/step}/slot-assembly.js +4 -4
  60. package/dist/{step → loop/step}/step-context.d.ts +11 -12
  61. package/dist/{step → loop/step}/step-context.js +5 -27
  62. package/dist/loop/tool-result.d.ts +4 -0
  63. package/dist/loop/tool-result.js +30 -0
  64. package/dist/loop/transcript.d.ts +2 -0
  65. package/dist/{session/seed.js → loop/transcript.js} +11 -41
  66. package/dist/loop/waits.d.ts +50 -0
  67. package/dist/loop/waits.js +11 -0
  68. package/dist/run/durable.d.ts +71 -0
  69. package/dist/run/durable.js +164 -0
  70. package/dist/run/index.d.ts +33 -0
  71. package/dist/run/index.js +38 -0
  72. package/dist/types/client.d.ts +88 -0
  73. package/dist/types/execution.d.ts +114 -0
  74. package/dist/utils/ids.d.ts +1 -0
  75. package/dist/utils/ids.js +11 -1
  76. package/package.json +18 -6
  77. package/dist/build/agent.d.ts +0 -17
  78. package/dist/build/agent.js +0 -32
  79. package/dist/build/assemble.d.ts +0 -7
  80. package/dist/build/assemble.js +0 -41
  81. package/dist/build/bind-tool.d.ts +0 -3
  82. package/dist/build/bind-tool.js +0 -18
  83. package/dist/build/builder.d.ts +0 -38
  84. package/dist/build/builder.js +0 -136
  85. package/dist/build/helpers.d.ts +0 -6
  86. package/dist/build/helpers.js +0 -4
  87. package/dist/build/manifest.d.ts +0 -7
  88. package/dist/build/manifest.js +0 -20
  89. package/dist/build/schema.d.ts +0 -14
  90. package/dist/build/schema.js +0 -205
  91. package/dist/errors.d.ts +0 -17
  92. package/dist/errors.js +0 -17
  93. package/dist/session/capability-state.d.ts +0 -14
  94. package/dist/session/capability-state.js +0 -67
  95. package/dist/session/event-log.d.ts +0 -12
  96. package/dist/session/event-log.js +0 -63
  97. package/dist/session/input-queue.d.ts +0 -41
  98. package/dist/session/input-queue.js +0 -94
  99. package/dist/session/output-contract.d.ts +0 -6
  100. package/dist/session/output-contract.js +0 -12
  101. package/dist/session/record.d.ts +0 -11
  102. package/dist/session/record.js +0 -26
  103. package/dist/session/scheduler.d.ts +0 -62
  104. package/dist/session/scheduler.js +0 -475
  105. package/dist/session/seed.d.ts +0 -10
  106. package/dist/session/session.d.ts +0 -17
  107. package/dist/session/session.js +0 -101
  108. package/dist/session/state.d.ts +0 -15
  109. package/dist/session/state.js +0 -48
  110. package/dist/session/submission-stream.d.ts +0 -16
  111. package/dist/session/submission-stream.js +0 -46
  112. package/dist/step/context-draft.d.ts +0 -10
  113. package/dist/step/resolve.d.ts +0 -10
  114. package/dist/step/run.d.ts +0 -33
  115. package/dist/turn/plan-runner.d.ts +0 -72
  116. package/dist/turn/plan-runner.js +0 -389
  117. package/dist/turn/runner.d.ts +0 -69
  118. package/dist/turn/runner.js +0 -159
  119. package/dist/types/manifest.d.ts +0 -19
  120. package/dist/types/middleware.d.ts +0 -95
  121. package/dist/types/model.d.ts +0 -184
  122. package/dist/types/session.d.ts +0 -230
  123. package/dist/types/session.js +0 -1
  124. package/dist/types/shared.d.ts +0 -249
  125. package/dist/types/shared.js +0 -1
  126. package/dist/types/tool.d.ts +0 -139
  127. package/dist/types/tool.js +0 -1
  128. package/dist/utils/immutable.d.ts +0 -5
  129. package/dist/utils/immutable.js +0 -68
  130. package/dist/utils/maps.d.ts +0 -1
  131. package/dist/utils/maps.js +0 -37
  132. package/dist/utils/observe.d.ts +0 -3
  133. package/dist/utils/observe.js +0 -13
  134. /package/dist/{model → loop/model}/prepared.js +0 -0
  135. /package/dist/{types/manifest.js → loop/step/runtime.js} +0 -0
  136. /package/dist/types/{middleware.js → client.js} +0 -0
  137. /package/dist/types/{model.js → execution.js} +0 -0
@@ -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 { isBrandedResponse, StepContext } from "./step-context.js";
3
4
  export async function runMiddleware(middleware, context, terminal, observe) {
4
5
  const dispatch = async (index) => {
@@ -49,13 +50,15 @@ export async function runMiddleware(middleware, context, terminal, observe) {
49
50
  handlerError ??= error;
50
51
  }
51
52
  }
53
+ if (handlerError instanceof HostSuspension)
54
+ throw handlerError;
52
55
  if (handlerError) {
53
56
  if (nextCalledTwice ||
54
57
  (isHarnessError(handlerError) && handlerError.code === "middleware.next-called-twice")) {
55
58
  return context.tripwire({
56
59
  code: "middleware.next-called-twice",
57
60
  message: message(handlerError),
58
- scope: "session",
61
+ scope: "execution",
59
62
  });
60
63
  }
61
64
  return context.tripwire({
@@ -70,8 +73,8 @@ export async function runMiddleware(middleware, context, terminal, observe) {
70
73
  message: `Middleware '${item.id}' must return the StepResponse from next()`,
71
74
  // Calling next() completed the step successfully; forgetting to
72
75
  // return that response is isolated to this step. A non-response
73
- // replacement remains a session-level middleware-contract breach.
74
- scope: result === undefined ? "step" : "session",
76
+ // replacement remains a execution-level middleware-contract breach.
77
+ scope: result === undefined ? "step" : "execution",
75
78
  });
76
79
  }
77
80
  return inner;
@@ -80,7 +83,7 @@ export async function runMiddleware(middleware, context, terminal, observe) {
80
83
  return context.tripwire({
81
84
  code: "middleware.invalid-response",
82
85
  message: `Middleware '${item.id}' must return a branded StepResponse`,
83
- scope: "session",
86
+ scope: "execution",
84
87
  });
85
88
  }
86
89
  return result;
@@ -0,0 +1,8 @@
1
+ import type { ContextMutationOptions, ContextSnapshot } from "@nylorun/core/define";
2
+ import type { ContextItem } from "@nylorun/core/define";
3
+ /** Per-step model-visible context supplied explicitly by middleware. */
4
+ export declare class ContextDraft {
5
+ #private;
6
+ set(middlewareId: string, middlewareOrder: number, slot: string, items: readonly ContextItem[], options?: ContextMutationOptions): void;
7
+ snapshot(): ContextSnapshot;
8
+ }
@@ -1,14 +1,10 @@
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 { SlotDraft } from "./slot-assembly.js";
4
4
  const CONTEXT_TYPE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
5
- /** Per-step runtime-context draft. Host context is injected fresh for every call. */
5
+ /** Per-step model-visible context supplied explicitly by middleware. */
6
6
  export class ContextDraft {
7
- hostContext;
8
7
  #slots = new SlotDraft();
9
- constructor(hostContext) {
10
- this.hostContext = hostContext;
11
- }
12
8
  set(middlewareId, middlewareOrder, slot, items, options) {
13
9
  if (!Array.isArray(items))
14
10
  throw new HarnessError("context.invalid-item", "Runtime context items must be an array");
@@ -27,14 +23,8 @@ export class ContextDraft {
27
23
  }
28
24
  snapshot() {
29
25
  const slots = this.#slots.values();
30
- const host = this.hostContext === undefined
31
- ? []
32
- : [Object.freeze({ type: "session", value: copyJson(this.hostContext) })];
33
- const items = Object.freeze([...host, ...slots.flatMap((slot) => slot.value)]);
34
- const contributors = Object.freeze([
35
- ...(this.hostContext === undefined ? [] : [hostContributor()]),
36
- ...slots.map((slot) => contributor(slot.owner, slot.reason)),
37
- ]);
26
+ const items = Object.freeze(slots.flatMap((slot) => slot.value));
27
+ const contributors = Object.freeze(slots.map((slot) => contributor(slot.owner, slot.reason)));
38
28
  return Object.freeze({ items, contributors });
39
29
  }
40
30
  }
@@ -48,13 +38,6 @@ function normalizeItem(item) {
48
38
  value: copyJson(item.value),
49
39
  });
50
40
  }
51
- function hostContributor() {
52
- return Object.freeze({
53
- middlewareId: "host",
54
- slot: "session",
55
- order: 0,
56
- });
57
- }
58
41
  function contributor(owner, reason) {
59
42
  return Object.freeze({
60
43
  middlewareId: owner.middlewareId,
@@ -0,0 +1,9 @@
1
+ import type { ModelConfigurationSnapshot } from "@nylorun/core/define";
2
+ import type { ToolDescriptor } from "@nylorun/core/define";
3
+ import type { BoundToolDefinition } from "@nylorun/core/define";
4
+ /** Executable configuration stays inside the invocation. */
5
+ export interface InternalModelConfigurationSnapshot extends Omit<ModelConfigurationSnapshot, "tools"> {
6
+ readonly tools: readonly BoundToolDefinition[];
7
+ }
8
+ export declare function describeTool(tool: BoundToolDefinition): ToolDescriptor;
9
+ export declare function describeConfiguration(configuration: InternalModelConfigurationSnapshot): ModelConfigurationSnapshot;
@@ -0,0 +1,22 @@
1
+ import { copyJson } from "@nylorun/core/define";
2
+ export function describeTool(tool) {
3
+ return copyJson({
4
+ name: tool.name,
5
+ ...(tool.description === undefined ? {} : { description: tool.description }),
6
+ owner: tool.owner,
7
+ inputSchema: { jsonSchema: tool.inputSchema.jsonSchema },
8
+ ...(tool.outputSchema === undefined
9
+ ? {}
10
+ : { outputSchema: { jsonSchema: tool.outputSchema.jsonSchema } }),
11
+ });
12
+ }
13
+ export function describeConfiguration(configuration) {
14
+ return copyJson({
15
+ version: configuration.version,
16
+ ...(configuration.model === undefined ? {} : { model: configuration.model }),
17
+ instructions: configuration.instructions,
18
+ tools: configuration.tools.map(describeTool),
19
+ toolContracts: configuration.toolContracts,
20
+ contributors: configuration.contributors,
21
+ });
22
+ }
@@ -0,0 +1,29 @@
1
+ import type { AgentDefinition } from "../../definition/agent-definition.js";
2
+ import type { Decision } from "@nylorun/core/define";
3
+ import type { JsonValue } from "@nylorun/core/define";
4
+ import type { PromptItem } from "@nylorun/core/define";
5
+ import type { InputEvent } from "@nylorun/core/define";
6
+ import type { ModelConfigurationDraft } from "./model-configuration.js";
7
+ export interface DynamicsContext {
8
+ readonly agent: AgentDefinition;
9
+ readonly info?: unknown;
10
+ readonly step: number;
11
+ readonly arrivals: readonly InputEvent[];
12
+ readonly messages: readonly PromptItem[];
13
+ sessionState: Record<string, JsonValue>;
14
+ }
15
+ /** Run all beforeModelCall handlers in capability `.use()` order. */
16
+ export declare function applyBeforeModelCall(ctx: DynamicsContext, configuration: ModelConfigurationDraft): Promise<{
17
+ readonly blocked?: string;
18
+ }>;
19
+ /** Run all afterModelCall handlers; merge with strictest-wins rules. */
20
+ export declare function applyAfterModelCall(ctx: DynamicsContext, args: {
21
+ readonly text?: string;
22
+ readonly toolCalls: readonly {
23
+ readonly id: string;
24
+ readonly name: string;
25
+ readonly args: JsonValue;
26
+ }[];
27
+ }): Promise<Decision>;
28
+ export declare function mergeDecisions(decisions: readonly Decision[]): Decision;
29
+ export declare function hasDynamics(agent: AgentDefinition): boolean;
@@ -0,0 +1,147 @@
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ /** Run all beforeModelCall handlers in capability `.use()` order. */
3
+ export async function applyBeforeModelCall(ctx, configuration) {
4
+ const patches = [];
5
+ for (const capability of ctx.agent.manifest.capabilities) {
6
+ const impl = ctx.agent.implementations[capability.id];
7
+ if (!impl?.beforeModelCall)
8
+ continue;
9
+ const raw = await impl.beforeModelCall({
10
+ input: projectInput(ctx.arrivals),
11
+ info: ctx.info,
12
+ state: { ...ctx.sessionState },
13
+ messages: ctx.messages,
14
+ step: ctx.step,
15
+ });
16
+ const patch = normalizeCapabilityPatch(capability.id, raw ?? {}, capability.id === "agent");
17
+ patches.push({ id: capability.id, patch, agentLevel: capability.id === "agent" });
18
+ }
19
+ if (!patches.length)
20
+ return {};
21
+ let blocked;
22
+ const capabilitySwitches = new Map();
23
+ const toolSwitches = new Map();
24
+ const extraInstructions = [];
25
+ for (const { id, patch, agentLevel } of patches) {
26
+ if (patch.block)
27
+ blocked = patch.block;
28
+ if (patch.state) {
29
+ for (const [key, value] of Object.entries(patch.state))
30
+ ctx.sessionState[key] = value;
31
+ }
32
+ if (patch.instructions)
33
+ extraInstructions.push(...patch.instructions);
34
+ if (patch.capabilities) {
35
+ for (const [name, on] of Object.entries(patch.capabilities)) {
36
+ if (!agentLevel && name !== id)
37
+ throw new HarnessError("configuration.invalid", `Capability '${id}' beforeModelCall may only toggle itself, not '${name}'`);
38
+ if (!ctx.agent.manifest.capabilities.some((item) => item.id === name))
39
+ throw new HarnessError("configuration.invalid", `beforeModelCall referenced unknown capability '${name}'`);
40
+ capabilitySwitches.set(name, on);
41
+ }
42
+ }
43
+ if (patch.tools) {
44
+ for (const [name, on] of Object.entries(patch.tools)) {
45
+ const known = ctx.agent.manifest.capabilities.some((cap) => cap.tools?.some((tool) => tool.name === name));
46
+ if (!known)
47
+ throw new HarnessError("configuration.invalid", `beforeModelCall referenced unknown tool '${name}'`);
48
+ if (!agentLevel) {
49
+ const owned = ctx.agent.manifest.capabilities
50
+ .find((cap) => cap.id === id)
51
+ ?.tools?.some((tool) => tool.name === name);
52
+ if (!owned)
53
+ throw new HarnessError("configuration.invalid", `Capability '${id}' beforeModelCall may only toggle its own tools`);
54
+ }
55
+ toolSwitches.set(name, on);
56
+ }
57
+ }
58
+ }
59
+ if (blocked)
60
+ return { blocked };
61
+ let order = 10_000;
62
+ for (const [capabilityId, on] of capabilitySwitches) {
63
+ if (on)
64
+ continue;
65
+ configuration.setTools("dynamics", order++, capabilityId, []);
66
+ }
67
+ for (const [toolName, on] of toolSwitches) {
68
+ if (on)
69
+ continue;
70
+ for (const capability of ctx.agent.manifest.capabilities) {
71
+ const tools = ctx.agent.implementations[capability.id]?.tools;
72
+ if (!tools?.[toolName])
73
+ continue;
74
+ const remaining = Object.values(tools).filter((tool) => tool.name !== toolName);
75
+ configuration.setTools("dynamics", order++, capability.id, remaining);
76
+ }
77
+ }
78
+ if (extraInstructions.length)
79
+ configuration.setInstructions("dynamics", order++, "dynamics", Object.freeze([...extraInstructions]));
80
+ return {};
81
+ }
82
+ /** Run all afterModelCall handlers; merge with strictest-wins rules. */
83
+ export async function applyAfterModelCall(ctx, args) {
84
+ const decisions = [];
85
+ for (const capability of ctx.agent.manifest.capabilities) {
86
+ const impl = ctx.agent.implementations[capability.id];
87
+ if (!impl?.afterModelCall)
88
+ continue;
89
+ const decision = await impl.afterModelCall(args, {
90
+ info: ctx.info,
91
+ state: { ...ctx.sessionState },
92
+ });
93
+ decisions.push(decision ?? {});
94
+ }
95
+ return mergeDecisions(decisions);
96
+ }
97
+ export function mergeDecisions(decisions) {
98
+ let block;
99
+ let retry;
100
+ const deny = new Map();
101
+ const approve = new Map();
102
+ let text;
103
+ for (const decision of decisions) {
104
+ if (decision.block)
105
+ block = decision.block;
106
+ if (decision.retry)
107
+ retry = decision.retry;
108
+ for (const item of decision.deny ?? [])
109
+ deny.set(item.id, item.reason);
110
+ for (const item of decision.approve ?? [])
111
+ approve.set(item.id, item.prompt);
112
+ if (decision.text !== undefined)
113
+ text = decision.text;
114
+ }
115
+ if (block)
116
+ return { block };
117
+ if (retry)
118
+ return { retry, ...(text === undefined ? {} : { text }) };
119
+ return {
120
+ ...(text === undefined ? {} : { text }),
121
+ ...(deny.size ? { deny: [...deny.entries()].map(([id, reason]) => ({ id, reason })) } : {}),
122
+ ...(approve.size
123
+ ? { approve: [...approve.entries()].map(([id, prompt]) => ({ id, prompt })) }
124
+ : {}),
125
+ };
126
+ }
127
+ export function hasDynamics(agent) {
128
+ return agent.manifest.capabilities.some((capability) => capability.beforeModelCall || capability.afterModelCall);
129
+ }
130
+ function normalizeCapabilityPatch(id, patch, agentLevel) {
131
+ if (patch.active === undefined)
132
+ return patch;
133
+ if (agentLevel)
134
+ throw new HarnessError("configuration.invalid", "Agent-level beforeModelCall must use capabilities{}, not active");
135
+ return {
136
+ ...patch,
137
+ capabilities: { ...(patch.capabilities ?? {}), [id]: patch.active },
138
+ };
139
+ }
140
+ function projectInput(arrivals) {
141
+ const message = arrivals.find((item) => item.kind === "user-message");
142
+ if (!message)
143
+ return undefined;
144
+ if ("text" in message && typeof message.text === "string")
145
+ return message.text;
146
+ return undefined;
147
+ }
@@ -1,5 +1,6 @@
1
- import type { ModelConfigurationMutationOptions, ModelConfigurationSnapshot, ModelDirective } from "../types/model.js";
2
- import type { ToolDefinition } from "../types/tool.js";
1
+ import type { InternalModelConfigurationSnapshot } from "./describe.js";
2
+ import type { ModelConfigurationMutationOptions, ModelDirective } from "@nylorun/core/define";
3
+ import type { ToolDefinition } from "@nylorun/core/define";
3
4
  /** Per-step, middleware-owned model configuration draft. It never persists past the call. */
4
5
  export declare class ModelConfigurationDraft {
5
6
  #private;
@@ -9,5 +10,5 @@ export declare class ModelConfigurationDraft {
9
10
  select(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
10
11
  replace(middlewareId: string, middlewareOrder: number, directive: ModelDirective, options?: Omit<ModelConfigurationMutationOptions, "order">): void;
11
12
  clear(options?: Omit<ModelConfigurationMutationOptions, "order">): void;
12
- snapshot(): ModelConfigurationSnapshot;
13
+ snapshot(): InternalModelConfigurationSnapshot;
13
14
  }
@@ -1,7 +1,7 @@
1
- import { bindTool } from "../build/bind-tool.js";
2
- import { HarnessError, isHarnessError } from "../errors.js";
1
+ import { bindTool } from "@nylorun/core/define";
2
+ import { HarnessError, isHarnessError } from "@nylorun/core/define";
3
3
  import { normalizeDirective, sameDirective } from "../model/normalize.js";
4
- import { copyJson } from "../utils/immutable.js";
4
+ import { copyJson } from "@nylorun/core/define";
5
5
  import { checkedReason, slotOwner, SlotDraft } from "./slot-assembly.js";
6
6
  /** Per-step, middleware-owned model configuration draft. It never persists past the call. */
7
7
  export class ModelConfigurationDraft {
@@ -0,0 +1,11 @@
1
+ import type { TurnOutputContract } from "@nylorun/core/define";
2
+ import type { InputEvent } from "@nylorun/core/define";
3
+ import type { ModelRequest } from "@nylorun/core/define";
4
+ import type { ToolResult } from "@nylorun/core/define";
5
+ import type { StepContext } from "./step-context.js";
6
+ export declare function resolveModelRequest(input: {
7
+ context: StepContext;
8
+ arrivals: readonly InputEvent[];
9
+ toolResults: readonly ToolResult[];
10
+ output?: TurnOutputContract;
11
+ }): ModelRequest;
@@ -1,17 +1,19 @@
1
+ import { describeConfiguration } from "./describe.js";
1
2
  export function resolveModelRequest(input) {
2
3
  const ctx = input.context;
4
+ const configuration = describeConfiguration(ctx.configurationSnapshot());
3
5
  return Object.freeze({
4
- sessionId: ctx.input.sessionId,
6
+ executionId: ctx.input.executionId,
5
7
  turnId: ctx.input.turnId,
6
8
  stepId: ctx.input.stepId,
7
9
  ...(ctx.selectedDirective === undefined ? {} : { model: ctx.selectedDirective }),
8
- configuration: ctx.configurationSnapshot(),
10
+ configuration,
9
11
  instructions: Object.freeze([...ctx.instructions]),
10
12
  context: ctx.contextSnapshot(),
11
13
  transcript: Object.freeze([...ctx.input.transcript]),
12
14
  arrivals: Object.freeze([...input.arrivals]),
13
15
  toolResults: Object.freeze([...input.toolResults]),
14
- tools: Object.freeze([...ctx.offeredTools]),
16
+ tools: configuration.tools,
15
17
  ...(input.output === undefined ? {} : { outputSchema: input.output.schema.jsonSchema }),
16
18
  });
17
19
  }
@@ -0,0 +1,30 @@
1
+ import type { ExecutionSnapshot, StepRuntime } from "./runtime.js";
2
+ import type { TurnOutputContract } from "@nylorun/core/define";
3
+ import type { ModelCandidate } from "@nylorun/core/define";
4
+ import type { InputEvent } from "@nylorun/core/define";
5
+ import type { ToolResult } from "@nylorun/core/define";
6
+ import type { ObserveEmit } from "../observe.js";
7
+ import { type SealedStepOutput } from "./seal.js";
8
+ export interface StepRunResult {
9
+ readonly stepId: string;
10
+ readonly candidate?: ModelCandidate;
11
+ readonly output: SealedStepOutput;
12
+ readonly requestedModelId?: string;
13
+ readonly modelInvocationId?: string;
14
+ readonly retry?: string;
15
+ }
16
+ export declare function runStep(input: {
17
+ agent: StepRuntime;
18
+ observe: ObserveEmit;
19
+ state: ExecutionSnapshot;
20
+ executionId: string;
21
+ turnId: string;
22
+ stepId: string;
23
+ turnNumber: number;
24
+ stepNumber: number;
25
+ arrivals: readonly InputEvent[];
26
+ toolResults: readonly ToolResult[];
27
+ signal: AbortSignal;
28
+ info?: unknown;
29
+ output?: TurnOutputContract;
30
+ }): Promise<StepRunResult>;
@@ -1,31 +1,34 @@
1
+ import { HostSuspension } from "../host-suspension.js";
1
2
  import { normalizeCandidate } from "../model/normalize.js";
2
- import { HarnessError, isHarnessError } from "../errors.js";
3
- import { copyJson } from "../utils/immutable.js";
3
+ import { HarnessError, isHarnessError } from "@nylorun/core/define";
4
+ import { copyJson } from "@nylorun/core/define";
4
5
  import { runMiddleware } from "./compose.js";
5
6
  import { ContextDraft } from "./context-draft.js";
6
7
  import { StepContext } from "./step-context.js";
7
- import { projectModelCall } from "./project.js";
8
+ import { projectModelCall } from "../model/project.js";
8
9
  import { resolveModelRequest } from "./resolve.js";
9
10
  import { sealStep } from "./seal.js";
10
11
  import { ModelConfigurationDraft } from "./model-configuration.js";
11
- import { createId } from "../utils/ids.js";
12
+ import { createId } from "../../utils/ids.js";
13
+ import { applyAfterModelCall, applyBeforeModelCall, hasDynamics, } from "./dynamics.js";
12
14
  export async function runStep(input) {
13
- let state = input.state;
14
15
  let requestedModelId;
16
+ let modelInvocationId;
17
+ let retryFeedback;
15
18
  const stepInput = Object.freeze({
16
- sessionId: input.sessionId,
19
+ executionId: input.executionId,
17
20
  turnId: input.turnId,
18
21
  stepId: input.stepId,
19
22
  turnNumber: input.turnNumber,
20
23
  stepNumber: input.stepNumber,
21
- session: input.session,
24
+ info: input.info,
22
25
  arrivals: Object.freeze([...input.arrivals]),
23
26
  toolResults: Object.freeze([...input.toolResults]),
24
27
  transcript: Object.freeze([...input.state.transcript]),
25
28
  });
26
29
  const configuration = new ModelConfigurationDraft();
27
- const runtimeContext = new ContextDraft(input.session.context);
28
- const context = new StepContext(stepInput, input.observe, configuration, runtimeContext, input.states);
30
+ const runtimeContext = new ContextDraft();
31
+ const context = new StepContext(stepInput, input.observe, configuration, runtimeContext);
29
32
  input.observe(() => ({
30
33
  type: "step.started",
31
34
  turnId: input.turnId,
@@ -35,13 +38,59 @@ export async function runStep(input) {
35
38
  attributes: snapshotStepStart(stepInput),
36
39
  }));
37
40
  await runMiddleware(input.agent.middleware, context, async () => {
41
+ input.signal.throwIfAborted();
38
42
  if (context.currentTripwire)
39
43
  return context.tripwire(context.currentTripwire);
44
+ const definition = input.agent.definition;
45
+ const dynamicsCtx = definition && hasDynamics(definition)
46
+ ? {
47
+ agent: definition,
48
+ info: input.info,
49
+ step: input.stepNumber - 1,
50
+ arrivals: input.arrivals,
51
+ messages: projectModelCall(resolveModelRequest({
52
+ context,
53
+ arrivals: input.arrivals,
54
+ toolResults: input.toolResults,
55
+ output: input.output,
56
+ })).prompt,
57
+ sessionState: input.agent.sessionState ?? {},
58
+ }
59
+ : undefined;
60
+ if (dynamicsCtx) {
61
+ try {
62
+ const before = await applyBeforeModelCall(dynamicsCtx, configuration);
63
+ if (input.agent.sessionState) {
64
+ Object.assign(input.agent.sessionState, dynamicsCtx.sessionState);
65
+ }
66
+ if (input.agent.recordAfterDynamics)
67
+ await input.agent.recordAfterDynamics();
68
+ if (before.blocked) {
69
+ return context.tripwire({
70
+ code: "dynamics.blocked",
71
+ message: before.blocked,
72
+ });
73
+ }
74
+ }
75
+ catch (error) {
76
+ if (error instanceof HostSuspension)
77
+ throw error;
78
+ return context.tripwire({
79
+ code: isHarnessError(error) ? error.code : "configuration.invalid",
80
+ message: error instanceof Error ? error.message : String(error),
81
+ });
82
+ }
83
+ }
40
84
  try {
41
- context.sealConfiguration(configuration.snapshot());
85
+ const selected = configuration.snapshot();
86
+ for (const tool of selected.tools)
87
+ input.agent.registry.reference(tool);
88
+ context.sealConfiguration(selected);
42
89
  context.sealContext(runtimeContext.snapshot());
43
90
  }
44
91
  catch (error) {
92
+ if (error instanceof HostSuspension)
93
+ throw error;
45
94
  const message = error instanceof Error ? error.message : String(error);
46
95
  return context.tripwire({
47
96
  code: isHarnessError(error) ? error.code : "configuration.invalid",
@@ -57,16 +106,10 @@ export async function runStep(input) {
57
106
  requestedModelId = request.model?.id;
58
107
  const call = projectModelCall(request);
59
108
  const invocationId = createId("invocation");
60
- const active = Object.freeze({
61
- kind: "model",
62
- turnId: input.turnId,
63
- stepId: input.stepId,
64
- invocationId,
65
- call,
66
- });
67
- state = await input.recordModelRequested(state, active);
109
+ modelInvocationId = invocationId;
68
110
  input.observe(() => ({
69
111
  type: "model.requested",
112
+ invocationId,
70
113
  turnId: input.turnId,
71
114
  stepId: input.stepId,
72
115
  ...(request.model?.id === undefined ? {} : { requestedModelId: request.model.id }),
@@ -91,6 +134,7 @@ export async function runStep(input) {
91
134
  const reported = Object.freeze({ adapter: value.adapter, call: copyJson(value.call) });
92
135
  input.observe(() => ({
93
136
  type: "model.prepared",
137
+ invocationId,
94
138
  turnId: input.turnId,
95
139
  stepId: input.stepId,
96
140
  ...(request.model?.id === undefined ? {} : { requestedModelId: request.model.id }),
@@ -100,18 +144,52 @@ export async function runStep(input) {
100
144
  });
101
145
  if (input.signal.aborted)
102
146
  throw input.signal.reason;
103
- if (isDeferred(outcome))
104
- return context.deferModel(Object.freeze({
105
- ...active,
106
- ...(outcome.token === undefined ? {} : { token: copyJson(outcome.token) }),
107
- }));
108
147
  const minted = context.mintFromModel(normalizeCandidate(outcome));
109
148
  const candidate = context.currentCandidate;
110
149
  if (!candidate)
111
150
  throw new HarnessError("model.candidate-missing", "Model candidate missing after mint");
151
+ if (dynamicsCtx) {
152
+ const text = candidate.output
153
+ ?.filter((block) => block.type === "text")
154
+ .map((block) => block.text)
155
+ .join("");
156
+ const toolCalls = (candidate.output ?? [])
157
+ .filter((block) => block.type === "tool-call")
158
+ .map((block) => {
159
+ const call = block;
160
+ return { id: call.id, name: call.name, args: call.args };
161
+ });
162
+ const decision = await applyAfterModelCall(dynamicsCtx, {
163
+ ...(text ? { text } : {}),
164
+ toolCalls,
165
+ });
166
+ if (input.agent.recordAfterDynamics)
167
+ await input.agent.recordAfterDynamics();
168
+ if (decision.block) {
169
+ return context.tripwire({ code: "dynamics.blocked", message: decision.block });
170
+ }
171
+ if (decision.retry) {
172
+ retryFeedback = decision.retry;
173
+ return context.tripwire({ code: "dynamics.retry", message: decision.retry });
174
+ }
175
+ if (decision.text !== undefined) {
176
+ minted.replace({
177
+ output: [{ type: "text", text: decision.text }],
178
+ });
179
+ }
180
+ for (const item of decision.deny ?? [])
181
+ minted.deny(item.id, item.reason);
182
+ for (const item of decision.approve ?? [])
183
+ minted.requireInteraction(item.id, {
184
+ kind: "approval",
185
+ prompt: item.prompt,
186
+ });
187
+ }
112
188
  return minted;
113
189
  }
114
190
  catch (error) {
191
+ if (error instanceof HostSuspension)
192
+ throw error;
115
193
  if (input.signal.aborted)
116
194
  throw input.signal.reason;
117
195
  return context.tripwire({
@@ -124,47 +202,20 @@ export async function runStep(input) {
124
202
  const candidate = output.kind === "tools" ? output.plan.candidate : context.currentCandidate;
125
203
  return Object.freeze({
126
204
  stepId: input.stepId,
127
- state,
128
205
  ...(candidate ? { candidate } : {}),
129
206
  ...(requestedModelId === undefined ? {} : { requestedModelId }),
130
207
  output,
208
+ ...(modelInvocationId ? { modelInvocationId } : {}),
209
+ ...(retryFeedback ? { retry: retryFeedback } : {}),
131
210
  });
132
211
  }
133
- function isDeferred(value) {
134
- return (typeof value === "object" && value !== null && "kind" in value && value.kind === "deferred");
135
- }
136
212
  function snapshotStepStart(input) {
137
- const session = Object.freeze({
138
- ...(input.session.userId === undefined ? {} : { userId: input.session.userId }),
139
- ...(input.session.context === undefined ? {} : { context: copyJson(input.session.context) }),
140
- });
141
213
  return Object.freeze({
142
- ...(Object.keys(session).length === 0 ? {} : { session }),
143
214
  arrivals: copyJson(input.arrivals),
144
215
  toolResults: copyJson(input.toolResults),
145
216
  transcript: input.transcript,
146
217
  });
147
218
  }
148
219
  function snapshotConfiguration(configuration) {
149
- return Object.freeze({
150
- version: configuration.version,
151
- ...(configuration.model === undefined ? {} : { model: copyJson(configuration.model) }),
152
- instructions: copyJson(configuration.instructions),
153
- tools: snapshotTools(configuration.tools),
154
- toolContracts: copyJson(configuration.toolContracts),
155
- contributors: copyJson(configuration.contributors),
156
- });
157
- }
158
- function snapshotTools(tools) {
159
- return Object.freeze(tools.map((tool) => Object.freeze({
160
- name: tool.name,
161
- ...(tool.description === undefined ? {} : { description: tool.description }),
162
- owner: tool.owner,
163
- inputSchema: Object.freeze({ jsonSchema: copyJson(tool.inputSchema.jsonSchema) }),
164
- ...(tool.outputSchema === undefined
165
- ? {}
166
- : {
167
- outputSchema: Object.freeze({ jsonSchema: copyJson(tool.outputSchema.jsonSchema) }),
168
- }),
169
- })));
220
+ return copyJson(configuration);
170
221
  }