@nylorun/agents 0.20.0-beta → 0.22.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 CHANGED
@@ -1,5 +1,143 @@
1
1
  # @nylorun/agents
2
2
 
3
+ ## 0.22.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - a0cf8b2: **HTTP tools take `GET` and `DELETE`, and URL path parameters.**
8
+
9
+ - `method` is `GET`, `POST` (default), `PUT`, `PATCH` or `DELETE`. `POST`, `PUT` and `PATCH` send the input as the JSON body, as before. `GET` and `DELETE` send no body: the input becomes the query string, after any query string written in the URL. An array repeats its name (`tag=a&tag=b`) and a `null` is left out.
10
+ - The URL's path may name input fields as `{field}`, as in `https://api.example.com/orders/{orderId}`. Each value is percent-encoded into the path and is not sent again in the body or query string. A value that is empty, `.` or `..`, or not a string, number or boolean is refused before the call is sent (`tool.invalid-input`).
11
+ - `http()` and the manifest contract check a tool against its input schema. Each `{field}` must be a required scalar property. For `GET` and `DELETE`, a property must not be an object and must not repeat a query parameter the URL already sets.
12
+ - An HTTP verifier still takes only `POST`, `PUT` or `PATCH` and a URL without `{field}` parameters, because it is sent `{ input, output, iteration }` as a body. A credential with `via` cannot serve a tool with `{field}` parameters (`http.credential`).
13
+ - core exports `httpToolRequest`, which maps a call's input to the request, and `httpPathParameters`. The Runtime's outbound `post()` is now `send()`, which can send a request without a body.
14
+
15
+ ### Patch Changes
16
+
17
+ - 620072d: **Docs and schemas match what the CLI and the Runtime do.**
18
+
19
+ - `@nylorun/core`: the environment manifest's JSON Schema (`@nylorun/core/schemas/environment-manifest.json`) takes the CLI's `{ path, from }` `files` entry, so an editor no longer flags a manifest that `nylorun environments manifests save` accepts. The Runtime's own schema is unchanged: the CLI turns the folder into `{ path, files }` before saving. `manifest_changed` says to save without `If-Match` (`--if-match`, `ifMatch`); no command takes `--force`.
20
+ - Tool names, skill names and MCP server names are each unique within their kind, as the builder and the Runtime check; a tool, a skill and a server may share a name. The docs said they were unique across kinds.
21
+ - `harness_mismatch` is described as what it is since protocol 14: an Environment's harness is fixed for its life (fork onto a manifest version to run another), and a session's turn is refused when its Environment id was deleted and made again with another harness, or an Environment changed in place before protocol 14 applies that change.
22
+ - `@nylorun/agents`: the README names `nylorun environments list | delete`, not `nylorun local sandbox ls | rm`.
23
+ - MIGRATION.md no longer offers a tool's `approval`, removed in protocol 11, as a replacement for step middleware.
24
+
25
+ - 000ff75: **READMEs and help text match protocol 14.**
26
+
27
+ - `nylorun --help` lists `environments fork`, `manifests` and `delete-version`. The package description no longer offers `nylo` as the Runtime client, and the README says nylorun depends on `@nylorun/agents` too.
28
+ - The READMEs name the current commands: `nylorun tenant keys`, `nylorun tenant mcp inspect`, `nylorun local logs` and `nylorun local reset`. `@nylorun/admin` says it speaks protocol 14.
29
+ - `@nylorun/agents`: a lost MCP or HTTP tool call is sent again with its key, then fails to the model as `tool.interrupted` (it is not left `uncertain`). Every manifest is v7. A2A takes `raw` files. A delegated agent cannot pause.
30
+ - `@nylorun/runtime`: the README describes protocol 14, `PUT /v1/sessions/{id}` says it answers an identical session (vaults are no longer attached), and the database-layout error names `nylorun local reset`.
31
+ - `@nylorun/create-agent`: `nylorun start` writes the project's keys (no key is derived any more).
32
+
33
+ - Pin core to the tested release.
34
+ - Updated dependencies [620072d]
35
+ - Updated dependencies [a0cf8b2]
36
+ - @nylorun/core@0.22.0-beta
37
+
38
+ ## 0.21.0-beta
39
+
40
+ ### Minor Changes
41
+
42
+ - 3a32a57: **Files by hash and images by digest (Q6, Q14).** An Environment's `files` entry may name files by content hash, and a pod Environment runs its image by digest once a pod ran it.
43
+
44
+ - **Files by hash.** `{ path, files: { "<relative path>": "sha256:<hex>" } }` copies each definition file the Tenant holds (`PUT /v1/files/sha256:<hex>`, at most 10 MiB each, at most 1000 per entry) under `path`, before `setup`. A file the Tenant does not hold is refused when the manifest is saved or the Environment made. The engine reads them as a folder through `host/files/{index}`. `@nylorun/agents`: `environmentFiles(path, contents)` hashes them, and `manifests.save(manifest, { files })` and `plan()` upload those the Runtime lacks.
45
+ - **Images by digest.** The sandboxes service reports the workload container's `imageID`; the first time a pod is ready the Runtime records it (`pod.imageDigest` in the view) and every later apply runs that digest, so a stop, relaunch or reset never pulls another image under the same tag. A fork pulls afresh. Migration 0027.
46
+ - **Studio** lists files by hash on the Environment page.
47
+
48
+ - af90d1e: **Forks: how an Environment gets a new manifest version (Q15, Q17).** `POST /v1/environments/{id}/fork` makes a new Environment `id` from a manifest version (default the source's manifest at its latest), its disk started from a version of the source's (`disk`: `latest`, the default, snapshots first; a version; or `empty`) and the secrets the new manifest declares copied inside the Runtime (`secrets: "none"` copies none): each value is unsealed and sealed again under the new vault, with the slot's binding. The source keeps what it runs, its disk and its sessions, and gets `environment.forked`; the new Environment's view has `forkedFrom`. `dryRun: true` answers the plan: the disk, the secrets copied and left behind, required slots still missing, the changes and the sessions that stay. Migration 0026.
49
+
50
+ - **Setup on a forked disk.** A pod fork onto other setup runs the new version's setup on the copied disk before its first turn: the engine connects first (the join answer's `afterSeed`), core writes the disk version through it, then the engine runs the setup, and turns wait until it reports it done. A fork onto the same setup, a restore and a lost-volume recovery skip it, as before.
51
+ - `@nylorun/core`: `ForkEnvironmentRequestSchema`, `EnvironmentForkPlanSchema`, `ForkEnvironmentResponseSchema`, `EnvironmentView.forkedFrom`, the `environment.forked` event and `forkedFrom` on `environment.created`.
52
+ - `@nylorun/agents`: `client.environments.fork(id, { id, manifest?, disk?, secrets?, labels?, ownerUserId? })` and `planFork()`.
53
+
54
+ - 104f24f: **Environment manifests, and Environments that never change (protocol 14, D63 to D65).** An Environment's definition can now be saved like an agent: a named, versioned environment manifest, written as a JSON file with a published schema and checked with a plan before it is saved. An Environment is made from one version and keeps it for life; a save changes no Environment. The Runtime speaks protocol 14 only; sessions, Environments and vaults carry over. See MIGRATION.md.
55
+
56
+ - **Environment manifests.** `PUT /v1/environment-manifests/{name}` saves the next version, or answers the latest `unchanged`; `If-Match: <version>` refuses a save over a newer one (`412 manifest_changed`), and `?dryRun=true` answers the plan (changes, errors, warnings, the Environments behind) and saves nothing. Each version records `savedBy`. `GET` (latest), `GET /v1/environment-manifests`, `.../versions`, `.../versions/{n}` and `DELETE` (refused while an Environment names it, `409 manifest_in_use`). Migration 0025.
57
+ - **Pinned Environments.** `PUT /v1/environments/{id}` takes `manifest: "name"` (the latest at creation) or `"name@n"`. On an existing Environment only `labels` and `ownerUserId` may change; fields that match are accepted, and anything else is `409 environment_immutable` with `details.fields`. The view's `recipe` is now `manifest`, with `manifestName`, `manifestVersion`, `manifestHash` and `slots`; `version` and `appliedVersion` no longer move. `environment.created` names the manifest.
58
+ - **Secret slots.** A manifest's (or inline) `secrets` declares each secret's name, type, binding, `required` and `description`, never a value. On such an Environment a secret write sends only `{ value }` (or `{ headers }`), the binding comes from the slot, and an undeclared name is `409 secret_not_in_manifest`.
59
+ - **`requires`.** Agent manifest v7 takes `requires: { sandbox?: "none" | "any" | "pod", secrets? }` (`.requires()` in `@nylorun/core/define`), checked when a session opens (`409 environment_unsuitable`, `details.missing`). Turn variants keep it.
60
+ - **Dry runs.** `PUT /v1/agents/{id}?dryRun=true` checks a definition and saves nothing (`dryRun: true` in the answer).
61
+ - `@nylorun/core`: `PROTOCOL_VERSION` 14, `HOST_PROTOCOL` `{ min: 14, max: 14 }`, required feature `environment-manifests`; `EnvironmentManifestSchema`, `EnvironmentSecretSlotSchema`, `parseEnvironmentManifestRef` and the view, plan and save schemas; JSON Schemas at `@nylorun/core/schemas/environment-manifest.json` and `@nylorun/core/schemas/agent-manifest.json` (`$id` `https://docs.nylorun.com/schemas/environment-manifest/v1.json` and `.../agent-manifest/v7.json`).
62
+ - `@nylorun/agents`: `client.environments.manifests` (`save`, `plan`, `get`, `list`, `versions`, `delete`), `environmentManifest()`, `EnvironmentInput` (was `EnvironmentRecipeInput`), value-only `secrets.put`, and `saveAgent(agent, { dryRun })`.
63
+ - **Studio** edits only an Environment's labels and owner guard, shows the manifest it was made from, and its setup and files are read-only.
64
+ - `nylorun environments put` help says what it runs never changes.
65
+
66
+ - c96b9a8: **An agent manifest is flat: manifest v7, a `runtime` block the Runtime writes, and tools known by name (protocol 13, Harness API 3).** A manifest listed capabilities, each with its own instructions, tools, skills and servers, and the Runtime added its own as more capabilities. Now the agent's parts sit at the top level, a plugin's skills and servers say which plugin brought them, and what the Runtime adds to a session's agent is one `runtime` block. A fresh start: the Runtime speaks protocol 13 only, so save your agents again; sessions from protocol 12 do not carry over. See MIGRATION.md.
67
+
68
+ - **Manifest v7.** `{ manifestSchemaVersion: 7, id, name?, description?, metadata?, outputSchema?, instructions?, tools?, skills?, mcpServers?, runtime? }`. Versions 3 to 6 and a `capabilities` field are refused with a message to rebuild. `SkillManifest.plugin` and `McpServerManifest.plugin` are `{ name, version? }`, set by `.plugin(dir)` from `plugin.json`.
69
+ - **The `runtime` block.** `{ sandbox?, tools?, instructions? }`, written only by the Runtime when a session pins the agent: the sandbox spec and tools, `save_artifact`, `read_artifact`, `load_skill` and `read_skill_resource`, the skill catalog and the notes that go with them. A definition or a turn variant that sets it is refused. `runtimeManifestFor` in `@nylorun/core/define` replaces `sandboxCapabilityManifest`, `artifactsCapabilityManifest` and `toolsCapabilityManifest`. `.skills()` and `.plugin()` no longer add the skill tools or the catalog.
70
+ - **Names.** An agent's tool may not take a Runtime tool's name (`RUNTIME_TOOL_NAMES`) or contain `__`, and names are unique across tools, skills and servers.
71
+ - **Turn variants** may remove tools, tighten MCP servers and change instructions, name, description and metadata. Skills are fixed, and a variant never sets `runtime` or changes `outputSchema`.
72
+ - **Tools by name.** `capabilityId` is gone from `tool.completed`, `mcp.discovered`, tool call rows, `EffectIntent`, gate requests, checkpoints and `SessionToolRef`. An HTTP tool's `server` is its URL's host. `Implementations` is `{ tools }`, keyed by tool name (`Agent.from(json, { tools })`), `AgentBinding.declarations` is `parts`, and observe events say `partId`.
73
+ - **Harness API 3.** `hello.capabilities` is `features`, and the `harness` pseudo-capability is gone: a native tool is known by its name, and one whose name is taken fails the turn at its start. `createHarness({ features })` in `@nylorun/harness`. `examples/harness-minimal` speaks it.
74
+ - **Studio.** The Agent tab shows instructions, tools (HTTP and agent badges), skills and MCP servers, each skill and server with its plugin's badge and a filter by plugin, and a session's Runtime section: its sandbox, tools and instructions.
75
+ - `@nylorun/core`: `PROTOCOL_VERSION` 13, `HOST_PROTOCOL` `{ min: 13, max: 13 }`, required feature `flat-manifest`, `HARNESS_API_VERSION` 3; `PluginLabel`, `RuntimeManifest`, `RUNTIME_TOOL_NAMES`, `isRuntimeToolName`, `runtimeToolOf`, `isSkillTool`, `SKILLS_USAGE` and `formatSkillCatalog` in `/define`.
76
+ - `@nylorun/agents`, `@nylorun/admin`, `@nylorun/studio` and `nylorun` send protocol 13.
77
+
78
+ - 94bf329: **Each model says what it reads, a file it does not read is refused before a turn starts, and a file is an artifact the model reads again after its turn (protocol 12).** A message could name any file, and a model that could not read it failed the turn at its model call; every later turn sent the file's bytes again. Now the Runtime knows what the Tenant's model reads, checks a message's files when it is accepted, and sends a file's bytes only in the turn that brought it. Protocol 12 only adds to 11: the Runtime serves both.
79
+
80
+ - **Model inputs.** A model reads `text`, and `image`, `pdf`, `office`, `audio` or `video` when its catalog says so or the operator adds it where its API carries it natively (images everywhere; PDFs on the Anthropic, OpenAI, Azure and Google APIs; Office documents on the OpenAI Responses APIs; audio and video on Google's APIs and OpenAI-compatible completions). `GET /v1/tenant/models` gives each model `inputs` and `addableInputs`, `GET /v1/tenant/providers` each provider's `inputs`, and **`PUT /v1/tenant/models/inputs`** (the Management API; `admin.models.setInputs({ provider, model, inputs })`) sets the added ones, kept in the Tenant setting `model.inputs`. The Model Gate gives the provider adapter a model with an added image, so it is sent.
81
+ - **A custom endpoint reads text only by default.** It used to be sent images. Set `settings.inputs` on `PUT /v1/tenant/model` (`image`, `pdf`, `audio`, `video`) for what yours reads; `office` is refused there. See MIGRATION.md.
82
+ - **The accept check.** A message naming a file of a type the Tenant's model does not read is `422 attachment_unsupported`, with `details.parts` (`artifactId`, `name`, `mediaType`, `reason`), and no turn starts. Only the type is checked, never the size. Without a model in the vault nothing is refused.
83
+ - **Files as artifacts.** After its turn a file is a reference line, `[Uploaded artifact "name" · <artifactId> v<n> · read_artifact to view]` (a tool's: `[Tool <name> returned artifact …]`). Our harness sends the turn's own files once more, as media, in one user message after the conversation, so a message reads the same in every later call. At the gate, an image the model reads is an image, a text file a `<file>` block, and any other file, or one that cannot be read, a note: no file fails a call. PDFs, Office documents, audio and video are notes until the gate sends them natively.
84
+ - **`read_artifact` for every agent.** Every agent's session has it (an agent that declares its own keeps it), so every agent session pins its definition with it added. It takes a `version`, reads the Tenant-wide artifacts the session's messages named, and shows any file the Tenant's model reads; one it does not read fails `artifact.unsupported`.
85
+ - **Compaction** keeps the summarized part's reference lines (`files` on the compaction entry), and counts the turn's files by type and size in its token estimate.
86
+ - `@nylorun/core`: `PROTOCOL_VERSION` 12, required feature `model-inputs`, `HOST_PROTOCOL` `{ min: 11, max: 12 }`, error code `attachment_unsupported`; `MODEL_INPUTS`, `ModelInputSchema`, `PutModelInputsRequestSchema`, `ModelInputsViewSchema`, `CustomModelSettings.inputs`; `mediaReferenceLine` and `mediaReferenceOf` in `/define`; `TranscriptCompactionEntry.files`.
87
+ - `@nylorun/agents`, `@nylorun/studio` and `nylorun` send protocol 12.
88
+
89
+ - b5ea2b5: **An agent is its manifest: step middleware and capability bundles are removed from authoring.** The Runtime already refused middleware; now the local engine runs none either, and an agent is built only from the builder's own methods. No protocol change. See MIGRATION.md.
90
+
91
+ - **Removed:** `.use()`, `.capability()`, `capability()`, `CapabilityBuilder`, `middleware()`, `model()`, `StepMiddleware`, `StepRequest`, `StepResponse`, and `Implementations[id].middleware`. To reuse tools and instructions, write a function that adds them; a bundle you publish is an Agent Plugin.
92
+ - **`@nylorun/agents`:** the standalone `skills()`, `plugin()` and `mcp()` factories are removed; use `.skills(dir)`, `.plugin(dir)` and `.mcp(servers)`. `.skills()` takes no options: the part takes the folder's name.
93
+ - **`@nylorun/harness`:** each step offers the definition's tools and instructions in order. The `middleware.entered`, `middleware.completed` and `middleware.lease-violation` observe events are gone, and a model request carries no `model` directive or runtime context items.
94
+
95
+ - f44c018: **Sessions on one Environment run their turns at the same time, and egress is recorded on the Environment (protocol 14, D66, D67).** A second session's turn on an Environment was refused with `409 environment_busy` until the first ended. Now any session on it may start a turn while others run; one turn per session stays. Which sessions work on one disk, and when, is the app's call. See MIGRATION.md, "Parallel turns, and egress on the Environment".
96
+
97
+ - **Turns.** `checkEnvironmentTurn` no longer refuses a turn because another session's turn runs, and the flow-tree exception is gone. `environment_busy` still answers stop, reset, snapshot and restore while any turn runs, and a turn while a disk copy runs.
98
+ - **Egress.** Every connection through egress-gate, allowed or refused, is `environment.egress` on the Environment's stream, with `runningTurns: [{ sessionId, turnId }]`, the turns running when it opened (at most 16). `egress.connection` leaves the session event catalog, and turn traces carry no `CONNECT` span.
99
+ - **Reading it.** `GET /v1/environments/{id}/events` takes `types` (comma-separated), `since` and `until` (ISO times); `client.environments.events(id, { types, since, until })` in `@nylorun/agents`, and `nylorun environments events <id> --types --since --until`.
100
+ - `@nylorun/core`: `EgressRunningTurnSchema`, `EgressRunningTurn`; `EgressConnectionPayloadSchema` gains `runningTurns`.
101
+
102
+ - 0b7cbbd: **Studio shows an Environment's connections (D67).** The Environment page's Events tab filters all events, its lifecycle, or the connections its sandbox opened, each with its host, outcome, bytes and the turns that were running. A session's Environment tab lists the connections a pod Environment opened while that session's turns ran, and names the other sessions whose turns ran too, since a connection is the Environment's. `@nylorun/agents` exports `ENVIRONMENT_EVENT_TYPES` and the types `EnvironmentEventType` and `EgressRunningTurn`.
103
+ - c8e640b: **A tool call is traced from the log alone to its skill or MCP server and the plugin that brought it.** No protocol change beyond 13. See MIGRATION.md.
104
+
105
+ - **`tool.completed`** gains `skill` and `plugin` (`{ name, version? }`), and tool call rows (`GET /v1/sessions/{id}/calls/tool`, `session.toolCalls()`) gain `skill` and `plugin`, `null` when there is none. An MCP call gets its server's plugin. `load_skill` and `read_skill_resource` get the skill they name, and a sandbox tool whose input names `/skills/<name>/` gets that skill; both get the skill's plugin.
106
+ - **`kind: "harness"`** (`TOOL_CALL_KINDS`): a tool the turn's harness declared in `hello.features.tools`. Such a call said `platform` before.
107
+ - **`mcp.discovered`** names each server's `plugin`.
108
+ - `@nylorun/core`: `skillOfToolCall`, `pluginOf` and `ToolCallTrace` in `/define`.
109
+ - **Studio** shows each tool call's kind, server or skill, and a plugin badge, in the chat and the event details and summaries, and the plugin of each server `mcp.discovered` lists.
110
+
111
+ ### Patch Changes
112
+
113
+ - bc22487: **`nylorun` does what `@nylorun/agents` does: `agents`, `sessions`, `environments` and `artifacts`.** Each is a group on the Runtime API, with the installation's application key, an identity provider's token (`NYLORUN_TOKEN`), and `--as <subject> [--scope <scope>]...` to act for a person.
114
+
115
+ - **`agents list | get | save`:** `save <manifest.json|-> --files <dir>...` saves an agent from its manifest, uploading the definition files it names (found by their SHA-256) that the Runtime lacks.
116
+ - **`sessions`:** `list`, `create`, `get`, `send` (text, `--data` JSON, or `--file <artifact>[@<version>]` parts), `approve [--deny]`, `respond`, `cancel`, `watch [--follow]` (events as JSON lines), `history`, `usage`, `calls [--tool]`, `trace`, and `exec <id> -- <command>` (bash in the session's sandbox; exits with its exit code).
117
+ - **`environments`:** `list`, `get`, `put <id> [<recipe.json|->] [--kind] [--label]`, `stop`, `reset`, `delete`, `snapshot`, `versions`, `restore`, `delete-version`, `events`, and `secrets list|get|put|rm`; a secret's JSON comes from a file or standard input, never the command line.
118
+ - **`artifacts`:** `list`, `get`, `upload <file|->` (`--to <id>` for a new version), `download`, `tree`, `diff`, `zip`, `link`, `rm`.
119
+ - **`nylorun` now depends on `@nylorun/agents`.** A release of `@nylorun/agents` releases nylorun too.
120
+ - **`@nylorun/agents`:** an incompatible Runtime's remedy names `npx nylorun@latest start` instead of a `nylorun runtime restart` that no longer exists.
121
+
122
+ - 9b07bdd: **Studio and the starter for environment manifests (protocol 14).**
123
+
124
+ - **Studio, Environments page:** lists the environment manifests (each one's latest version, its kind, how many Environments were made from it, and who saved it). It saves a new version from JSON, with Check showing the plan first, under `If-Match`, so a save over someone else's is refused. It also makes an Environment from a version. Studio is one more client: any credential that may write Environments can do the same from the CLI or the SDK.
125
+ - **Studio, Environment page:** shows the manifest version it was made from and whether a newer one exists ("Fork to vN"), the Environment it was forked from, its secret slots set or missing, and its pod's image digest. **Fork** opens the fork with its plan (disk, secrets copied and left behind, slots still missing, changes) before it makes the new Environment.
126
+ - **The starter** ships `environments/scratch.json` (a virtual workspace with no network). `src/main.ts` saves each manifest in `environments/` beside the agents.
127
+ - `@nylorun/agents` exports the manifest and fork contract types (`EnvironmentManifestView`, `EnvironmentManifestPlan`, `EnvironmentForkPlan`, …).
128
+
129
+ - Pin core to the tested release.
130
+ - Updated dependencies [3a32a57]
131
+ - Updated dependencies [af90d1e]
132
+ - Updated dependencies [104f24f]
133
+ - Updated dependencies [c96b9a8]
134
+ - Updated dependencies [94bf329]
135
+ - Updated dependencies [b5ea2b5]
136
+ - Updated dependencies [aff9bb8]
137
+ - Updated dependencies [f44c018]
138
+ - Updated dependencies [c8e640b]
139
+ - @nylorun/core@0.21.0-beta
140
+
3
141
  ## 0.20.0-beta
4
142
 
5
143
  ### Minor Changes
package/README.md CHANGED
@@ -46,7 +46,7 @@ const refundOrder = http({
46
46
  input: z.object({ orderId: z.string(), amount: z.number() }), // or a JSON Schema object
47
47
  output: z.object({ refundId: z.string() }), // optional
48
48
  url: "https://billing.example.com/refunds",
49
- method: "POST", // default; or PUT, PATCH
49
+ method: "POST", // default; or GET, PUT, PATCH, DELETE
50
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
52
  });
@@ -54,9 +54,51 @@ const refundOrder = http({
54
54
  export const support = Agent({ id: "support" }).tools(refundOrder);
55
55
  ```
56
56
 
57
- The tool goes into the manifest as `http`, and the Runtime's
58
- Tool Gate sends the model's input as the JSON body, under the Host's address
59
- policy (`NYLORUN_ENDPOINT_*`), with `content-type: application/json`,
57
+ The method decides where the input goes:
58
+
59
+ | Method | Request |
60
+ | -------------------------------- | -------------------------------------------------------------- |
61
+ | `POST` (default), `PUT`, `PATCH` | The input as the JSON body (`content-type: application/json`) |
62
+ | `GET`, `DELETE` | No body: the input as the query string |
63
+
64
+ The URL's path may name input fields as `{field}`. Each one must be a required string,
65
+ number or boolean field of `input`. Its value goes into the path percent-encoded (so `/`,
66
+ `?` and `#` cannot change the request), and it is not sent again in the body or query
67
+ string. A query string written in the URL is kept, and the input's fields come after it.
68
+ In the query string an array repeats its name (`tag=a&tag=b`) and a `null` is left out.
69
+
70
+ `http()`, and the Runtime when it saves a manifest, refuse a tool whose URL names a field
71
+ the input does not require. For `GET` and `DELETE` they also refuse a field that is an object (a query string cannot hold one), and a
72
+ field that repeats a query parameter the URL already sets. A call is refused before it is
73
+ sent (`tool.invalid-input`) when a URL value is empty, `.` or `..`, or not a string, number
74
+ or boolean.
75
+
76
+ ```ts
77
+ // GET https://api.open-meteo.com/v1/forecast?current=…&latitude=22.57&longitude=88.36
78
+ const forecast = http({
79
+ name: "get_forecast",
80
+ description: "Current weather at a latitude and longitude.",
81
+ input: z.object({
82
+ latitude: z.number(),
83
+ longitude: z.number(),
84
+ temperature_unit: z.enum(["celsius", "fahrenheit"]).optional(),
85
+ }),
86
+ url: "https://api.open-meteo.com/v1/forecast?current=temperature_2m,relative_humidity_2m,wind_speed_10m&timezone=auto",
87
+ method: "GET",
88
+ });
89
+
90
+ // DELETE https://billing.example.com/orders/A-17?reason=duplicate
91
+ const cancelOrder = http({
92
+ name: "cancel_order",
93
+ input: z.object({ orderId: z.string(), reason: z.string() }),
94
+ url: "https://billing.example.com/orders/{orderId}",
95
+ method: "DELETE",
96
+ credential: "billing",
97
+ });
98
+ ```
99
+
100
+ The tool goes into the manifest as `http`, and the Runtime's Tool Gate sends
101
+ the request under the Host's address policy (`NYLORUN_ENDPOINT_*`), with
60
102
  `Nylorun-Session-Id`, `Nylorun-Turn-Id`, `Nylorun-Agent-Id` and an
61
103
  `Idempotency-Key` that is the same when the Runtime re-sends the call after a
62
104
  restart, so your service can drop duplicates. A `2xx` JSON answer is the output
@@ -71,17 +113,21 @@ crash, a restart or a lost connection to the gateway sends the call again with
71
113
  that key, up to 3 sends; after that the model gets `tool.interrupted` ("may
72
114
  have run") and decides. A tool that honors the key runs once; one that ignores
73
115
  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
116
+ `session.toolCalls()` marks where. Both also say where each call ran (`kind`:
117
+ `mcp`, `http`, `sandbox`, `platform` for the Runtime's own tools, or `harness`),
118
+ its `server`, the `skill` it ran and the `plugin` that brought its server or
119
+ skill. A sandbox command (`bash`, `write` and the
75
120
  rest) is never run twice by the Runtime: a crash fails it to the model as
76
121
  `tool.interrupted`. When the model decides the same call again, in a later
77
122
  turn say, that is a new call with a new key: put a business key (an order or
78
123
  charge id) in the tool's input schema to refuse it too.
79
124
 
80
125
  `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`
83
- sends the call there instead, such as a gateway, with the session owner in its
84
- identity header, and a `401` is `credential_rejected` to the model, not retried.
126
+ Environment must hold a credential bound to the tool's exact URL (as written,
127
+ `{field}` parameters included), else the call fails before it is sent
128
+ (`http.credential`). A credential with `via` sends the call there instead, such
129
+ as a gateway, with the session owner in its identity header (a gateway has one
130
+ URL, so a tool with `{field}` parameters fails with `http.credential`), and a `401` is `credential_rejected` to the model, not retried.
85
131
  Whether a call waits for a person is not the tool's to say: it is the Tenant's
86
132
  [tool policy](#tool-policy). An HTTP tool is also a flow stage (see [Flow agents](#flow-agents)), and runs
87
133
  only on the Runtime (a local `run()` reports `http.runtime-only`).
@@ -143,7 +189,7 @@ ask, until it ends.
143
189
  (format 0 to 2), a link without credentials, or a file that does not
144
190
  validate fails with `connection_missing`: run `npx nylorun start` in the
145
191
  project again. The Project files are read by `@nylorun/core/project`, as
146
- `@nylorun/admin`, `nylorun` and `nylo` read them.
192
+ `@nylorun/admin` and `nylorun` read them.
147
193
 
148
194
  Sources never mix. Partial environment fails with `connection_missing`. A
149
195
  Runtime serves one Tenant, so nothing names it: the `tenant` option is gone
@@ -178,12 +224,7 @@ await session.input("Look up item 123", { idempotencyKey: requestId });
178
224
  const history = await session.history();
179
225
  ```
180
226
 
181
- ```sh
182
- eval "$(npx -p nylorun nylo env)"
183
- # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
184
- ```
185
-
186
- Every request sets `Nylorun-Protocol` (11) and no `Nylorun-Tenant`: the Runtime
227
+ Every request sets `Nylorun-Protocol` (14) and no `Nylorun-Tenant`: the Runtime
187
228
  serves one Tenant. Before the first authenticated
188
229
  request, `Transport` fetches `/health` once, checks protocol compatibility, and
189
230
  throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
@@ -203,7 +244,7 @@ const assistant = Agent({ id: "assistant", name: "Order assistant" })
203
244
 
204
245
  Each subdirectory under the catalog must contain a `SKILL.md` with YAML frontmatter (`name`, `description`) per [Agent Skills](https://agentskills.io/home). Every file of a skill's folder, binary included (not `.git/`, `node_modules/`, OS files like `.DS_Store`, or `.env` and `.env.*` files, which hold secrets), is part of the definition: the manifest names each one by path and SHA-256 (`skills.<name>.files`), at most 500 files of 10 MiB each. `saveAgent` uploads the files the Runtime does not hold (`client.files`, `PUT /v1/files/sha256:<hex>`) before it puts the definition, which the Runtime refuses while it names a file it lacks (`400 definition_files_missing`).
205
246
 
206
- The Runtime serves the skills itself, with no call to your process: `load_skill` returns a skill's `SKILL.md` body and the paths of its other files, and `read_skill_resource` returns a text file. A session with a sandbox also has each skill's files read-only under `/skills/<name>/`, so the model can run a skill's scripts with `bash`. Run without a Runtime, the skill tools only say so.
247
+ The Runtime serves the skills itself, with no call to your process. When a session pins the agent, the Runtime adds the skill catalog to the instructions and two tools to the manifest's `runtime` block: `load_skill` returns a skill's `SKILL.md` body and the paths of its other files, and `read_skill_resource` returns a text file. A session with a sandbox also has each skill's files read-only under `/skills/<name>@<hash>/`, so the model can run a skill's scripts with `bash`.
207
248
 
208
249
  Declare MCP servers with `.mcp(...)` (same map shape as agent-plugins `mcpServers`):
209
250
 
@@ -217,16 +258,34 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
217
258
  });
218
259
  ```
219
260
 
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.
261
+ Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the manifest's `mcpServers`. 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's `skills` and `mcpServers`, each labelled `plugin: { name, version }` from its `plugin.json`, and a stdio server in its `mcp.json` throws. Tool names, skill names and server names are each unique within their kind; a tool, a skill and a server may share a name, since a server's tools are called `<server>__<tool>`.
221
262
 
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).
263
+ 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.
223
264
 
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`.
265
+ 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 tenant 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`.
225
266
 
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:
267
+ 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). It is made from one version of an **environment manifest** or from inline fields, and never changes what it runs after that (protocol 14). The agent names no Environment, so the same agent runs in any Tenant; it may state what it needs (`.requires()`). Whether an Environment serves one session, one person or a project is your app's choice:
227
268
 
228
269
  ```ts
229
- // One per person or project: get-or-create by id (or change its recipe), write its secrets, open sessions on it.
270
+ import { environmentManifest } from "@nylorun/agents";
271
+
272
+ // A kind of Environment, saved like an agent: a named, versioned definition (or a JSON file with $schema).
273
+ const analystBox = environmentManifest({
274
+ name: "analyst-box",
275
+ kind: "pod",
276
+ image: "python:3.13-slim",
277
+ network: { allowedHosts: ["pypi.org", "files.pythonhosted.org", "api.github.com"] },
278
+ setup: [{ command: "pip install pandas" }],
279
+ secrets: { GH_TOKEN: { type: "environment_secret", allowedHosts: ["api.github.com"], required: true } },
280
+ });
281
+ const plan = await client.environments.manifests.plan(analystBox); // changes, errors, warnings, Environments behind
282
+ await client.environments.manifests.save(analystBox, { ifMatch: plan.from ?? 0 });
283
+
284
+ // One per person or project, made from the manifest's latest version and pinned to it.
285
+ await client.environments.put("users/alice/analyst", { manifest: "analyst-box", ownerUserId: "alice" });
286
+ await client.environments.secrets.put("users/alice/analyst", "GH_TOKEN", { value: process.env.ALICE_GITHUB_TOKEN! });
287
+
288
+ // Or from inline fields: get-or-create by id, write its secrets, open sessions on it.
230
289
  await client.environments.put("users/alice/web", {
231
290
  kind: "virtual", // the default; "pod" for a container with its own volume, "none" for a vault without compute
232
291
  network: { allowedHosts: ["pypi.org", "files.pythonhosted.org"] }, // no egress unless you list hosts
@@ -249,13 +308,13 @@ const { session: scratch, release } = await client.environments.forSession({
249
308
  await release();
250
309
  ```
251
310
 
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.
311
+ 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 local 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
312
 
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.
313
+ An Environment never changes what it runs (protocol 14, its view's `manifest`): a `put` on an existing one may change its `labels` and owner guard, fields that match are accepted (so get-or-create stays one call), and anything else is `409 environment_immutable`. A save of a new manifest version changes no Environment: make a new one, which can start from the old one's disk (`from`). The Runtime checks what an Environment runs 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
314
 
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)).
315
+ 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)). When what an Environment runs declares a secret's slot (a manifest's `secrets`), send only `{ value }` (or `{ headers }`): the type and binding come from the slot, and a name it does not declare is `409 secret_not_in_manifest`. An agent built with `.requires({ sandbox: "pod", secrets: ["GH_TOKEN"] })` opens a session only on an Environment that has them (`409 environment_unsuitable`). `saveAgent(agent, { dryRun: true })` checks a definition and saves nothing.
257
316
 
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).
317
+ Sessions on one Environment share its `/workspace` and run their turns at the same time (one turn per session); which sessions work on one disk, and when, is your app's call. Stop, reset, snapshot and restore wait until no turn runs (`409 environment_busy`). 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, { types, since, until })` (its lifecycle, and `environment.egress` for every connection its sandbox opens, naming the turns that were running), `stop(id)`, `reset(id)` and `delete(id)` manage them, and `npx nylorun environments list | delete` does the same from the command line. 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).
259
318
 
260
319
  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)):
261
320
 
@@ -296,7 +355,7 @@ The tool is named after the agent's `id` and takes `{ task: string }`; the agent
296
355
 
297
356
  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. 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.
298
357
 
299
- Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, send the next message with a turn manifest that has the specialist's instructions instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, and its tools cannot declare `approval` (keep those on the parent). In the local engine (`@nylorun/harness/run`), a delegated agent's `tool({ run })` tools start with empty `ctx.state`, can read `ctx.agent` (`{ id, path, delegationId }`), and fail with `delegation.interaction-unsupported` on `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor`. Delegation is not an approval boundary; approvals live on tools.
358
+ Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, send the next message with a turn manifest that has the specialist's instructions instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, and cannot pause for input, an approval or a wait (keep those on the parent). In the local engine (`@nylorun/harness/run`), a delegated agent's `tool({ run })` tools start with empty `ctx.state`, can read `ctx.agent` (`{ id, path, delegationId }`), and fail with `delegation.interaction-unsupported` on `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor`. Delegation is not an approval boundary.
300
359
 
301
360
  ## Flow agents
302
361
 
@@ -382,7 +441,8 @@ an answer that does not match `output` fails the stage with its code (`http.stat
382
441
  is retried with the Loop's input, not the feedback.
383
442
 
384
443
  A Loop may be judged by your service instead of an agent: `http({ url, method?,
385
- credential?, timeoutMs? })`, with no name or input, is an HTTP verifier. The Runtime
444
+ credential?, timeoutMs? })`, with no name or input, is an HTTP verifier. Its method is
445
+ `POST`, `PUT` or `PATCH` and its URL has no `{field}` parameters, because the Runtime
386
446
  sends `{ input, output, iteration }` (the Loop's input, the attempt's output, its
387
447
  number) and reads a verdict, `{ pass, feedback? }`, from the answer; anything else, or a
388
448
  failed request, fails the Loop (`loop.verify-failed`). Its verdicts are recorded as
@@ -443,10 +503,11 @@ then `dev`. Agents do not hash the manifest; the Runtime pins the digest of the
443
503
  definition a session opens with. Model selection and model credentials belong to
444
504
  the Tenant.
445
505
 
446
- The Runtime makes a lost HTTP tool call `uncertain` instead of repeating its external effect.
506
+ The Runtime sends a lost MCP or HTTP tool call again with the same key, then fails it to the
507
+ model as `tool.interrupted` (see **How tool calls are delivered**).
447
508
  In the local engine (`@nylorun/harness/run`), `tool({ run })` tools receive state, info,
448
- identity, resume, signal, approval/response helpers and memoized `step`. Arbitrary middleware
449
- closures are rejected for durable definitions. Agent definitions have no `.run()`; explicit
509
+ identity, resume, signal, approval/response helpers and memoized `step`. Agent definitions
510
+ have no `.run()`; explicit
450
511
  local execution is available through `@nylorun/harness/run`.
451
512
 
452
513
  ## AG-UI
@@ -574,8 +635,10 @@ What the Runtime answers (Host feature `a2a-endpoint`):
574
635
  on the same `taskId`.
575
636
  - The `messageId` is the idempotency key: a retried message returns the same
576
637
  task.
577
- - Text parts, or a single data part, in; the turn's output as the artifact
578
- `output` (text, or JSON as a data part). Files are
638
+ - Text parts, a single data part, or text and `raw` file parts in (at most
639
+ 20 MiB each, stored as artifacts of the context's session); the turn's
640
+ output as the artifact `output` (text, or JSON as a data part). A file by
641
+ `url`, or one the Tenant's model does not read, is
579
642
  `ContentTypeNotSupportedError`.
580
643
  - Clients must send `A2A-Version: 1.0` (header or `?A2A-Version=`).
581
644
  - Not yet: `ListTasks`, streaming and `SubscribeToTask`
@@ -707,7 +770,7 @@ The Tenant's signing keys sign capability links and run tokens.
707
770
  needed). Listing, rotating
708
771
  and revoking them is management: `admin.signingKeys.list()`, `.rotate()`
709
772
  (`{ force: true }` for incidents) and `.revoke(kid)` in `@nylorun/admin`, or
710
- `nylo access signing-keys …` from the terminal.
773
+ `nylorun tenant signing-keys …` from the terminal.
711
774
 
712
775
  ## In the browser
713
776
 
package/dist/builder.d.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  import { Agent as CoreAgent, AgentBuilder as CoreAgentBuilder } from "@nylorun/core/define";
2
2
  import type { AgentOptions, ToolSchemaSource } from "@nylorun/core/define";
3
- import { type SkillsOptions } from "./skills/index.js";
4
3
  /**
5
- * The agent builder with the capabilities that read files: `.skills()` and `.plugin()`.
4
+ * The agent builder with the methods that read files: `.skills()` and `.plugin()`.
6
5
  * `@nylorun/agents/define` exports the portable builder from core, without them.
7
6
  */
8
7
  export declare class AgentBuilder<Info = unknown, Schema extends ToolSchemaSource | undefined = undefined, Id extends string = string> extends CoreAgentBuilder<Info, Schema, Id> {
9
8
  /** Load an Agent Skills folder. */
10
- skills(directory: string, options?: SkillsOptions): this;
9
+ skills(directory: string): this;
11
10
  /** Attach an Agent Plugin package. */
12
11
  plugin(directory: string): this;
13
12
  output<S extends ToolSchemaSource>(schema: S): AgentBuilder<Info, S, Id>;
package/dist/builder.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { Agent as CoreAgent, AgentBuilder as CoreAgentBuilder, } from "@nylorun/core/define";
2
- import { skills } from "./skills/index.js";
2
+ import { skills } from "./skills/skills.js";
3
3
  import { plugin } from "./plugins/plugin.js";
4
4
  /**
5
- * The agent builder with the capabilities that read files: `.skills()` and `.plugin()`.
5
+ * The agent builder with the methods that read files: `.skills()` and `.plugin()`.
6
6
  * `@nylorun/agents/define` exports the portable builder from core, without them.
7
7
  */
8
8
  export class AgentBuilder extends CoreAgentBuilder {
9
9
  /** Load an Agent Skills folder. */
10
- skills(directory, options) {
11
- return this.capability(skills(directory, options));
10
+ skills(directory) {
11
+ return this.withPart(skills(directory), "skills()");
12
12
  }
13
13
  /** Attach an Agent Plugin package. */
14
14
  plugin(directory) {
15
- return this.capability(plugin(directory));
15
+ return this.withPart(plugin(directory), "plugin()");
16
16
  }
17
17
  output(schema) {
18
18
  return super.output(schema);
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from "./session-client.js";
2
2
  import { AgentsClient } from "./session-client.js";
3
3
  import type { Destination } from "./http.js";
4
4
  /**
5
- * Build a Tenant API client.
5
+ * Build a Runtime API client.
6
6
  * Explicit connection fields keep today's sync Transport resolution (including
7
7
  * environment fill-ins). With no connection fields, resolves via
8
8
  * `resolveConnection` (environment → project link), which reads the Project's files: it is
@@ -9,7 +9,7 @@ export declare class ConnectionError extends Error {
9
9
  constructor(message: string);
10
10
  }
11
11
  /**
12
- * Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
12
+ * Resolve a Runtime API connection: options → environment → project link (D§7.1). A connection is a
13
13
  * Runtime URL and a key: the Runtime serves one Tenant, so nothing names it.
14
14
  */
15
15
  export declare function resolveConnection(options?: {
@@ -42,7 +42,7 @@ function readProjectLink(cwd) {
42
42
  };
43
43
  }
44
44
  /**
45
- * Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
45
+ * Resolve a Runtime API connection: options → environment → project link (D§7.1). A connection is a
46
46
  * Runtime URL and a key: the Runtime serves one Tenant, so nothing names it.
47
47
  */
48
48
  export async function resolveConnection(options) {