@nylorun/agents 0.20.0-beta → 0.21.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,108 @@
1
1
  # @nylorun/agents
2
2
 
3
+ ## 0.21.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
9
+ - **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.
10
+ - **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.
11
+ - **Studio** lists files by hash on the Environment page.
12
+
13
+ - 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.
14
+
15
+ - **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.
16
+ - `@nylorun/core`: `ForkEnvironmentRequestSchema`, `EnvironmentForkPlanSchema`, `ForkEnvironmentResponseSchema`, `EnvironmentView.forkedFrom`, the `environment.forked` event and `forkedFrom` on `environment.created`.
17
+ - `@nylorun/agents`: `client.environments.fork(id, { id, manifest?, disk?, secrets?, labels?, ownerUserId? })` and `planFork()`.
18
+
19
+ - 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.
20
+
21
+ - **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.
22
+ - **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.
23
+ - **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`.
24
+ - **`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.
25
+ - **Dry runs.** `PUT /v1/agents/{id}?dryRun=true` checks a definition and saves nothing (`dryRun: true` in the answer).
26
+ - `@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`).
27
+ - `@nylorun/agents`: `client.environments.manifests` (`save`, `plan`, `get`, `list`, `versions`, `delete`), `environmentManifest()`, `EnvironmentInput` (was `EnvironmentRecipeInput`), value-only `secrets.put`, and `saveAgent(agent, { dryRun })`.
28
+ - **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.
29
+ - `nylorun environments put` help says what it runs never changes.
30
+
31
+ - 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.
32
+
33
+ - **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`.
34
+ - **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.
35
+ - **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.
36
+ - **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`.
37
+ - **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`.
38
+ - **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.
39
+ - **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.
40
+ - `@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`.
41
+ - `@nylorun/agents`, `@nylorun/admin`, `@nylorun/studio` and `nylorun` send protocol 13.
42
+
43
+ - 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.
44
+
45
+ - **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.
46
+ - **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.
47
+ - **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.
48
+ - **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.
49
+ - **`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`.
50
+ - **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.
51
+ - `@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`.
52
+ - `@nylorun/agents`, `@nylorun/studio` and `nylorun` send protocol 12.
53
+
54
+ - 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.
55
+
56
+ - **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.
57
+ - **`@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.
58
+ - **`@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.
59
+
60
+ - 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".
61
+
62
+ - **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.
63
+ - **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.
64
+ - **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`.
65
+ - `@nylorun/core`: `EgressRunningTurnSchema`, `EgressRunningTurn`; `EgressConnectionPayloadSchema` gains `runningTurns`.
66
+
67
+ - 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`.
68
+ - 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.
69
+
70
+ - **`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.
71
+ - **`kind: "harness"`** (`TOOL_CALL_KINDS`): a tool the turn's harness declared in `hello.features.tools`. Such a call said `platform` before.
72
+ - **`mcp.discovered`** names each server's `plugin`.
73
+ - `@nylorun/core`: `skillOfToolCall`, `pluginOf` and `ToolCallTrace` in `/define`.
74
+ - **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.
75
+
76
+ ### Patch Changes
77
+
78
+ - 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.
79
+
80
+ - **`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.
81
+ - **`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).
82
+ - **`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.
83
+ - **`artifacts`:** `list`, `get`, `upload <file|->` (`--to <id>` for a new version), `download`, `tree`, `diff`, `zip`, `link`, `rm`.
84
+ - **`nylorun` now depends on `@nylorun/agents`.** A release of `@nylorun/agents` releases nylorun too.
85
+ - **`@nylorun/agents`:** an incompatible Runtime's remedy names `npx nylorun@latest start` instead of a `nylorun runtime restart` that no longer exists.
86
+
87
+ - 9b07bdd: **Studio and the starter for environment manifests (protocol 14).**
88
+
89
+ - **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.
90
+ - **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.
91
+ - **The starter** ships `environments/scratch.json` (a virtual workspace with no network). `src/main.ts` saves each manifest in `environments/` beside the agents.
92
+ - `@nylorun/agents` exports the manifest and fork contract types (`EnvironmentManifestView`, `EnvironmentManifestPlan`, `EnvironmentForkPlan`, …).
93
+
94
+ - Pin core to the tested release.
95
+ - Updated dependencies [3a32a57]
96
+ - Updated dependencies [af90d1e]
97
+ - Updated dependencies [104f24f]
98
+ - Updated dependencies [c96b9a8]
99
+ - Updated dependencies [94bf329]
100
+ - Updated dependencies [b5ea2b5]
101
+ - Updated dependencies [aff9bb8]
102
+ - Updated dependencies [f44c018]
103
+ - Updated dependencies [c8e640b]
104
+ - @nylorun/core@0.21.0-beta
105
+
3
106
  ## 0.20.0-beta
4
107
 
5
108
  ### Minor Changes
package/README.md CHANGED
@@ -71,7 +71,10 @@ crash, a restart or a lost connection to the gateway sends the call again with
71
71
  that key, up to 3 sends; after that the model gets `tool.interrupted` ("may
72
72
  have run") and decides. A tool that honors the key runs once; one that ignores
73
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
74
+ `session.toolCalls()` marks where. Both also say where each call ran (`kind`:
75
+ `mcp`, `http`, `sandbox`, `platform` for the Runtime's own tools, or `harness`),
76
+ its `server`, the `skill` it ran and the `plugin` that brought its server or
77
+ skill. A sandbox command (`bash`, `write` and the
75
78
  rest) is never run twice by the Runtime: a crash fails it to the model as
76
79
  `tool.interrupted`. When the model decides the same call again, in a later
77
80
  turn say, that is a new call with a new key: put a business key (an order or
@@ -143,7 +146,7 @@ ask, until it ends.
143
146
  (format 0 to 2), a link without credentials, or a file that does not
144
147
  validate fails with `connection_missing`: run `npx nylorun start` in the
145
148
  project again. The Project files are read by `@nylorun/core/project`, as
146
- `@nylorun/admin`, `nylorun` and `nylo` read them.
149
+ `@nylorun/admin` and `nylorun` read them.
147
150
 
148
151
  Sources never mix. Partial environment fails with `connection_missing`. A
149
152
  Runtime serves one Tenant, so nothing names it: the `tenant` option is gone
@@ -178,12 +181,7 @@ await session.input("Look up item 123", { idempotencyKey: requestId });
178
181
  const history = await session.history();
179
182
  ```
180
183
 
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
184
+ Every request sets `Nylorun-Protocol` (14) and no `Nylorun-Tenant`: the Runtime
187
185
  serves one Tenant. Before the first authenticated
188
186
  request, `Transport` fetches `/health` once, checks protocol compatibility, and
189
187
  throws `IncompatibleRuntimeError` / `incompatible_host` when the Host range or
@@ -203,7 +201,7 @@ const assistant = Agent({ id: "assistant", name: "Order assistant" })
203
201
 
204
202
  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
203
 
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.
204
+ 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
205
 
208
206
  Declare MCP servers with `.mcp(...)` (same map shape as agent-plugins `mcpServers`):
209
207
 
@@ -217,16 +215,34 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
217
215
  });
218
216
  ```
219
217
 
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.
218
+ 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. Names are unique across the agent's tools, skills and servers.
221
219
 
222
220
  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).
223
221
 
224
222
  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`.
225
223
 
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:
224
+ 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
225
 
228
226
  ```ts
229
- // One per person or project: get-or-create by id (or change its recipe), write its secrets, open sessions on it.
227
+ import { environmentManifest } from "@nylorun/agents";
228
+
229
+ // A kind of Environment, saved like an agent: a named, versioned definition (or a JSON file with $schema).
230
+ const analystBox = environmentManifest({
231
+ name: "analyst-box",
232
+ kind: "pod",
233
+ image: "python:3.13-slim",
234
+ network: { allowedHosts: ["pypi.org", "files.pythonhosted.org", "api.github.com"] },
235
+ setup: [{ command: "pip install pandas" }],
236
+ secrets: { GH_TOKEN: { type: "environment_secret", allowedHosts: ["api.github.com"], required: true } },
237
+ });
238
+ const plan = await client.environments.manifests.plan(analystBox); // changes, errors, warnings, Environments behind
239
+ await client.environments.manifests.save(analystBox, { ifMatch: plan.from ?? 0 });
240
+
241
+ // One per person or project, made from the manifest's latest version and pinned to it.
242
+ await client.environments.put("users/alice/analyst", { manifest: "analyst-box", ownerUserId: "alice" });
243
+ await client.environments.secrets.put("users/alice/analyst", "GH_TOKEN", { value: process.env.ALICE_GITHUB_TOKEN! });
244
+
245
+ // Or from inline fields: get-or-create by id, write its secrets, open sessions on it.
230
246
  await client.environments.put("users/alice/web", {
231
247
  kind: "virtual", // the default; "pod" for a container with its own volume, "none" for a vault without compute
232
248
  network: { allowedHosts: ["pypi.org", "files.pythonhosted.org"] }, // no egress unless you list hosts
@@ -249,13 +265,13 @@ const { session: scratch, release } = await client.environments.forSession({
249
265
  await release();
250
266
  ```
251
267
 
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.
268
+ 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
269
 
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.
270
+ 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
271
 
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)).
272
+ 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
273
 
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).
274
+ 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 local 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).
259
275
 
260
276
  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
277
 
@@ -445,8 +461,8 @@ the Tenant.
445
461
 
446
462
  The Runtime makes a lost HTTP tool call `uncertain` instead of repeating its external effect.
447
463
  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
464
+ identity, resume, signal, approval/response helpers and memoized `step`. Agent definitions
465
+ have no `.run()`; explicit
450
466
  local execution is available through `@nylorun/harness/run`.
451
467
 
452
468
  ## AG-UI
@@ -707,7 +723,7 @@ The Tenant's signing keys sign capability links and run tokens.
707
723
  needed). Listing, rotating
708
724
  and revoking them is management: `admin.signingKeys.list()`, `.rotate()`
709
725
  (`{ force: true }` for incidents) and `.revoke(kid)` in `@nylorun/admin`, or
710
- `nylo access signing-keys …` from the terminal.
726
+ `nylorun tenant signing-keys …` from the terminal.
711
727
 
712
728
  ## In the browser
713
729
 
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);
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * Environments (protocol 11, D55, D56): `client.environments`. An Environment is one sandbox
3
- * (kind `virtual`, `pod` or `none`) with a recipe that can change, and one vault of secrets for
4
- * its shell, MCP servers and HTTP tools. It has its own id and outlives the sessions that name
5
- * it; whether it serves one session, one person or a project is the app's choice, made here:
3
+ * (kind `virtual`, `pod` or `none`) made from an environment manifest's version or from inline
4
+ * fields, and one vault of secrets for its shell, MCP servers and HTTP tools. It never changes
5
+ * what it runs after it is made (protocol 14, D63): a new version reaches an agent through a new
6
+ * Environment. It has its own id and outlives the sessions that name it; whether it serves one
7
+ * session, one person or a project is the app's choice, made here:
6
8
  *
7
9
  * - `environments.forSession(...)`: one Environment for one session. It creates the
8
10
  * Environment, opens the session on it, and `release()` deletes the Environment (and its
9
11
  * vault) when the session is done.
10
- * - `environments.put(id, recipe)`: one Environment per person or per project, by a
11
- * deterministic id (`users/alice/web`); get-or-create, or change its recipe, in one call.
12
- * Write its secrets with `environments.secrets.put(id, name, secret)` and open sessions on it
13
- * with `createSession({ environment: id })`.
12
+ * - `environments.put(id, { manifest: "analyst-box" })` (or inline fields): one Environment per
13
+ * person or per project, by a deterministic id (`users/alice/web`); get-or-create in one call.
14
+ * Write its secrets with `environments.secrets.put(id, name, { value })` and open sessions on
15
+ * it with `createSession({ environment: id })`.
16
+ * - `environments.manifests.save(manifest)`: save an environment manifest's next version, like
17
+ * `saveAgent`; `plan(manifest)` answers what a save would do and saves nothing.
14
18
  *
15
19
  * Ids may hold `/`; they are sent percent-encoded as one path segment. Secret values are
16
20
  * write-only: no call reads one back.
@@ -18,14 +22,39 @@
18
22
  * Disk versions (Host feature `environment-disk-versions`): `snapshot(id)` keeps a version of
19
23
  * the Environment's disk between turns, `restore(id, version)` puts one back, and
20
24
  * `put(id, { from: { environment, version } })` creates a new Environment from another's version.
25
+ *
26
+ * `fork(id, { id: next })` is how an Environment gets a new manifest version (Q15): a new
27
+ * Environment made from it, with the source's disk and secrets; `planFork` answers what it would do.
21
28
  */
22
- import { type DeleteEnvironmentResponse, type EnvironmentEvent, type EnvironmentRestore, type EnvironmentSecretView, type EnvironmentSnapshot, type EnvironmentVersions, type EnvironmentView, type PutEnvironmentRequest, type PutEnvironmentSecretRequest } from "@nylorun/core/contracts";
29
+ import { ENVIRONMENT_MANIFEST_SCHEMA_VERSION, type DeleteEnvironmentManifestResponse, type EnvironmentManifest, type EnvironmentManifestPlan, type EnvironmentManifestVersions, type EnvironmentForkPlan, type EnvironmentManifestView, type ForkEnvironmentRequest, type SaveEnvironmentManifestResponse, type DeleteEnvironmentResponse, type EnvironmentEvent, type EnvironmentEventType, type EnvironmentRestore, type EnvironmentSecretView, type EnvironmentSnapshot, type EnvironmentVersions, type EnvironmentView, type PutEnvironmentRequest, type PutEnvironmentSecretRequest } from "@nylorun/core/contracts";
23
30
  import { type Transport } from "./http.js";
24
31
  import type { CreateSessionOptions, SessionClient } from "./session-client.js";
25
- /** What an Environment is made of: its kind, recipe, labels and owner guard. */
26
- export type EnvironmentRecipeInput = Omit<PutEnvironmentRequest, "requestId">;
27
- /** One secret of an Environment's vault, as written. */
28
- export type EnvironmentSecretInput = PutEnvironmentSecretRequest;
32
+ /**
33
+ * What an Environment is made from: an environment manifest (`manifest: "name"` or `"name@n"`)
34
+ * or the inline fields, and its labels, owner guard and `from`.
35
+ */
36
+ export type EnvironmentInput = Omit<PutEnvironmentRequest, "requestId">;
37
+ /**
38
+ * One secret of an Environment's vault, as written: typed, or only its value (`{ value }`, or
39
+ * `{ headers }` for a `headers` secret) when what the Environment runs declares its slot (D64).
40
+ */
41
+ export type EnvironmentSecretInput = PutEnvironmentSecretRequest | {
42
+ readonly value: string;
43
+ } | {
44
+ readonly headers: Readonly<Record<string, string>>;
45
+ };
46
+ /** An environment manifest as authored: `manifestSchemaVersion` may be left out. */
47
+ export type EnvironmentManifestInput = Omit<EnvironmentManifest, "manifestSchemaVersion"> & {
48
+ readonly manifestSchemaVersion?: typeof ENVIRONMENT_MANIFEST_SCHEMA_VERSION;
49
+ /** For editors; dropped here, as the CLI drops it. */
50
+ readonly $schema?: string;
51
+ };
52
+ /**
53
+ * An environment manifest (protocol 14, D63): checked now against the schema the Runtime
54
+ * enforces, with `manifestSchemaVersion` filled in and `$schema` dropped. Save it with
55
+ * `client.environments.manifests.save()`, or make an Environment from it inline.
56
+ */
57
+ export declare function environmentManifest(input: EnvironmentManifestInput): EnvironmentManifest;
29
58
  /** A session opened on an Environment of its own (`environments.forSession`). */
30
59
  export interface SessionEnvironmentHandle {
31
60
  readonly session: SessionClient;
@@ -38,8 +67,11 @@ export interface SessionEnvironmentHandle {
38
67
  export interface ForSessionOptions {
39
68
  /** The session to open, as `createSession` takes it, without `environment`. */
40
69
  readonly session: Omit<CreateSessionOptions, "environment">;
41
- /** The Environment's recipe and labels. Default: a virtual one within the Tenant's defaults. */
42
- readonly recipe?: EnvironmentRecipeInput;
70
+ /**
71
+ * What the Environment is made from (a `manifest` or inline fields) and its labels. Default: a
72
+ * virtual one within the Tenant's defaults.
73
+ */
74
+ readonly recipe?: EnvironmentInput;
43
75
  /** Secrets to write before the session opens, by name. */
44
76
  readonly secrets?: Readonly<Record<string, EnvironmentSecretInput>>;
45
77
  /** The Environment's id. Default: `sessions/<session id>`. */
@@ -54,7 +86,9 @@ export declare class EnvironmentSecretsClient {
54
86
  /**
55
87
  * Creates or replaces secret `name` (`PUT /v1/environments/{id}/secrets/{name}`): for
56
88
  * `environment_secret` and `environment_variable` the name is the variable's. A second
57
- * credential for a host or a URL is a 409 `credential_conflict`.
89
+ * credential for a host or a URL is a 409 `credential_conflict`. When what the Environment
90
+ * runs declares the secret's slot (D64), send only `{ value }` (or `{ headers }`); a name it
91
+ * does not declare is `409 secret_not_in_manifest`.
58
92
  */
59
93
  put(environmentId: string, name: string, secret: EnvironmentSecretInput, options?: {
60
94
  signal?: AbortSignal;
@@ -72,6 +106,64 @@ export declare class EnvironmentSecretsClient {
72
106
  id: string;
73
107
  }>;
74
108
  }
109
+ /**
110
+ * Environment manifests (protocol 14, D63): `client.environments.manifests`. A save writes the
111
+ * manifest's next version, or answers the latest unchanged, and changes no Environment: an
112
+ * Environment keeps the version it was made from.
113
+ */
114
+ /** The bytes of files by hash, by `sha256:<hex>` (`environmentFiles(...).bytes`). */
115
+ export type EnvironmentFileBytes = Readonly<Record<string, Uint8Array>>;
116
+ /**
117
+ * A manifest `files` entry by content hash (Q6): `contents` (each file's path under `path` and
118
+ * its bytes or text) hashed, as `entry` for the manifest and `bytes` for `manifests.save`'s
119
+ * `files`, which uploads them. Each file holds at most 10 MiB.
120
+ */
121
+ export declare function environmentFiles(path: string, contents: Readonly<Record<string, Uint8Array | string>>): Promise<{
122
+ entry: {
123
+ path: string;
124
+ files: Record<string, string>;
125
+ };
126
+ bytes: Record<string, Uint8Array>;
127
+ }>;
128
+ export declare class EnvironmentManifestsClient {
129
+ private readonly transport;
130
+ constructor(transport: Transport);
131
+ private path;
132
+ private send;
133
+ /**
134
+ * Saves `manifest` (`PUT /v1/environment-manifests/{name}`). `ifMatch`: the version you read
135
+ * (`0` for none yet); a save over a newer one is `412 manifest_changed`. `files`: the bytes of
136
+ * its files by hash (`environmentFiles(...).bytes`), uploaded first when the Runtime lacks them.
137
+ */
138
+ save(manifest: EnvironmentManifestInput, options?: {
139
+ ifMatch?: number;
140
+ files?: EnvironmentFileBytes;
141
+ signal?: AbortSignal;
142
+ }): Promise<SaveEnvironmentManifestResponse>;
143
+ /** What saving `manifest` would do (`?dryRun=true`): changes, errors, warnings, and the Environments behind. */
144
+ plan(manifest: EnvironmentManifestInput, options?: {
145
+ ifMatch?: number;
146
+ files?: EnvironmentFileBytes;
147
+ signal?: AbortSignal;
148
+ }): Promise<EnvironmentManifestPlan>;
149
+ /** The latest version of manifest `name`, or version `version`. */
150
+ get(name: string, options?: {
151
+ version?: number;
152
+ signal?: AbortSignal;
153
+ }): Promise<EnvironmentManifestView>;
154
+ /** Each manifest's latest version. */
155
+ list(options?: {
156
+ signal?: AbortSignal;
157
+ }): Promise<EnvironmentManifestView[]>;
158
+ /** Every version of manifest `name`, newest first. */
159
+ versions(name: string, options?: {
160
+ signal?: AbortSignal;
161
+ }): Promise<EnvironmentManifestVersions>;
162
+ /** Deletes every version; refused while an Environment was made from one (`409 manifest_in_use`). */
163
+ delete(name: string, options?: {
164
+ signal?: AbortSignal;
165
+ }): Promise<DeleteEnvironmentManifestResponse>;
166
+ }
75
167
  export declare class EnvironmentsClient {
76
168
  private readonly transport;
77
169
  /** `AgentsClient.createSession`, bound. */
@@ -82,13 +174,17 @@ export declare class EnvironmentsClient {
82
174
  private path;
83
175
  /** The Environment's secrets. */
84
176
  get secrets(): EnvironmentSecretsClient;
177
+ /** Environment manifests (protocol 14, D63): named, versioned definitions of a kind of Environment. */
178
+ get manifests(): EnvironmentManifestsClient;
85
179
  /**
86
180
  * Creates the Environment and its vault, or finds the one with this id
87
- * (`PUT /v1/environments/{id}`). On an existing one, `kind` and `storage` are fixed (a
88
- * different one is a 409), labels replace, and any other change writes the recipe and bumps
89
- * its `version`: a pod's image and resources apply when it next starts from stopped.
181
+ * (`PUT /v1/environments/{id}`), from an environment manifest (`{ manifest: "name" }` takes
182
+ * the latest version, `"name@n"` version n) or from inline fields. It never changes what it
183
+ * runs after that (protocol 14): on an existing one, labels replace and `ownerUserId` sets or
184
+ * clears the owner guard, fields that match are accepted, and anything else is
185
+ * `409 environment_immutable`.
90
186
  */
91
- put(environmentId: string, recipe?: EnvironmentRecipeInput, options?: {
187
+ put(environmentId: string, recipe?: EnvironmentInput, options?: {
92
188
  signal?: AbortSignal;
93
189
  }): Promise<EnvironmentView>;
94
190
  get(environmentId: string, options?: {
@@ -109,7 +205,7 @@ export declare class EnvironmentsClient {
109
205
  id: string;
110
206
  kind: "none" | "pod" | "virtual";
111
207
  labels: Record<string, string>;
112
- recipe: {
208
+ manifest: {
113
209
  image?: string | undefined;
114
210
  resources?: {
115
211
  cpus: number;
@@ -129,12 +225,60 @@ export declare class EnvironmentsClient {
129
225
  cwd?: string | undefined;
130
226
  timeout?: string | undefined;
131
227
  }[] | undefined;
132
- files?: {
228
+ files?: ({
133
229
  artifactId: string;
134
230
  version: number;
135
231
  path: string;
136
- }[] | undefined;
232
+ } | {
233
+ path: string;
234
+ files: Record<string, string>;
235
+ })[] | undefined;
236
+ secrets?: Record<string, {
237
+ required?: boolean | undefined;
238
+ description?: string | undefined;
239
+ type: "environment_secret";
240
+ allowedHosts: string[];
241
+ inject?: {
242
+ header: string;
243
+ format: string;
244
+ } | undefined;
245
+ } | {
246
+ required?: boolean | undefined;
247
+ description?: string | undefined;
248
+ type: "environment_variable";
249
+ } | {
250
+ via?: string | undefined;
251
+ identity?: {
252
+ header: string;
253
+ } | undefined;
254
+ required?: boolean | undefined;
255
+ description?: string | undefined;
256
+ type: "bearer";
257
+ url: string;
258
+ } | {
259
+ via?: string | undefined;
260
+ identity?: {
261
+ header: string;
262
+ } | undefined;
263
+ required?: boolean | undefined;
264
+ description?: string | undefined;
265
+ type: "headers";
266
+ url: string;
267
+ }> | undefined;
137
268
  };
269
+ manifestName?: string | undefined;
270
+ manifestVersion?: number | undefined;
271
+ manifestHash?: string | undefined;
272
+ forkedFrom?: {
273
+ environment: string;
274
+ disk: number | null;
275
+ } | undefined;
276
+ slots?: {
277
+ name: string;
278
+ type: string;
279
+ required: boolean;
280
+ set: boolean;
281
+ }[] | undefined;
138
282
  version: number;
139
283
  appliedVersion: number;
140
284
  ownerUserId: string | null;
@@ -145,6 +289,7 @@ export declare class EnvironmentsClient {
145
289
  volumeGeneration: number;
146
290
  hostEpoch: number;
147
291
  reason?: string | undefined;
292
+ imageDigest?: string | undefined;
148
293
  } | undefined;
149
294
  lastUsedAt: string;
150
295
  disk?: {
@@ -186,6 +331,24 @@ export declare class EnvironmentsClient {
186
331
  restore(environmentId: string, version: number | "latest", options?: {
187
332
  signal?: AbortSignal;
188
333
  }): Promise<EnvironmentRestore>;
334
+ /**
335
+ * Forks the Environment into a new one, `options.id` (Q15): how an Environment gets a new
336
+ * manifest version, since none ever changes what it runs. It is made from `options.manifest`
337
+ * (default the source's manifest at its latest version), its disk starts from `options.disk`
338
+ * (`latest`, the default, snapshots first; a version; or `empty`) and the secrets the new
339
+ * manifest declares are copied (`options.secrets: "none"` copies none). The source keeps what
340
+ * it runs, its disk and its sessions. Answers the new Environment and the plan it ran.
341
+ */
342
+ fork(environmentId: string, options: Omit<ForkEnvironmentRequest, "requestId" | "dryRun"> & {
343
+ signal?: AbortSignal;
344
+ }): Promise<{
345
+ environment: EnvironmentView;
346
+ plan: EnvironmentForkPlan;
347
+ }>;
348
+ /** What `fork` would do (the disk, the secrets copied and left behind, the changes), making nothing. */
349
+ planFork(environmentId: string, options: Omit<ForkEnvironmentRequest, "requestId" | "dryRun"> & {
350
+ signal?: AbortSignal;
351
+ }): Promise<EnvironmentForkPlan>;
189
352
  /** The Environment's disk versions, newest first. */
190
353
  versions(environmentId: string, options?: {
191
354
  signal?: AbortSignal;
@@ -202,9 +365,16 @@ export declare class EnvironmentsClient {
202
365
  delete(environmentId: string, options?: {
203
366
  signal?: AbortSignal;
204
367
  }): Promise<DeleteEnvironmentResponse>;
205
- /** The Environment's lifecycle events, from `from` on. */
368
+ /**
369
+ * The Environment's events, from `from` on: its lifecycle, and `environment.egress` for every
370
+ * connection its sandbox opens (with the turns that were running). `types` keeps only those
371
+ * types; `since` and `until` keep events recorded in that window (ISO times or `Date`s).
372
+ */
206
373
  events(environmentId: string, options?: {
207
374
  from?: number;
375
+ types?: readonly EnvironmentEventType[];
376
+ since?: string | Date;
377
+ until?: string | Date;
208
378
  signal?: AbortSignal;
209
379
  }): Promise<EnvironmentEvent[]>;
210
380
  /**