@nylorun/runtime 0.12.0-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.
- package/CHANGELOG.md +31 -0
- package/dist/core/flow-host.js +3 -0
- package/dist/tenant/commands.js +14 -16
- package/dist/tenant/effects.js +60 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5278b4e: **Flow agents on workflow manifest v2.** A flow agent compiles to `workflowSchemaVersion: 2` and runs on the new `flow-2` engine; v1 workflows (`Chain`, `Switch`, `Parallel`, `Map`, `Loop`) keep running on `flow-1`, so in-flight runs finish as they began.
|
|
8
|
+
|
|
9
|
+
- **Manifest (core).** Any node may carry `id` and `input`; there are no slots. `chain` and `parallel` are bare collections, a Map is `{ map: { each } }` over its input, and a Loop has `max?` and `decide?`. The header adds `name`, `description`, `metadata`, `inputSchema` and `outputSchema`, and `agents` embeds every agent the flow runs, so one manifest hash covers the whole flow. `Agent.from(json, { nodes, agents })` rebuilds a flow agent. Path and key helpers (`leafPath`, `stageKey`, `forEachFlowNode`, `embeddedAgent`, …) are exported from `@nylorun/core/define`.
|
|
10
|
+
- **Paths (harness).** Linked agent sessions are named by the agent: its id, `[i]` per Map item, and a nested flow agent's id in front. Control stages add nothing, so wrapping a step in a Loop keeps its session. Functions are bound under stage keys (`route:on`, `@1.default.1:input`) and all receive `{ input, results, flowInput }`; `flowInput` works inside nested `flow()`. A Loop without `decide` retries with the verifier's feedback up to `max` and then fails with `loop.exhausted`. Nested flow agents run inline.
|
|
11
|
+
- **Runtime.** Leaves of a v2 workflow resolve from its embedded `agents` (with the workflow's plugin roots, keyed `<agent>/<capability>`) instead of the registry. `loop.iteration` is emitted only for a Loop's own agent turns.
|
|
12
|
+
- **Agents SDK.** `saveAgent(flowAgent)` PUTs one document; application mode registers the flow's executor with its manifest hash. An executor leaves `fn`, `verify` and tool actions of a v2 flow unclaimed when they belong to another manifest hash, because stage keys can shift between deploys; it reports each skipped action once through `onError`.
|
|
13
|
+
- **Studio.** Draws v2 manifests: agents and tools at their session paths, control stages at their stage keys, nested flow agents inline, and lights a Map's agent from its item sessions.
|
|
14
|
+
- Build diagnostics: `flow.duplicate-leaf`, `flow.duplicate-id`, `flow.agent-conflict`, `flow.v1-workflow`, `loop.invalid-verify`, `workflow.flow-agent-child`. Also fixes v1 flows passing an agent step's output to the next step still wrapped in the runtime's turn marker.
|
|
15
|
+
|
|
16
|
+
- 426fd27: **Flow agents as subagents.** `.subagents(flowAgent)` lets a ReAct agent delegate to a flow agent. The flow's workflow manifest v2 is inlined in the delegating tool (`ToolManifest.agent` may be a workflow manifest), so it is saved with the parent and served by the parent's executor, plugin roots included (`<flow>/<agent>/<capability>`).
|
|
17
|
+
|
|
18
|
+
- **Engine (harness).** A call to a flow subagent is one durable `agent` effect (`role: "delegate"`) between the delegation's start and settle points; the flow's output is the tool result, and a failed flow is a failed tool result. Local `run()` refuses flow subagents, which need the Runtime.
|
|
19
|
+
- **Runtime.** The effect starts a linked flow session from the parent's pinned manifest, fresh per call, and settles when the flow's turn ends. Cancelling any session now cascades to the linked sessions it started, not only a workflow's.
|
|
20
|
+
- **Core.** `delegation.flow-unsupported` now only reports a workflow built with `Chain`, `Switch`, `Parallel`, `Map` or `Loop`. New helpers: `flowDelegatesOf`, `flowDelegateManifest`, `isFlowDelegate`; `delegatesOf` lists only the ReAct agents run in-process.
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Pin core to the tested release.
|
|
25
|
+
- Pin harness to the tested release.
|
|
26
|
+
- Updated dependencies [e537a82]
|
|
27
|
+
- Updated dependencies [5278b4e]
|
|
28
|
+
- Updated dependencies [426fd27]
|
|
29
|
+
- Updated dependencies [8cda500]
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @nylorun/core@0.8.0-beta
|
|
32
|
+
- @nylorun/harness@0.20.0-beta
|
|
33
|
+
|
|
3
34
|
## 0.12.0-beta
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/dist/core/flow-host.js
CHANGED
|
@@ -61,6 +61,9 @@ export function deriveSessionId(workflowSessionId, path, ...parts) {
|
|
|
61
61
|
* (path, turnId, iterations) so prior verdicts cannot leak across Loop iterations.
|
|
62
62
|
*/
|
|
63
63
|
export function deriveAgentEffectSessionId(workflowSessionId, path, request) {
|
|
64
|
+
// A flow agent used as a tool starts fresh on every call, like any agent used as a tool.
|
|
65
|
+
if (request.context?.role === "delegate")
|
|
66
|
+
return deriveSessionId(workflowSessionId, path, "delegate", String(request.context.delegationId ?? request.turnId));
|
|
64
67
|
if (request.context?.role === "verify-agent") {
|
|
65
68
|
const iterations = request.iterations ?? "-";
|
|
66
69
|
const iterParts = iterations !== "-" && iterations.length > 0 ? iterations.split(".") : [];
|
package/dist/tenant/commands.js
CHANGED
|
@@ -192,13 +192,13 @@ export async function command(ctx, id, input, scope) {
|
|
|
192
192
|
else if (command.type === "cancel") {
|
|
193
193
|
cancelledTurnId = s.activeTurnId;
|
|
194
194
|
const workflowCancel = isWorkflowManifest(s.manifest);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
// Linked sessions this one started: a workflow's agents, or flow agents an agent
|
|
196
|
+
// uses as tools.
|
|
197
|
+
const cascade = await planCancelCascade({
|
|
198
|
+
t,
|
|
199
|
+
workflowSessionId: id,
|
|
200
|
+
turnId: cancelledTurnId,
|
|
201
|
+
});
|
|
202
202
|
s.status = "cancelled";
|
|
203
203
|
if (workflowCancel) {
|
|
204
204
|
await fenceWorkflowActions({
|
|
@@ -245,15 +245,13 @@ export async function command(ctx, id, input, scope) {
|
|
|
245
245
|
s.error = undefined;
|
|
246
246
|
// Cascade: cancel linked agent sessions deepest-first (after fencing this session), each
|
|
247
247
|
// in its own transaction after this one commits (child sessions are never locked here).
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
cascadeCancelIds.push(agentId);
|
|
256
|
-
}
|
|
248
|
+
for (const agentId of cascade.agentSessionIds) {
|
|
249
|
+
const agent = await t.get("sessions", agentId);
|
|
250
|
+
if (!agent ||
|
|
251
|
+
!agent.activeTurnId ||
|
|
252
|
+
["idle", "completed", "failed", "cancelled"].includes(agent.status))
|
|
253
|
+
continue;
|
|
254
|
+
cascadeCancelIds.push(agentId);
|
|
257
255
|
}
|
|
258
256
|
}
|
|
259
257
|
else {
|
package/dist/tenant/effects.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { embeddedAgent, flowDelegateManifest, hashManifest, isWorkflowManifestV2, } from "@nylorun/core/define";
|
|
2
|
+
import { countActiveFlowWork, deriveAgentEffectSessionId, isFlowEffect, isFlowToolEffect, isWorkflowManifest, linkedMessageKey, linkedTurnEnd, } from "../core/flow-host.js";
|
|
2
3
|
import { mayDispatchMore } from "../core/limits.js";
|
|
3
4
|
import { canonical } from "../store/canonical.js";
|
|
4
5
|
import { isOwnershipLost } from "../store/ownership.js";
|
|
@@ -10,7 +11,7 @@ import { sandboxCapabilityOf } from "../sandbox/manager.js";
|
|
|
10
11
|
import { owningSandboxSessionId, sandboxSpecOf } from "../sandbox/share.js";
|
|
11
12
|
import { loadSession, ownedSession, sandboxLookup, sessionOf, } from "./context.js";
|
|
12
13
|
import { fail } from "./http.js";
|
|
13
|
-
import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, } from "./session.js";
|
|
14
|
+
import { actionTarget, linkedAgentOutput, manifestFor, mcpToolOf, pinnedTool, turnManifestOf, } from "./session.js";
|
|
14
15
|
import { command } from "./commands.js";
|
|
15
16
|
import { assistantMessage, toolCompleted, toolIds } from "./transcript.js";
|
|
16
17
|
import { abortKind } from "./worker.js";
|
|
@@ -386,8 +387,7 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
386
387
|
await ownedSession(t, lease, workflow.id);
|
|
387
388
|
if (exists)
|
|
388
389
|
return;
|
|
389
|
-
const definition =
|
|
390
|
-
fail(404, "Definition not found");
|
|
390
|
+
const definition = await leafDefinition(t, workflow, body, request);
|
|
391
391
|
const sandboxOwnerId = sandboxSpecOf(workflow.manifest) || workflow.sandboxOwnerId
|
|
392
392
|
? owningSandboxSessionId(workflow, await sandboxLookup(t, workflow.sandboxOwnerId))
|
|
393
393
|
: undefined;
|
|
@@ -440,13 +440,15 @@ export async function resolveNewFlowEffect(ctx, request, signal, lease) {
|
|
|
440
440
|
return store.tx(async (t) => {
|
|
441
441
|
const s = await ownedSession(t, lease, request.sessionId);
|
|
442
442
|
const agent = await sessionOf(t, agentSessionId);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
443
|
+
// Only a Loop's own agent turns are iterations; a step elsewhere in the flow is not.
|
|
444
|
+
if (typeof request.context.loopPath === "string")
|
|
445
|
+
await t.event(s.id, s.activeTurnId, "loop.iteration", {
|
|
446
|
+
path: request.context.loopPath,
|
|
447
|
+
n,
|
|
448
|
+
sessionId: agentSessionId,
|
|
449
|
+
turnId: accepted.turnId ?? undefined,
|
|
450
|
+
manifestHash: agent.checkpoint?.manifestHash,
|
|
451
|
+
});
|
|
450
452
|
await t.event(s.id, s.activeTurnId, "node.agent", {
|
|
451
453
|
path,
|
|
452
454
|
iterations,
|
|
@@ -562,3 +564,50 @@ export async function authorize(ctx, sessionId, request) {
|
|
|
562
564
|
}
|
|
563
565
|
return result;
|
|
564
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* The definition a flow leaf's session runs. A v2 workflow embeds its leaves, so they
|
|
569
|
+
* come from the workflow's own manifest and can't drift from it; v1 leaves come from the
|
|
570
|
+
* registry by id.
|
|
571
|
+
*/
|
|
572
|
+
async function leafDefinition(t, workflow, body, request) {
|
|
573
|
+
// A flow agent used as a tool: its manifest is inlined in the parent's pinned tool.
|
|
574
|
+
if (request.context.role === "delegate" && !isWorkflowManifest(workflow.manifest)) {
|
|
575
|
+
const flow = flowDelegateManifest(turnManifestOf(workflow), body.agentId);
|
|
576
|
+
if (!flow)
|
|
577
|
+
return fail(404, `Agent '${body.agentId}' is not a flow agent used as a tool`);
|
|
578
|
+
return {
|
|
579
|
+
manifest: flow,
|
|
580
|
+
manifestHash: hashManifest(flow),
|
|
581
|
+
implementationVersion: workflow.implementationVersion,
|
|
582
|
+
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (isWorkflowManifestV2(workflow.manifest)) {
|
|
586
|
+
const leaf = embeddedAgent(workflow.manifest, body.flow ?? [], body.agentId);
|
|
587
|
+
if (!leaf || isWorkflowManifestV2(leaf))
|
|
588
|
+
fail(404, `Agent '${body.agentId}' is not embedded in workflow '${workflow.manifest.id}'`);
|
|
589
|
+
const manifest = leaf;
|
|
590
|
+
return {
|
|
591
|
+
manifest,
|
|
592
|
+
manifestHash: hashManifest(manifest),
|
|
593
|
+
implementationVersion: workflow.implementationVersion,
|
|
594
|
+
pluginRoots: leafPluginRoots(workflow.pluginRoots, body.agentId),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
const definition = await t.get("definitions", body.agentId);
|
|
598
|
+
if (!definition)
|
|
599
|
+
fail(404, "Definition not found");
|
|
600
|
+
return definition;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* A v2 workflow keys its leaves' plugin roots `<agentId>/<capability>`, and an agent keys
|
|
604
|
+
* a flow agent it uses as a tool the same way (`<flowId>/<leafId>/<capability>`).
|
|
605
|
+
*/
|
|
606
|
+
function leafPluginRoots(roots, agentId) {
|
|
607
|
+
const own = {};
|
|
608
|
+
const prefix = `${agentId}/`;
|
|
609
|
+
for (const [key, root] of Object.entries(roots ?? {}))
|
|
610
|
+
if (key.startsWith(prefix))
|
|
611
|
+
own[key.slice(prefix.length)] = root;
|
|
612
|
+
return own;
|
|
613
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const RUNTIME_VERSION = "0.
|
|
1
|
+
export declare const RUNTIME_VERSION = "0.13.0-beta";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0-beta",
|
|
4
4
|
"description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
59
59
|
"@earendil-works/pi-ai": "0.87.0",
|
|
60
60
|
"@hono/node-server": "^2.1.1",
|
|
61
|
-
"@nylorun/harness": "0.
|
|
62
|
-
"@nylorun/core": "0.
|
|
61
|
+
"@nylorun/harness": "0.20.0-beta",
|
|
62
|
+
"@nylorun/core": "0.8.0-beta",
|
|
63
63
|
"@restatedev/restate-sdk": "1.16.9",
|
|
64
64
|
"@s2-dev/streamstore": "0.27.0",
|
|
65
65
|
"just-bash": "3.4.2",
|