@nylorun/agents 0.7.0-beta → 0.9.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 +67 -0
- package/README.md +199 -109
- package/dist/ag-ui/handler.d.ts +42 -0
- package/dist/ag-ui/handler.js +397 -0
- package/dist/ag-ui/history.d.ts +7 -0
- package/dist/ag-ui/history.js +60 -0
- package/dist/ag-ui/index.d.ts +7 -0
- package/dist/ag-ui/index.js +6 -0
- package/dist/ag-ui/node.d.ts +5 -0
- package/dist/ag-ui/node.js +51 -0
- package/dist/ag-ui/sse.d.ts +9 -0
- package/dist/ag-ui/sse.js +7 -0
- package/dist/ag-ui/translate.d.ts +38 -0
- package/dist/ag-ui/translate.js +222 -0
- package/dist/builder.d.ts +20 -0
- package/dist/builder.js +26 -0
- package/dist/client.d.ts +18 -1
- package/dist/client.js +54 -2
- package/dist/executor.js +83 -19
- package/dist/http.d.ts +10 -1
- package/dist/http.js +24 -4
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -2
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +1 -1
- package/dist/sandbox/index.d.ts +2 -2
- package/dist/sandbox/index.js +1 -1
- package/package.json +8 -3
- package/dist/mcp/mcp.d.ts +0 -30
- package/dist/mcp/mcp.js +0 -80
- package/dist/sandbox/sandbox.d.ts +0 -27
- package/dist/sandbox/sandbox.js +0 -92
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e537a82: **One `Agent` builder: named methods and flow agents.** Every capability has its own method, and deterministic workflows are written on the same builder. ReAct agent manifests are unchanged: the new syntax compiles to exactly what the old syntax produced.
|
|
8
|
+
|
|
9
|
+
- ReAct agents: `.instructions()`, `.tools()`, `.subagents()`, `.mcp()` (a server's `name` defaults to its key; calls merge), `.skills()`, `.plugin()`, `.capability()`, `.sandbox()`, `.beforeTurn()`, `.beforeModel()`, `.afterModel()`, `.afterTurn()`, `.output()`. `capability({ id })` returns a builder with the same methods.
|
|
10
|
+
- Flow agents: `.step()`, `.switch({ ...cases, default }, { on })`, `.parallel()`, `.map()` (runs over its input), `.loop(body, { verify, max | decide })`, plus `.input()`, `.output()`, `.sandbox()`, `flow()` for nested sequences and `.withId()`. Functions receive `{ input, results, flowInput }`, typed from each step's output schema. A flow agent compiles to a workflow manifest (v2, see the flow-manifest-v2 changeset).
|
|
11
|
+
- Build diagnostics: `agent.mixed-body`, `flow.no-model`, `agent.single-value`, `flow.empty`, `loop.max-required`, `loop.invalid-max`, `mcp.duplicate-server`, `delegation.flow-unsupported`.
|
|
12
|
+
- `sandbox()` and `mcp()` move to `@nylorun/core/define` (still exported from `@nylorun/agents`).
|
|
13
|
+
- Deprecated, warning once each: options-form `instructions`/`tools`/`outputSchema` (`NYLORUN_DEP_AGENT_OPTIONS`), `.use(capability)` (`NYLORUN_DEP_USE`), `.before()`/`.after()` (`NYLORUN_DEP_HOOKS`), and `capability({ tools, instructions, … })` (`NYLORUN_DEP_CAPABILITY_OPTIONS`). See MIGRATION.md.
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
- **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`.
|
|
18
|
+
- **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.
|
|
19
|
+
- **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.
|
|
20
|
+
- **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`.
|
|
21
|
+
- **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.
|
|
22
|
+
- 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.
|
|
23
|
+
|
|
24
|
+
- 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>`).
|
|
25
|
+
|
|
26
|
+
- **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.
|
|
27
|
+
- **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.
|
|
28
|
+
- **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.
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- Pin core to the tested release.
|
|
33
|
+
- Updated dependencies [e537a82]
|
|
34
|
+
- Updated dependencies [5278b4e]
|
|
35
|
+
- Updated dependencies [426fd27]
|
|
36
|
+
- Updated dependencies [8cda500]
|
|
37
|
+
- @nylorun/core@0.8.0-beta
|
|
38
|
+
|
|
39
|
+
## 0.8.0-beta
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- a322696: **`@nylorun/agents/ag-ui`: serve agents to AG-UI clients from your own server.** `createAgUiHandler({ basePath, agents, subject })` returns a web-standard `fetch` handler (plus `run`, `history`, `reattach` and `cancel`), and `toNodeListener` adapts it to `node:http` and Express.
|
|
44
|
+
|
|
45
|
+
- One session per person, agent and thread; the AG-UI message id is the idempotency key, so a retried run replays the same turn.
|
|
46
|
+
- Approvals become AG-UI interrupts and resume through `runAgent({ resume })`.
|
|
47
|
+
- History returns a plain AG-UI `Message[]` with the ids the live run used; reattach continues a run from `Last-Event-ID`.
|
|
48
|
+
- Needs a Runtime with `transcript-events`. Adds the `@ag-ui/core` dependency (`~1.0.0`), loaded only by this subpath.
|
|
49
|
+
|
|
50
|
+
`AgentsClient.hostFeatures()` returns the Runtime's protocol features, including optional ones.
|
|
51
|
+
|
|
52
|
+
- 844bff3: **Act for a person: `Nylorun-Subject` and `Nylorun-Scopes`.** An app server that holds the Tenant key can name the person each request is for, and the Runtime enforces it (optional Host feature `subject-headers`).
|
|
53
|
+
|
|
54
|
+
- `client.as(subject, { scopes })` in `@nylorun/agents` sends both headers on every call, event streams included. Scopes: `agents:read`, `agents:write`, `sessions:own`, `vaults:own`, `tenant:settings`; default `["sessions:own"]`.
|
|
55
|
+
- The Runtime limits a subject to the routes its scopes allow (`403 scope_required`) and to its own sessions and vaults: another owner's session, vault or sandbox is the same `404` as a missing one, including `PUT` on its session id (was `409`). Reset, config seed, executors, actions and the sandbox tool routes are open to no subject. Only application keys may send the headers.
|
|
56
|
+
- The AG-UI handler calls the Runtime as each person and requires `subject-headers`; new optional `scopes` option. The host's `session()` parameters can no longer replace a session's id, agent or owner.
|
|
57
|
+
- Core exports `SUBJECT_HEADER`, `SCOPES_HEADER`, `SUBJECT_SCOPES` and `parseSubjectHeaders`. Postgres Tenant schemas migrate to version 2 (an indexed session owner column).
|
|
58
|
+
|
|
59
|
+
Requests without `Nylorun-Subject` are unchanged.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- 42272f8: The AG-UI handler answers `400` (`invalid_request`) when the Runtime refuses what the client sent, such as a `Last-Event-ID` it cannot read, instead of `502`.
|
|
64
|
+
- Pin core to the tested release.
|
|
65
|
+
- Updated dependencies [a322696]
|
|
66
|
+
- Updated dependencies [844bff3]
|
|
67
|
+
- Updated dependencies [a322696]
|
|
68
|
+
- @nylorun/core@0.7.0-beta
|
|
69
|
+
|
|
3
70
|
## 0.7.0-beta
|
|
4
71
|
|
|
5
72
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -41,11 +41,9 @@ Sources never mix. Partial environment fails with `connection_missing`.
|
|
|
41
41
|
import { Agent, createClient, connectAgents, tool } from "@nylorun/agents";
|
|
42
42
|
import { z } from "zod";
|
|
43
43
|
|
|
44
|
-
const assistant = Agent({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
instructions: "Use the available tools.",
|
|
48
|
-
tools: [
|
|
44
|
+
const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
45
|
+
.instructions("Use the available tools.")
|
|
46
|
+
.tools(
|
|
49
47
|
tool({
|
|
50
48
|
name: "lookup",
|
|
51
49
|
input: z.object({ id: z.string() }),
|
|
@@ -53,8 +51,7 @@ const assistant = Agent({
|
|
|
53
51
|
return { id, owner: ctx.info };
|
|
54
52
|
},
|
|
55
53
|
}),
|
|
56
|
-
|
|
57
|
-
});
|
|
54
|
+
);
|
|
58
55
|
|
|
59
56
|
// Explicit Tenant API client (options or env / link via createClient()).
|
|
60
57
|
const client = createClient({
|
|
@@ -97,68 +94,57 @@ throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
|
|
|
97
94
|
required features do not match. Re-exports include `PROTOCOL_FEATURES`,
|
|
98
95
|
`ERROR_CODES` and `compareVersions`.
|
|
99
96
|
|
|
100
|
-
Load Agent Skills from a local catalog folder with
|
|
97
|
+
Load Agent Skills from a local catalog folder with `.skills(path)`:
|
|
101
98
|
|
|
102
99
|
```ts
|
|
103
|
-
import { Agent
|
|
104
|
-
|
|
105
|
-
const assistant = Agent({
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
tools: [lookupOrder],
|
|
110
|
-
}).use(skills("./assistant-skills"));
|
|
100
|
+
import { Agent } from "@nylorun/agents";
|
|
101
|
+
|
|
102
|
+
const assistant = Agent({ id: "assistant", name: "Order assistant" })
|
|
103
|
+
.instructions("Use lookup_order for orders.")
|
|
104
|
+
.tools(lookupOrder)
|
|
105
|
+
.skills("./assistant-skills");
|
|
111
106
|
```
|
|
112
107
|
|
|
113
108
|
Each subdirectory under the catalog must contain a `SKILL.md` with YAML frontmatter (`name`, `description`) per [Agent Skills](https://agentskills.io/home). Supporting files (for example `references/`) are available through `read_skill_resource` after `load_skill`. The helper sets both the manifest skill catalog and the on-disk skill records so you do not duplicate content.
|
|
114
109
|
|
|
115
|
-
Declare MCP servers with
|
|
110
|
+
Declare MCP servers with `.mcp(...)` (same map shape as agent-plugins `mcpServers`):
|
|
116
111
|
|
|
117
112
|
```ts
|
|
118
|
-
import { Agent
|
|
119
|
-
|
|
120
|
-
const assistant = Agent({
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
})
|
|
125
|
-
mcp({
|
|
126
|
-
github: {
|
|
127
|
-
name: "github",
|
|
128
|
-
type: "streamable-http",
|
|
129
|
-
url: "https://mcp.example.com/github",
|
|
130
|
-
},
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
113
|
+
import { Agent } from "@nylorun/agents";
|
|
114
|
+
|
|
115
|
+
const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
116
|
+
.instructions("Use the available tools.")
|
|
117
|
+
.mcp({
|
|
118
|
+
github: { type: "streamable-http", url: "https://mcp.example.com/github" },
|
|
119
|
+
});
|
|
133
120
|
```
|
|
134
121
|
|
|
135
|
-
Each key must equal
|
|
122
|
+
Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the same capability. Transports follow [Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers): `stdio`, `streamable-http`, and `sse`. Attach an Agent Plugin package with `.plugin(path)`.
|
|
136
123
|
|
|
137
|
-
Give an agent an isolated computer with
|
|
124
|
+
Give an agent an isolated computer with `.sandbox()`:
|
|
138
125
|
|
|
139
126
|
```ts
|
|
140
|
-
import { Agent
|
|
127
|
+
import { Agent } from "@nylorun/agents";
|
|
141
128
|
|
|
142
|
-
const analyst = Agent({
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}).use(sandbox());
|
|
129
|
+
const analyst = Agent({ id: "analyst" })
|
|
130
|
+
.instructions("Analyse the data the user gives you. Use Python.")
|
|
131
|
+
.sandbox();
|
|
146
132
|
```
|
|
147
133
|
|
|
148
134
|
The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a Linux machine with a persistent `/workspace`. These tools run in the Runtime, not in your process, so sandbox-only agents need no connected executor. The agent declares what it needs; the Runtime decides where it runs (today an emulated shell in the Runtime process). Every option is optional plain data:
|
|
149
135
|
|
|
150
136
|
```ts
|
|
151
|
-
.
|
|
137
|
+
.sandbox({
|
|
152
138
|
image: "python:3.13", // any OCI image; default python:3.13-slim
|
|
153
139
|
network: { preset: "dev", allow: ["api.example.com"] }, // "none" | "dev" (default) | "open"
|
|
154
140
|
resources: { cpus: 2, memory: "2GiB" },
|
|
155
141
|
idle: "15m", // stop compute when idle; files persist
|
|
156
|
-
})
|
|
142
|
+
})
|
|
157
143
|
```
|
|
158
144
|
|
|
159
145
|
The `dev` preset allows package registries and code hosts. Private networks, loopback, the host and cloud metadata endpoints are always blocked. Options from the full design that are not in this version (`setup`, `files`, `secrets`, `mount`, `onStart`, …) throw a `SandboxError` that says so.
|
|
160
146
|
|
|
161
|
-
|
|
147
|
+
Add an agent with `.subagents()` to let the model delegate to it:
|
|
162
148
|
|
|
163
149
|
```ts
|
|
164
150
|
import { Agent } from "@nylorun/agents";
|
|
@@ -168,54 +154,47 @@ const researcher = Agent({
|
|
|
168
154
|
id: "researcher",
|
|
169
155
|
description:
|
|
170
156
|
"Investigates an order's history. Returns a short summary with the ids it relied on.",
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
tools
|
|
174
|
-
|
|
175
|
-
summary: z.string(),
|
|
176
|
-
evidence: z.array(z.string()),
|
|
177
|
-
}),
|
|
178
|
-
});
|
|
157
|
+
})
|
|
158
|
+
.instructions("Investigate one question about one order. Be exhaustive, then be brief.")
|
|
159
|
+
.tools(searchOrders, readTicket)
|
|
160
|
+
.output(z.object({ summary: z.string(), evidence: z.array(z.string()) }));
|
|
179
161
|
|
|
180
|
-
const support = Agent({
|
|
181
|
-
|
|
182
|
-
instructions:
|
|
162
|
+
const support = Agent({ id: "support" })
|
|
163
|
+
.instructions(
|
|
183
164
|
"For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
|
|
184
|
-
|
|
185
|
-
|
|
165
|
+
)
|
|
166
|
+
.tools(lookupOrder, refundOrder)
|
|
167
|
+
.subagents(researcher);
|
|
186
168
|
```
|
|
187
169
|
|
|
188
170
|
The tool is named after the agent's `id` and takes `{ task: string }`; the agent's `description` (required) is what the parent's model reads to decide when to delegate. The child starts with a fresh context: it sees its own instructions and the task, nothing of the parent's conversation, and only its final text (or `outputSchema` result) comes back. It keeps its own tools, hooks, skills and MCP servers, served by the executor you already run for the parent (`connectAgents({ agents: [support] })` serves both), shares the session's sandbox, and starts with empty `ctx.state`. Tools can read `ctx.agent` (`{ id, path, delegationId }`). Several delegation calls in one model response run in parallel. An empty answer, a failure (with the child's last text marked as evidence) or a cancelled child reaches the parent's model as a failed tool result, never as success. The Runtime emits `delegation.started` and `delegation.completed`; `session.history({ agent })` filters by `delegationId` (one child invocation) or by path such as `support/researcher` (every concurrent child that shares that path).
|
|
189
171
|
|
|
190
|
-
|
|
172
|
+
A flow agent can be a subagent too: `.subagents(researchFlow)` with a `description` like any other. Its manifest is inlined in the parent's, so it is saved with the parent and served by the parent's executor. When the model calls it, the flow runs on the Runtime in its own linked session (a fresh one per call, linked from the parent's with a `node.agent` event at `support/<flow id>`), its agents in theirs, and the flow's output comes back as the tool result; a failed flow is a failed tool result. Cancelling the parent cancels the flow. Flow agents as subagents run on the Runtime only, not in a local `run()`. An approval asked for inside the flow waits on the flow's own session.
|
|
173
|
+
|
|
174
|
+
Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, switch capabilities with a `.beforeModel()` patch instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, its tools cannot declare `approval` (keep those on the parent), and `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor` inside it fail with `delegation.interaction-unsupported`. Delegation is not an approval boundary; approvals live on tools.
|
|
191
175
|
|
|
192
|
-
##
|
|
176
|
+
## Flow agents
|
|
193
177
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
`
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
routed by `(workflowId, key)`.
|
|
178
|
+
An agent's body is either a ReAct loop (the model decides) or a flow (your code
|
|
179
|
+
decides). A flow agent is built from stages, each shaped `.stage(whatRuns, { how })`,
|
|
180
|
+
and is registered, saved and opened as a session like any agent: put it in
|
|
181
|
+
`export const agents`, `saveAgent(flowAgent)`, then `createSession({ agentId })`
|
|
182
|
+
and `session.input(value)`. `input` sends string values as `content` and other JSON
|
|
183
|
+
as `data`. On the wire a flow agent is a workflow manifest v2 (`kind: "workflow"`,
|
|
184
|
+
`workflowSchemaVersion: 2`) that embeds the agents it runs, so one document and one
|
|
185
|
+
manifest hash cover the whole flow; its agents are not listed on their own.
|
|
203
186
|
|
|
204
187
|
```ts
|
|
205
|
-
import { Agent,
|
|
188
|
+
import { Agent, tool } from "@nylorun/agents";
|
|
206
189
|
import { z } from "zod";
|
|
207
190
|
|
|
208
|
-
const planner = Agent({
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
outputSchema: z.object({ tasks: z.array(z.string()) }),
|
|
212
|
-
}).build();
|
|
191
|
+
const planner = Agent({ id: "planner" })
|
|
192
|
+
.instructions("Return { tasks: string[] }.")
|
|
193
|
+
.output(z.object({ tasks: z.array(z.string()) }));
|
|
213
194
|
|
|
214
|
-
const coder = Agent({
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
outputSchema: z.object({ summary: z.string() }),
|
|
218
|
-
}).build();
|
|
195
|
+
const coder = Agent({ id: "coder" })
|
|
196
|
+
.instructions("Implement one task. Return { summary }.")
|
|
197
|
+
.output(z.object({ summary: z.string() }));
|
|
219
198
|
|
|
220
199
|
const openPr = tool({
|
|
221
200
|
name: "open-pr",
|
|
@@ -226,43 +205,51 @@ const openPr = tool({
|
|
|
226
205
|
},
|
|
227
206
|
});
|
|
228
207
|
|
|
229
|
-
export const shipFeature =
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
over: (plan) => plan.tasks,
|
|
236
|
-
each: Loop({
|
|
237
|
-
id: "code",
|
|
238
|
-
run: coder,
|
|
239
|
-
verify: () => ({ pass: true }),
|
|
240
|
-
decide: ({ output }) => ({ output }),
|
|
241
|
-
}),
|
|
208
|
+
export const shipFeature = Agent({ id: "ship-feature" })
|
|
209
|
+
.step(planner)
|
|
210
|
+
.map(
|
|
211
|
+
Agent({ id: "code" }).loop(coder, {
|
|
212
|
+
verify: ({ output }) => (output.summary ? { pass: true } : { pass: false, feedback: "Say what you changed." }),
|
|
213
|
+
max: 2,
|
|
242
214
|
}),
|
|
243
|
-
{
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
summaries: value.map((item: { summary: string }) => item.summary),
|
|
247
|
-
}),
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
});
|
|
215
|
+
{ id: "implement", input: ({ input }) => input.tasks },
|
|
216
|
+
)
|
|
217
|
+
.step(openPr, { input: ({ input }) => ({ summaries: input.map((item) => item.summary) }) });
|
|
251
218
|
|
|
252
219
|
export const agents = [shipFeature];
|
|
253
220
|
```
|
|
254
221
|
|
|
255
|
-
|
|
|
222
|
+
| Stage | Role |
|
|
256
223
|
| --- | --- |
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
224
|
+
| **`.step(x, { id?, input? })`** | Run one agent, tool or `flow()`; its output is the next stage's input |
|
|
225
|
+
| **`.switch({ ...cases, default? }, { on })`** | `on({ input })` returns a case name; exactly that case runs |
|
|
226
|
+
| **`.parallel(branches)`** | Fixed named branches at once, same input; output is an object |
|
|
227
|
+
| **`.map(each)`** | Run `each` once per item of the input, which must be an array; output is an array |
|
|
228
|
+
| **`.loop(body, { verify, max?, decide? })`** | Run, verify, run again with the feedback until it passes; needs `max` or `decide` |
|
|
229
|
+
|
|
230
|
+
Every function receives one object: `{ input, results, flowInput }`. `input` is
|
|
231
|
+
what the stage received, `results` holds earlier steps' outputs by step id, and
|
|
232
|
+
`flowInput` is the agent's own input. The `input` option computes a stage's input;
|
|
233
|
+
the `id` option names a step. `flow()` builds a sequence with no id for a case,
|
|
234
|
+
branch, map item or loop body that is more than one step. Types flow from each
|
|
235
|
+
step's `.output()` schema to the next stage's `input`.
|
|
236
|
+
|
|
237
|
+
Agents inside a flow keep their own sessions, linked from the flow's session and
|
|
238
|
+
named by the agent: a step's id (the agent's id, or `{ id }`), with `[i]` for each
|
|
239
|
+
Map item and a nested flow agent's id in front of its own agents'. Control stages
|
|
240
|
+
add nothing, so wrapping a step in `.loop()` or moving it between cases keeps its
|
|
241
|
+
session. An agent may appear once per flow; use it again under a new id with
|
|
242
|
+
`.step(writer, { id: "final-writer" })` or `writer.withId("…")`
|
|
243
|
+
(`flow.duplicate-leaf`). Functions are bound under stage keys: a stage's `id`, or
|
|
244
|
+
its position such as `@1.default.1`, plus `:input`, `:on`, `:verify` or `:decide`.
|
|
245
|
+
Name the stages you may reorder.
|
|
246
|
+
|
|
247
|
+
`Chain`, `Switch`, `Parallel`, `Map` and `Loop` still build workflow manifest v1
|
|
248
|
+
directly and run as before; a flow agent can't be a child of them.
|
|
249
|
+
|
|
250
|
+
The agents in a flow share one sandbox: declare it with `.sandbox(spec)` on the
|
|
251
|
+
flow agent and give the agents that use it `.sandbox()` with no options (an agent
|
|
252
|
+
that declares its own spec must match), attach with `createSession({ …, sandbox: { session } })`, call
|
|
266
253
|
built-ins via `session.sandbox` (application) or `ctx.sandbox` (executor).
|
|
267
254
|
Observe with `session.observe({ follow: true })` to merge linked agent streams;
|
|
268
255
|
`pending()` lists waits across the tree. Studio renders the manifest tree and
|
|
@@ -286,4 +273,107 @@ After registration, `connectAgents` opens authenticated fetch SSE before discove
|
|
|
286
273
|
|
|
287
274
|
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use `before`/`after` hooks. The executor runs every capability registered at a hook point in one action. Agent definitions have no `.run()`; explicit local execution is available through `@nylorun/harness/run`.
|
|
288
275
|
|
|
289
|
-
|
|
276
|
+
## AG-UI
|
|
277
|
+
|
|
278
|
+
`@nylorun/agents/ag-ui` serves the Tenant's agents to any
|
|
279
|
+
[AG-UI](https://docs.ag-ui.com) client (`@ag-ui/client`'s `HttpAgent`,
|
|
280
|
+
CopilotKit, a desktop renderer) from your own server. Your server signs people
|
|
281
|
+
in and names the person each request is for; the handler maps each AG-UI thread
|
|
282
|
+
to one session per person, agent and thread, and streams it as AG-UI events.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { createServer } from "node:http";
|
|
286
|
+
import { createAgUiHandler, toNodeListener } from "@nylorun/agents/ag-ui";
|
|
287
|
+
import support from "./agents/support.js";
|
|
288
|
+
|
|
289
|
+
const agui = createAgUiHandler({
|
|
290
|
+
basePath: "/api/agui",
|
|
291
|
+
agents: [support], // nothing else is reachable
|
|
292
|
+
subject: async (request) => (await getSignedInUser(request))?.id, // undefined → 401
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
createServer(toNodeListener(agui)).listen(3000);
|
|
296
|
+
// Next.js, Hono, Bun, Deno, Workers: export or mount `agui.fetch` directly.
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| Method and path under `basePath` | Operation |
|
|
300
|
+
| --- | --- |
|
|
301
|
+
| `POST /{agentId}` | Run: an AG-UI `RunAgentInput` in, server-sent events out |
|
|
302
|
+
| `GET /{agentId}/threads/{threadId}/messages` | History as a plain AG-UI `Message[]`, usable as `HttpAgent`'s `initialMessages` |
|
|
303
|
+
| `GET /{agentId}/threads/{threadId}/events` | Reattach: the rest of a run after a dropped connection, from `Last-Event-ID` (or `?cursor=`); `204` when nothing is left |
|
|
304
|
+
| `POST /{agentId}/threads/{threadId}/cancel` | Cancel the running turn |
|
|
305
|
+
|
|
306
|
+
`run`, `history`, `reattach` and `cancel` are also on the handler for your own
|
|
307
|
+
routing. The default `client` is `createClient()`; pass `client` to use another
|
|
308
|
+
one, and `session(subject, agentId)` to add the person's `vaultIds` or
|
|
309
|
+
`credentialSelections` (keep `info` stable: it is part of the session's
|
|
310
|
+
identity).
|
|
311
|
+
|
|
312
|
+
- Each AG-UI message id is the command's idempotency key: a retried run replays
|
|
313
|
+
the same turn instead of starting a second one.
|
|
314
|
+
- An approval ends the run with an AG-UI interrupt (`reason: "tool_approval"`,
|
|
315
|
+
`toolCallId` the model's call id). Resume with
|
|
316
|
+
`runAgent({ resume: [{ interruptId, status: "resolved", payload: { approved: true } }] })`.
|
|
317
|
+
Other interactions answer with `payload` as the response.
|
|
318
|
+
- Every event that ends a group carries the Runtime cursor as its SSE `id`.
|
|
319
|
+
`HttpAgent` does not reconnect by itself; call the reattach route with the
|
|
320
|
+
last id you received.
|
|
321
|
+
- Closing the connection stops reading; it never cancels the turn.
|
|
322
|
+
- A busy or paused thread ends the run with `RUN_ERROR` code `session_busy`.
|
|
323
|
+
- The handler calls the Runtime as each person (`client.as(subject, { scopes })`,
|
|
324
|
+
below), so the Runtime itself keeps one person out of another's threads.
|
|
325
|
+
`scopes` defaults to `["sessions:own"]`; add `"vaults:own"` when `session()`
|
|
326
|
+
attaches the person's vaults. A `subject` the Runtime cannot name (see below)
|
|
327
|
+
answers `500` (`subject_invalid`).
|
|
328
|
+
- The handler needs a Runtime with the optional features `transcript-events`
|
|
329
|
+
and `subject-headers` and answers `502` (`runtime_feature_missing`) without
|
|
330
|
+
them.
|
|
331
|
+
|
|
332
|
+
A complete web backend with a test is in
|
|
333
|
+
[examples/src/ag-ui](../examples/README.md#an-agent-in-your-web-app-ag-ui).
|
|
334
|
+
|
|
335
|
+
Limitations: assistant text arrives once per model step (no token streaming);
|
|
336
|
+
no reasoning, state, activity or subagent events, and an agent used as a tool
|
|
337
|
+
shows only its result; frontend tools in `RunAgentInput.tools` are rejected
|
|
338
|
+
(`400`); one text part per user message; earlier messages cannot be edited or
|
|
339
|
+
regenerated. Browsers always go through your server: never let one call the
|
|
340
|
+
Runtime directly.
|
|
341
|
+
|
|
342
|
+
## Acting for a person (app servers)
|
|
343
|
+
|
|
344
|
+
A server that signs people in and calls the Runtime for them (an "app server")
|
|
345
|
+
keeps the Tenant key to itself and names the person on each call:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { createClient } from "@nylorun/agents";
|
|
349
|
+
|
|
350
|
+
const app = createClient(); // the Tenant key, on the server only
|
|
351
|
+
|
|
352
|
+
// Per request, after your own sign-in:
|
|
353
|
+
const person = app.as(`app:${user.id}`, { scopes: ["sessions:own", "vaults:own"] });
|
|
354
|
+
await person.createSession({ agentId: "support", ownerUserId: `app:${user.id}` });
|
|
355
|
+
await person.listSessions(); // only this person's sessions
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`as()` sends `Nylorun-Subject` and `Nylorun-Scopes` on every call, event
|
|
359
|
+
streams included, and the Runtime (optional feature `subject-headers`)
|
|
360
|
+
enforces both: another person's sessions and vaults answer the same `404` as
|
|
361
|
+
missing ones, and a route outside the scopes answers `403` (`scope_required`).
|
|
362
|
+
Nothing is minted, cached or refreshed, and the copy shares the client's
|
|
363
|
+
compatibility check, so calling `as()` per request is cheap.
|
|
364
|
+
|
|
365
|
+
| Scope | Allows |
|
|
366
|
+
| --- | --- |
|
|
367
|
+
| `sessions:own` | The person's own sessions: create, list, read, stream, message, approve, respond, cancel |
|
|
368
|
+
| `vaults:own` | The person's own vaults and credentials |
|
|
369
|
+
| `agents:read` | Listing the Tenant's agents |
|
|
370
|
+
| `agents:write` | Saving agents; listing agents, models and providers |
|
|
371
|
+
| `tenant:settings` | The Tenant's status, model provider and sandbox settings |
|
|
372
|
+
|
|
373
|
+
No scope reaches Tenant reset, config seed, executors, actions or the sandbox
|
|
374
|
+
tool routes; call those without `as()`. A subject is 1–200 visible ASCII
|
|
375
|
+
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
376
|
+
any `Nylorun-*` header its own clients send, and only an application key can act
|
|
377
|
+
for a subject: an executor key that tries is `403`.
|
|
378
|
+
|
|
379
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness. `/ag-ui` adds `@ag-ui/core`; nothing else imports it. Use `/define`, `/client`, `/executor` or `/ag-ui` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../harness/HOST_CONTRACT.md).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BuiltWorkflow } from "@nylorun/core/define";
|
|
2
|
+
import { type SubjectScope } from "@nylorun/core/contracts";
|
|
3
|
+
import { type AgentSource, type AgentsClient } from "../client.js";
|
|
4
|
+
/** Per-session parameters the host may add, such as the person's vault for MCP servers. */
|
|
5
|
+
export interface AgUiSessionOptions {
|
|
6
|
+
/** Part of the session's identity: keep it stable for a subject and agent, or runs get 409. */
|
|
7
|
+
info?: Record<string, unknown>;
|
|
8
|
+
vaultIds?: readonly string[];
|
|
9
|
+
credentialSelections?: Parameters<AgentsClient["createSession"]>[0]["credentialSelections"];
|
|
10
|
+
}
|
|
11
|
+
export interface AgUiHandlerOptions {
|
|
12
|
+
/** Agents this endpoint may run: built agents, workflows or their ids. Nothing else is reachable. */
|
|
13
|
+
agents: readonly (AgentSource | BuiltWorkflow | string)[];
|
|
14
|
+
/** The signed-in person the request is for. `undefined` answers 401; there is no anonymous mode. */
|
|
15
|
+
subject(request: Request): string | undefined | Promise<string | undefined>;
|
|
16
|
+
/** Where `fetch` is mounted, e.g. `/api/agui`. Default `/`. */
|
|
17
|
+
basePath?: string;
|
|
18
|
+
/** Application client. Default: `createClient()`, from the environment or the Project link. */
|
|
19
|
+
client?: AgentsClient | Promise<AgentsClient>;
|
|
20
|
+
/**
|
|
21
|
+
* What each subject may do through this endpoint. Default `["sessions:own"]`; add
|
|
22
|
+
* `vaults:own` if `session()` attaches the person's vaults.
|
|
23
|
+
*/
|
|
24
|
+
scopes?: readonly SubjectScope[];
|
|
25
|
+
/** Optional per-session parameters, e.g. the person's vault for connected accounts. */
|
|
26
|
+
session?(subject: string, agentId: string): AgUiSessionOptions | Promise<AgUiSessionOptions>;
|
|
27
|
+
}
|
|
28
|
+
export interface AgUiHandler {
|
|
29
|
+
/** One entry point that routes by method and path under `basePath`. Bound. */
|
|
30
|
+
readonly fetch: (request: Request) => Promise<Response>;
|
|
31
|
+
/** `POST`: an AG-UI `RunAgentInput` in, the run's events out as server-sent events. */
|
|
32
|
+
run(request: Request, agentId: string): Promise<Response>;
|
|
33
|
+
/** `GET`: the thread's messages as a plain AG-UI `Message[]`. */
|
|
34
|
+
history(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
35
|
+
/** `GET`: the rest of a run after a dropped connection, from `Last-Event-ID` or `?cursor=`. */
|
|
36
|
+
reattach(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
37
|
+
/** `POST`: cancels the thread's running turn. */
|
|
38
|
+
cancel(request: Request, agentId: string, threadId: string): Promise<Response>;
|
|
39
|
+
}
|
|
40
|
+
/** A session per subject, agent and thread; one subject can never name another's session. */
|
|
41
|
+
export declare function sessionIdFor(subject: string, agentId: string, threadId: string): string;
|
|
42
|
+
export declare function createAgUiHandler(options: AgUiHandlerOptions): AgUiHandler;
|