@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
@@ -0,0 +1,164 @@
1
+ import { HarnessError } from "@nylorun/core/define";
2
+ import { AgentManifestSchema } from "@nylorun/core/contracts";
3
+ import { agentFrom } from "@nylorun/core/define";
4
+ import { definitionFor } from "../definition/agent-definition.js";
5
+ import { schemaFromJSON } from "@nylorun/core/define";
6
+ import { hashManifest } from "@nylorun/core/define";
7
+ import { withDeterministicIds } from "../utils/ids.js";
8
+ import { execute } from "../loop/run.js";
9
+ import { initializeExecutionState } from "../loop/initial-state.js";
10
+ import { HostSuspension } from "../loop/host-suspension.js";
11
+ import { CHECKPOINT_VERSION, ENGINE_VERSION } from "../compatibility.js";
12
+ export function createDurableCheckpoint(input) {
13
+ AgentManifestSchema.parse(input.manifest);
14
+ return {
15
+ version: CHECKPOINT_VERSION,
16
+ engineVersion: ENGINE_VERSION,
17
+ manifestHash: hashManifest(input.manifest),
18
+ sessionId: input.sessionId,
19
+ turnId: input.turnId,
20
+ segment: input.segment ?? 0,
21
+ input: input.input,
22
+ ...(input.state ? { state: input.state } : {}),
23
+ ...(input.info ? { info: input.info } : {}),
24
+ };
25
+ }
26
+ /** Deterministic reconstruction uses individually journaled effects, never a replayable whole loop.
27
+ * A suspended checkpoint is the immutable segment start plus the host's durable effect journal.
28
+ * Returning waiting releases all execution stacks; no process-local continuation survives.
29
+ */
30
+ export async function runDurable(options) {
31
+ const { manifest, checkpoint, host } = options;
32
+ AgentManifestSchema.parse(manifest);
33
+ if (checkpoint.version !== CHECKPOINT_VERSION ||
34
+ checkpoint.engineVersion !== ENGINE_VERSION ||
35
+ checkpoint.manifestHash !== hashManifest(manifest))
36
+ throw new HarnessError("execution.incompatible", "Incompatible durable checkpoint");
37
+ if (checkpoint.state &&
38
+ (checkpoint.state.manifestHash !== checkpoint.manifestHash ||
39
+ checkpoint.state.agentId !== manifest.id ||
40
+ checkpoint.state.executionId !== checkpoint.sessionId))
41
+ throw new HarnessError("execution.incompatible", "Checkpoint definition/session mismatch");
42
+ let serial = 0;
43
+ const pending = new Map();
44
+ const inFlight = new Set();
45
+ const effect = async (kind, input, context, capabilityId, toolName, identity) => {
46
+ const effectId = `${checkpoint.turnId}:${checkpoint.segment}:${kind}:${identity ?? ++serial}`;
47
+ const request = JSON.parse(JSON.stringify({
48
+ effectId,
49
+ sessionId: checkpoint.sessionId,
50
+ turnId: checkpoint.turnId,
51
+ agentId: manifest.id,
52
+ manifestHash: checkpoint.manifestHash,
53
+ kind,
54
+ input,
55
+ context,
56
+ ...(capabilityId ? { capabilityId } : {}),
57
+ ...(toolName ? { toolName } : {}),
58
+ }));
59
+ const operation = host.resolveEffect(request);
60
+ inFlight.add(operation);
61
+ let result;
62
+ try {
63
+ result = await operation;
64
+ }
65
+ finally {
66
+ inFlight.delete(operation);
67
+ }
68
+ if (result.status !== "completed") {
69
+ pending.set(effectId, result.status);
70
+ throw new HostSuspension(effectId, result.status);
71
+ }
72
+ return result.outcome;
73
+ };
74
+ let patchTail = Promise.resolve();
75
+ const implementations = {};
76
+ for (const capability of manifest.capabilities) {
77
+ const tools = {};
78
+ for (const tool of capability.tools ?? [])
79
+ tools[tool.name] = {
80
+ name: tool.name,
81
+ ...(tool.description === undefined ? {} : { description: tool.description }),
82
+ inputSchema: schemaFromJSON(tool.inputSchema),
83
+ ...(tool.outputSchema ? { outputSchema: schemaFromJSON(tool.outputSchema) } : {}),
84
+ async execute(args, ctx) {
85
+ const previous = patchTail;
86
+ let release;
87
+ patchTail = new Promise((resolve) => {
88
+ release = resolve;
89
+ });
90
+ try {
91
+ const outcome = await effect("tool", args, {
92
+ executionId: ctx.executionId,
93
+ turnId: ctx.turnId,
94
+ stepId: ctx.stepId,
95
+ callId: ctx.callId,
96
+ invocationId: ctx.invocationId,
97
+ idempotencyKey: ctx.idempotencyKey,
98
+ info: ctx.info,
99
+ state: ctx.state.entries(),
100
+ resume: ctx.resume,
101
+ }, capability.id, tool.name, ctx.invocationId);
102
+ // Apply concurrently resolved patches in manifest call order, independent of host I/O timing.
103
+ await previous;
104
+ for (const [key, value] of Object.entries(outcome.statePatch ?? {}))
105
+ ctx.state.set(key, value);
106
+ const value = outcome.value;
107
+ return value?.kind === "interaction-required"
108
+ ? {
109
+ ...value,
110
+ interaction: {
111
+ ...value.interaction,
112
+ id: `${checkpoint.turnId}:${checkpoint.segment}:interaction:${ctx.invocationId}`,
113
+ },
114
+ }
115
+ : value;
116
+ }
117
+ finally {
118
+ await previous;
119
+ release();
120
+ }
121
+ },
122
+ };
123
+ implementations[capability.id] = {
124
+ tools,
125
+ ...(capability.beforeModelCall
126
+ ? {
127
+ beforeModelCall: async (args) => (await effect("beforeModelCall", args, { info: checkpoint.info }, capability.id))
128
+ .value,
129
+ }
130
+ : {}),
131
+ ...(capability.afterModelCall
132
+ ? {
133
+ afterModelCall: async (args, ctx) => (await effect("afterModelCall", args, ctx, capability.id)).value,
134
+ }
135
+ : {}),
136
+ };
137
+ }
138
+ const definition = definitionFor(agentFrom(manifest, implementations));
139
+ // Manifest reconstruction must retain definition identity, including schemas and instructions.
140
+ if (definition.hash !== checkpoint.manifestHash)
141
+ throw new HarnessError("execution.incompatible", "Reconstructed definition hash mismatch");
142
+ try {
143
+ const result = await withDeterministicIds(`${checkpoint.turnId}_${checkpoint.segment}`, () => execute(definition, {
144
+ state: checkpoint.state ??
145
+ initializeExecutionState(definition, { executionId: checkpoint.sessionId }),
146
+ input: checkpoint.input,
147
+ info: checkpoint.info,
148
+ signal: options.signal,
149
+ onModelCall: async (call, ctx) => (await effect("model", call, { request: ctx.request, invocationId: ctx.invocationId }, undefined, undefined, ctx.invocationId)).value,
150
+ }));
151
+ return { status: result.status, checkpoint: { ...checkpoint, state: result.state }, result };
152
+ }
153
+ catch (error) {
154
+ // Parallel tool dispatch can suspend more than one action. Drain persistence before returning.
155
+ await Promise.allSettled([...inFlight]);
156
+ if (!(error instanceof HostSuspension))
157
+ throw error;
158
+ return {
159
+ status: [...pending.values()].includes("uncertain") ? "uncertain" : "waiting",
160
+ checkpoint,
161
+ effectIds: [...pending.keys()],
162
+ };
163
+ }
164
+ }
@@ -0,0 +1,33 @@
1
+ import { checkCompatibility } from "../definition/compatibility.js";
2
+ import type { Implementations } from "@nylorun/core/define";
3
+ import type { AgentBinding, BuiltAgent } from "@nylorun/core/define";
4
+ import type { AgentManifest } from "@nylorun/core/define";
5
+ import type { ExecutionState, RunOptions, RunResult } from "../types/execution.js";
6
+ import { execute } from "../loop/run.js";
7
+ import { createExecutionState, validateExecutionState } from "../loop/state.js";
8
+ export interface RunBinding<Info = unknown> {
9
+ readonly manifest: AgentManifest;
10
+ readonly implementations: Implementations<Info>;
11
+ /** Optional authoring binding preserves live schemas and executable snapshots. */
12
+ readonly authoring?: AgentBinding;
13
+ }
14
+ export interface BoundRunOptions<Info = unknown> extends RunOptions<Info> {
15
+ readonly binding: RunBinding<Info>;
16
+ }
17
+ /**
18
+ * Run the model/tool loop from a definition binding + optional checkpoint.
19
+ * This is the public execution surface Runtime (and tests) should call.
20
+ */
21
+ export declare function run<Info = unknown, Output = string>(options: BoundRunOptions<Info>): Promise<RunResult<Output>>;
22
+ /** Create a fresh checkpoint bound to a manifest hash. */
23
+ export declare function createRunState(binding: RunBinding, options?: {
24
+ readonly executionId?: string;
25
+ readonly state?: import("@nylorun/core/define").JsonObject;
26
+ }): ExecutionState;
27
+ /** Build a run binding from an explicitly authored agent. */
28
+ export declare function bindingFromAgent(agent: BuiltAgent): RunBinding;
29
+ export { checkCompatibility, createExecutionState, validateExecutionState, execute };
30
+ export type { Implementations, ExecutionState, RunResult, RunOptions };
31
+ export { createDurableCheckpoint, runDurable } from "./durable.js";
32
+ export type { DurableCheckpoint, HostEffect, EffectResolution, DurableHost, DurableResult, } from "./durable.js";
33
+ export type { ModelAdapter, ModelCandidate, ModelCall, ModelRequest } from "@nylorun/core/define";
@@ -0,0 +1,38 @@
1
+ import { definitionFor, definitionFromBinding } from "../definition/agent-definition.js";
2
+ import { checkCompatibility } from "../definition/compatibility.js";
3
+ import { bindingFromAgent as authoringBindingFromAgent, agentFrom } from "@nylorun/core/define";
4
+ import { execute } from "../loop/run.js";
5
+ import { createExecutionState, validateExecutionState } from "../loop/state.js";
6
+ import { initializeExecutionState } from "../loop/initial-state.js";
7
+ /**
8
+ * Run the model/tool loop from a definition binding + optional checkpoint.
9
+ * This is the public execution surface Runtime (and tests) should call.
10
+ */
11
+ export async function run(options) {
12
+ const definition = resolveDefinition(options.binding);
13
+ if (options.state?.manifestHash) {
14
+ const compatibility = checkCompatibility(definition.manifest, options.state);
15
+ if (!compatibility.ok)
16
+ throw compatibility.error;
17
+ }
18
+ const { binding: _, ...runOptions } = options;
19
+ return execute(definition, runOptions);
20
+ }
21
+ /** Create a fresh checkpoint bound to a manifest hash. */
22
+ export function createRunState(binding, options) {
23
+ const definition = resolveDefinition(binding);
24
+ return initializeExecutionState(definition, options);
25
+ }
26
+ function resolveDefinition(binding) {
27
+ if (binding.authoring)
28
+ return definitionFromBinding(binding.authoring);
29
+ const agent = agentFrom(binding.manifest, binding.implementations);
30
+ return definitionFor(agent);
31
+ }
32
+ /** Build a run binding from an explicitly authored agent. */
33
+ export function bindingFromAgent(agent) {
34
+ const authoring = authoringBindingFromAgent(agent);
35
+ return { manifest: authoring.manifest, implementations: authoring.implementations, authoring };
36
+ }
37
+ export { checkCompatibility, createExecutionState, validateExecutionState, execute };
38
+ export { createDurableCheckpoint, runDurable } from "./durable.js";
@@ -0,0 +1,88 @@
1
+ import type { JsonObject, JsonValue } from "@nylorun/core/define";
2
+ import type { MessageInput } from "@nylorun/core/define";
3
+ import type { SavedToolCall } from "./execution.js";
4
+ /**
5
+ * Definition-parameterized client types (H11). Types only — harness does not implement Session.
6
+ * Runtime imports these for `openSession` / stream typing later.
7
+ */
8
+ export interface InputAccepted {
9
+ readonly status: "accepted" | "rejected";
10
+ readonly error?: {
11
+ readonly code: string;
12
+ readonly message: string;
13
+ };
14
+ readonly turnId?: string;
15
+ readonly cursor?: string;
16
+ readonly steered?: boolean;
17
+ }
18
+ export type Result<Output = unknown> = {
19
+ readonly status: "completed";
20
+ readonly output: Output;
21
+ } | {
22
+ readonly status: "paused";
23
+ readonly pending: readonly SavedToolCall[];
24
+ } | {
25
+ readonly status: "failed";
26
+ readonly error: {
27
+ readonly code: string;
28
+ readonly message: string;
29
+ };
30
+ } | {
31
+ readonly status: "cancelled";
32
+ };
33
+ export interface Turn<Output = unknown> {
34
+ readonly id: string;
35
+ readonly sessionId: string;
36
+ readonly status: "running" | "settled";
37
+ readonly result?: Result<Output>;
38
+ }
39
+ export type Event<Output = unknown> = {
40
+ readonly type: "turn.started";
41
+ readonly cursor: string;
42
+ readonly at: string;
43
+ readonly sessionId: string;
44
+ readonly turnId: string;
45
+ } | {
46
+ readonly type: "status";
47
+ readonly status: "thinking" | "calling_tool" | "waiting" | "requires_action";
48
+ readonly sessionId: string;
49
+ readonly turnId: string;
50
+ } | {
51
+ readonly type: "text.delta";
52
+ readonly text: string;
53
+ readonly sessionId: string;
54
+ readonly turnId: string;
55
+ } | {
56
+ readonly type: "tool.started" | "tool.progress" | "tool.completed" | "tool.failed";
57
+ readonly tool: string;
58
+ readonly sessionId: string;
59
+ readonly turnId: string;
60
+ } | {
61
+ readonly type: "interaction.required";
62
+ readonly id: string;
63
+ readonly prompt: string;
64
+ readonly options?: JsonObject;
65
+ readonly sessionId: string;
66
+ readonly turnId: string;
67
+ } | {
68
+ readonly type: "turn.settled";
69
+ readonly result: Result<Output>;
70
+ readonly sessionId: string;
71
+ readonly turnId: string;
72
+ };
73
+ /** Structural agent identity used to parameterize Session/Event types. */
74
+ export interface AgentIdentity {
75
+ readonly id: string;
76
+ }
77
+ export interface Session<A extends AgentIdentity = AgentIdentity, Output = unknown> {
78
+ readonly id: string;
79
+ readonly agentId: A["id"];
80
+ input(message: MessageInput | string): Promise<InputAccepted>;
81
+ stream(opts?: {
82
+ readonly after?: string;
83
+ }): AsyncIterable<Event<Output>>;
84
+ history(): Promise<readonly JsonValue[]>;
85
+ approve(id: string, approved: boolean): Promise<void>;
86
+ respond(id: string, value: JsonValue): Promise<void>;
87
+ cancel(): Promise<void>;
88
+ }
@@ -0,0 +1,114 @@
1
+ import type { ModelAdapter } from "@nylorun/core/define";
2
+ import type { InputEvent, MessageInput, TranscriptEntry } from "@nylorun/core/define";
3
+ import type { ObserveEvent } from "@nylorun/core/define";
4
+ import type { JsonObject, JsonValue, Tripwire } from "@nylorun/core/define";
5
+ import type { RequiredInteraction, ToolExecutionResume, ToolOutcome, ToolResult } from "@nylorun/core/define";
6
+ export type ExecutionInput = MessageInput | InputEvent | {
7
+ readonly kind: "continue";
8
+ } | {
9
+ readonly kind: "settle";
10
+ readonly invocationId: string;
11
+ readonly outcome: Extract<ToolOutcome, {
12
+ kind: "completed" | "failed" | "denied";
13
+ }>;
14
+ } | {
15
+ readonly kind: "wait-resolve";
16
+ readonly waitId: string;
17
+ readonly value?: JsonValue;
18
+ };
19
+ /** Portable reference to deployed code. Applications must preserve this value intact. */
20
+ export interface ToolReference {
21
+ readonly capabilityId: string;
22
+ readonly toolName: string;
23
+ readonly descriptor: {
24
+ readonly name: string;
25
+ readonly description?: string;
26
+ readonly inputSchema: JsonObject;
27
+ readonly outputSchema?: JsonObject;
28
+ };
29
+ }
30
+ export interface SavedToolCall {
31
+ readonly callId: string;
32
+ readonly invocationId: string;
33
+ readonly toolName: string;
34
+ readonly args: JsonValue;
35
+ readonly rawArgs: JsonValue;
36
+ readonly reference: ToolReference;
37
+ readonly status: "pending" | "active" | "settled" | "interaction" | "deferred";
38
+ readonly result?: ToolResult;
39
+ readonly interaction?: RequiredInteraction;
40
+ readonly interactionPhase?: "before" | "execute";
41
+ readonly resume?: ToolExecutionResume;
42
+ readonly token?: JsonValue;
43
+ /** Optional wait metadata for H6 durable waits. */
44
+ readonly wait?: {
45
+ readonly kind: "ask" | "approve" | "sleep" | "waitFor";
46
+ readonly waitId: string;
47
+ readonly name?: string;
48
+ };
49
+ }
50
+ export interface ExecutionPlan {
51
+ readonly turnId: string;
52
+ readonly stepId: string;
53
+ readonly stepNumber: number;
54
+ readonly order: readonly {
55
+ readonly callId: string;
56
+ readonly toolName: string;
57
+ }[];
58
+ readonly immediate: readonly ToolResult[];
59
+ readonly calls: readonly SavedToolCall[];
60
+ }
61
+ /** Versioned continuation data, not a live execution object. */
62
+ export interface ExecutionState {
63
+ readonly version: 1;
64
+ readonly agentId: string;
65
+ /** Definition binding — required for resume after Phase 2. Optional on legacy states. */
66
+ readonly manifestHash?: string;
67
+ readonly executionId: string;
68
+ readonly outputContract?: JsonObject;
69
+ readonly revision: number;
70
+ readonly turnCount: number;
71
+ readonly transcript: readonly TranscriptEntry[];
72
+ readonly status: "ready" | "active" | "completed" | "paused" | "cancelled" | "failed";
73
+ readonly plan?: ExecutionPlan;
74
+ /** Abandoned actions retained for reconciliation, never automatically dispatched. */
75
+ readonly cancelledCalls?: readonly SavedToolCall[];
76
+ /**
77
+ * Durable session memory (tools write via `ctx.state`; beforeModelCall reads).
78
+ * Runtime persists this with the checkpoint.
79
+ */
80
+ readonly state?: JsonObject;
81
+ }
82
+ export type ExecutionEvent = (ObserveEvent | {
83
+ readonly type: "execution.completed" | "execution.paused" | "execution.cancelled" | "execution.failed" | "observation.failed";
84
+ readonly attributes?: JsonObject;
85
+ }) & {
86
+ readonly executionId: string;
87
+ readonly sequence: number;
88
+ readonly runId: string;
89
+ };
90
+ export interface RunOptions<Info = unknown> {
91
+ readonly state?: ExecutionState;
92
+ readonly input: ExecutionInput;
93
+ readonly onModelCall: ModelAdapter;
94
+ readonly info?: Info;
95
+ readonly signal?: AbortSignal;
96
+ readonly onEvent?: (event: ExecutionEvent) => void | Promise<void>;
97
+ readonly record?: (state: ExecutionState) => void | Promise<void>;
98
+ }
99
+ export type RunResult<Output = string> = {
100
+ readonly status: "completed";
101
+ readonly state: ExecutionState;
102
+ readonly output: Output;
103
+ } | {
104
+ readonly status: "paused";
105
+ readonly state: ExecutionState;
106
+ readonly pending: readonly SavedToolCall[];
107
+ } | {
108
+ readonly status: "cancelled";
109
+ readonly state: ExecutionState;
110
+ } | {
111
+ readonly status: "failed";
112
+ readonly state: ExecutionState;
113
+ readonly error: Tripwire;
114
+ };
@@ -1 +1,2 @@
1
+ export declare function withDeterministicIds<T>(seed: string, fn: () => T): T;
1
2
  export declare function createId(prefix: string): string;
package/dist/utils/ids.js CHANGED
@@ -1,3 +1,13 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const scope = new AsyncLocalStorage();
3
+ export function withDeterministicIds(seed, fn) {
4
+ return scope.run({ seed, counts: new Map() }, fn);
5
+ }
1
6
  export function createId(prefix) {
2
- return `${prefix}_${globalThis.crypto.randomUUID()}`;
7
+ const value = scope.getStore();
8
+ if (!value)
9
+ return `${prefix}_${globalThis.crypto.randomUUID()}`;
10
+ const count = (value.counts.get(prefix) ?? 0) + 1;
11
+ value.counts.set(prefix, count);
12
+ return `${prefix}_${value.seed}_${count}`;
3
13
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nylorun/harness",
3
- "version": "0.12.0-beta",
4
- "description": "Nylorun's TypeScript agent runtime. See github.com/nylorun/harness.",
3
+ "version": "0.15.0-beta",
4
+ "description": "Nylorun's TypeScript agent execution engine. See github.com/nylorun/harness.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -28,16 +28,25 @@
28
28
  "types": "./dist/index.d.ts",
29
29
  "import": "./dist/index.js"
30
30
  },
31
+ "./run": {
32
+ "types": "./dist/run/index.d.ts",
33
+ "import": "./dist/run/index.js"
34
+ },
31
35
  "./model/adapters": {
32
- "types": "./dist/model/adapters.d.ts",
33
- "import": "./dist/model/adapters.js"
36
+ "types": "./dist/loop/model/adapters.d.ts",
37
+ "import": "./dist/loop/model/adapters.js"
38
+ },
39
+ "./compatibility": {
40
+ "types": "./dist/compatibility.d.ts",
41
+ "import": "./dist/compatibility.js"
34
42
  }
35
43
  },
36
44
  "files": [
37
45
  "dist",
38
46
  "README.md",
39
47
  "CHANGELOG.md",
40
- "LICENSE"
48
+ "LICENSE",
49
+ "HOST_CONTRACT.md"
41
50
  ],
42
51
  "sideEffects": false,
43
52
  "publishConfig": {
@@ -66,7 +75,10 @@
66
75
  "vitest": "^5.0.0",
67
76
  "zod": "^4.1.12"
68
77
  },
78
+ "dependencies": {
79
+ "@nylorun/core": "0.1.1-beta"
80
+ },
69
81
  "peerDependencies": {
70
- "zod": "^4.1.12"
82
+ "zod": "^4.5.4"
71
83
  }
72
84
  }
@@ -1,17 +0,0 @@
1
- import type { AgentManifest } from "../types/manifest.js";
2
- import type { BoundMiddleware } from "../types/middleware.js";
3
- import type { ModelAdapter } from "../types/model.js";
4
- import type { Session, SessionRunOptions } from "../types/session.js";
5
- export interface LoopAgent {
6
- readonly middleware: readonly BoundMiddleware[];
7
- readonly invoke: ModelAdapter;
8
- }
9
- export declare class BuiltAgent {
10
- readonly middleware: readonly BoundMiddleware[];
11
- readonly manifest: AgentManifest;
12
- readonly id: string;
13
- readonly name: string;
14
- private constructor();
15
- run(options: SessionRunOptions): Session;
16
- }
17
- export declare function bindAgent(middleware: readonly BoundMiddleware[], manifest: AgentManifest): BuiltAgent;
@@ -1,32 +0,0 @@
1
- import { LiveSession } from "../session/session.js";
2
- import { createId } from "../utils/ids.js";
3
- import { HarnessError } from "../errors.js";
4
- let createBoundAgent;
5
- export class BuiltAgent {
6
- middleware;
7
- manifest;
8
- id;
9
- name;
10
- constructor(middleware, manifest) {
11
- this.middleware = middleware;
12
- this.manifest = manifest;
13
- this.id = manifest.id;
14
- this.name = manifest.name;
15
- }
16
- static {
17
- createBoundAgent = (middleware, manifest) => new BuiltAgent(middleware, manifest);
18
- }
19
- run(options) {
20
- const seeded = "seed" in options && options.seed !== undefined;
21
- if (seeded &&
22
- (("id" in options && options.id !== undefined) ||
23
- ("userId" in options && options.userId !== undefined) ||
24
- ("context" in options && options.context !== undefined)))
25
- throw new HarnessError("session.invalid-seed", "Seeded run options cannot include id, userId, or context outside the seed");
26
- const id = (seeded ? options.seed.id : "id" in options ? options.id : undefined) ?? createId("session");
27
- return new LiveSession(id, Object.freeze({ middleware: this.middleware, invoke: options.onModelCall }), options);
28
- }
29
- }
30
- export function bindAgent(middleware, manifest) {
31
- return createBoundAgent(middleware, manifest);
32
- }
@@ -1,7 +0,0 @@
1
- import type { BuildResult } from "../types/manifest.js";
2
- import type { BoundMiddleware } from "../types/middleware.js";
3
- import { type BuiltAgent } from "./agent.js";
4
- export declare function assembleAgent(middleware: readonly BoundMiddleware[], identity: Readonly<{
5
- id: string;
6
- name: string;
7
- }>): BuildResult<BuiltAgent>;
@@ -1,41 +0,0 @@
1
- import { bindAgent } from "./agent.js";
2
- import { createManifest } from "./manifest.js";
3
- const diagnostic = (code, message, extra = {}) => Object.freeze({ code, message, ...extra });
4
- export function assembleAgent(middleware, identity) {
5
- const diagnostics = [];
6
- if (typeof identity.id !== "string" || identity.id.length === 0) {
7
- diagnostics.push(diagnostic("agent.invalid-id", "Agent id must be a non-empty string"));
8
- }
9
- if (typeof identity.name !== "string" || identity.name.length === 0) {
10
- diagnostics.push(diagnostic("agent.invalid-name", "Agent name must be a non-empty string"));
11
- }
12
- const middlewareIds = new Set();
13
- const frozen = [];
14
- for (const item of middleware) {
15
- if (!item.id)
16
- diagnostics.push(diagnostic("middleware.invalid-id", "Middleware id must not be empty"));
17
- else if (middlewareIds.has(item.id))
18
- diagnostics.push(diagnostic("middleware.duplicate-id", `Duplicate middleware id '${item.id}'`));
19
- else if (typeof item.handle !== "function")
20
- diagnostics.push(diagnostic("middleware.invalid", `Middleware '${item.id}' must provide a function`));
21
- else {
22
- middlewareIds.add(item.id);
23
- frozen.push(Object.freeze({
24
- id: item.id,
25
- handle: item.handle,
26
- ...(item.state === undefined ? {} : { state: item.state }),
27
- ...(item.contributions === undefined ? {} : { contributions: item.contributions }),
28
- }));
29
- }
30
- }
31
- if (diagnostics.length)
32
- return Object.freeze({ ok: false, diagnostics: Object.freeze(diagnostics) });
33
- const frozenMiddleware = Object.freeze(frozen);
34
- const manifest = createManifest({
35
- id: identity.id,
36
- name: identity.name,
37
- middleware: frozenMiddleware,
38
- });
39
- const agent = bindAgent(frozenMiddleware, manifest);
40
- return Object.freeze({ ok: true, agent, manifest });
41
- }
@@ -1,3 +0,0 @@
1
- import type { BoundToolDefinition, ToolDefinition } from "../types/tool.js";
2
- /** Prepares an executable Tool and records its middleware slot provenance. */
3
- export declare function bindTool(item: ToolDefinition, owner: BoundToolDefinition["owner"]): BoundToolDefinition;
@@ -1,18 +0,0 @@
1
- import { HarnessError } from "../errors.js";
2
- import { normalizedSchemasFor } from "./schema.js";
3
- /** Prepares an executable Tool and records its middleware slot provenance. */
4
- export function bindTool(item, owner) {
5
- if (!item.name)
6
- throw new HarnessError("tool.invalid-name", "Tool name must not be empty");
7
- if (typeof item.execute !== "function")
8
- throw new HarnessError("tool.invalid", `Tool '${item.name}' must provide execute()`);
9
- const schemas = normalizedSchemasFor(item);
10
- return Object.freeze({
11
- name: item.name,
12
- ...(item.description ? { description: item.description } : {}),
13
- inputSchema: schemas.inputSchema,
14
- ...(schemas.outputSchema === undefined ? {} : { outputSchema: schemas.outputSchema }),
15
- execute: item.execute.bind(item),
16
- owner: Object.freeze({ ...owner }),
17
- });
18
- }