@nylorun/harness 0.15.0-beta → 0.16.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.
- package/CHANGELOG.md +12 -0
- package/HOST_CONTRACT.md +5 -5
- package/dist/definition/registry.js +1 -1
- package/dist/loop/step/dynamics.js +4 -2
- package/dist/run/durable.d.ts +9 -0
- package/dist/run/durable.js +57 -45
- package/dist/run/index.d.ts +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3a88f51: Ship Agent-Plugins (`plugin()` / `loadPlugin`), Skills (`load_skill` / skill resources), Runtime MCP pool + vault credentials, and manifest v3 capability fields. Validate completed tool `output` against the tool output schema so ordinary tools with `outputSchema` no longer false-fail as `tool.invalid-output`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Pin core to the tested release.
|
|
12
|
+
- Updated dependencies [3a88f51]
|
|
13
|
+
- @nylorun/core@0.2.0-beta
|
|
14
|
+
|
|
3
15
|
## 0.15.0-beta
|
|
4
16
|
|
|
5
17
|
### Major Changes
|
package/HOST_CONTRACT.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# Durable execution and session protocol — beta freeze
|
|
2
2
|
|
|
3
|
-
Artifact: `@nylorun/harness@0.14.0-beta.3`. Package version is separate from protocol 1, definition schema
|
|
3
|
+
Artifact: `@nylorun/harness@0.14.0-beta.3`. Package version is separate from protocol 1, definition schema 3, durable checkpoint 1 / engine `hosted-1`, and inner execution checkpoint 1. Public execution API and shared contracts are the compatibility boundary. Cloud installs published npm packages and does not import the OSS Runtime.
|
|
4
4
|
|
|
5
5
|
## HTTP (both hosts)
|
|
6
6
|
|
|
7
|
-
Bearer application credentials: `PUT /v1/agents/:agentId` (`PutAgentRequest`: requestId, complete manifest, implementationVersion); `PUT /v1/sessions/:sessionId` (`PutSessionRequest`: requestId, agentId, trusted ownerUserId, optional info); `GET /v1/sessions/:sessionId`; `POST /v1/sessions/:sessionId/commands`; `GET /v1/sessions/:sessionId/items?cursor=...`; `GET /v1/sessions/:sessionId/events?cursor=...` (SSE, Last-Event-ID also accepted)
|
|
7
|
+
Bearer application credentials: `PUT /v1/agents/:agentId` (`PutAgentRequest`: requestId, complete manifest, implementationVersion, optional pluginRoots); `PUT /v1/sessions/:sessionId` (`PutSessionRequest`: requestId, agentId, trusted ownerUserId, optional info, optional vaultIds, optional credentialSelections); `GET /v1/sessions/:sessionId`; `POST /v1/sessions/:sessionId/commands`; `GET /v1/sessions/:sessionId/items?cursor=...`; `GET /v1/sessions/:sessionId/events?cursor=...` (SSE, Last-Event-ID also accepted); vault administration `POST/GET/DELETE /v1/vaults` and `/v1/vaults/:vaultId/credentials`. Commands use `SessionCommandSchema`. Vault creates and credential rotations use `idempotencyKey`; requestId is excluded from comparison; the same key and body replay the stored metadata response; a changed body is 409. Credential responses omit secret values. `GET /v1/vaults?ownerUserId=` lists that user's vaults. A session attaches only vaults with the same ownerUserId. A later session PUT may replace vaultIds and credentialSelections; agentId, ownerUserId, and info stay the creation identity. GET session returns id, agentId, ownerUserId, status, activeTurnId, vaultIds, credentialSelections, mcpSnapshot, mcpDiagnostics, plus inspectable waits/uncertainty as available. mcpSnapshot is null until the first turn discovers MCP tools; it is then pinned for the session and contains no secrets. pluginRoots are stored beside the agent definition and are not part of the manifest hash. History returns `{items: LiveEvent[], cursor: string|null}`; empty history cursor is null. Executor credentials cannot call vault routes.
|
|
8
8
|
|
|
9
|
-
Separate scoped executor bearer credentials: `GET /v1/executors/connect` emits `event: work_available` / `data: {"type":"work_available"}` after connection and committed work; `GET /v1/actions` returns `{actions: Action[]}`; `POST /v1/actions/:actionId/claim` uses ActionClaimRequest; `POST /v1/actions/:actionId/heartbeat` uses ActionHeartbeatRequest and returns leaseExpiresAt. Claim returns ActionClaim. Action result is the `action_result` session command with executor authorization, never observer authorization. Claim identity is server-generated and generation monotonically increases. Credentials
|
|
9
|
+
Separate scoped executor bearer credentials: `GET /v1/executors/connect` emits `event: work_available` / `data: {"type":"work_available"}` after connection and committed work; `GET /v1/actions` returns `{actions: Action[]}`; `POST /v1/actions/:actionId/claim` uses ActionClaimRequest; `POST /v1/actions/:actionId/heartbeat` uses ActionHeartbeatRequest and returns leaseExpiresAt. Claim returns ActionClaim. Action result is the `action_result` session command with executor authorization, never observer authorization. Claim identity is server-generated and generation monotonically increases. Credentials authorize an executor by agentId. A manifestHash stored on an existing token is ignored, and claim requests do not send one. In-flight actions for that agent stay claimable after the registered digest changes. Clients cannot broaden their agent scope. Subscribe before discovery; rediscover after reconnect; notifications never authorize execution.
|
|
10
10
|
|
|
11
11
|
All command idempotency keys bind to canonical request content within a session; requestId is transport metadata and is excluded from content comparison; exact semantic duplicates return the stored original response; changed content conflicts (409). A completed action's matching claim/generation/outcome returns its original receipt even after expiry. Conflicting duplicates and any other stale/expired claim are rejected. Heartbeat cannot resurrect an expired claim. Expired in-flight customer work is uncertain, not automatically reoffered. Results after cancellation cannot resume a turn. Application observers cannot claim or submit action results. No hardcoded production credentials.
|
|
12
12
|
|
|
13
13
|
## Engine host
|
|
14
14
|
|
|
15
|
-
`createDurableCheckpoint({manifest,sessionId,turnId,input,state?,info?,segment?})`, `runDurable({manifest,checkpoint,host,signal?})` from `/run`. `DurableHost.resolveEffect(HostEffect)` atomically creates or reads an individually identified effect and returns `{status:'completed',outcome:{value,statePatch?}}` or `{status:'pending'|'uncertain'}`. Model effect `input` is the complete ModelCall (instructions/history/tools/output schema); context contains a JSON projection of ModelRequest and invocationId. Its completed value is a ModelCandidate or string. Runtime provider code owns credentials and invocation. Tools/hooks have capabilityId, toolName for tools, serialized input and context. Tool result value is a ToolOutcome or a plain tool output; statePatch contains changed session keys. Before/after hook values are Patch/Decision. Tool context includes invocationId, resume, state, info and stable idempotencyKey. Customer implementationVersion is pinned by the runtime outside durable checkpoints.
|
|
15
|
+
`createDurableCheckpoint({manifest,sessionId,turnId,input,state?,info?,segment?})`, `runDurable({manifest,checkpoint,host,signal?,sessionTools?})` from `/run`. `DurableHost.resolveEffect(HostEffect)` atomically creates or reads an individually identified effect and returns `{status:'completed',outcome:{value,statePatch?}}` or `{status:'pending'|'uncertain'}`. Model effect `input` is the complete ModelCall (instructions/history/tools/output schema); context contains a JSON projection of ModelRequest and invocationId. Its completed value is a ModelCandidate or string. Runtime provider code owns credentials and invocation. Tools/hooks have capabilityId, toolName for tools, serialized input and context. Tool result value is a ToolOutcome or a plain tool output; statePatch contains changed session keys. Before/after hook values are Patch/Decision. Tool context includes invocationId, resume, state, info and stable idempotencyKey. Customer implementationVersion is pinned by the runtime outside durable checkpoints.
|
|
16
16
|
|
|
17
17
|
Persist checkpoint BEFORE scheduling, effect intent BEFORE provider invocation or notification, and outcome BEFORE resumption. Journaling an entire runDurable call as one replayable operation is forbidden. The durable path deterministically reconstructs a segment from its immutable starting checkpoint and individually cached effect outcomes. A waiting result returns that starting checkpoint: do not replace it with an intermediate preview. The journal is required continuation data and must be retained with the checkpoint. Stable IDs include turn/segment and deterministic invocation identity. There is no live continuation or request-held promise while waiting. Runtimes serialize work per session, check cancellation before effects and before settlement, and fence ownership. Lost provider acknowledgement becomes uncertain; do not repeat it merely because a worker restarts.
|
|
18
18
|
|
|
@@ -22,6 +22,6 @@ Host resolveEffect must bind the effect ID to the original request and reject dr
|
|
|
22
22
|
|
|
23
23
|
## Definitions, validation, compatibility
|
|
24
24
|
|
|
25
|
-
`/define` is execution-free and browser-bundleable; Agent has no `.run()`. Explicit local execution uses `/run` run + bindingFromAgent(agent.build()). `/contracts` never imports checkpoint internals. Tools and before/after hooks are supported remotely. Arbitrary legacy middleware closures are rejected for durable definitions because they cannot be reconstructed from manifests. JSON schemas are reconstructed through Zod's JSON Schema importer; unsupported schemas fail closed. The original manifest
|
|
25
|
+
`/define` is execution-free and browser-bundleable; Agent has no `.run()`. Explicit local execution uses `/run` run + bindingFromAgent(agent.build()). `/contracts` never imports checkpoint internals. Tools and before/after hooks are supported remotely. Arbitrary legacy middleware closures are rejected for durable definitions because they cannot be reconstructed from manifests. JSON schemas are reconstructed through Zod's JSON Schema importer; unsupported schemas fail closed. The original manifest document must survive reconstruction. Runtime is the only hasher: it hashes the accepted manifest and pins that digest on the session and on each action. Agents do not hash. Model configuration is host-owned. The executor claims by agent id. Runtime checks a successful tool result against the output schema copied onto the action and stores a schema mismatch as a failed outcome on a completed action.
|
|
26
26
|
|
|
27
27
|
No session migration, arbitrary checkpoint migration, broad model-support, or conformance claims follow from compiling this interface. Studio and conformance tooling remain outside production harness.
|
|
@@ -8,7 +8,7 @@ export class ToolRegistry {
|
|
|
8
8
|
owners = new WeakMap();
|
|
9
9
|
constructor(middleware, snapshots) {
|
|
10
10
|
for (const capability of middleware) {
|
|
11
|
-
for (const tool of capability.tools ?? []) {
|
|
11
|
+
for (const tool of [...(capability.tools ?? []), ...(capability.sessionTools ?? [])]) {
|
|
12
12
|
const key = JSON.stringify([capability.id, tool.name]);
|
|
13
13
|
if (this.tools.has(key) || this.owners.has(tool))
|
|
14
14
|
throw new HarnessError("tool.invalid", `Duplicate registered tool '${tool.name}' in '${capability.id}'`);
|
|
@@ -42,13 +42,15 @@ export async function applyBeforeModelCall(ctx, configuration) {
|
|
|
42
42
|
}
|
|
43
43
|
if (patch.tools) {
|
|
44
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)
|
|
45
|
+
const known = ctx.agent.manifest.capabilities.some((cap) => cap.tools?.some((tool) => tool.name === name) ||
|
|
46
|
+
ctx.agent.implementations[cap.id]?.tools?.[name] !== undefined);
|
|
46
47
|
if (!known)
|
|
47
48
|
throw new HarnessError("configuration.invalid", `beforeModelCall referenced unknown tool '${name}'`);
|
|
48
49
|
if (!agentLevel) {
|
|
49
|
-
const
|
|
50
|
+
const declared = ctx.agent.manifest.capabilities
|
|
50
51
|
.find((cap) => cap.id === id)
|
|
51
52
|
?.tools?.some((tool) => tool.name === name);
|
|
53
|
+
const owned = declared || ctx.agent.implementations[id]?.tools?.[name] !== undefined;
|
|
52
54
|
if (!owned)
|
|
53
55
|
throw new HarnessError("configuration.invalid", `Capability '${id}' beforeModelCall may only toggle its own tools`);
|
|
54
56
|
}
|
package/dist/run/durable.d.ts
CHANGED
|
@@ -50,6 +50,14 @@ export type DurableResult = {
|
|
|
50
50
|
readonly checkpoint: DurableCheckpoint;
|
|
51
51
|
readonly result: RunResult<unknown>;
|
|
52
52
|
};
|
|
53
|
+
/** Discovered tool advertised for this execution. It is not part of the hashed manifest. */
|
|
54
|
+
export interface DurableSessionTool {
|
|
55
|
+
readonly capabilityId: string;
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly description?: string;
|
|
58
|
+
readonly inputSchema: JsonObject;
|
|
59
|
+
readonly outputSchema?: JsonObject;
|
|
60
|
+
}
|
|
53
61
|
export declare function createDurableCheckpoint(input: {
|
|
54
62
|
manifest: AgentManifest;
|
|
55
63
|
sessionId: string;
|
|
@@ -68,4 +76,5 @@ export declare function runDurable(options: {
|
|
|
68
76
|
checkpoint: DurableCheckpoint;
|
|
69
77
|
host: DurableHost;
|
|
70
78
|
signal?: AbortSignal;
|
|
79
|
+
sessionTools?: readonly DurableSessionTool[];
|
|
71
80
|
}): Promise<DurableResult>;
|
package/dist/run/durable.js
CHANGED
|
@@ -72,54 +72,59 @@ export async function runDurable(options) {
|
|
|
72
72
|
return result.outcome;
|
|
73
73
|
};
|
|
74
74
|
let patchTail = Promise.resolve();
|
|
75
|
+
const hostedTool = (capabilityId, tool) => ({
|
|
76
|
+
name: tool.name,
|
|
77
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
78
|
+
inputSchema: schemaFromJSON(tool.inputSchema),
|
|
79
|
+
...(tool.outputSchema ? { outputSchema: schemaFromJSON(tool.outputSchema) } : {}),
|
|
80
|
+
async execute(args, ctx) {
|
|
81
|
+
const previous = patchTail;
|
|
82
|
+
let release;
|
|
83
|
+
patchTail = new Promise((resolve) => {
|
|
84
|
+
release = resolve;
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
const outcome = await effect("tool", args, {
|
|
88
|
+
executionId: ctx.executionId,
|
|
89
|
+
turnId: ctx.turnId,
|
|
90
|
+
stepId: ctx.stepId,
|
|
91
|
+
callId: ctx.callId,
|
|
92
|
+
invocationId: ctx.invocationId,
|
|
93
|
+
idempotencyKey: ctx.idempotencyKey,
|
|
94
|
+
info: ctx.info,
|
|
95
|
+
state: ctx.state.entries(),
|
|
96
|
+
resume: ctx.resume,
|
|
97
|
+
}, capabilityId, tool.name, ctx.invocationId);
|
|
98
|
+
// Apply concurrently resolved patches in manifest call order, independent of host I/O timing.
|
|
99
|
+
await previous;
|
|
100
|
+
for (const [key, value] of Object.entries(outcome.statePatch ?? {}))
|
|
101
|
+
ctx.state.set(key, value);
|
|
102
|
+
const value = outcome.value;
|
|
103
|
+
return value?.kind === "interaction-required"
|
|
104
|
+
? {
|
|
105
|
+
...value,
|
|
106
|
+
interaction: {
|
|
107
|
+
...value.interaction,
|
|
108
|
+
id: `${checkpoint.turnId}:${checkpoint.segment}:interaction:${ctx.invocationId}`,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
: value;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await previous;
|
|
115
|
+
release();
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
75
119
|
const implementations = {};
|
|
120
|
+
const sessionTools = options.sessionTools ?? [];
|
|
76
121
|
for (const capability of manifest.capabilities) {
|
|
77
122
|
const tools = {};
|
|
78
123
|
for (const tool of capability.tools ?? [])
|
|
79
|
-
tools[tool.name] =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
};
|
|
124
|
+
tools[tool.name] = hostedTool(capability.id, tool);
|
|
125
|
+
for (const tool of sessionTools)
|
|
126
|
+
if (tool.capabilityId === capability.id)
|
|
127
|
+
tools[tool.name] = hostedTool(capability.id, tool);
|
|
123
128
|
implementations[capability.id] = {
|
|
124
129
|
tools,
|
|
125
130
|
...(capability.beforeModelCall
|
|
@@ -135,7 +140,14 @@ export async function runDurable(options) {
|
|
|
135
140
|
: {}),
|
|
136
141
|
};
|
|
137
142
|
}
|
|
138
|
-
const definition = definitionFor(agentFrom(manifest, implementations
|
|
143
|
+
const definition = definitionFor(agentFrom(manifest, implementations, sessionTools.length === 0
|
|
144
|
+
? undefined
|
|
145
|
+
: {
|
|
146
|
+
sessionTools: sessionTools.map((tool) => ({
|
|
147
|
+
capabilityId: tool.capabilityId,
|
|
148
|
+
name: tool.name,
|
|
149
|
+
})),
|
|
150
|
+
}));
|
|
139
151
|
// Manifest reconstruction must retain definition identity, including schemas and instructions.
|
|
140
152
|
if (definition.hash !== checkpoint.manifestHash)
|
|
141
153
|
throw new HarnessError("execution.incompatible", "Reconstructed definition hash mismatch");
|
package/dist/run/index.d.ts
CHANGED
|
@@ -29,5 +29,5 @@ export declare function bindingFromAgent(agent: BuiltAgent): RunBinding;
|
|
|
29
29
|
export { checkCompatibility, createExecutionState, validateExecutionState, execute };
|
|
30
30
|
export type { Implementations, ExecutionState, RunResult, RunOptions };
|
|
31
31
|
export { createDurableCheckpoint, runDurable } from "./durable.js";
|
|
32
|
-
export type { DurableCheckpoint, HostEffect, EffectResolution, DurableHost, DurableResult, } from "./durable.js";
|
|
32
|
+
export type { DurableCheckpoint, DurableSessionTool, HostEffect, EffectResolution, DurableHost, DurableResult, } from "./durable.js";
|
|
33
33
|
export type { ModelAdapter, ModelCandidate, ModelCall, ModelRequest } from "@nylorun/core/define";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0-beta",
|
|
4
4
|
"description": "Nylorun's TypeScript agent execution engine. See github.com/nylorun/harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -70,15 +70,15 @@
|
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^26.5.0",
|
|
72
72
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
73
|
-
"prettier": "^3.9.
|
|
73
|
+
"prettier": "^3.9.8",
|
|
74
74
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
75
|
-
"vitest": "^5.0.
|
|
76
|
-
"zod": "^4.
|
|
75
|
+
"vitest": "^5.0.1",
|
|
76
|
+
"zod": "^4.6.5"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@nylorun/core": "0.
|
|
79
|
+
"@nylorun/core": "0.2.0-beta"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"zod": "^4.5
|
|
82
|
+
"zod": "^4.6.5"
|
|
83
83
|
}
|
|
84
84
|
}
|