@nylorun/harness 0.11.1-beta → 0.13.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 (131) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +124 -5
  3. package/dist/definition/agent-definition.d.ts +11 -0
  4. package/dist/definition/agent-definition.js +12 -0
  5. package/dist/definition/assemble.d.ts +22 -0
  6. package/dist/{build → definition}/assemble.js +6 -7
  7. package/dist/definition/bind-agent.d.ts +9 -0
  8. package/dist/definition/bind-agent.js +21 -0
  9. package/dist/{build → definition}/bind-tool.d.ts +2 -1
  10. package/dist/{build → definition}/bind-tool.js +1 -0
  11. package/dist/definition/bound.d.ts +16 -0
  12. package/dist/definition/builder.d.ts +30 -0
  13. package/dist/definition/builder.js +94 -0
  14. package/dist/definition/declaration.d.ts +3 -0
  15. package/dist/definition/declaration.js +53 -0
  16. package/dist/{build → definition}/helpers.d.ts +1 -1
  17. package/dist/{build → definition}/manifest.d.ts +3 -1
  18. package/dist/definition/manifest.js +45 -0
  19. package/dist/{session → definition}/output-contract.d.ts +2 -2
  20. package/dist/{session → definition}/output-contract.js +1 -1
  21. package/dist/definition/registry.d.ts +10 -0
  22. package/dist/definition/registry.js +48 -0
  23. package/dist/{build → definition}/schema.d.ts +4 -4
  24. package/dist/errors.d.ts +1 -1
  25. package/dist/execution/dispatch.d.ts +2 -0
  26. package/dist/execution/dispatch.js +164 -0
  27. package/dist/execution/initial-state.d.ts +3 -0
  28. package/dist/execution/initial-state.js +14 -0
  29. package/dist/execution/invocation.d.ts +21 -0
  30. package/dist/execution/invocation.js +32 -0
  31. package/dist/{model → execution/model}/adapters.d.ts +2 -2
  32. package/dist/{model → execution/model}/adapters.js +2 -2
  33. package/dist/{model → execution/model}/normalize.d.ts +2 -2
  34. package/dist/{model → execution/model}/normalize.js +2 -2
  35. package/dist/{model → execution/model}/prepared.d.ts +3 -3
  36. package/dist/{step → execution/model}/project.d.ts +1 -1
  37. package/dist/{step → execution/model}/project.js +2 -10
  38. package/dist/execution/observe.d.ts +17 -0
  39. package/dist/execution/observe.js +53 -0
  40. package/dist/execution/options.d.ts +10 -0
  41. package/dist/execution/options.js +49 -0
  42. package/dist/execution/resume.d.ts +4 -0
  43. package/dist/execution/resume.js +52 -0
  44. package/dist/execution/run.d.ts +5 -0
  45. package/dist/execution/run.js +256 -0
  46. package/dist/execution/state.d.ts +8 -0
  47. package/dist/execution/state.js +208 -0
  48. package/dist/{step → execution/step}/canonicalize.d.ts +2 -2
  49. package/dist/{step → execution/step}/canonicalize.js +2 -2
  50. package/dist/{step → execution/step}/compose.d.ts +3 -2
  51. package/dist/{step → execution/step}/compose.js +5 -5
  52. package/dist/{step → execution/step}/context-draft.d.ts +3 -5
  53. package/dist/{step → execution/step}/context-draft.js +5 -22
  54. package/dist/execution/step/describe.d.ts +9 -0
  55. package/dist/execution/step/describe.js +22 -0
  56. package/dist/{step → execution/step}/model-configuration.d.ts +4 -3
  57. package/dist/{step → execution/step}/model-configuration.js +3 -3
  58. package/dist/execution/step/resolve.d.ts +11 -0
  59. package/dist/{step → execution/step}/resolve.js +5 -3
  60. package/dist/execution/step/run.d.ts +29 -0
  61. package/dist/{step → execution/step}/run.js +19 -53
  62. package/dist/execution/step/runtime.d.ts +32 -0
  63. package/dist/execution/step/runtime.js +1 -0
  64. package/dist/{step → execution/step}/seal.d.ts +13 -8
  65. package/dist/{step → execution/step}/seal.js +8 -6
  66. package/dist/{step → execution/step}/slot-assembly.d.ts +4 -4
  67. package/dist/{step → execution/step}/slot-assembly.js +4 -4
  68. package/dist/{step → execution/step}/step-context.d.ts +11 -12
  69. package/dist/{step → execution/step}/step-context.js +5 -27
  70. package/dist/execution/tool-result.d.ts +4 -0
  71. package/dist/execution/tool-result.js +30 -0
  72. package/dist/execution/transcript.d.ts +2 -0
  73. package/dist/{session/seed.js → execution/transcript.js} +9 -39
  74. package/dist/index.d.ts +15 -12
  75. package/dist/index.js +5 -5
  76. package/dist/types/agent.d.ts +9 -0
  77. package/dist/types/agent.js +1 -0
  78. package/dist/types/execution.d.ts +97 -0
  79. package/dist/types/execution.js +1 -0
  80. package/dist/types/manifest.d.ts +16 -13
  81. package/dist/types/middleware.d.ts +11 -40
  82. package/dist/types/model.d.ts +10 -10
  83. package/dist/types/observe.d.ts +165 -0
  84. package/dist/types/observe.js +1 -0
  85. package/dist/types/shared.d.ts +1 -225
  86. package/dist/types/tool.d.ts +21 -19
  87. package/dist/types/transcript.d.ts +65 -0
  88. package/dist/types/transcript.js +1 -0
  89. package/dist/utils/canonical.d.ts +1 -0
  90. package/dist/utils/canonical.js +10 -0
  91. package/dist/utils/immutable.d.ts +1 -0
  92. package/dist/utils/immutable.js +8 -0
  93. package/package.json +5 -5
  94. package/dist/build/agent.d.ts +0 -18
  95. package/dist/build/agent.js +0 -37
  96. package/dist/build/assemble.d.ts +0 -8
  97. package/dist/build/builder.d.ts +0 -46
  98. package/dist/build/builder.js +0 -151
  99. package/dist/build/manifest.js +0 -20
  100. package/dist/session/capability-state.d.ts +0 -14
  101. package/dist/session/capability-state.js +0 -67
  102. package/dist/session/event-log.d.ts +0 -12
  103. package/dist/session/event-log.js +0 -63
  104. package/dist/session/input-queue.d.ts +0 -41
  105. package/dist/session/input-queue.js +0 -94
  106. package/dist/session/record.d.ts +0 -11
  107. package/dist/session/record.js +0 -26
  108. package/dist/session/scheduler.d.ts +0 -62
  109. package/dist/session/scheduler.js +0 -485
  110. package/dist/session/seed.d.ts +0 -10
  111. package/dist/session/session.d.ts +0 -18
  112. package/dist/session/session.js +0 -103
  113. package/dist/session/state.d.ts +0 -15
  114. package/dist/session/state.js +0 -48
  115. package/dist/session/submission-stream.d.ts +0 -16
  116. package/dist/session/submission-stream.js +0 -46
  117. package/dist/step/resolve.d.ts +0 -10
  118. package/dist/step/run.d.ts +0 -33
  119. package/dist/turn/plan-runner.d.ts +0 -70
  120. package/dist/turn/plan-runner.js +0 -388
  121. package/dist/turn/runner.d.ts +0 -69
  122. package/dist/turn/runner.js +0 -158
  123. package/dist/types/session.d.ts +0 -227
  124. package/dist/utils/maps.d.ts +0 -1
  125. package/dist/utils/maps.js +0 -37
  126. package/dist/utils/observe.d.ts +0 -8
  127. package/dist/utils/observe.js +0 -29
  128. /package/dist/{types/session.js → definition/bound.js} +0 -0
  129. /package/dist/{build → definition}/helpers.js +0 -0
  130. /package/dist/{build → definition}/schema.js +0 -0
  131. /package/dist/{model → execution/model}/prepared.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - Breaking beta: replace `AgentManifest.middleware` with a capability catalog. The published
8
+ snapshot is `id`, `name`, optional `outputSchema`, and `capabilities` (`kind`,
9
+ `hasMiddleware`, declared instructions / tool JSON schemas / model controls). Join traces by
10
+ capability id. `MiddlewareManifest` is removed; import `CapabilityManifest`.
11
+ - Breaking beta: expose `BuiltAgent` as a type-only facade from `Agent(...).use(...).build()`.
12
+ Hide compiled middleware, tool registries, and output validators. `AgentBuilder` accepts public
13
+ agent options. `createExecutionState` requires the original built agent. Drop agent-level
14
+ `executionVersion`; `ExecutionState.version` stays `1` and leftover keys are ignored. Model
15
+ adapters receive immutable `ToolDescriptor` metadata only. Remove `defineToolFamily` /
16
+ `ToolFamily` / `capability.toolFamilies` and the internal Bound\* / `SealedToolCall` root
17
+ exports. Rename the per-run bag from `scope` to `info`.
18
+ - c5bbb1a: Breaking beta: make Harness `run()` a direct async state-in/state-out executor with
19
+ serializable pauses, application `info`, cancellation signals, awaited recording, and
20
+ agent-level output schemas. Runtime owns session scheduling with memory-default or exclusive
21
+ local storage and imports Harness contracts. Isolate Node adapters under `runtime/node`, stream
22
+ observations incrementally, and add opt-in bounded token previews with Studio reconciliation.
23
+ Migrate consumers and deployment guidance together; legacy event records remain archived, not
24
+ automatically replayed.
25
+
26
+ ## 0.12.0-beta
27
+
28
+ ### Minor Changes
29
+
30
+ - 4badb5b: Move model execution to session startup, provide Runtime as a mountable Hono router, and generate Hono-first projects with supervised application and Studio development. Studio now resolves root-relative Runtime endpoints correctly for custom mount paths.
31
+
3
32
  ## 0.11.1-beta
4
33
 
5
34
  ### Patch Changes
package/README.md CHANGED
@@ -1,10 +1,129 @@
1
1
  # `@nylorun/harness`
2
2
 
3
- Nylorun's TypeScript agent runtime.
3
+ Observable, portable, composable agent execution. Harness runs a model/tool loop with invocation-local progress; an application or optional Runtime owns sessions.
4
4
 
5
- For installation, examples, API details, and release notes, see the
6
- [Nylorun Harness repository](https://github.com/nylorun/harness).
5
+ > **Breaking beta:** this interface replaces the Session-returning `run()` API. See [migration](../MIGRATION.md).
7
6
 
8
- > **Experimental beta.** Install with `npm install @nylorun/harness@beta`.
7
+ ## Run an agent
9
8
 
10
- Repository development: [contributing](../CONTRIBUTING.md). Package publication: [releasing](../RELEASING.md).
9
+ ```ts
10
+ import { Agent } from "@nylorun/harness";
11
+
12
+ const agent = Agent({ id: "assistant", name: "Assistant" }).build();
13
+ const result = await agent.run({
14
+ input: "Hello",
15
+ onModelCall: async (call) => "Hello back!", // Your model adapter
16
+ });
17
+ if (result.status === "completed") console.log(result.output);
18
+
19
+ const next = await agent.run({
20
+ state: result.state,
21
+ input: "Continue our conversation",
22
+ onModelCall,
23
+ });
24
+ ```
25
+
26
+ Every call returns a Promise. Omitted state starts an empty execution. The built agent retains definitions, not progress; supplied state is never mutated. Multiple calls may use the same agent concurrently. Applications must coordinate concurrent updates to the same conversation.
27
+
28
+ Outcomes are `completed` (with `output`), `paused` (with `pending`), `cancelled`, or `failed` (with `error`). Each includes serializable `state`. Invalid options, incompatible state, and invalid continuation inputs reject before model/tool execution. Operational failures return `failed`. Recording failures reject.
29
+
30
+ ## Built agent interface
31
+
32
+ `Agent(...).use(...).build()` returns a `BuiltAgent<Info, Output>`. Import `BuiltAgent` with `import type`; it has no runtime constructor or `instanceof` check. Its public members are `id`, `name`, `manifest`, and `run`. The manifest is a frozen capability catalog (`id`, `name`, optional `outputSchema`, and `capabilities`); compiled middleware, the tool registry, and output validators remain internal. Correlate traces by `event.middlewareId` / `ToolReference.capabilityId` to `manifest.capabilities[].id`.
33
+
34
+ `createExecutionState(agent)` requires the original built agent so it can include the configured output contract. Adding application-owned methods such as `close()` with `Object.assign(agent, { close })` preserves this identity. If you wrap or copy an agent, initialize state using the original agent. State remains serializable and inspectable.
35
+
36
+ Model adapters receive immutable `ToolDescriptor` values in `context.request.tools` and `context.request.configuration.tools`. Descriptors preserve `name`, optional `description`, `owner`, `inputSchema.jsonSchema`, and optional `outputSchema.jsonSchema`. They contain no executable tool, original definition, or validator. Implement tools through registered capabilities; adapters return tool-call candidates for Harness to validate and dispatch.
37
+
38
+ ## Optional controls
39
+
40
+ ```ts
41
+ const result = await agent.run({
42
+ state,
43
+ input,
44
+ onModelCall,
45
+ info: { userId, tenantId },
46
+ signal: controller.signal,
47
+ onEvent: (event) => console.log(event),
48
+ record: (state) => sessionStore.save(sessionId, state),
49
+ });
50
+ ```
51
+
52
+ `info` is extra application data available as `request.info` in middleware and `context.info` in tools. Type it with `Agent<MyInfo>(...)`. Harness does not put info into state, observations, or model requests. Middleware can deliberately add model-visible data through its existing `request.context` interface. The application owns authentication and authorization, including checking access to the original resource after continuation.
53
+
54
+ `signal` is cooperative: Harness checks before further work, forwards it to adapters and tools, and waits for dispatched work to settle. Cancellation preserves settled results, cannot undo external effects, and cannot forcibly stop an uncooperative dependency. Harness has no cancellation or shutdown method. The developer owns clients, connections, subprocesses, and their cleanup.
55
+
56
+ To cancel a saved pause, call `run({ state, input: { kind: "continue" }, signal: AbortSignal.abort(), onModelCall })`. This settles the pending plan without dispatching it. `state.cancelledCalls` retains unresolved invocation references and deferred tokens for reconciliation, outside the model transcript; cancelling a deferred job does not stop external work by itself. Runtime's `host.cancel(agent, sessionId)` applies this transition and persists it before accepting replacement work.
57
+
58
+ `onEvent` reports ordered events with execution, run, turn, step, model invocation, and tool call identifiers where applicable. Observer exceptions produce `observation.failed` diagnostics without changing results. Observations describe execution; they do not acknowledge durable commits. Explicit model-visible content and tool output can appear in events, so applications still control sensitive data they put there.
59
+
60
+ `record` receives independent immutable JSON snapshots, serially. Harness awaits each recording barrier: accepted plans after middleware unwinds and before dispatch, active work markers, settled tool batches, pauses, and final outcomes. A rejected recording stops further dispatch; active work has settled before rejection. An external operation may succeed before a write fails. An intermediate active snapshot requires reconciliation and never authorizes automatic replay.
61
+
62
+ ## Final output contracts
63
+
64
+ ```ts
65
+ import { z } from "zod";
66
+ const agent = Agent({
67
+ id: "extractor",
68
+ name: "Extractor",
69
+ outputSchema: z.object({ answer: z.string() }),
70
+ }).build();
71
+ // A completed result.output is inferred as { answer: string }.
72
+ ```
73
+
74
+ The schema belongs to the agent definition, is supplied to adapters, and validates the accepted final response after middleware. Adapters return a JSON output block for structured results. There is no per-run or per-turn schema override. Tool output schemas remain tool-specific. With an explicitly typed info bag and output schema, use `Agent<MyInfo, typeof schema>({...})`.
75
+
76
+ ## Register and expose tools
77
+
78
+ ```ts
79
+ const lookup = {
80
+ name: "lookup",
81
+ inputSchema: z.object({ id: z.string() }),
82
+ execute: async ({ id }, { info, signal }) => ({
83
+ kind: "completed" as const,
84
+ output: await database.lookup(id, { info, signal }),
85
+ }),
86
+ };
87
+ const agent = Agent({ id: "assistant", name: "Assistant" })
88
+ .use({ id: "database", tools: [lookup] })
89
+ .build();
90
+ ```
91
+
92
+ `.use()` registers code. Middleware selects registered tools through `request.configuration.tools.set(slot, tools)`. Changing the subset needs no restoration callback. Unregistered executable closures are rejected before a model call. Middleware wrapping order is preserved; tool plans are accepted only after the stack unwinds.
93
+
94
+ When identity depends on discovered data, keep one registered tool and put that data in the arguments (or authorize it from `info`):
95
+
96
+ ```ts
97
+ const queryTable = {
98
+ name: "query_table",
99
+ inputSchema: z.object({ table: z.string(), query: z.string() }),
100
+ execute: async ({ table, query }, { info, signal }) => ({
101
+ kind: "completed" as const,
102
+ output: await warehouse.query({ table, query, principal: info, signal }),
103
+ }),
104
+ };
105
+ ```
106
+
107
+ ## Pause and continue
108
+
109
+ An approval or response request, or a tool returning `{ kind: "deferred", token }`, settles the invocation as `paused`. Persist the returned state intact. Rebuild the same agent definition and call `run()` again:
110
+
111
+ ```ts
112
+ await agent.run({
113
+ state: savedState,
114
+ input: { kind: "approve", interactionId, approved: true },
115
+ info: freshApplicationInfo,
116
+ onModelCall,
117
+ });
118
+ // A response uses { kind: "respond", interactionId, value }.
119
+ // A deferred settlement uses { kind: "settle", invocationId, outcome:
120
+ // { kind: "completed", output: jobResult } }.
121
+ ```
122
+
123
+ Registered tools restore by name from the rebuilt definition. Accepted descriptors, arguments, ordering, decisions, and invocation IDs survive; settled tools and middleware are not rerun for restoration. An execute-time interaction may re-enter that tool with `context.resume`; use its saved token to continue, not repeat prior effects.
124
+
125
+ `createExecutionState(agent)` and `validateExecutionState(value)` help initialize and validate state. Applications should not hand-author pending plans. State is a trusted continuation record, not an authorization credential; never accept client-controlled state without validation and access control.
126
+
127
+ Missing definitions, changed contracts or bindings, and duplicate/mismatched continuation inputs reject. Family versions must change for incompatible implementations. Schema comparisons cannot detect arbitrary behavior changes inside JavaScript functions. Hosts own agent generation, drain of old definitions, and any migrate-in-place of saved state.
128
+
129
+ Completed turns and explicit pauses can continue in a new process. Automatic crash replay and deferred model continuation are not supported. One-shot child work is `await child.run(...)`; separately addressable child sessions belong to a host.
@@ -0,0 +1,11 @@
1
+ import type { BoundMiddleware } from "./bound.js";
2
+ import type { ToolRegistry } from "./registry.js";
3
+ import type { TurnOutputContract } from "./output-contract.js";
4
+ export interface AgentDefinition {
5
+ readonly id: string;
6
+ readonly middleware: readonly BoundMiddleware[];
7
+ readonly registry: ToolRegistry;
8
+ readonly output?: TurnOutputContract;
9
+ }
10
+ export declare function registerDefinition(agent: object, definition: AgentDefinition): void;
11
+ export declare function definitionFor(agent: object): AgentDefinition;
@@ -0,0 +1,12 @@
1
+ import { HarnessError } from "../errors.js";
2
+ // Definition lookup only; execution progress is never retained here.
3
+ const definitions = new WeakMap();
4
+ export function registerDefinition(agent, definition) {
5
+ definitions.set(agent, definition);
6
+ }
7
+ export function definitionFor(agent) {
8
+ const definition = definitions.get(agent);
9
+ if (!definition)
10
+ throw new HarnessError("execution.invalid-input", "createExecutionState() requires the original agent returned by Agent(...).build(); initialize wrapper state with that original agent.");
11
+ return definition;
12
+ }
@@ -0,0 +1,22 @@
1
+ import type { BoundMiddleware } from "./bound.js";
2
+ import type { BuiltAgent } from "../types/agent.js";
3
+ import type { AgentManifest } from "../types/manifest.js";
4
+ import type { BuildDiagnostic } from "../types/shared.js";
5
+ import type { RunOptions, RunResult } from "../types/execution.js";
6
+ import type { ToolSchemaSource } from "../types/tool.js";
7
+ import type { AgentDefinition } from "./agent-definition.js";
8
+ type BuildResult<Agent> = {
9
+ readonly ok: true;
10
+ readonly agent: Agent;
11
+ readonly manifest: AgentManifest;
12
+ } | {
13
+ readonly ok: false;
14
+ readonly diagnostics: readonly BuildDiagnostic[];
15
+ };
16
+ type AgentRun = (definition: AgentDefinition, options: RunOptions<any>) => Promise<RunResult<any>>;
17
+ export declare function assembleAgent(middleware: readonly BoundMiddleware[], identity: Readonly<{
18
+ id: string;
19
+ name: string;
20
+ outputSchema?: ToolSchemaSource;
21
+ }>, run: AgentRun): BuildResult<BuiltAgent>;
22
+ export {};
@@ -1,7 +1,7 @@
1
- import { bindAgent } from "./agent.js";
1
+ import { bindAgent } from "./bind-agent.js";
2
2
  import { createManifest } from "./manifest.js";
3
3
  const diagnostic = (code, message, extra = {}) => Object.freeze({ code, message, ...extra });
4
- export function assembleAgent(middleware, invoke, identity) {
4
+ export function assembleAgent(middleware, identity, run) {
5
5
  const diagnostics = [];
6
6
  if (typeof identity.id !== "string" || identity.id.length === 0) {
7
7
  diagnostics.push(diagnostic("agent.invalid-id", "Agent id must be a non-empty string"));
@@ -9,9 +9,6 @@ export function assembleAgent(middleware, invoke, identity) {
9
9
  if (typeof identity.name !== "string" || identity.name.length === 0) {
10
10
  diagnostics.push(diagnostic("agent.invalid-name", "Agent name must be a non-empty string"));
11
11
  }
12
- if (typeof invoke !== "function") {
13
- diagnostics.push(diagnostic("harness.invalid-model", "A model invoke function is required"));
14
- }
15
12
  const middlewareIds = new Set();
16
13
  const frozen = [];
17
14
  for (const item of middleware) {
@@ -26,7 +23,8 @@ export function assembleAgent(middleware, invoke, identity) {
26
23
  frozen.push(Object.freeze({
27
24
  id: item.id,
28
25
  handle: item.handle,
29
- ...(item.state === undefined ? {} : { state: item.state }),
26
+ hasMiddleware: item.hasMiddleware,
27
+ ...(item.tools === undefined ? {} : { tools: item.tools }),
30
28
  ...(item.contributions === undefined ? {} : { contributions: item.contributions }),
31
29
  }));
32
30
  }
@@ -37,8 +35,9 @@ export function assembleAgent(middleware, invoke, identity) {
37
35
  const manifest = createManifest({
38
36
  id: identity.id,
39
37
  name: identity.name,
38
+ outputSchema: identity.outputSchema,
40
39
  middleware: frozenMiddleware,
41
40
  });
42
- const agent = bindAgent(frozenMiddleware, invoke, manifest);
41
+ const agent = bindAgent(frozenMiddleware, manifest, identity, run);
43
42
  return Object.freeze({ ok: true, agent, manifest });
44
43
  }
@@ -0,0 +1,9 @@
1
+ import type { AgentManifest } from "../types/manifest.js";
2
+ import type { BoundMiddleware } from "./bound.js";
3
+ import type { BuiltAgent } from "../types/agent.js";
4
+ import type { RunOptions, RunResult } from "../types/execution.js";
5
+ import type { ToolSchemaSource } from "../types/tool.js";
6
+ import { type AgentDefinition } from "./agent-definition.js";
7
+ export declare function bindAgent(middleware: readonly BoundMiddleware[], manifest: AgentManifest, options: {
8
+ outputSchema?: ToolSchemaSource;
9
+ } | undefined, run: (definition: AgentDefinition, options: RunOptions<any>) => Promise<RunResult<any>>): BuiltAgent;
@@ -0,0 +1,21 @@
1
+ import { bindOutputContract } from "./output-contract.js";
2
+ import { registerDefinition } from "./agent-definition.js";
3
+ import { ToolRegistry } from "./registry.js";
4
+ export function bindAgent(middleware, manifest, options = {}, run) {
5
+ const definition = Object.freeze({
6
+ id: manifest.id,
7
+ middleware,
8
+ registry: new ToolRegistry(middleware),
9
+ ...(options.outputSchema === undefined
10
+ ? {}
11
+ : { output: bindOutputContract(options.outputSchema) }),
12
+ });
13
+ const agent = {
14
+ id: definition.id,
15
+ name: manifest.name,
16
+ manifest,
17
+ run: (options) => run(definition, options),
18
+ };
19
+ registerDefinition(agent, definition);
20
+ return agent;
21
+ }
@@ -1,3 +1,4 @@
1
- import type { BoundToolDefinition, ToolDefinition } from "../types/tool.js";
1
+ import type { BoundToolDefinition } from "./bound.js";
2
+ import type { ToolDefinition } from "../types/tool.js";
2
3
  /** Prepares an executable Tool and records its middleware slot provenance. */
3
4
  export declare function bindTool(item: ToolDefinition, owner: BoundToolDefinition["owner"]): BoundToolDefinition;
@@ -8,6 +8,7 @@ export function bindTool(item, owner) {
8
8
  throw new HarnessError("tool.invalid", `Tool '${item.name}' must provide execute()`);
9
9
  const schemas = normalizedSchemasFor(item);
10
10
  return Object.freeze({
11
+ source: item,
11
12
  name: item.name,
12
13
  ...(item.description ? { description: item.description } : {}),
13
14
  inputSchema: schemas.inputSchema,
@@ -0,0 +1,16 @@
1
+ import type { MiddlewareContributions, StepMiddleware } from "../types/middleware.js";
2
+ import type { ToolDefinition, ToolExecutionContext, ToolInputSchema, ToolOutcome, ToolOutputSchema, ToolOwner, ToolSchema } from "../types/tool.js";
3
+ export interface BoundToolDefinition<Info = unknown> extends Omit<ToolDefinition<ToolInputSchema, Info, ToolOutputSchema | undefined>, "inputSchema" | "outputSchema" | "execute"> {
4
+ readonly inputSchema: ToolSchema<unknown>;
5
+ readonly outputSchema?: ToolSchema<unknown>;
6
+ readonly execute: (args: unknown, context: ToolExecutionContext<Info>) => Promise<ToolOutcome>;
7
+ readonly owner: ToolOwner;
8
+ readonly source: ToolDefinition<any, any, any>;
9
+ }
10
+ export interface BoundMiddleware {
11
+ readonly id: string;
12
+ readonly handle: StepMiddleware;
13
+ readonly hasMiddleware: boolean;
14
+ readonly tools?: readonly ToolDefinition<any, any, any>[];
15
+ readonly contributions?: MiddlewareContributions;
16
+ }
@@ -0,0 +1,30 @@
1
+ import type { CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
2
+ import type { BuildDiagnostic } from "../types/shared.js";
3
+ import { HarnessError } from "../errors.js";
4
+ import type { ToolSchemaSource, SchemaOutput } from "../types/tool.js";
5
+ import type { BuiltAgent } from "../types/agent.js";
6
+ export interface AgentOptions<Schema extends ToolSchemaSource | undefined = undefined> {
7
+ readonly outputSchema?: Schema;
8
+ readonly id: string;
9
+ readonly name: string;
10
+ readonly instructions?: string | readonly string[];
11
+ }
12
+ export declare class AgentBuildError extends HarnessError {
13
+ readonly diagnostics: readonly BuildDiagnostic[];
14
+ constructor(diagnostics: readonly BuildDiagnostic[]);
15
+ }
16
+ export declare class AgentLifecycleError extends HarnessError {
17
+ constructor(message: string);
18
+ }
19
+ export declare function Agent<Info = unknown, Schema extends ToolSchemaSource | undefined = undefined>(options: AgentOptions<Schema>): AgentBuilder<Info, Schema>;
20
+ export declare class AgentBuilder<Info = unknown, Schema extends ToolSchemaSource | undefined = undefined> {
21
+ #private;
22
+ constructor(options: AgentOptions<Schema>);
23
+ use(middleware: StepMiddleware<Info>): this;
24
+ use(id: string, middleware: StepMiddleware<Info>): this;
25
+ use(declaration: CapabilityDeclaration<Info>): this;
26
+ build(): BuiltAgent<Info, Schema extends ToolSchemaSource ? SchemaOutput<Schema> : string>;
27
+ private nextMiddlewareId;
28
+ private push;
29
+ private assertOpen;
30
+ }
@@ -0,0 +1,94 @@
1
+ import { HarnessError } from "../errors.js";
2
+ import { execute } from "../execution/run.js";
3
+ import { assembleAgent } from "./assemble.js";
4
+ import { compileDeclaration } from "./declaration.js";
5
+ export class AgentBuildError extends HarnessError {
6
+ diagnostics;
7
+ constructor(diagnostics) {
8
+ super("agent.build-failed", diagnostics.map((item) => item.message).join("; ") || "Agent build failed");
9
+ this.diagnostics = diagnostics;
10
+ this.name = "AgentBuildError";
11
+ }
12
+ }
13
+ export class AgentLifecycleError extends HarnessError {
14
+ constructor(message) {
15
+ super("agent.lifecycle-sealed", message);
16
+ this.name = "AgentLifecycleError";
17
+ }
18
+ }
19
+ export function Agent(options) {
20
+ return new AgentBuilder(options);
21
+ }
22
+ export class AgentBuilder {
23
+ #state;
24
+ constructor(options) {
25
+ this.#state = createState(options);
26
+ }
27
+ use(idOrMiddleware, middleware) {
28
+ if (typeof idOrMiddleware === "function") {
29
+ return this.push({
30
+ id: this.nextMiddlewareId(),
31
+ handle: idOrMiddleware,
32
+ hasMiddleware: true,
33
+ });
34
+ }
35
+ if (typeof idOrMiddleware === "object")
36
+ return this.push(compileDeclaration(idOrMiddleware));
37
+ return this.push({
38
+ id: idOrMiddleware,
39
+ handle: middleware,
40
+ hasMiddleware: true,
41
+ });
42
+ }
43
+ build() {
44
+ if (this.#state.agent)
45
+ return this.#state.agent;
46
+ if (this.#state.error)
47
+ throw this.#state.error;
48
+ this.#state.sealed = true;
49
+ const result = assembleAgent(this.#state.middleware, {
50
+ id: this.#state.id,
51
+ name: this.#state.name,
52
+ outputSchema: this.#state.outputSchema,
53
+ }, execute);
54
+ if (!result.ok) {
55
+ this.#state.error = new AgentBuildError(result.diagnostics);
56
+ throw this.#state.error;
57
+ }
58
+ this.#state.agent = result.agent;
59
+ return this.#state.agent;
60
+ }
61
+ nextMiddlewareId() {
62
+ const taken = new Set(this.#state.middleware.map((item) => item.id));
63
+ let id;
64
+ do {
65
+ this.#state.middlewareSeq += 1;
66
+ id = `middleware-${this.#state.middlewareSeq}`;
67
+ } while (taken.has(id));
68
+ return id;
69
+ }
70
+ push(entry) {
71
+ this.assertOpen();
72
+ this.#state.middleware.push(entry);
73
+ return this;
74
+ }
75
+ assertOpen() {
76
+ if (this.#state.sealed)
77
+ throw new AgentLifecycleError("AgentBuilder cannot be changed after build()");
78
+ }
79
+ }
80
+ function createState(options) {
81
+ const middleware = [];
82
+ if (options.instructions !== undefined) {
83
+ const instructions = typeof options.instructions === "string" ? [options.instructions] : options.instructions;
84
+ middleware.push(compileDeclaration({ id: "agent", instructions }));
85
+ }
86
+ return {
87
+ id: options.id,
88
+ outputSchema: options.outputSchema,
89
+ name: options.name,
90
+ middleware,
91
+ sealed: false,
92
+ middlewareSeq: 0,
93
+ };
94
+ }
@@ -0,0 +1,3 @@
1
+ import type { BoundMiddleware } from "./bound.js";
2
+ import type { CapabilityDeclaration } from "../types/middleware.js";
3
+ export declare function compileDeclaration<State>(declaration: CapabilityDeclaration<State>): BoundMiddleware;
@@ -0,0 +1,53 @@
1
+ export function compileDeclaration(declaration) {
2
+ const tools = copyItems(declaration.tools, declaration.id);
3
+ const instructions = copyItems(declaration.instructions, declaration.id);
4
+ const model = declaration.model;
5
+ const contributions = snapshotContributions(instructions?.items, tools?.items, model);
6
+ const handle = async (request, next) => {
7
+ if (tools)
8
+ request.configuration.tools.set(tools.slot, tools.items);
9
+ if (instructions)
10
+ request.configuration.instructions.set(instructions.slot, instructions.items);
11
+ if (model)
12
+ request.configuration.model.select(model);
13
+ return declaration.middleware ? declaration.middleware(request, next) : next();
14
+ };
15
+ return {
16
+ id: declaration.id,
17
+ handle,
18
+ hasMiddleware: declaration.middleware !== undefined,
19
+ tools: tools?.items,
20
+ ...(contributions === undefined ? {} : { contributions }),
21
+ };
22
+ }
23
+ function snapshotContributions(instructions, tools, model) {
24
+ const snapInstructions = instructions === undefined ? undefined : Object.freeze([...instructions]);
25
+ const snapTools = tools === undefined
26
+ ? undefined
27
+ : Object.freeze(tools.map((tool) => Object.freeze({
28
+ name: tool.name,
29
+ ...(tool.description === undefined ? {} : { description: tool.description }),
30
+ })));
31
+ const snapModel = model === undefined ? undefined : snapshotModel(model);
32
+ if (snapInstructions === undefined && snapTools === undefined && snapModel === undefined) {
33
+ return undefined;
34
+ }
35
+ return Object.freeze({
36
+ ...(snapInstructions === undefined ? {} : { instructions: snapInstructions }),
37
+ ...(snapTools === undefined ? {} : { tools: snapTools }),
38
+ ...(snapModel === undefined ? {} : { model: snapModel }),
39
+ });
40
+ }
41
+ function snapshotModel(model) {
42
+ return Object.freeze({
43
+ ...(model.id === undefined ? {} : { id: model.id }),
44
+ ...(model.controls === undefined ? {} : { controls: Object.freeze({ ...model.controls }) }),
45
+ });
46
+ }
47
+ function copyItems(value, defaultSlot) {
48
+ if (value === undefined)
49
+ return undefined;
50
+ if (!("items" in value))
51
+ return Object.freeze({ slot: defaultSlot, items: Object.freeze([...value]) });
52
+ return Object.freeze({ slot: value.slot, items: Object.freeze([...value.items]) });
53
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ModelAdapter } from "../types/model.js";
2
2
  import type { StepMiddleware } from "../types/middleware.js";
3
3
  import type { ToolDefinition, ToolInputSchema, ToolOutputSchema } from "../types/tool.js";
4
- export declare const tool: <InputSchema extends ToolInputSchema, State = never, OutputSchema extends ToolOutputSchema | undefined = undefined>(value: ToolDefinition<InputSchema, State, OutputSchema>) => ToolDefinition<InputSchema, State, OutputSchema>;
4
+ export declare const tool: <InputSchema extends ToolInputSchema, Info = unknown, OutputSchema extends ToolOutputSchema | undefined = undefined>(value: ToolDefinition<InputSchema, Info, OutputSchema>) => ToolDefinition<InputSchema, Info, OutputSchema>;
5
5
  export declare const model: <T extends ModelAdapter>(value: T) => T;
6
6
  export declare const middleware: <T extends StepMiddleware>(value: T) => T;
@@ -1,7 +1,9 @@
1
+ import type { BoundMiddleware } from "./bound.js";
1
2
  import type { AgentManifest } from "../types/manifest.js";
2
- import type { BoundMiddleware } from "../types/middleware.js";
3
+ import type { ToolSchemaSource } from "../types/tool.js";
3
4
  export declare function createManifest(input: {
4
5
  id: string;
5
6
  name: string;
7
+ outputSchema?: ToolSchemaSource;
6
8
  middleware: readonly BoundMiddleware[];
7
9
  }): AgentManifest;
@@ -0,0 +1,45 @@
1
+ import { bindOutputContract } from "./output-contract.js";
2
+ import { normalizedSchemasFor } from "./schema.js";
3
+ import { deepFreeze } from "../utils/immutable.js";
4
+ export function createManifest(input) {
5
+ const capabilities = input.middleware.map((item) => projectCapability(item));
6
+ return deepFreeze({
7
+ id: input.id,
8
+ name: input.name,
9
+ ...(input.outputSchema === undefined
10
+ ? {}
11
+ : { outputSchema: bindOutputContract(input.outputSchema).schema.jsonSchema }),
12
+ capabilities,
13
+ });
14
+ }
15
+ function projectCapability(item) {
16
+ const contributions = item.contributions;
17
+ return {
18
+ id: item.id,
19
+ kind: capabilityKind(item),
20
+ hasMiddleware: item.hasMiddleware,
21
+ ...(contributions?.instructions === undefined
22
+ ? {}
23
+ : { instructions: contributions.instructions }),
24
+ ...(item.tools === undefined ? {} : { tools: item.tools.map((tool) => projectTool(tool)) }),
25
+ ...(contributions?.model === undefined ? {} : { model: contributions.model }),
26
+ };
27
+ }
28
+ function capabilityKind(item) {
29
+ if (item.id === "agent")
30
+ return "agent";
31
+ if (item.contributions !== undefined)
32
+ return "capability";
33
+ return "middleware";
34
+ }
35
+ function projectTool(tool) {
36
+ const schemas = normalizedSchemasFor(tool);
37
+ return {
38
+ name: tool.name,
39
+ ...(tool.description === undefined ? {} : { description: tool.description }),
40
+ inputSchema: schemas.inputSchema.jsonSchema,
41
+ ...(schemas.outputSchema === undefined
42
+ ? {}
43
+ : { outputSchema: schemas.outputSchema.jsonSchema }),
44
+ };
45
+ }
@@ -1,6 +1,6 @@
1
- import type { BoundToolSchema, ToolSchemaSource } from "../types/tool.js";
1
+ import type { ToolSchema, ToolSchemaSource } from "../types/tool.js";
2
2
  /** Runtime-only validator paired with the portable JSON Schema projected to models. */
3
3
  export interface TurnOutputContract {
4
- readonly schema: BoundToolSchema<unknown>;
4
+ readonly schema: ToolSchema<unknown>;
5
5
  }
6
6
  export declare function bindOutputContract(source: ToolSchemaSource): TurnOutputContract;
@@ -1,5 +1,5 @@
1
1
  import { HarnessError, isHarnessError } from "../errors.js";
2
- import { normalizeSchema } from "../build/schema.js";
2
+ import { normalizeSchema } from "./schema.js";
3
3
  export function bindOutputContract(source) {
4
4
  try {
5
5
  return Object.freeze({ schema: normalizeSchema(source, "output") });
@@ -0,0 +1,10 @@
1
+ import type { BoundMiddleware, BoundToolDefinition } from "./bound.js";
2
+ import type { ToolReference } from "../types/execution.js";
3
+ /** Definition-only lookup; no per-run selections or progress live here. */
4
+ export declare class ToolRegistry {
5
+ private readonly tools;
6
+ private readonly owners;
7
+ constructor(middleware: readonly BoundMiddleware[]);
8
+ reference(tool: BoundToolDefinition): ToolReference;
9
+ restore(reference: ToolReference): BoundToolDefinition;
10
+ }