@nylorun/harness 0.7.0-beta.1 → 0.8.0-beta.1

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to `@nylorun/harness` are documented in this file.
4
4
 
5
5
  The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the public API is experimental: breaking changes may occur in minor releases, while patch releases are reserved for compatible fixes.
6
6
 
7
+ ## [0.8.0-beta.1] - 2026-08-31
8
+
9
+ ### Breaking changes
10
+
11
+ - `Agent()` now takes identity options instead of a model adapter. Migrate
12
+ `Agent(adapter).use(...).build()` to
13
+ `Agent({ id, name, instructions }).use(...).with(adapter).build()`.
14
+ - `.build()` exists only on `BoundAgentBuilder`, the type returned by a single `.with(onModelCall)`.
15
+ `AgentBuilder` has `.use()` and `.with()` only.
16
+ - `AgentManifest` now includes `id` and `name`. Built agents expose the same fields as `agent.id`
17
+ and `agent.name`.
18
+
19
+ ### Added
20
+
21
+ - Optional constructor `instructions` compile as reserved `agent` middleware. A string is
22
+ normalized to one instruction. Capability-specific instructions still go through `.use()`.
23
+
7
24
  ## [0.7.0-beta.1] - 2026-08-30
8
25
 
9
26
  ### Breaking changes
@@ -75,3 +92,4 @@ The project follows [Semantic Versioning](https://semver.org/). Before 1.0, the
75
92
  [0.5.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
76
93
  [0.6.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
77
94
  [0.7.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
95
+ [0.8.0-beta.1]: https://github.com/nylorun/harness/tree/main/harness
package/README.md CHANGED
@@ -18,8 +18,13 @@ const echo = tool({
18
18
  execute: async ({ text }) => ({ kind: "completed", output: text }),
19
19
  });
20
20
 
21
- const agent = Agent(adapter)
22
- .use({ id: "echo", instructions: ["Use echo when asked."], tools: [echo] })
21
+ const agent = Agent({
22
+ id: "echo",
23
+ name: "Echo",
24
+ instructions: "Use echo when asked.",
25
+ })
26
+ .use({ id: "echo", tools: [echo] })
27
+ .with(adapter)
23
28
  .build();
24
29
 
25
30
  const result = await agent.run().input("Echo hello").completed;
@@ -10,6 +10,8 @@ export declare class BuiltAgent {
10
10
  #private;
11
11
  readonly middleware: readonly BoundMiddleware[];
12
12
  readonly manifest: AgentManifest;
13
+ readonly id: string;
14
+ readonly name: string;
13
15
  private constructor();
14
16
  run(options?: SessionRunOptions): Session;
15
17
  }
@@ -5,10 +5,14 @@ let createBoundAgent;
5
5
  export class BuiltAgent {
6
6
  middleware;
7
7
  manifest;
8
+ id;
9
+ name;
8
10
  #loopAgent;
9
11
  constructor(middleware, invoke, manifest) {
10
12
  this.middleware = middleware;
11
13
  this.manifest = manifest;
14
+ this.id = manifest.id;
15
+ this.name = manifest.name;
12
16
  this.#loopAgent = Object.freeze({
13
17
  middleware,
14
18
  invoke,
@@ -2,4 +2,7 @@ import type { BuildResult } from "../types/manifest.js";
2
2
  import type { BoundMiddleware } from "../types/middleware.js";
3
3
  import type { ModelAdapter } from "../types/model.js";
4
4
  import { type BuiltAgent } from "./agent.js";
5
- export declare function assembleAgent(middleware: readonly BoundMiddleware[], invoke: ModelAdapter): BuildResult<BuiltAgent>;
5
+ export declare function assembleAgent(middleware: readonly BoundMiddleware[], invoke: ModelAdapter, identity: Readonly<{
6
+ id: string;
7
+ name: string;
8
+ }>): BuildResult<BuiltAgent>;
@@ -1,8 +1,14 @@
1
1
  import { bindAgent } from "./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) {
4
+ export function assembleAgent(middleware, invoke, identity) {
5
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
+ }
6
12
  if (typeof invoke !== "function") {
7
13
  diagnostics.push(diagnostic("harness.invalid-model", "A model invoke function is required"));
8
14
  }
@@ -27,7 +33,11 @@ export function assembleAgent(middleware, invoke) {
27
33
  if (diagnostics.length)
28
34
  return Object.freeze({ ok: false, diagnostics: Object.freeze(diagnostics) });
29
35
  const frozenMiddleware = Object.freeze(frozen);
30
- const manifest = createManifest({ middleware: frozenMiddleware });
36
+ const manifest = createManifest({
37
+ id: identity.id,
38
+ name: identity.name,
39
+ middleware: frozenMiddleware,
40
+ });
31
41
  const agent = bindAgent(frozenMiddleware, invoke, manifest);
32
42
  return Object.freeze({ ok: true, agent, manifest });
33
43
  }
@@ -1,8 +1,24 @@
1
- import type { CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
1
+ import type { BoundMiddleware, CapabilityDeclaration, StepMiddleware } from "../types/middleware.js";
2
2
  import type { ModelAdapter } from "../types/model.js";
3
3
  import type { BuildDiagnostic } from "../types/shared.js";
4
4
  import { HarnessError } from "../errors.js";
5
5
  import type { BuiltAgent } from "./agent.js";
6
+ export interface AgentOptions {
7
+ readonly id: string;
8
+ readonly name: string;
9
+ readonly instructions?: string | readonly string[];
10
+ }
11
+ interface BuilderState {
12
+ readonly id: string;
13
+ readonly name: string;
14
+ readonly middleware: BoundMiddleware[];
15
+ invoke?: ModelAdapter;
16
+ bound: boolean;
17
+ sealed: boolean;
18
+ agent?: BuiltAgent;
19
+ error?: AgentBuildError;
20
+ middlewareSeq: number;
21
+ }
6
22
  export declare class AgentBuildError extends HarnessError {
7
23
  readonly diagnostics: readonly BuildDiagnostic[];
8
24
  constructor(diagnostics: readonly BuildDiagnostic[]);
@@ -10,19 +26,21 @@ export declare class AgentBuildError extends HarnessError {
10
26
  export declare class AgentLifecycleError extends HarnessError {
11
27
  constructor(message: string);
12
28
  }
13
- export declare function Agent(model: ModelAdapter): AgentBuilder;
29
+ export declare function Agent(options: AgentOptions): AgentBuilder;
14
30
  export declare class AgentBuilder {
15
- private readonly invoke;
16
- private readonly middleware;
17
- private sealed;
18
- private agent?;
19
- private error?;
20
- private middlewareSeq;
21
- constructor(invoke: ModelAdapter);
31
+ private readonly state;
32
+ constructor(state: BuilderState);
22
33
  use(middleware: StepMiddleware): this;
23
34
  use(id: string, middleware: StepMiddleware): this;
24
35
  use<State>(declaration: CapabilityDeclaration<State>): this;
25
- build(): BuiltAgent;
36
+ with(onModelCall: ModelAdapter): BoundAgentBuilder;
26
37
  private nextMiddlewareId;
27
38
  private push;
39
+ private assertOpen;
40
+ }
41
+ export declare class BoundAgentBuilder {
42
+ private readonly state;
43
+ constructor(state: BuilderState);
44
+ build(): BuiltAgent;
28
45
  }
46
+ export {};
@@ -14,18 +14,13 @@ export class AgentLifecycleError extends HarnessError {
14
14
  this.name = "AgentLifecycleError";
15
15
  }
16
16
  }
17
- export function Agent(model) {
18
- return new AgentBuilder(model);
17
+ export function Agent(options) {
18
+ return new AgentBuilder(createState(options));
19
19
  }
20
20
  export class AgentBuilder {
21
- invoke;
22
- middleware = [];
23
- sealed = false;
24
- agent;
25
- error;
26
- middlewareSeq = 0;
27
- constructor(invoke) {
28
- this.invoke = invoke;
21
+ state;
22
+ constructor(state) {
23
+ this.state = state;
29
24
  }
30
25
  use(idOrMiddleware, middleware) {
31
26
  if (typeof idOrMiddleware === "function") {
@@ -35,35 +30,70 @@ export class AgentBuilder {
35
30
  return this.push(compileDeclaration(idOrMiddleware));
36
31
  return this.push({ id: idOrMiddleware, handle: middleware });
37
32
  }
38
- build() {
39
- if (this.agent)
40
- return this.agent;
41
- if (this.error)
42
- throw this.error;
43
- this.sealed = true;
44
- const result = assembleAgent(this.middleware, this.invoke);
45
- if (!result.ok) {
46
- this.error = new AgentBuildError(result.diagnostics);
47
- throw this.error;
48
- }
49
- this.agent = result.agent;
50
- return this.agent;
33
+ with(onModelCall) {
34
+ this.assertOpen("with()");
35
+ this.state.bound = true;
36
+ this.state.invoke = onModelCall;
37
+ return new BoundAgentBuilder(this.state);
51
38
  }
52
39
  nextMiddlewareId() {
53
- const taken = new Set(this.middleware.map((item) => item.id));
40
+ const taken = new Set(this.state.middleware.map((item) => item.id));
54
41
  let id;
55
42
  do {
56
- this.middlewareSeq += 1;
57
- id = `middleware-${this.middlewareSeq}`;
43
+ this.state.middlewareSeq += 1;
44
+ id = `middleware-${this.state.middlewareSeq}`;
58
45
  } while (taken.has(id));
59
46
  return id;
60
47
  }
61
48
  push(entry) {
62
- if (this.sealed)
63
- throw new AgentLifecycleError("AgentBuilder cannot be changed after build()");
64
- this.middleware.push(entry);
49
+ this.assertOpen("build()");
50
+ this.state.middleware.push(entry);
65
51
  return this;
66
52
  }
53
+ assertOpen(after) {
54
+ if (this.state.bound)
55
+ throw new AgentLifecycleError("AgentBuilder cannot be changed after with()");
56
+ if (this.state.sealed)
57
+ throw new AgentLifecycleError(`AgentBuilder cannot be changed after ${after}`);
58
+ }
59
+ }
60
+ export class BoundAgentBuilder {
61
+ state;
62
+ constructor(state) {
63
+ this.state = state;
64
+ }
65
+ build() {
66
+ if (this.state.agent)
67
+ return this.state.agent;
68
+ if (this.state.error)
69
+ throw this.state.error;
70
+ this.state.sealed = true;
71
+ const result = assembleAgent(this.state.middleware, this.state.invoke, {
72
+ id: this.state.id,
73
+ name: this.state.name,
74
+ });
75
+ if (!result.ok) {
76
+ this.state.error = new AgentBuildError(result.diagnostics);
77
+ throw this.state.error;
78
+ }
79
+ this.state.agent = result.agent;
80
+ return this.state.agent;
81
+ }
82
+ }
83
+ function createState(options) {
84
+ const middleware = [];
85
+ if (options.instructions !== undefined) {
86
+ const instructions = typeof options.instructions === "string" ? [options.instructions] : options.instructions;
87
+ middleware.push(compileDeclaration({ id: "agent", instructions }));
88
+ }
89
+ return {
90
+ id: options.id,
91
+ name: options.name,
92
+ middleware,
93
+ bound: false,
94
+ sealed: false,
95
+ middlewareSeq: 0,
96
+ };
67
97
  }
68
98
  function compileDeclaration(declaration) {
69
99
  const tools = copyItems(declaration.tools, declaration.id);
@@ -1,5 +1,7 @@
1
1
  import type { AgentManifest } from "../types/manifest.js";
2
2
  import type { BoundMiddleware } from "../types/middleware.js";
3
3
  export declare function createManifest(input: {
4
+ id: string;
5
+ name: string;
4
6
  middleware: readonly BoundMiddleware[];
5
7
  }): AgentManifest;
@@ -2,6 +2,8 @@ import { deepFreeze } from "../utils/immutable.js";
2
2
  export function createManifest(input) {
3
3
  const middleware = input.middleware.map(({ id }) => ({ id }));
4
4
  return deepFreeze({
5
+ id: input.id,
6
+ name: input.name,
5
7
  middleware,
6
8
  });
7
9
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } from "./build/builder.js";
1
+ export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, BoundAgentBuilder, } from "./build/builder.js";
2
+ export type { AgentOptions } from "./build/builder.js";
2
3
  export { HarnessError, isHarnessError } from "./errors.js";
3
4
  export type { HarnessErrorCode, HarnessErrorDetails, HarnessErrorOptions } from "./errors.js";
4
5
  export { BuiltAgent } from "./build/agent.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError } from "./build/builder.js";
1
+ export { Agent, AgentBuilder, AgentBuildError, AgentLifecycleError, BoundAgentBuilder, } from "./build/builder.js";
2
2
  export { HarnessError, isHarnessError } from "./errors.js";
3
3
  export { BuiltAgent } from "./build/agent.js";
4
4
  export { middleware, model, tool } from "./build/helpers.js";
@@ -1,6 +1,8 @@
1
1
  import type { BoundMiddleware } from "./middleware.js";
2
2
  import type { BuildDiagnostic } from "./shared.js";
3
3
  export interface AgentManifest {
4
+ readonly id: string;
5
+ readonly name: string;
4
6
  readonly middleware: readonly {
5
7
  readonly id: string;
6
8
  }[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/harness",
3
- "version": "0.7.0-beta.1",
3
+ "version": "0.8.0-beta.1",
4
4
  "description": "A provider-neutral, in-memory agent loop for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",