@nylorun/agents 0.19.0-beta → 0.20.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 +84 -0
- package/README.md +165 -96
- package/dist/ag-ui/handler.d.ts +4 -4
- package/dist/artifacts.d.ts +14 -1
- package/dist/artifacts.js +28 -0
- package/dist/environments.d.ts +217 -0
- package/dist/environments.js +174 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/reads.d.ts +1 -1
- package/dist/session-client.d.ts +101 -18
- package/dist/session-client.js +36 -16
- package/package.json +4 -4
- package/dist/sandboxes.d.ts +0 -106
- package/dist/sandboxes.js +0 -109
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.20.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5165451: **A pod's disk can start prepared, and every Environment's disk has versions (R2d steps 2 and 3).** Before, a new pod volume was empty and a disk could only be kept or reset. Now:
|
|
8
|
+
|
|
9
|
+
- **Setup and input files** (pods, Host feature `environment-setup`): `PUT /v1/environments/{id}` takes `files` (versions of Tenant-wide artifacts, copied in: a file at its `path`, a folder under it) and `setup` (commands run in order with `/bin/sh -c`, `cwd` default `/workspace`, `timeout` default 10m and at most 30m, at most 16 of each). They run once per new disk, after the pod's engine joins and before its first turn, with the Environment's variables, secrets (as the sentinel) and egress. A failed command fails the pod (`observed: failed`, reason `setup: command N …`, an `environment.failed` event with the exit code and the end of the output) and the turn waiting for it; the next turn start runs the setup again. A virtual Environment refuses both with a `400`.
|
|
10
|
+
- **Disk versions** (Host feature `environment-disk-versions`): `POST /v1/environments/{id}/snapshot` keeps a version of the disk between turns (`/workspace`, and a pod's `/harness` but the engine's own files) as a version of a folder artifact the Environment owns, with modes, modification times, links and empty directories (folder manifest `nylorun.folder.v2`); each file's bytes are stored once, and an unchanged disk makes no version. `POST .../restore` puts a version back: a virtual Environment at once, a pod on a new volume seeded before its next turn (`disk.operation` in the view meanwhile; turns wait). `PUT` with `from: { environment, version }` creates an Environment from another's version. `GET .../versions` and `DELETE .../versions/{n}` list and delete them; their trees, files, diffs and zips read through `/v1/artifacts`. A lost pod volume with a version is recovered from the latest one instead of refusing turns until a reset. Snapshot and restore during a turn are `409 environment_busy`. `EnvironmentView.disk` gives the latest version and the copy in progress. Migration `0021_disk_versions`.
|
|
11
|
+
- `@nylorun/agents`: `client.environments.snapshot`, `restore`, `versions` and `deleteVersion`; `put` takes `setup`, `files` and `from`.
|
|
12
|
+
|
|
13
|
+
- 35e29b7: **Environments replace sandboxes and vaults (protocol 11). This is a fresh start: upgrading deletes your sandboxes, their disks and every vault except the model's, and closes every open session (its history stays readable).** List what you have before upgrading; see "Environments replace sandboxes and vaults" in MIGRATION.md.
|
|
14
|
+
|
|
15
|
+
An Environment is where sessions run: one sandbox (kind `virtual`, `pod` or `none`) with a recipe that can change, and one vault of secrets for its shell, MCP servers and HTTP tools. The app decides who uses which Environment; the Runtime keeps it by id and holds its lifecycle.
|
|
16
|
+
|
|
17
|
+
- `@nylorun/core`: protocol 11 (the Host accepts 11 only; older clients get `426`), required feature `environments`. `PutEnvironmentRequestSchema`, `EnvironmentViewSchema` (`recipe`, `version`, `appliedVersion`, `ownerUserId`, `lastUsedAt`), `PutEnvironmentSecretRequestSchema`, `EnvironmentSecretViewSchema`, `ENVIRONMENT_EVENT_TYPES`, `TenantEnvironmentsConfigSchema`, `environmentGranted`. `PutSessionRequest.environment` replaces `sandbox`, `vaultIds` and `credentialSelections`; `SessionView.environment` replaces the session's sandbox and vault fields. Error codes `environment_busy`, `environment_unavailable`, `environment_lost`, `environment_not_granted`, `environment_owner_mismatch` and `session_closed` (the `sandbox_*` codes and `sandbox_expired` are gone); session events `environment.state`, `environment.exec` and `environment.attached`; scope `environments:write`. Credential coverage takes `environmentId` and answers `covered` or `missing`; the MCP preview takes `environmentId`. The vault Management schemas are removed.
|
|
18
|
+
- `@nylorun/runtime`: `/v1/environments/{id}` replaces `/v1/sandboxes/{id}`: `PUT` creates, finds or changes one (`kind` and `storage` fixed; a recipe change bumps `version`; a pod gets a new image or resources at its next start from stopped, a new allowlist within seconds), with list by label, events (`nylorun.environment-event/1`), stop, reset and delete. Each Environment owns one vault: `PUT|GET|DELETE /v1/environments/{id}/secrets/{name}` on the Runtime API, write-only, with a second credential for a host or URL refused at the write (`409 credential_conflict`). An optional owner guard (`ownerUserId`) is checked when a session opens and at every turn start. `lifecycle.deleteAfter` deletes an Environment unused that long; TTL and `onExpiry` are gone. Kind `none` holds secrets for MCP-only agents. `/v1/tenant/vaults`, user and installation vaults, the Tenant's default sandbox, inline sandboxes and `{ session }` sharing are removed. The Tenant setting is `environments.config` (`limits.environments`). Identity files grant `environments:` (an old `sandboxes:` key, or `sandboxes:write`, stops the Runtime with a message naming the new key). Migration `0020_environments`.
|
|
19
|
+
- `@nylorun/harness`: the Harness API claims `environment.state` and `environment.exec`, and its workspace fields name `environmentId`.
|
|
20
|
+
- `@nylorun/agents`: `client.environments` (`put`, `get`, `list`, `page`, `stop`, `reset`, `delete`, `events`, `secrets.put|list|get|delete`, `forSession`) replaces `client.sandboxes`; `createSession({ environment })`; `sessions.page({ environmentId })` filters by Environment. The AG-UI handler's session options take `environment`.
|
|
21
|
+
- `@nylorun/admin`: `admin.coverage({ agentId, environmentId })` replaces `admin.vaults`; `admin.mcp.preview({ url, environmentId })`.
|
|
22
|
+
- `@nylorun/studio`: Environments replace Sandboxes, with a create form, stop, reset, delete and their secrets; the Credentials forms write an Environment's secrets; New session picks an Environment and shows its coverage.
|
|
23
|
+
- `nylorun`: `nylorun sandbox ls|rm` list and delete Environments; `nylorun mcp inspect --environment <id>` replaces `--vault`; `nylo reset --environments` replaces `--sandboxes`; `nylo doctor sandbox` reports the Environment limits.
|
|
24
|
+
|
|
25
|
+
- 3013501: **Tool calls are delivered at least once, and every one is on the record (R5 step 1).** A gateway restart used to park the sessions it interrupted as `uncertain` until someone cancelled them, a harness could call tools with no record, and nothing listed a session's tool calls. Now every tool call ends in the log with its outcome, a crash sends it again with the same key, and the gateway runs only calls the effect journal recorded.
|
|
26
|
+
|
|
27
|
+
- **Every outcome in the log.** Each tool call ends in one `tool.completed` with `status` (`completed`: the tool answered; `failed`: the Runtime ended it, `tool.disabled`, `tool.unavailable` or `tool.interrupted`; `cancelled`), `kind` (`mcp`, `http`, `sandbox`, `platform`), `server`, `attempts` and `durationMs`. Refused and cancelled calls are in the log for the first time.
|
|
28
|
+
- **`GET /v1/sessions/{id}/calls/tool`** pages a session's tool calls from the effect journal, in the order they were first sent: tool, input, status, sends, error and timing. Same credentials and paging as `/calls/model`; `@nylorun/agents` adds `session.toolCalls()`. Host feature `tool-calls`.
|
|
29
|
+
- **At least once, with the same key.** An MCP or HTTP tool call that a crash, a shutdown or a lost gateway hop interrupted is sent again with its effect id as the key, up to 3 sends; then it fails to the model as `tool.interrupted` ("may have run") and the session continues. A gateway that restarts mid call runs the call again under its key instead of answering `uncertain`. HTTP tools get the key as `Idempotency-Key`, as before; MCP servers now get it in `_meta["nylorun/idempotency-key"]` on `tools/call`. A tool that honors the key acts once; `attempts` above 1 marks where one may have acted twice.
|
|
30
|
+
- **Sandbox commands are never run twice.** A sandbox or core-served call a crash interrupted has no key to dedupe on, so it fails to the model as `tool.interrupted` at once, and the model decides whether to run it again.
|
|
31
|
+
- **No tool call parks a session.** The `uncertain` state for tool calls, its cancel-only exit and R2b's read-only/idempotent `mcp.lost` exception are gone: a lost MCP answer is `mcp.lost` to the model for every tool. Model calls keep their own rules.
|
|
32
|
+
- **The gateway runs only journaled calls.** Under a run token, `/nylorun/v1/tool-calls` and `/nylorun/v1/http-calls` need the call's effect id as `Idempotency-Key` (`400 key_required`), and the effect journal must hold it running, for the run's turn, the same tool and the same input (`409 effect_not_journaled`). A harness that skips the journal gets nothing run.
|
|
33
|
+
- `@nylorun/harness`: `HarnessExecutors.resends(effect, error, run)` says a gate hop lost a call's answer; the harness then asks core again with the same intent and sends the call again.
|
|
34
|
+
- **Approval** stays in the manifest and our harness still asks for it. Only the Runtime's own harness asks: one built from the Harness API can skip it until tool policy moves to the gateway (R5 step 2).
|
|
35
|
+
|
|
36
|
+
- 6061a3a: **Tool approvals are the Tenant's tool policy (R5 step 2).** An agent's manifest used to say which tool calls wait for a person, and only the Runtime's own harness asked; a harness built from the Harness API could skip it. Now the operator sets one tool policy for every agent and every harness, and the Runtime enforces it before anything is sent.
|
|
37
|
+
|
|
38
|
+
- **`GET`/`PUT /v1/tenant/tool-policy`** (the Management API; `admin.toolPolicy.get()`/`.put({ rules })`): rules `{ effect: "allow" | "ask" | "deny", tool?, server?, kind?, agent?, when?, reason? }`. A rule matches a call when every selector it sets matches (`tool` and `server` take `*` globs; `when` tests input fields by dotted path with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule decides; a call no rule matches runs.
|
|
39
|
+
- **Enforced for every harness.** Core decides an MCP or HTTP tool call as it journals it. `deny` fails it to the model as `tool.denied`, unsent. `ask` holds the call (`held` at `/calls/tool`) and pauses the turn with one `approval` interaction per held call (`turn.paused`, with the tool, its input and the reason). `approve` sends that exact call, or fails it as `tool.denied`. The Tool Gate runs only calls the journal holds running, so a held or refused call cannot run. Nothing holds a process while it waits: an approval survives a restart. A flow's HTTP stages are decided the same way.
|
|
40
|
+
- **`approval` leaves the manifest.** `PUT /v1/agents/:id` and `saveAgent()` refuse a definition that sets `approval` on an HTTP tool, an MCP server or an MCP tool's settings; `http({ approval })` and `.mcp({ … approval })` throw; a turn manifest can no longer add one. The engine never asks for an HTTP or MCP tool's approval itself. A session pinned to a manifest saved with `approval: "always"` keeps it, as an ask.
|
|
41
|
+
- `@nylorun/studio`: a paused session shows an approval card for each held call, with its tool, why it is held and its input, and **Approve** / **Deny**.
|
|
42
|
+
- Migration: move each `approval: "always"` to an `ask` rule (see MIGRATION.md).
|
|
43
|
+
|
|
44
|
+
- 7800358: **Each turn is one OpenTelemetry trace, projected from the session log (R7).** An operator could not see what a turn did or where its time went without grepping container logs. Now every turn is a trace with a span for each model call, tool call and egress tunnel, exported over OTLP to any collector, readable per turn from the Runtime, and joined to the trace of the app that started it. No protocol change.
|
|
45
|
+
|
|
46
|
+
- **Traces from the log.** A turn's end is its root span, `invoke_agent <agent>`; each model call is a `chat <model>` span, each tool call and flow HTTP stage an `execute_tool <tool>` span, each egress-gate tunnel a `CONNECT <host>` span, and each pause and answer a marker. A model or gate tool span is the gate's own start and end; usage and cost are the gate's (`nylorun.usage.mismatch` when the harness reported other numbers). Attribute names follow the OTel GenAI conventions at one pinned commit, recorded as `nylorun.semconv.genai` on the resource. Ids are derived from the log under a key the Tenant keeps (setting `trace.key`), so every export and read of a turn gives the same ids.
|
|
47
|
+
- **OTLP export, off until you set an endpoint.** With `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) the Runtime posts OTLP/HTTP JSON from a replication slot of its own, `nylorun_trace_export`, in batches of up to 512 spans or every 2 s; `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` work as usual. No turn waits for it. A collector that is down costs only its spans: after three retries a batch is dropped and logged as `trace_export_dropped`, and the slot moves on, so Postgres keeps no WAL for it. `nylorun start` passes these variables to the runtime container.
|
|
48
|
+
- **References by default.** Spans point at log events (`nylorun.event.cursor`) and carry no content unless you opt in per kind with `NYLORUN_TRACE_CONTENT` (`user`, `outputs`, `tool-results`, `errors`), or all of them with `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`, each cut at 8 KiB. A span never carries a credential value, a prompt, a person's id, an identity header or an idempotency key.
|
|
49
|
+
- **`GET /v1/sessions/{id}/turns/{turnId}/trace`** answers a turn's spans as OTLP JSON, the same spans the exporter posts: look at a turn without a collector, or post it to one to backfill. Application keys, as the `/calls` reads. Host feature `traces`; `@nylorun/agents` adds `session.trace(turnId)`, and `@nylorun/core/contracts` `TurnTraceSchema`.
|
|
50
|
+
- **Trace context in and out.** A `traceparent` (and `tracestate`) on `POST /v1/sessions/{id}/commands`, an AG-UI run or an A2A message puts the turn in the caller's trace; `command.message` records it as `trace`. The turn hands its context on: to the harness in `TurnStart.trace`, to HTTP tools as `traceparent` beside `Idempotency-Key`, to MCP servers as `traceparent` on the call's requests, and to sandbox commands as `TRACEPARENT`.
|
|
51
|
+
- **Harness API** (additive): the `effect.intent` answer `execute` may carry the tool call's `traceparent`. `@nylorun/harness`: `HarnessExecutors.tool` takes it as a fourth argument (`ToolCallContext`).
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- b6214d9: Add opt-in artifact metadata pagination to `GET /v1/artifacts?limit=…`, shared
|
|
56
|
+
`ArtifactListItem`/`ArtifactPage` contracts and SDK `artifacts.page()` (default 50,
|
|
57
|
+
maximum 200). Hosts advertise `artifact-reads`. Pages order by creation time and
|
|
58
|
+
ID descending, filter by session, kind and labels, bind cursors to the Tenant and
|
|
59
|
+
filters, and reapply ownership and agent grants on each read. Legacy unpaged
|
|
60
|
+
responses remain unchanged. A Drizzle migration adds the creation-order index;
|
|
61
|
+
pages reuse the existing bounded read-only pool. Studio and every other Runtime
|
|
62
|
+
client can consume this public API without database access.
|
|
63
|
+
- a0cbca1: **Clearer wording when a definition runs your code.** The refusal for a code tool or a flow's tool stage, the `hooks were removed` error and `nylo endpoints` now say that the Runtime runs agents from their manifests alone, instead of that it runs no code of yours during a session. Only the message text changed; anything matching the old wording (for example `runs no code of yours during a session`) needs the new text.
|
|
64
|
+
- Pin core to the tested release.
|
|
65
|
+
- Updated dependencies [17a97e9]
|
|
66
|
+
- Updated dependencies [b6214d9]
|
|
67
|
+
- Updated dependencies [e917bd6]
|
|
68
|
+
- Updated dependencies [5165451]
|
|
69
|
+
- Updated dependencies [35e29b7]
|
|
70
|
+
- Updated dependencies [e917bd6]
|
|
71
|
+
- Updated dependencies [a0cbca1]
|
|
72
|
+
- Updated dependencies [3013501]
|
|
73
|
+
- Updated dependencies [6061a3a]
|
|
74
|
+
- Updated dependencies [7d496e4]
|
|
75
|
+
- Updated dependencies [81c783e]
|
|
76
|
+
- Updated dependencies [7800358]
|
|
77
|
+
- @nylorun/core@0.20.0-beta
|
|
78
|
+
|
|
79
|
+
## 0.19.1-beta
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- Pin core to the tested release.
|
|
84
|
+
- Updated dependencies [212364b]
|
|
85
|
+
- @nylorun/core@0.19.0-beta
|
|
86
|
+
|
|
3
87
|
## 0.19.0-beta
|
|
4
88
|
|
|
5
89
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
3
|
Runtime API client package: definition authoring and a session client. The Tenant's
|
|
4
|
-
settings, models,
|
|
4
|
+
settings, models, credential coverage, signing keys and application keys are the Management
|
|
5
5
|
API's, through [`@nylorun/admin`](../admin/README.md). Depends only on `@nylorun/core` among Nylorun
|
|
6
6
|
packages. A developer application's production tree should contain only this
|
|
7
7
|
package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
8
|
-
[runtime/src/CONTEXT.md](
|
|
8
|
+
[runtime/server/src/CONTEXT.md](../../runtime/server/src/CONTEXT.md).
|
|
9
9
|
|
|
10
10
|
## Application entry: save your agents
|
|
11
11
|
|
|
@@ -14,12 +14,12 @@ package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
|
14
14
|
import { createClient } from "@nylorun/agents";
|
|
15
15
|
import { agents } from "../agents/index.js";
|
|
16
16
|
|
|
17
|
-
const client = createClient();
|
|
17
|
+
const client = await createClient();
|
|
18
18
|
for (const agent of agents) await client.saveAgent(agent);
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The Runtime runs each agent from its manifest and the files saved with it
|
|
22
|
-
|
|
21
|
+
The Runtime runs each agent entirely from its manifest and the files saved with it
|
|
22
|
+
(skills), so a session needs nothing from your process. Your services are reached as tools: [HTTP
|
|
23
23
|
tools](#http-tools) and remote MCP servers. `saveAgent` refuses, before sending anything, an
|
|
24
24
|
agent with a tool that would run your code (`tool({ run })`) or a flow agent with a tool
|
|
25
25
|
stage: make it an `http()` tool or serve it from a remote MCP server. `tool({ run })` still
|
|
@@ -29,7 +29,7 @@ runs in the local engine (`@nylorun/harness/run`). `implementationVersion` defau
|
|
|
29
29
|
`createClient()` finds the Runtime through the two `NYLORUN_*` variables or the Project link
|
|
30
30
|
that `npx nylorun start` writes; with neither, it fails with `connection_missing` and names
|
|
31
31
|
those steps. Action endpoints (`createActionHandler`), and before them `connectAgents` and
|
|
32
|
-
executors, were removed; see [MIGRATION.md](
|
|
32
|
+
executors, were removed; see [MIGRATION.md](../../guides/MIGRATION.md#action-endpoints-are-removed).
|
|
33
33
|
|
|
34
34
|
## HTTP tools
|
|
35
35
|
|
|
@@ -47,15 +47,14 @@ const refundOrder = http({
|
|
|
47
47
|
output: z.object({ refundId: z.string() }), // optional
|
|
48
48
|
url: "https://billing.example.com/refunds",
|
|
49
49
|
method: "POST", // default; or PUT, PATCH
|
|
50
|
-
credential: "billing", // optional: a
|
|
50
|
+
credential: "billing", // optional: the session's Environment holds a credential for this URL
|
|
51
51
|
timeoutMs: 20_000, // default 60000, at most 300000
|
|
52
|
-
approval: "always", // optional: each call waits for session.approve()
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
export const support = Agent({ id: "support" }).tools(refundOrder);
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
The tool goes into the manifest as `http
|
|
57
|
+
The tool goes into the manifest as `http`, and the Runtime's
|
|
59
58
|
Tool Gate sends the model's input as the JSON body, under the Host's address
|
|
60
59
|
policy (`NYLORUN_ENDPOINT_*`), with `content-type: application/json`,
|
|
61
60
|
`Nylorun-Session-Id`, `Nylorun-Turn-Id`, `Nylorun-Agent-Id` and an
|
|
@@ -63,21 +62,73 @@ policy (`NYLORUN_ENDPOINT_*`), with `content-type: application/json`,
|
|
|
63
62
|
restart, so your service can drop duplicates. A `2xx` JSON answer is the output
|
|
64
63
|
(text when it is not JSON and the tool has no `output`), checked against
|
|
65
64
|
`output`. Any other status (with the start of its body), a timeout, a refused
|
|
66
|
-
address or a mismatched output is a tool error the model sees.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
address or a mismatched output is a tool error the model sees.
|
|
66
|
+
|
|
67
|
+
**How tool calls are delivered.** Each MCP or HTTP tool call the model decides
|
|
68
|
+
is sent at least once, and every send carries the same key: `Idempotency-Key`
|
|
69
|
+
for an HTTP tool, `_meta["nylorun/idempotency-key"]` on an MCP `tools/call`. A
|
|
70
|
+
crash, a restart or a lost connection to the gateway sends the call again with
|
|
71
|
+
that key, up to 3 sends; after that the model gets `tool.interrupted` ("may
|
|
72
|
+
have run") and decides. A tool that honors the key runs once; one that ignores
|
|
73
|
+
it may act twice, and `attempts` above 1 in `tool.completed` and in
|
|
74
|
+
`session.toolCalls()` marks where. A sandbox command (`bash`, `write` and the
|
|
75
|
+
rest) is never run twice by the Runtime: a crash fails it to the model as
|
|
76
|
+
`tool.interrupted`. When the model decides the same call again, in a later
|
|
77
|
+
turn say, that is a new call with a new key: put a business key (an order or
|
|
78
|
+
charge id) in the tool's input schema to refuse it too.
|
|
79
|
+
|
|
80
|
+
`credential` works as for a remote MCP server: the vault of the session's
|
|
81
|
+
Environment must hold a credential bound to the tool's exact URL, else the call
|
|
82
|
+
fails before it is sent (`http.credential`). A credential with `via`
|
|
73
83
|
sends the call there instead, such as a gateway, with the session owner in its
|
|
74
|
-
identity header, and a `401` is `credential_rejected` to the model, not retried.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`.mcp({ shop: { type: "streamable-http", url, approval: "always" } })`.
|
|
78
|
-
An HTTP tool is also a flow stage (see [Flow agents](#flow-agents)), and runs
|
|
84
|
+
identity header, and a `401` is `credential_rejected` to the model, not retried.
|
|
85
|
+
Whether a call waits for a person is not the tool's to say: it is the Tenant's
|
|
86
|
+
[tool policy](#tool-policy). An HTTP tool is also a flow stage (see [Flow agents](#flow-agents)), and runs
|
|
79
87
|
only on the Runtime (a local `run()` reports `http.runtime-only`).
|
|
80
88
|
|
|
89
|
+
## Tool policy
|
|
90
|
+
|
|
91
|
+
Which MCP and HTTP tool calls run, wait for a person, or are refused is the
|
|
92
|
+
Tenant's tool policy, set by the operator on the Management API with a
|
|
93
|
+
management key (`PUT /v1/tenant/tool-policy`, `admin.toolPolicy.put()` in
|
|
94
|
+
`@nylorun/admin`). The Runtime applies it to every call of every harness as it
|
|
95
|
+
journals the call, before anything is sent, and its Tool Gate runs only a call
|
|
96
|
+
the journal holds running, so a harness cannot skip it.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { createAdmin } from "@nylorun/admin";
|
|
100
|
+
|
|
101
|
+
await createAdmin().toolPolicy.put({
|
|
102
|
+
rules: [
|
|
103
|
+
{ effect: "ask", tool: "refund_order", reason: "Refunds need a person" },
|
|
104
|
+
{ effect: "deny", tool: "refund_order", when: { amount: { gt: 10_000 } } },
|
|
105
|
+
{ effect: "ask", server: "github", tool: "github__delete_*" },
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A rule matches a call when every selector it sets matches: `tool` (the name the
|
|
111
|
+
model calls it by; `*` matches any run of characters), `server` (an MCP
|
|
112
|
+
server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it) and
|
|
113
|
+
`when` (fields of the call's input by dotted path, each tested with `equals`,
|
|
114
|
+
`in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule
|
|
115
|
+
decides, and a call no rule matches runs:
|
|
116
|
+
|
|
117
|
+
- `deny`: the call is never sent; the model gets `tool.denied` with the rule's
|
|
118
|
+
`reason`.
|
|
119
|
+
- `ask`: the turn pauses (`turn.paused`, with the tool, its input and the
|
|
120
|
+
reason). `session.approve(interactionId, true)` sends that exact call;
|
|
121
|
+
`false` fails it to the model as `tool.denied`, unsent. Nothing holds a
|
|
122
|
+
process while it waits, so an approval can wait a day, or a Runtime restart.
|
|
123
|
+
Studio shows an approval card; AG-UI ends the run with a `tool_approval`
|
|
124
|
+
interrupt.
|
|
125
|
+
- `allow`: it runs.
|
|
126
|
+
|
|
127
|
+
Sandbox tools never cross the gate: their network side is egress policy. A
|
|
128
|
+
manifest no longer sets `approval` (the Runtime and `saveAgent` refuse it); a
|
|
129
|
+
session pinned to a manifest saved with `approval: "always"` keeps it, as an
|
|
130
|
+
ask, until it ends.
|
|
131
|
+
|
|
81
132
|
## Connection resolution
|
|
82
133
|
|
|
83
134
|
`resolveConnection` / `createClient()`:
|
|
@@ -128,11 +179,11 @@ const history = await session.history();
|
|
|
128
179
|
```
|
|
129
180
|
|
|
130
181
|
```sh
|
|
131
|
-
eval "$(npx
|
|
182
|
+
eval "$(npx -p nylorun nylo env)"
|
|
132
183
|
# → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
|
|
133
184
|
```
|
|
134
185
|
|
|
135
|
-
Every request sets `Nylorun-Protocol` (
|
|
186
|
+
Every request sets `Nylorun-Protocol` (11) and no `Nylorun-Tenant`: the Runtime
|
|
136
187
|
serves one Tenant. Before the first authenticated
|
|
137
188
|
request, `Transport` fetches `/health` once, checks protocol compatibility, and
|
|
138
189
|
throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
|
|
@@ -168,42 +219,56 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
|
168
219
|
|
|
169
220
|
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. Nylorun accepts remote servers only: `streamable-http` and `sse` ([Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers)); a `stdio` server is refused, so run it behind an HTTP transport and declare its URL. Attach an Agent Plugin package with `.plugin(path)`: its skills and remote MCP servers join the agent, and a stdio server in its `mcp.json` throws.
|
|
170
221
|
|
|
171
|
-
Set a server's tools one by one with `tools`, keyed by the server's own tool names, `"*"` for the rest: `{ "*": { enabled: false }, search_issues: { enabled: true }, create_issue: { enabled: true
|
|
222
|
+
Set a server's tools one by one with `tools`, keyed by the server's own tool names, `"*"` for the rest: `{ "*": { enabled: false }, search_issues: { enabled: true }, create_issue: { enabled: true } }` is an allowlist (whether a call waits for a person is the [tool policy](#tool-policy)). When an agent's MCP tools would fill more than a tenth of the model's context window, the Runtime defers them: the model finds them with `tool_search` and runs them with `tool_call`. `deferred: true` or `false`, on a tool or on the server, decides instead. Either setting makes the manifest v6 (see MIGRATION.md).
|
|
172
223
|
|
|
173
|
-
A manifest never carries a server's credential, and its `headers` hold no secret.
|
|
224
|
+
A manifest never carries a server's credential, and its `headers` hold no secret. You write a credential for the server's URL into an Environment (a token or a header map, or a gateway's key with `via` and an identity header for servers that need each person's sign-in, with `client.environments.secrets.put`), and sessions that name the Environment use it: [DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools), which also shows `nylorun mcp inspect` for listing a server's tools first. After `saveAgent`, `admin.coverage({ agentId, environmentId })` in `@nylorun/admin` says which credential each server and HTTP tool would get in that Environment ([Checking an agent's credentials](../../guides/DEPLOYMENT.md#checking-an-agents-credentials)). The model knows each tool as `server__tool`, with characters outside `[A-Za-z0-9_-]` replaced by `_`; a failed call reaches it with a code (`credential_rejected` on a `401`, `mcp.unreachable`, …), and a result past 32 KiB, or an image, becomes an artifact of the session that it reads with `read_artifact`.
|
|
174
225
|
|
|
175
|
-
|
|
226
|
+
A session runs in an **Environment**: one sandbox and one vault, with an id you choose, which outlives the sessions that name it (protocol 11). The agent declares nothing, so the same agent runs with or without one, in any Tenant. Whether an Environment serves one session, one person or a project is your app's choice:
|
|
176
227
|
|
|
177
228
|
```ts
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
},
|
|
229
|
+
// One per person or project: get-or-create by id (or change its recipe), write its secrets, open sessions on it.
|
|
230
|
+
await client.environments.put("users/alice/web", {
|
|
231
|
+
kind: "virtual", // the default; "pod" for a container with its own volume, "none" for a vault without compute
|
|
232
|
+
network: { allowedHosts: ["pypi.org", "files.pythonhosted.org"] }, // no egress unless you list hosts
|
|
233
|
+
ownerUserId: "alice", // the owner guard: only alice's sessions may name it
|
|
234
|
+
labels: { team: "data" },
|
|
235
|
+
lifecycle: { deleteAfter: "30d" }, // deleted once unused this long
|
|
185
236
|
});
|
|
237
|
+
await client.environments.secrets.put("users/alice/web", "GH_TOKEN", {
|
|
238
|
+
type: "environment_secret",
|
|
239
|
+
value: process.env.ALICE_GITHUB_TOKEN!,
|
|
240
|
+
allowedHosts: ["api.github.com"],
|
|
241
|
+
});
|
|
242
|
+
const session = await client.createSession({ agentId: "analyst", ownerUserId: "alice", environment: "users/alice/web" });
|
|
243
|
+
|
|
244
|
+
// One per session: created with the session (id `sessions/<session id>`), deleted by release().
|
|
245
|
+
const { session: scratch, release } = await client.environments.forSession({
|
|
246
|
+
session: { agentId: "analyst", ownerUserId: "alice" },
|
|
247
|
+
recipe: { network: { allowedHosts: ["pypi.org"] } },
|
|
248
|
+
});
|
|
249
|
+
await release();
|
|
186
250
|
```
|
|
187
251
|
|
|
188
|
-
|
|
252
|
+
With a sandbox (kind `virtual` or `pod`), the model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a persistent `/workspace`. These tools run in the Runtime, not in your process. A `virtual` Environment is an emulated shell in the Runtime's harness, which is not a VM boundary and takes no `image`; a `pod` runs `image` with its own volume (`storage`) once the Tenant has sandbox pods (`nylorun sandbox enable`). Kind `none` has a vault and no sandbox, for agents that use only remote MCP servers and HTTP tools. A session that names no Environment has no sandbox and no credentials.
|
|
253
|
+
|
|
254
|
+
`kind` and `storage` are fixed once the Environment exists (a different one is a `409`); `labels` replace, and any other change bumps its `version`: network changes apply within seconds, a pod gets a new `image` or `resources` when it next starts from stopped, and a virtual one's change reaches the sessions opened after it. The Runtime checks a recipe against the Tenant's limits (`GET`/`PUT /v1/tenant/sandbox`: a network ceiling, a resource maximum, the idle timeout) and answers `400` with every problem it finds. `lifecycle.idle` stops a pod after that long without a turn (at most the Tenant's); `lifecycle.deleteAfter` deletes the Environment, its vault and its files once nothing has used it that long. Private networks, loopback, the host and cloud metadata endpoints are always blocked.
|
|
255
|
+
|
|
256
|
+
Secrets are write-only: `client.environments.secrets.put(id, name, secret)` creates or replaces one, and `list`, `get` and `delete` never return a value. An `environment_secret` (`{ value, allowedHosts, inject? }`) shows its name to commands as the sentinel `nylorun-managed`, and egress-gate sets its header on HTTPS requests to `allowedHosts` (pods only); an `environment_variable` (`{ value }`) is a plain variable; `bearer` (`{ url, value, via?, identity? }`) and `headers` (`{ url, headers, via?, identity? }`) are credentials for the MCP servers and HTTP tools at `url`. A second secret for one host, or a second credential for one URL, is `409 credential_conflict` at the write ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#credentials)).
|
|
189
257
|
|
|
190
|
-
|
|
258
|
+
Sessions on one Environment share its `/workspace`, and one turn runs in it at a time: a second session's turn is refused with `409 environment_busy` until the first ends. Deleting a session only detaches it. With `ownerUserId` set, a session of another owner is `403 environment_owner_mismatch` (a caller acting for a person gets the `404` of a missing Environment), checked when it opens and at every turn start. `client.environments.list({ labels })`, `page`, `get(id)`, `events(id)`, `stop(id)`, `reset(id)` and `delete(id)` manage them, and `npx nylorun sandbox ls | rm` does the same on a local Tenant. Acting for a person (`app.as(...)`), changing an Environment or its secrets needs `environments:write`. A trusted issuer's token reaches only the Environments its issuer grants (the identity file's `environments` templates, exact ids or prefixes ending in `/*`), checked at every turn start. The Tenant holds at most `limits.environments` of them (`PUT /v1/tenant/sandbox`, default 100).
|
|
191
259
|
|
|
192
|
-
A
|
|
260
|
+
A pod's disk can start prepared: `files` copies versions of Tenant-wide artifacts in, then `setup` runs its commands, once per new disk and before its first turn (a failed command fails the pod, and the next turn start runs the setup again). Between turns, `client.environments.snapshot(id)` keeps a version of the disk, `restore(id, version)` puts one back (a pod on a new volume seeded before its next turn), `put(newId, { from: { environment: id, version: "latest" } })` starts a new Environment from it, and `versions(id)` and `deleteVersion(id, n)` manage them ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#environments)):
|
|
193
261
|
|
|
194
262
|
```ts
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
263
|
+
await client.environments.put("projects/acme", {
|
|
264
|
+
kind: "pod",
|
|
265
|
+
files: [{ artifactId: repoTarball.artifactId, version: 1, path: "/workspace/app" }],
|
|
266
|
+
setup: [{ command: "npm ci", cwd: "/workspace/app", timeout: "15m" }],
|
|
199
267
|
});
|
|
200
|
-
|
|
201
|
-
await client.
|
|
202
|
-
await client.createSession({ agentId: "analyst", ownerUserId, sandbox: { id: "team-a/proj-42" } });
|
|
268
|
+
const { version } = await client.environments.snapshot("projects/acme");
|
|
269
|
+
await client.environments.put("projects/acme-try", { kind: "pod", from: { environment: "projects/acme", version } });
|
|
203
270
|
```
|
|
204
271
|
|
|
205
|
-
Sessions attached to one sandbox share its `/workspace`, and one turn runs in it at a time: a second session's turn is refused with `409 sandbox_busy` until the first ends. Deleting a session only detaches it. A spec is fixed once the sandbox exists; `labels` can change. `client.sandboxes.list({ labels })`, `get(id)` and `delete(id)` manage them, and `npx nylorun sandbox ls | rm` does the same on a local Tenant. A trusted issuer's token reaches only the sandboxes its issuer grants (the identity file's `sandboxes` templates, exact ids or prefixes ending in `/*`), checked at every turn start; creating and deleting through one needs `sandboxes:write`. The Tenant holds at most `limits.sandboxes` of them (`PUT /v1/tenant/sandbox`, default 100).
|
|
206
|
-
|
|
207
272
|
Add an agent with `.subagents()` to let the model delegate to it:
|
|
208
273
|
|
|
209
274
|
```ts
|
|
@@ -243,9 +308,9 @@ session like any agent: put it in `export const agents`, `saveAgent(flowAgent)`,
|
|
|
243
308
|
(`kind: "workflow"`, `workflowSchemaVersion: 3`) that embeds the agents it runs, so one
|
|
244
309
|
document and one manifest hash cover the whole flow; its agents are not listed on their own.
|
|
245
310
|
|
|
246
|
-
A flow
|
|
247
|
-
gets the previous stage's output. So each agent returns, through its
|
|
248
|
-
what the next stage needs.
|
|
311
|
+
A flow passes data between its stages itself: the first stage gets the flow's input, and
|
|
312
|
+
every later stage gets the previous stage's output. So each agent returns, through its
|
|
313
|
+
`.output()` schema, what the next stage needs.
|
|
249
314
|
|
|
250
315
|
```ts
|
|
251
316
|
import { Agent, VerdictSchema, http } from "@nylorun/agents";
|
|
@@ -284,28 +349,9 @@ export const shipFeature = Agent({ id: "ship-feature" })
|
|
|
284
349
|
export const agents = [shipFeature];
|
|
285
350
|
```
|
|
286
351
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
the wait:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
const prOpener = Agent({ id: "pr-opener" })
|
|
294
|
-
.instructions("Open one pull request with open_pr from the summaries you are given.")
|
|
295
|
-
.tools(
|
|
296
|
-
http({
|
|
297
|
-
name: "open_pr",
|
|
298
|
-
description: "Open a pull request with the given summaries.",
|
|
299
|
-
input: z.object({ summaries: z.array(z.string()) }),
|
|
300
|
-
url: "https://ci.example.com/pull-requests",
|
|
301
|
-
credential: "github",
|
|
302
|
-
approval: "always",
|
|
303
|
-
}),
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
// …
|
|
307
|
-
.pipe(prWriter, prOpener);
|
|
308
|
-
```
|
|
352
|
+
The Tenant's [tool policy](#tool-policy) decides a flow's HTTP stages as it decides an
|
|
353
|
+
agent's HTTP tool calls: an `ask` rule on `open_pr` pauses the flow's turn until a person
|
|
354
|
+
approves the call, and the flow session's `pending()` lists the wait.
|
|
309
355
|
|
|
310
356
|
| Stage | Role |
|
|
311
357
|
| --- | --- |
|
|
@@ -329,12 +375,11 @@ body. Its input is the previous output, which must match the tool's `input` (an
|
|
|
329
375
|
object); a mismatch the build can see, such as an agent with no `.output()` before it,
|
|
330
376
|
is refused (`flow.input-mismatch`), and any other fails the stage when it runs
|
|
331
377
|
(`tool.invalid-input`). The Runtime makes the request as for an agent's HTTP tool, with
|
|
332
|
-
`Nylorun-Agent-Id` set to the flow agent's id and `credential` taken from the
|
|
333
|
-
session's
|
|
378
|
+
`Nylorun-Agent-Id` set to the flow agent's id and `credential` taken from the
|
|
379
|
+
vault of the flow session's Environment; its answer is the next stage's input. A failure status, a timeout or
|
|
334
380
|
an answer that does not match `output` fails the stage with its code (`http.status`,
|
|
335
381
|
`http.timeout`, `tool.invalid-output`, …). A Loop body that starts with an HTTP stage
|
|
336
|
-
is retried with the Loop's input, not the feedback.
|
|
337
|
-
supported on a flow stage yet (`flow.approval-unsupported`).
|
|
382
|
+
is retried with the Loop's input, not the feedback.
|
|
338
383
|
|
|
339
384
|
A Loop may be judged by your service instead of an agent: `http({ url, method?,
|
|
340
385
|
credential?, timeoutMs? })`, with no name or input, is an HTTP verifier. The Runtime
|
|
@@ -374,21 +419,21 @@ nested flow agent's id in front of its own agents'. Control stages add nothing,
|
|
|
374
419
|
wrapping a step in `.loop()` or moving it between cases keeps its session. An agent
|
|
375
420
|
may appear once per flow; use it again under a new id with `writer.withId("final-writer")`
|
|
376
421
|
(`flow.duplicate-leaf`). A code tool stage (`.pipe(tool({ run }))`) runs only in the local
|
|
377
|
-
engine: the Runtime runs
|
|
378
|
-
an HTTP stage instead.
|
|
422
|
+
engine: the Runtime runs flows from their manifests alone, so `saveAgent` refuses a flow
|
|
423
|
+
agent with one. Use an HTTP stage instead.
|
|
379
424
|
|
|
380
|
-
The agents in a flow share one
|
|
381
|
-
`createSession({ …,
|
|
382
|
-
|
|
383
|
-
|
|
425
|
+
The agents in a flow share one Environment: open the flow's session on it,
|
|
426
|
+
`createSession({ …, environment: id })`, and every linked session in the flow
|
|
427
|
+
inherits it; turns within one flow do not block each other. Other sessions share
|
|
428
|
+
it by naming the same id (see `client.environments`). Call the sandbox
|
|
384
429
|
built-ins via `session.sandbox` (application).
|
|
385
430
|
Observe with `session.observe({ follow: true })` to merge linked agent streams;
|
|
386
431
|
`pending()` lists waits across the tree. Studio renders the manifest tree and
|
|
387
432
|
live node status. Source examples:
|
|
388
433
|
`examples/agents/{chain,switch,parallel,map,loop,ship-feature}/` (outside the
|
|
389
434
|
default release registry). Host contract:
|
|
390
|
-
[HOST_CONTRACT.md](
|
|
391
|
-
workflow Loop): [harness/src/CONTEXT.md](
|
|
435
|
+
[HOST_CONTRACT.md](../../runtime/harness/HOST_CONTRACT.md). Vocabulary (turn loop vs
|
|
436
|
+
workflow Loop): [runtime/harness/src/CONTEXT.md](../../runtime/harness/src/CONTEXT.md).
|
|
392
437
|
|
|
393
438
|
Use `session.observe({ cursor, signal })` for resumable canonical events, `session.inspect()` for waiting/uncertain state, and `approve`, `respond`, or `cancel` with an explicit stable idempotency key. Retry the same semantic command with the same key. `ownerUserId` must come from trusted server authentication. Application credentials are not browser credentials; browser applications need an authorized backend. Definition authoring is browser-bundleable.
|
|
394
439
|
|
|
@@ -446,8 +491,8 @@ createServer(toNodeListener(agui)).listen(3000);
|
|
|
446
491
|
|
|
447
492
|
`run`, `history`, `reattach` and `cancel` are also on the handler for your own
|
|
448
493
|
routing. The default `client` is `createClient()`; pass `client` to use another
|
|
449
|
-
one, and `session(subject, agentId)` to add the person's `
|
|
450
|
-
|
|
494
|
+
one, and `session(subject, agentId)` to add the person's `environment` (their
|
|
495
|
+
Environment's id: its sandbox and its vault's credentials) or `info`. They apply when the thread's session is
|
|
451
496
|
created, on its first run; later runs keep them. Whatever the browser sends in
|
|
452
497
|
`forwardedProps.nylorun` is replaced.
|
|
453
498
|
|
|
@@ -471,7 +516,7 @@ created, on its first run; later runs keep them. Whatever the browser sends in
|
|
|
471
516
|
package itself: the Runtime does the protocol work.
|
|
472
517
|
|
|
473
518
|
A complete web backend with a test is in
|
|
474
|
-
[examples/src/ag-ui](
|
|
519
|
+
[examples/src/ag-ui](../../examples/README.md#an-agent-in-your-web-app-ag-ui).
|
|
475
520
|
|
|
476
521
|
Limitations: assistant text arrives once per model step (no token streaming);
|
|
477
522
|
no reasoning, state, activity or subagent events, and an agent used as a tool
|
|
@@ -537,7 +582,7 @@ What the Runtime answers (Host feature `a2a-endpoint`):
|
|
|
537
582
|
(`UnsupportedOperationError`; the card says `streaming: false`), push
|
|
538
583
|
notifications, the extended card, and approvals. An agent that pauses for an
|
|
539
584
|
approval shows `INPUT_REQUIRED`, refuses a reply, and can be canceled, so do
|
|
540
|
-
not publish agents
|
|
585
|
+
not publish agents whose tools the Tenant's tool policy asks about yet.
|
|
541
586
|
- The handler calls the Runtime as the partner's subject with the scope
|
|
542
587
|
`sessions:own` only, so one partner never reaches another's tasks. It sends
|
|
543
588
|
no header the partner chose except `A2A-Version` and `A2A-Extensions`. Without
|
|
@@ -571,6 +616,29 @@ refuse a larger upload with `413 limit_exceeded`, and nothing is stored. Acting
|
|
|
571
616
|
a person (`as()`, or a trusted issuer's token), a client reaches only the artifacts of that
|
|
572
617
|
person's sessions, and an upload names one of them.
|
|
573
618
|
|
|
619
|
+
Hosts advertising `artifact-reads` also support bounded metadata pages, newest
|
|
620
|
+
first. `artifacts.page()` defaults to 50 rows (1–200), checks feature discovery,
|
|
621
|
+
and returns `{ artifacts, nextCursor }`. Pass the same filters on every page:
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
const filters = { kind: "file" as const, labels: { team: "design" } };
|
|
625
|
+
let cursor: string | undefined;
|
|
626
|
+
do {
|
|
627
|
+
const page = await client.artifacts.page({ ...filters, cursor });
|
|
628
|
+
for (const artifact of page.artifacts) console.log(artifact.name, artifact.latestVersion);
|
|
629
|
+
cursor = page.nextCursor ?? undefined;
|
|
630
|
+
} while (cursor);
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Omit `sessionId` for all reachable artifacts, including Tenant-owned artifacts
|
|
634
|
+
when using an application key as itself. With `sessionId`, only that session's
|
|
635
|
+
artifacts are included; acting for a person also applies ownership and agent
|
|
636
|
+
grants on every page. Every label must match exactly. Pages omit version histories
|
|
637
|
+
and bytes: use `get()` and the existing version/download methods for those.
|
|
638
|
+
Cursors bind to the Tenant and filters. Pages are independent reads; refresh from
|
|
639
|
+
the beginning for newer artifacts. The existing unbounded `list()` keeps its
|
|
640
|
+
oldest-first behavior and does not require `artifact-reads`.
|
|
641
|
+
|
|
574
642
|
### Outputs: folder artifacts
|
|
575
643
|
|
|
576
644
|
At the end of each turn, the Runtime exports the files the agent wrote into
|
|
@@ -601,7 +669,7 @@ keeps its application key to itself and names the person on each call:
|
|
|
601
669
|
```ts
|
|
602
670
|
import { createClient } from "@nylorun/agents";
|
|
603
671
|
|
|
604
|
-
const app = createClient(); // the application key, on the server only
|
|
672
|
+
const app = await createClient(); // the application key, on the server only
|
|
605
673
|
|
|
606
674
|
// Per request, after your own sign-in:
|
|
607
675
|
const person = app.as(`app:${user.id}`, { scopes: ["sessions:own"] });
|
|
@@ -621,14 +689,15 @@ compatibility check, so calling `as()` per request is cheap.
|
|
|
621
689
|
| `sessions:own` | The person's own sessions: create, list, read, stream, message, approve, respond, cancel; and their artifacts |
|
|
622
690
|
| `agents:read` | Listing the Tenant's agents |
|
|
623
691
|
| `agents:write` | Saving agents; listing agents |
|
|
692
|
+
| `environments:write` | Creating, changing, stopping, resetting and deleting Environments, and writing their secrets (reading them takes `sessions:own` or this) |
|
|
624
693
|
|
|
625
694
|
No scope reaches the sandbox tool routes; call them without `as()`. `tenant:settings` is retired (protocol 8): it grants
|
|
626
|
-
nothing, and no subject reaches the Management API (`/v1/tenant/*`).
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
([DEPLOYMENT.md](
|
|
695
|
+
nothing, and no subject reaches the Management API (`/v1/tenant/*`).
|
|
696
|
+
`sandboxes:write` (protocol 10) is refused with a message naming
|
|
697
|
+
`environments:write`. Credentials live in Environments: the server writes a
|
|
698
|
+
person's own keys into their Environment (with `ownerUserId` set to them) and
|
|
699
|
+
names it when it opens their sessions
|
|
700
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#credentials)). A subject is 1–200 visible ASCII
|
|
632
701
|
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
633
702
|
any `Nylorun-*` header its own clients send, and only an application key can act
|
|
634
703
|
for a subject.
|
|
@@ -644,8 +713,8 @@ and revoking them is management: `admin.signingKeys.list()`, `.rotate()`
|
|
|
644
713
|
|
|
645
714
|
A web page or an app calls the Runtime directly with the JWT your identity
|
|
646
715
|
provider gave the person: list the provider in the Runtime's identity file
|
|
647
|
-
([Trusted issuers](
|
|
648
|
-
token's subject, scopes, agents and
|
|
716
|
+
([Trusted issuers](../../guides/DEPLOYMENT.md#trusted-issuers)), and the Runtime takes the
|
|
717
|
+
token's subject, scopes, agents and Environment grants from it. Nylorun mints no
|
|
649
718
|
token and ships no browser client: use your provider's SDK for sign-in and send
|
|
650
719
|
the token as `Authorization: Bearer <token>` with `Nylorun-Protocol`, through a
|
|
651
720
|
reverse proxy that answers CORS (the Runtime sends no CORS headers). An expired
|
|
@@ -653,7 +722,7 @@ token answers `401` with `code: "token_expired"`; `GET /v1/me` shows what a toke
|
|
|
653
722
|
renders to. For a chat UI, give `@ag-ui/client`'s `HttpAgent` a `fetch` that adds
|
|
654
723
|
those headers.
|
|
655
724
|
|
|
656
|
-
The SDK depends only on core within the Nylorun packages; installing it does not install harness or any AG-UI or A2A package. Use `/define`, `/client`, `/ag-ui` or `/a2a` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](
|
|
725
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness or any AG-UI or A2A package. Use `/define`, `/client`, `/ag-ui` or `/a2a` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../../runtime/harness/HOST_CONTRACT.md).
|
|
657
726
|
|
|
658
727
|
## Embedding Studio (desktop apps)
|
|
659
728
|
|
package/dist/ag-ui/handler.d.ts
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
import { type SubjectScope } from "@nylorun/core/contracts";
|
|
12
12
|
import type { BuiltWorkflow } from "@nylorun/core/define";
|
|
13
13
|
import { type AgentSource, type AgentsClient } from "../client.js";
|
|
14
|
-
/** Per-session parameters the host may add, such as the person's
|
|
14
|
+
/** Per-session parameters the host may add, such as the person's Environment. */
|
|
15
15
|
export interface AgUiSessionOptions {
|
|
16
16
|
/** Set when the thread's session is created, on its first run; later runs keep them. */
|
|
17
17
|
info?: Record<string, unknown>;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/** The Environment the thread's session runs in: its sandbox and its vault's credentials. */
|
|
19
|
+
environment?: string;
|
|
20
20
|
}
|
|
21
21
|
export interface AgUiHandlerOptions {
|
|
22
22
|
/** Agents this endpoint may run: built agents, workflows or their ids. Nothing else is reachable. */
|
|
@@ -29,7 +29,7 @@ export interface AgUiHandlerOptions {
|
|
|
29
29
|
client?: AgentsClient | Promise<AgentsClient>;
|
|
30
30
|
/** What each subject may do through this endpoint. Default `["sessions:own"]`. */
|
|
31
31
|
scopes?: readonly SubjectScope[];
|
|
32
|
-
/** Optional per-session parameters, e.g. the person's
|
|
32
|
+
/** Optional per-session parameters, e.g. the person's Environment with their keys. */
|
|
33
33
|
session?(subject: string, agentId: string): AgUiSessionOptions | Promise<AgUiSessionOptions>;
|
|
34
34
|
}
|
|
35
35
|
export interface AgUiHandler {
|