@nylorun/runtime 0.22.0-beta → 0.24.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 +161 -0
- package/README.md +95 -30
- package/dist/adapters/execution/restate.d.ts +15 -3
- package/dist/adapters/execution/restate.js +78 -84
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/adapters/streams/s2.js +1 -13
- package/dist/api/a2a/endpoint.js +5 -1
- package/dist/api/a2a/routes.d.ts +2 -0
- package/dist/api/a2a/routes.js +2 -2
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +4 -2
- package/dist/api/ag-ui/history.js +4 -1
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +11 -10
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +4953 -1204
- package/dist/api/components.js +25 -16
- package/dist/api/gate/keys.js +7 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +39 -3
- package/dist/api/http/app.js +4 -4
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +283 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +45 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +11 -6
- package/dist/api/http/routes/tenant.js +32 -10
- package/dist/api/openapi.js +31 -26
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +563 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +1 -1
- package/dist/artifacts/workspace.js +1 -1
- package/dist/core/flow-host.d.ts +3 -22
- package/dist/core/flow-host.js +11 -37
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +10 -43
- package/dist/execution/types.d.ts +11 -9
- package/dist/execution/types.js +5 -7
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +28 -1
- package/dist/gates/egress.js +115 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +6 -2
- package/dist/gates/http-tool.js +3 -5
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +52 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +5 -1
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +134 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +10 -68
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +12 -5
- package/dist/harness/calls.js +21 -12
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +18 -5
- package/dist/harness/pod.d.ts +34 -0
- package/dist/harness/pod.js +41 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +13 -5
- package/dist/harness/setup.d.ts +27 -0
- package/dist/harness/setup.js +150 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +174 -44
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +8 -0
- package/dist/harness-api/server.js +93 -13
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +96 -0
- package/dist/harness-api/tool-journal.js +102 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +6 -3
- package/dist/harness-api/workspace.js +27 -11
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/execution.d.ts +8 -6
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +64 -21
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +70 -0
- package/dist/host/stack-config.js +67 -1
- package/dist/infra/execution.d.ts +18 -1
- package/dist/infra/execution.js +28 -4
- package/dist/infra/index.d.ts +1 -1
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +5 -5
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +999 -2211
- package/dist/mcp/connect.d.ts +25 -20
- package/dist/mcp/connect.js +29 -30
- package/dist/mcp/pool.d.ts +2 -2
- package/dist/mcp/pool.js +1 -1
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -20
- package/dist/mcp/snapshot.d.ts +5 -11
- package/dist/mcp/snapshot.js +6 -7
- package/dist/model/pi-model.js +8 -0
- package/dist/openapi.json +6955 -2526
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +34 -0
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -20
- package/dist/sandbox/manager.d.ts +16 -8
- package/dist/sandbox/manager.js +33 -17
- package/dist/sandbox/placement.d.ts +13 -12
- package/dist/sandbox/placement.js +29 -12
- package/dist/sandbox/pods/client.d.ts +6 -1
- package/dist/sandbox/pods/lifecycle.d.ts +28 -27
- package/dist/sandbox/pods/lifecycle.js +14 -26
- package/dist/sandbox/pods/reconcile.js +21 -15
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +16 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +35 -27
- package/dist/sandbox/resolve.js +66 -41
- package/dist/sandbox/session-sandbox.js +1 -1
- package/dist/sandbox/setup.d.ts +61 -0
- package/dist/sandbox/setup.js +123 -0
- package/dist/sandbox/share.d.ts +11 -21
- package/dist/sandbox/share.js +12 -41
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/control.js +8 -1
- package/dist/store/postgres/drizzle/0018_wake_outbox_retries.sql +3 -0
- package/dist/store/postgres/drizzle/0019_sandbox_signal_outbox.sql +18 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +117 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1213 -779
- package/dist/store/postgres/schema.js +136 -76
- package/dist/store/postgres/store.js +283 -117
- package/dist/store/types.d.ts +206 -88
- package/dist/streams/relay/core.js +1 -13
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +121 -49
- package/dist/tenant/artifact-tool.js +3 -4
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +94 -14
- package/dist/tenant/context.d.ts +67 -33
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/control.d.ts +8 -1
- package/dist/tenant/control.js +2 -0
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +37 -20
- package/dist/tenant/environments.d.ts +167 -0
- package/dist/tenant/environments.js +823 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +30 -4
- package/dist/tenant/scheduler.d.ts +7 -0
- package/dist/tenant/scheduler.js +38 -7
- package/dist/tenant/session-streams.d.ts +0 -1
- package/dist/tenant/session-streams.js +1 -13
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +13 -13
- package/dist/tenant/sessions.js +61 -138
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/streams.js +14 -29
- package/dist/tenant/sweep.d.ts +39 -12
- package/dist/tenant/sweep.js +168 -63
- package/dist/tenant/transcript.d.ts +210 -20
- package/dist/tenant/transcript.js +62 -11
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +42 -0
- package/dist/traces/ids.js +85 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +317 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +64 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/service.d.ts +47 -48
- package/dist/vault/service.js +321 -382
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +14 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/tenant/sandboxes.d.ts +0 -122
- package/dist/tenant/sandboxes.js +0 -562
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,166 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.24.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e917bd6: **Bring your own harness: harness ids, harness-owned state, and who wrote each event (R4.1, R4.2, R4.4, R4.9).**
|
|
8
|
+
|
|
9
|
+
- **Harness ids and routing.** An Environment's `harness` takes any harness id (`^[a-z][a-z0-9-]{1,62}$`). It is no longer only `nylorun`.
|
|
10
|
+
- **The pin.** A session pins its harness when it opens: its Environment's harness, else `nylorun`. A flow always runs `nylorun`. `GET /v1/sessions/{id}` returns the pin as `harness`.
|
|
11
|
+
- **Routing.** Only a connection whose `hello.name` is that id takes the session's runs.
|
|
12
|
+
- **Our harness's name.** It now says `nylorun` in `hello`. A harness that says `@nylorun/harness` is refused with a message naming the new id, so an older harness image needs this Runtime's.
|
|
13
|
+
- **Placement.** It is keyed by the real harness id, and `"*": {hosts: []}` refuses any harness the map does not name.
|
|
14
|
+
- **Drift.** A turn on an Environment that now runs another harness is `409 harness_mismatch`; open a new session. For a pod Environment, a changed `harness` applies at the pod's next wake, like its image.
|
|
15
|
+
- **Per-harness tokens.** `nylorun-operate harness token put <id>` makes a custom harness container's own Harness API credential, bound to its id; `list` and `rm` manage them. `NYLORUN_HARNESS_TOKEN` stays ours, bound to `nylorun`.
|
|
16
|
+
- **A custom harness as a pod's image.** A pod Environment whose harness is not ours runs its `image` as the harness: the image's own entrypoint (`entrypoint: "image"` to the sandboxes service), with nothing copied in. It needs `image`.
|
|
17
|
+
- **Harness-owned state.** Core stores a harness's turn state as sent and never reads it. Our engine now aligns its state with the turn's manifest itself.
|
|
18
|
+
- **Limits for a custom harness.** Its state is at most 256 KiB, or its turn fails. It cannot pause for approvals in Harness API 2.
|
|
19
|
+
- **Native tools.** It may declare the tools it runs itself in `hello` (`capabilities.tools`). Their calls (capability `harness`) are journaled like built-ins, and the log records a real `tool.completed` instead of `tool.unavailable`.
|
|
20
|
+
- **Who wrote each event.** The writer sets an event's `source` and `evidence`; the event's type no longer decides them. Events written before this release still say `loop` or `api`.
|
|
21
|
+
- **`client`**: a command, with a new `principal` taken from its credential.
|
|
22
|
+
- **`harness`**: what a harness reported, with its harness id and `runId`. Evidence `claimed`.
|
|
23
|
+
- **`core`**: core's own logic. Evidence `observed`.
|
|
24
|
+
- **`effectId` on model calls.** `GET /v1/sessions/{id}/calls/model` returns each call's `effectId`, so a metered call joins the effect journal.
|
|
25
|
+
- **The published contract grows.** It adds `HarnessCheckpointSchema` (the checkpoint fields a harness reads), `NativeToolSchema`, the harness id helpers, `HARNESS_TOOLS_CAPABILITY`, `MAX_HARNESS_STATE_BYTES`, and the gate error codes `key_required` and `effect_not_journaled`. It also documents the effect context keys, the model request hash rule and how a turn's status maps to its output method.
|
|
26
|
+
|
|
27
|
+
- 5165451: **A pod's disk can start prepared, and every Environment's disk has versions (R2d steps 2 and 3).** Before, a new pod volume was empty and a disk could only be kept or reset. Now:
|
|
28
|
+
|
|
29
|
+
- **Setup and input files** (pods, Host feature `environment-setup`): `PUT /v1/environments/{id}` takes `files` (versions of Tenant-wide artifacts, copied in: a file at its `path`, a folder under it) and `setup` (commands run in order with `/bin/sh -c`, `cwd` default `/workspace`, `timeout` default 10m and at most 30m, at most 16 of each). They run once per new disk, after the pod's engine joins and before its first turn, with the Environment's variables, secrets (as the sentinel) and egress. A failed command fails the pod (`observed: failed`, reason `setup: command N …`, an `environment.failed` event with the exit code and the end of the output) and the turn waiting for it; the next turn start runs the setup again. A virtual Environment refuses both with a `400`.
|
|
30
|
+
- **Disk versions** (Host feature `environment-disk-versions`): `POST /v1/environments/{id}/snapshot` keeps a version of the disk between turns (`/workspace`, and a pod's `/harness` but the engine's own files) as a version of a folder artifact the Environment owns, with modes, modification times, links and empty directories (folder manifest `nylorun.folder.v2`); each file's bytes are stored once, and an unchanged disk makes no version. `POST .../restore` puts a version back: a virtual Environment at once, a pod on a new volume seeded before its next turn (`disk.operation` in the view meanwhile; turns wait). `PUT` with `from: { environment, version }` creates an Environment from another's version. `GET .../versions` and `DELETE .../versions/{n}` list and delete them; their trees, files, diffs and zips read through `/v1/artifacts`. A lost pod volume with a version is recovered from the latest one instead of refusing turns until a reset. Snapshot and restore during a turn are `409 environment_busy`. `EnvironmentView.disk` gives the latest version and the copy in progress. Migration `0021_disk_versions`.
|
|
31
|
+
- `@nylorun/agents`: `client.environments.snapshot`, `restore`, `versions` and `deleteVersion`; `put` takes `setup`, `files` and `from`.
|
|
32
|
+
|
|
33
|
+
- 35e29b7: **Environments replace sandboxes and vaults (protocol 11). This is a fresh start: upgrading deletes your sandboxes, their disks and every vault except the model's, and closes every open session (its history stays readable).** List what you have before upgrading; see "Environments replace sandboxes and vaults" in MIGRATION.md.
|
|
34
|
+
|
|
35
|
+
An Environment is where sessions run: one sandbox (kind `virtual`, `pod` or `none`) with a recipe that can change, and one vault of secrets for its shell, MCP servers and HTTP tools. The app decides who uses which Environment; the Runtime keeps it by id and holds its lifecycle.
|
|
36
|
+
|
|
37
|
+
- `@nylorun/core`: protocol 11 (the Host accepts 11 only; older clients get `426`), required feature `environments`. `PutEnvironmentRequestSchema`, `EnvironmentViewSchema` (`recipe`, `version`, `appliedVersion`, `ownerUserId`, `lastUsedAt`), `PutEnvironmentSecretRequestSchema`, `EnvironmentSecretViewSchema`, `ENVIRONMENT_EVENT_TYPES`, `TenantEnvironmentsConfigSchema`, `environmentGranted`. `PutSessionRequest.environment` replaces `sandbox`, `vaultIds` and `credentialSelections`; `SessionView.environment` replaces the session's sandbox and vault fields. Error codes `environment_busy`, `environment_unavailable`, `environment_lost`, `environment_not_granted`, `environment_owner_mismatch` and `session_closed` (the `sandbox_*` codes and `sandbox_expired` are gone); session events `environment.state`, `environment.exec` and `environment.attached`; scope `environments:write`. Credential coverage takes `environmentId` and answers `covered` or `missing`; the MCP preview takes `environmentId`. The vault Management schemas are removed.
|
|
38
|
+
- `@nylorun/runtime`: `/v1/environments/{id}` replaces `/v1/sandboxes/{id}`: `PUT` creates, finds or changes one (`kind` and `storage` fixed; a recipe change bumps `version`; a pod gets a new image or resources at its next start from stopped, a new allowlist within seconds), with list by label, events (`nylorun.environment-event/1`), stop, reset and delete. Each Environment owns one vault: `PUT|GET|DELETE /v1/environments/{id}/secrets/{name}` on the Runtime API, write-only, with a second credential for a host or URL refused at the write (`409 credential_conflict`). An optional owner guard (`ownerUserId`) is checked when a session opens and at every turn start. `lifecycle.deleteAfter` deletes an Environment unused that long; TTL and `onExpiry` are gone. Kind `none` holds secrets for MCP-only agents. `/v1/tenant/vaults`, user and installation vaults, the Tenant's default sandbox, inline sandboxes and `{ session }` sharing are removed. The Tenant setting is `environments.config` (`limits.environments`). Identity files grant `environments:` (an old `sandboxes:` key, or `sandboxes:write`, stops the Runtime with a message naming the new key). Migration `0020_environments`.
|
|
39
|
+
- `@nylorun/harness`: the Harness API claims `environment.state` and `environment.exec`, and its workspace fields name `environmentId`.
|
|
40
|
+
- `@nylorun/agents`: `client.environments` (`put`, `get`, `list`, `page`, `stop`, `reset`, `delete`, `events`, `secrets.put|list|get|delete`, `forSession`) replaces `client.sandboxes`; `createSession({ environment })`; `sessions.page({ environmentId })` filters by Environment. The AG-UI handler's session options take `environment`.
|
|
41
|
+
- `@nylorun/admin`: `admin.coverage({ agentId, environmentId })` replaces `admin.vaults`; `admin.mcp.preview({ url, environmentId })`.
|
|
42
|
+
- `@nylorun/studio`: Environments replace Sandboxes, with a create form, stop, reset, delete and their secrets; the Credentials forms write an Environment's secrets; New session picks an Environment and shows its coverage.
|
|
43
|
+
- `nylorun`: `nylorun sandbox ls|rm` list and delete Environments; `nylorun mcp inspect --environment <id>` replaces `--vault`; `nylo reset --environments` replaces `--sandboxes`; `nylo doctor sandbox` reports the Environment limits.
|
|
44
|
+
|
|
45
|
+
- e917bd6: **The Harness API is a published contract (R4.5).** Everything a harness sends or reads now has a schema in `@nylorun/core/harness-api`, and the Runtime validates with those same schemas:
|
|
46
|
+
- the transport and the pod join (`HARNESS_API_PATH`, `HARNESS_API_HEADER`, `HOST_JOIN_PATH`, `HostJoinRequestSchema`, `HostJoinAnswerSchema`);
|
|
47
|
+
- the Model and Tool Gate bodies (`ModelCallBodySchema`, `ToolCallBodySchema`, `HttpCallBodySchema`, the MCP bodies, `GATE_ERROR_CODES`);
|
|
48
|
+
- the MCP snapshot (`McpSnapshotSchema`);
|
|
49
|
+
- transcript entries (`TranscriptEntrySchema`);
|
|
50
|
+
- the effect id rule (`effectIdOf`, `isEffectIdOf`).
|
|
51
|
+
|
|
52
|
+
The build also writes them as one JSON Schema document, `@nylorun/core/harness-api/schema.json`, for harnesses written in other languages.
|
|
53
|
+
|
|
54
|
+
Other changes:
|
|
55
|
+
- `TurnStart` gains an optional `trace` (W3C `traceparent`) for the turn's trace context. Nothing fills it yet.
|
|
56
|
+
- Core refuses a `hello` whose `api` is not `2`.
|
|
57
|
+
- **The fixture model answers at the Model Gate (R4.3).** With a Tenant's `fixtureModel` seeded, the gate answers model calls itself and meters each one, like any model: a `model_usage` row with provider `fixture` and cost 0, readable at `GET /v1/sessions/{id}/calls/model`. Our harness no longer answers it locally.
|
|
58
|
+
- The fixture model also answers scripted markers: `fixture:text`, `fixture:tool <name> <json>`, `fixture:budget` and `fixture:slow <seconds>`.
|
|
59
|
+
- **The sandboxes service can run a harness image (R4.4, Go).** `PUT /v1/pods/{name}` accepts `entrypoint: "image"`: the workload image's own `ENTRYPOINT` and `CMD` run, with no engine copied in and no `harnessImage`. The default, `entrypoint: "engine"`, is unchanged. The Runtime starts sending it with the harness pin in a later change.
|
|
60
|
+
|
|
61
|
+
- 3013501: **Tool calls are delivered at least once, and every one is on the record (R5 step 1).** A gateway restart used to park the sessions it interrupted as `uncertain` until someone cancelled them, a harness could call tools with no record, and nothing listed a session's tool calls. Now every tool call ends in the log with its outcome, a crash sends it again with the same key, and the gateway runs only calls the effect journal recorded.
|
|
62
|
+
|
|
63
|
+
- **Every outcome in the log.** Each tool call ends in one `tool.completed` with `status` (`completed`: the tool answered; `failed`: the Runtime ended it, `tool.disabled`, `tool.unavailable` or `tool.interrupted`; `cancelled`), `kind` (`mcp`, `http`, `sandbox`, `platform`), `server`, `attempts` and `durationMs`. Refused and cancelled calls are in the log for the first time.
|
|
64
|
+
- **`GET /v1/sessions/{id}/calls/tool`** pages a session's tool calls from the effect journal, in the order they were first sent: tool, input, status, sends, error and timing. Same credentials and paging as `/calls/model`; `@nylorun/agents` adds `session.toolCalls()`. Host feature `tool-calls`.
|
|
65
|
+
- **At least once, with the same key.** An MCP or HTTP tool call that a crash, a shutdown or a lost gateway hop interrupted is sent again with its effect id as the key, up to 3 sends; then it fails to the model as `tool.interrupted` ("may have run") and the session continues. A gateway that restarts mid call runs the call again under its key instead of answering `uncertain`. HTTP tools get the key as `Idempotency-Key`, as before; MCP servers now get it in `_meta["nylorun/idempotency-key"]` on `tools/call`. A tool that honors the key acts once; `attempts` above 1 marks where one may have acted twice.
|
|
66
|
+
- **Sandbox commands are never run twice.** A sandbox or core-served call a crash interrupted has no key to dedupe on, so it fails to the model as `tool.interrupted` at once, and the model decides whether to run it again.
|
|
67
|
+
- **No tool call parks a session.** The `uncertain` state for tool calls, its cancel-only exit and R2b's read-only/idempotent `mcp.lost` exception are gone: a lost MCP answer is `mcp.lost` to the model for every tool. Model calls keep their own rules.
|
|
68
|
+
- **The gateway runs only journaled calls.** Under a run token, `/nylorun/v1/tool-calls` and `/nylorun/v1/http-calls` need the call's effect id as `Idempotency-Key` (`400 key_required`), and the effect journal must hold it running, for the run's turn, the same tool and the same input (`409 effect_not_journaled`). A harness that skips the journal gets nothing run.
|
|
69
|
+
- `@nylorun/harness`: `HarnessExecutors.resends(effect, error, run)` says a gate hop lost a call's answer; the harness then asks core again with the same intent and sends the call again.
|
|
70
|
+
- **Approval** stays in the manifest and our harness still asks for it. Only the Runtime's own harness asks: one built from the Harness API can skip it until tool policy moves to the gateway (R5 step 2).
|
|
71
|
+
|
|
72
|
+
- 6061a3a: **Tool approvals are the Tenant's tool policy (R5 step 2).** An agent's manifest used to say which tool calls wait for a person, and only the Runtime's own harness asked; a harness built from the Harness API could skip it. Now the operator sets one tool policy for every agent and every harness, and the Runtime enforces it before anything is sent.
|
|
73
|
+
|
|
74
|
+
- **`GET`/`PUT /v1/tenant/tool-policy`** (the Management API; `admin.toolPolicy.get()`/`.put({ rules })`): rules `{ effect: "allow" | "ask" | "deny", tool?, server?, kind?, agent?, when?, reason? }`. A rule matches a call when every selector it sets matches (`tool` and `server` take `*` globs; `when` tests input fields by dotted path with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). The strictest matching rule decides; a call no rule matches runs.
|
|
75
|
+
- **Enforced for every harness.** Core decides an MCP or HTTP tool call as it journals it. `deny` fails it to the model as `tool.denied`, unsent. `ask` holds the call (`held` at `/calls/tool`) and pauses the turn with one `approval` interaction per held call (`turn.paused`, with the tool, its input and the reason). `approve` sends that exact call, or fails it as `tool.denied`. The Tool Gate runs only calls the journal holds running, so a held or refused call cannot run. Nothing holds a process while it waits: an approval survives a restart. A flow's HTTP stages are decided the same way.
|
|
76
|
+
- **`approval` leaves the manifest.** `PUT /v1/agents/:id` and `saveAgent()` refuse a definition that sets `approval` on an HTTP tool, an MCP server or an MCP tool's settings; `http({ approval })` and `.mcp({ … approval })` throw; a turn manifest can no longer add one. The engine never asks for an HTTP or MCP tool's approval itself. A session pinned to a manifest saved with `approval: "always"` keeps it, as an ask.
|
|
77
|
+
- `@nylorun/studio`: a paused session shows an approval card for each held call, with its tool, why it is held and its input, and **Approve** / **Deny**.
|
|
78
|
+
- Migration: move each `approval: "always"` to an `ask` rule (see MIGRATION.md).
|
|
79
|
+
|
|
80
|
+
- 7d496e4: **Every connection a pod Environment makes to the internet is on the record (R7, egress events).** egress-gate used to keep only log lines, so a session's log could not say which hosts its turn reached or what the gate refused. Now each connection through egress-gate is one event, written by the gate itself.
|
|
81
|
+
|
|
82
|
+
- **`egress.connection`** in the session log of the turn that held the Environment when the connection opened, with that turn's id even if the turn has ended by the time it closes. Source `egress-gate`, evidence `observed`.
|
|
83
|
+
- **`environment.egress`**, the same payload, on the Environment's own stream (`GET /v1/environments/{id}/events`) for traffic while no turn holds the Environment.
|
|
84
|
+
- **The payload:** `environmentId`, `host`, `port`, `outcome` (`allowed` or `refused`), `reason` and `status` for a refusal, `mode` (`tunnel` or `credential`), `credentialBound`, `bytesUp` and `bytesDown`, `startedAt` (the gate's clock at CONNECT) and `durationMs`. A host a shell credential is bound to adds `requests` and the `credentialIds` the gate set. It never carries a header value, a URL path or a body.
|
|
85
|
+
- **One event per connection:** written when it closes, or at once when the gate refuses it (a host not in `network.allowedHosts`, a port other than 443 or 80, a private address, too many tunnels, a name that does not resolve). A refusal before the Environment is known (no token, an expired or stale one) stays a log line only.
|
|
86
|
+
- **Never in the way:** a write happens after the connection closes or is answered, and a failed write is logged as `egress_record_failed` without touching the tunnel. The gateway's `egress_refused` and `egress_terminated` log lines stay.
|
|
87
|
+
|
|
88
|
+
- 81c783e: **Every row that ends a call or a turn carries its own timing (R7.1).** The session log said what happened but not how long it took: a model call's outcome had no time at all, a tool call's only core's window, a flow's HTTP stage had no outcome row, and a turn's end had neither its duration nor its cost. Now each one is a span on its own, ready for the traces that follow, with no protocol change.
|
|
89
|
+
|
|
90
|
+
- **Model calls.** `message.assistant`, `model.failed` and `context.compacted` carry the call's `effectId` and `durationMs` (from the intent to the outcome). When the Model Gate metered the call they also carry the gate's own `gate: { startedAt, endedAt }`, `firstTokenMs` (to the provider's first stream event) and `meteredUsage` (`inputTokens`, `outputTokens`, `totalTokens`, and `cachedTokens` and `costUsd` when not zero), beside the `usage` the harness reported. The usage ledger keeps the gate's times (migration 0022: `model_usage.started_at`, `ended_at`, `first_token_ms`).
|
|
91
|
+
- **One row per model call.** A step that said nothing (no text, no tool calls, as a structured answer) now writes `message.assistant` with `text: ""`, and each partial summary of a long compaction writes `context.compacted` with `partial: true`. The AG-UI and A2A histories skip both; `isEmptyAssistantMessage` in `@nylorun/core/contracts` tells your own UI which steps said nothing.
|
|
92
|
+
- **Tool calls.** `tool.completed` carries the call's `effectId`, the Tool Gate's own `gate: { startedAt, endedAt }` for an MCP or HTTP call it ran (absent for sandbox and core-served tools), and `policy: { decision: "ask" | "deny", reason, approvedBy? }` when the Tenant's tool policy held or refused the call.
|
|
93
|
+
- **Flow HTTP stages.** A new event type, `node.completed` (`path`, `kind`, `key`, `iterations`, `effectId`, `status`, `durationMs`, `gate`), ends each HTTP stage and HTTP verifier that `node.started` began, and a tool stage the Runtime refused to run. Clients ignore event types they do not know.
|
|
94
|
+
- **Turns.** `turn.completed`, `turn.failed` and `turn.cancelled` carry `durationMs` (from the turn's start to its end, waits for a person included), `pausedMs` (how much of that it waited paused), `agentId` and `totals: { steps, tokens, costUsd }`, the same totals `GET /v1/tenant/usage?scope=turn` returns. A turn started before this release has no `durationMs`.
|
|
95
|
+
|
|
96
|
+
- 7800358: **Each turn is one OpenTelemetry trace, projected from the session log (R7).** An operator could not see what a turn did or where its time went without grepping container logs. Now every turn is a trace with a span for each model call, tool call and egress tunnel, exported over OTLP to any collector, readable per turn from the Runtime, and joined to the trace of the app that started it. No protocol change.
|
|
97
|
+
|
|
98
|
+
- **Traces from the log.** A turn's end is its root span, `invoke_agent <agent>`; each model call is a `chat <model>` span, each tool call and flow HTTP stage an `execute_tool <tool>` span, each egress-gate tunnel a `CONNECT <host>` span, and each pause and answer a marker. A model or gate tool span is the gate's own start and end; usage and cost are the gate's (`nylorun.usage.mismatch` when the harness reported other numbers). Attribute names follow the OTel GenAI conventions at one pinned commit, recorded as `nylorun.semconv.genai` on the resource. Ids are derived from the log under a key the Tenant keeps (setting `trace.key`), so every export and read of a turn gives the same ids.
|
|
99
|
+
- **OTLP export, off until you set an endpoint.** With `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) the Runtime posts OTLP/HTTP JSON from a replication slot of its own, `nylorun_trace_export`, in batches of up to 512 spans or every 2 s; `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` work as usual. No turn waits for it. A collector that is down costs only its spans: after three retries a batch is dropped and logged as `trace_export_dropped`, and the slot moves on, so Postgres keeps no WAL for it. `nylorun start` passes these variables to the runtime container.
|
|
100
|
+
- **References by default.** Spans point at log events (`nylorun.event.cursor`) and carry no content unless you opt in per kind with `NYLORUN_TRACE_CONTENT` (`user`, `outputs`, `tool-results`, `errors`), or all of them with `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true`, each cut at 8 KiB. A span never carries a credential value, a prompt, a person's id, an identity header or an idempotency key.
|
|
101
|
+
- **`GET /v1/sessions/{id}/turns/{turnId}/trace`** answers a turn's spans as OTLP JSON, the same spans the exporter posts: look at a turn without a collector, or post it to one to backfill. Application keys, as the `/calls` reads. Host feature `traces`; `@nylorun/agents` adds `session.trace(turnId)`, and `@nylorun/core/contracts` `TurnTraceSchema`.
|
|
102
|
+
- **Trace context in and out.** A `traceparent` (and `tracestate`) on `POST /v1/sessions/{id}/commands`, an AG-UI run or an A2A message puts the turn in the caller's trace; `command.message` records it as `trace`. The turn hands its context on: to the harness in `TurnStart.trace`, to HTTP tools as `traceparent` beside `Idempotency-Key`, to MCP servers as `traceparent` on the call's requests, and to sandbox commands as `TRACEPARENT`.
|
|
103
|
+
- **Harness API** (additive): the `effect.intent` answer `execute` may carry the tool call's `traceparent`. `@nylorun/harness`: `HarnessExecutors.tool` takes it as a fourth argument (`ToolCallContext`).
|
|
104
|
+
|
|
105
|
+
### Patch Changes
|
|
106
|
+
|
|
107
|
+
- 17a97e9: **An artifact names the Environment whose disk it holds.** `ArtifactView` (and so `ArtifactListItem` in artifact pages) gains optional `environmentId`: set on the folder artifact that holds an Environment's disk versions (R2d step 3), absent on a session's or the Tenant's own artifacts. Before, such an artifact read as a Tenant-owned one with no way to tell whose disk it was.
|
|
108
|
+
- b6214d9: Add opt-in artifact metadata pagination to `GET /v1/artifacts?limit=…`, shared
|
|
109
|
+
`ArtifactListItem`/`ArtifactPage` contracts and SDK `artifacts.page()` (default 50,
|
|
110
|
+
maximum 200). Hosts advertise `artifact-reads`. Pages order by creation time and
|
|
111
|
+
ID descending, filter by session, kind and labels, bind cursors to the Tenant and
|
|
112
|
+
filters, and reapply ownership and agent grants on each read. Legacy unpaged
|
|
113
|
+
responses remain unchanged. A Drizzle migration adds the creation-order index;
|
|
114
|
+
pages reuse the existing bounded read-only pool. Studio and every other Runtime
|
|
115
|
+
client can consume this public API without database access.
|
|
116
|
+
- a708b27: **The example harness, on harness ids (R4.8).**
|
|
117
|
+
|
|
118
|
+
- **A new fixture marker.** `fixture:unoffered <name> <json>` makes the fixture model call a tool the turn does not offer, so a test can see that such a call runs nothing and the turn goes on. The other markers are unchanged.
|
|
119
|
+
- **The example harness.** `examples/harness-minimal` says `minimal` in `hello`, connects with its own token from `nylorun-operate harness token put minimal`, and runs beside ours: sessions on an Environment whose `harness` is `minimal` go to it. As a pod Environment's `image` it is the pod's harness.
|
|
120
|
+
- **A session's workspace is its own harness's.** Off a pod, a session's sandbox routes, `save_artifact` and turn-end export go only to a connection of the session's harness that serves workspaces. Before, a custom harness's session was served from our harness's workspace, which is not that harness's files. Without such a connection the request is refused. Sweeps and removes still go to ours.
|
|
121
|
+
- **The Harness API reference and guide.** `guides/HARNESS_API.md` covers every section a harness implements, with a walkthrough for building one.
|
|
122
|
+
|
|
123
|
+
- dec813c: **A gateway shutdown no longer leaves an unhandled rejection.** When a keyed MCP or HTTP tool call was re-sent and the re-send's client went away before the gateway joined the running call, closing the gateway mid call rejected that join with nobody listening ("The gateway is shutting down"), which Node reports as an unhandled rejection. The gateway now handles it: the call still stops on shutdown and the next gateway sends it again under its key, as before.
|
|
124
|
+
- 11c3b2a: **The Runtime's own tokens never reach a session's record from what a harness reports.** A harness's commands hold the egress token in their proxy variables, and a command that prints its environment, or a model that repeats it, would put that token into a tool result. Core now replaces every Nylorun-signed token before recording it:
|
|
125
|
+
- where it looks: tool and model outcomes, error messages, claimed sandbox events, and a turn's output, state and transcript edits;
|
|
126
|
+
- what a token is: a compact JWT whose `typ` starts with `nylorun-`, such as egress, run or host tokens;
|
|
127
|
+
- what replaces it: `[nylorun token redacted]`.
|
|
128
|
+
|
|
129
|
+
This applies to every harness, ours included. The leak showed when the example harness ran as a sandbox pod's image. Other values are unchanged.
|
|
130
|
+
|
|
131
|
+
- 02c2d24: **The runtime image keeps the repository's new layout.** The Runtime's code moved to `runtime/server/` and the harness to `runtime/harness/` in the repository, and the image follows it: the entry point is `/app/runtime/server/dist/host/main.js` (was `/app/runtime/dist/host/main.js`). The image's `ENTRYPOINT`, `nylorun-operate` and the sandboxes service (`ghcr.io/nylorun/sandboxes`, released with the Runtime) use the new path; anything that runs a file under `/app/runtime/dist/` directly needs the new one.
|
|
132
|
+
- a0cbca1: **Clearer wording when a definition runs your code.** The refusal for a code tool or a flow's tool stage, the `hooks were removed` error and `nylo endpoints` now say that the Runtime runs agents from their manifests alone, instead of that it runs no code of yours during a session. Only the message text changed; anything matching the old wording (for example `runs no code of yours during a session`) needs the new text.
|
|
133
|
+
- Pin core to the tested release.
|
|
134
|
+
- Pin harness to the tested release.
|
|
135
|
+
- Updated dependencies [17a97e9]
|
|
136
|
+
- Updated dependencies [b6214d9]
|
|
137
|
+
- Updated dependencies [e917bd6]
|
|
138
|
+
- Updated dependencies [5165451]
|
|
139
|
+
- Updated dependencies [35e29b7]
|
|
140
|
+
- Updated dependencies [e917bd6]
|
|
141
|
+
- Updated dependencies [a0cbca1]
|
|
142
|
+
- Updated dependencies [3013501]
|
|
143
|
+
- Updated dependencies [6061a3a]
|
|
144
|
+
- Updated dependencies [7d496e4]
|
|
145
|
+
- Updated dependencies [81c783e]
|
|
146
|
+
- Updated dependencies [7800358]
|
|
147
|
+
- Updated dependencies
|
|
148
|
+
- @nylorun/core@0.20.0-beta
|
|
149
|
+
- @nylorun/harness@0.26.0-beta
|
|
150
|
+
|
|
151
|
+
## 0.23.0-beta
|
|
152
|
+
|
|
153
|
+
### Minor Changes
|
|
154
|
+
|
|
155
|
+
- 72a4b52: The outbox no longer stalls the Tenant sweep, holds up commands or loses a pod sandbox's signals, and Workers register versioned Restate deployments.
|
|
156
|
+
|
|
157
|
+
- A wake that keeps failing (Restate refuses it, or an older Runtime does not know its reason) is retried with backoff and, after ten failures, parked: logged once at error and kept in the outbox for an operator. It no longer fails the sweep, so Restate never pauses the Tenant's sweep object over it. Migration 0018 adds `attempts`, `retry_at` and `parked_at` to `nylorun.wakes`.
|
|
158
|
+
- The sweep drains the whole outbox and pages through every orphaned session in each pass, within a 2 s budget, instead of 100 rows per pass.
|
|
159
|
+
- A command's wake is sent after commit without the request waiting for it; a failed send is logged at warn and sent again by the sweep's next pass. The sweep's grace is compared on the database's clock and is now 30 s.
|
|
160
|
+
- A pod sandbox's reconcile and idle timer go through the outbox (migration 0019, `nylorun.sandbox_signals`), so a crash after commit no longer loses them, and a host revocation goes over the control bus (`host.revoked`), so it reaches every process with the Tenant open. `Tx.afterCommit` is removed.
|
|
161
|
+
- Sends to Restate's ingress use `@restatedev/restate-sdk-clients` 1.17.2, each with an idempotency key.
|
|
162
|
+
- A Worker registers `<NYLORUN_WORKER_URL>/nylorun/<version>`, the version being the new `NYLORUN_WORKER_VERSION` or the Runtime's own, without forcing in a container, so a rolling upgrade keeps the old deployment for the invocations it started. A build whose Worker code changed under the same Runtime version sets its own `NYLORUN_WORKER_VERSION`.
|
|
163
|
+
|
|
3
164
|
## 0.22.0-beta
|
|
4
165
|
|
|
5
166
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The independent OSS **Runtime Host** consumes `@nylorun/harness/run` and
|
|
|
4
4
|
`@nylorun/core/contracts`. Cloud installs published `@nylorun/harness` from npm
|
|
5
5
|
and does not import this package. Every Tenant serves two APIs on one URL: the
|
|
6
6
|
**Runtime API** for developers (agents, sessions with AG-UI and
|
|
7
|
-
A2A,
|
|
7
|
+
A2A, Environments and their secrets, artifacts), whose client is `@nylorun/agents`, and the
|
|
8
8
|
**Management API** for operators (`/v1/tenant/*`), whose client is `@nylorun/admin`.
|
|
9
9
|
Vocabulary: [src/CONTEXT.md](./src/CONTEXT.md).
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ Restate and S2: `nylorun start` runs them, as a local Tenant, on a developer mac
|
|
|
24
24
|
package is a library with no bin; its Host entry is `@nylorun/runtime/server`
|
|
25
25
|
(`dist/host/main.js`), which requires `NYLORUN_DATABASE_URL`. For tests and
|
|
26
26
|
ephemeral embeds, use `startEphemeralRuntime()` from `@nylorun/runtime/core`.
|
|
27
|
-
See [MIGRATION.md](
|
|
27
|
+
See [MIGRATION.md](../../guides/MIGRATION.md).
|
|
28
28
|
|
|
29
29
|
A Runtime serves one **Tenant**: an installation is one Runtime with its own
|
|
30
30
|
Postgres database, Restate and S2 basin, and two Tenants are two installations. No
|
|
@@ -44,8 +44,9 @@ or deleted key stops authenticating at once. A database written by a Runtime tha
|
|
|
44
44
|
database (`tenant_<id>` schemas), or by a pre-release build of one Tenant per database
|
|
45
45
|
(`schema_version` tables), is refused: this release starts fresh on a new database. Restate runs one advance of a session at a time, delivers its wakes and holds
|
|
46
46
|
its timers and the Tenant's sweep (Durable Session Execution), and journals no effect:
|
|
47
|
-
checkpoints, effects, events and the
|
|
48
|
-
written to the outbox in the transaction that causes it, so a commit
|
|
47
|
+
checkpoints, effects, events and the outbox are the Session Store's, and a wake or a pod
|
|
48
|
+
Environment's reconcile is written to the outbox in the transaction that causes it, so a commit
|
|
49
|
+
never loses it ([src/CONTEXT.md](src/CONTEXT.md)); every session's events are relayed from the record to
|
|
49
50
|
its own S2 stream, which history and SSE read (Durable Streams). One image runs every **service**,
|
|
50
51
|
and `--service` picks what a process runs: `core` serves the Runtime API, the
|
|
51
52
|
Management API and SSE and runs the stream relay, `loop` runs advances (the Worker), and
|
|
@@ -65,19 +66,32 @@ core's process. A `harness` process needs `NYLORUN_HARNESS_URL`,
|
|
|
65
66
|
`/harness`), answers `/health` on `127.0.0.1:4300`, and refuses to start with a
|
|
66
67
|
database, Restate, keys or gates credential in its environment. A local Tenant
|
|
67
68
|
runs it in the `harness` container, on a network with only the runtime and the
|
|
68
|
-
gateway ([DEPLOYMENT.md](
|
|
69
|
+
gateway ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#the-harness-agent-turns-mcp-servers-and-workspaces)).
|
|
69
70
|
|
|
70
71
|
The container is configured by its environment, which a local Tenant's Compose file
|
|
71
72
|
sets: `NYLORUN_DATABASE_URL` (required), `NYLORUN_RESTATE_INGRESS_URL`,
|
|
72
|
-
`NYLORUN_RESTATE_ADMIN_URL`, `NYLORUN_WORKER_URL` and
|
|
73
|
+
`NYLORUN_RESTATE_ADMIN_URL`, `NYLORUN_WORKER_URL`, `NYLORUN_WORKER_VERSION` and
|
|
73
74
|
`NYLORUN_RESTATE_IDENTITY_KEY` (Restate), `NYLORUN_S2_ENDPOINT` and
|
|
74
75
|
`NYLORUN_S2_TOKEN`, and in container mode `NYLORUN_LISTEN_HOST`,
|
|
75
76
|
`NYLORUN_LISTEN_PORT`, `NYLORUN_ALLOWED_HOSTS` and `NYLORUN_PUBLIC_URL`.
|
|
77
|
+
A Worker registers its Restate deployment at a versioned URL,
|
|
78
|
+
`<NYLORUN_WORKER_URL>/nylorun/<version>`, where the version is `NYLORUN_WORKER_VERSION`
|
|
79
|
+
(letters, digits, `.`, `_`, `-`) or, by default, the Runtime's own. In a container it never
|
|
80
|
+
replaces a deployment already registered there (`force: false`): during a rolling upgrade
|
|
81
|
+
the new version's Workers register next to the old ones, Restate sends new invocations to
|
|
82
|
+
the new deployment and lets the old one finish what it started, and every Worker of one
|
|
83
|
+
version shares its deployment through one load-balanced `NYLORUN_WORKER_URL`. A build that
|
|
84
|
+
changes the Worker's code without changing the Runtime's version (an image built from a
|
|
85
|
+
branch) sets its own `NYLORUN_WORKER_VERSION`: without force Restate keeps the deployment it
|
|
86
|
+
already has at a URL and does not discover the Worker again, so the changed code would run
|
|
87
|
+
under the old deployment's registration, and a Worker whose services that deployment lacks
|
|
88
|
+
refuses to start. Outside a container (a development
|
|
89
|
+
Host) the registration replaces the deployment at that URL.
|
|
76
90
|
`NYLORUN_MANAGEMENT_KEY_FILE` names the bootstrap secret (below).
|
|
77
91
|
`NYLORUN_IDENTITY_FILE` names the identity
|
|
78
92
|
file, a YAML list of the trusted issuers whose JWTs the Runtime API accepts
|
|
79
93
|
(Host feature `trusted-issuers`), read once at boot; a malformed file stops the
|
|
80
|
-
boot ([DEPLOYMENT.md](
|
|
94
|
+
boot ([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#trusted-issuers)). A process that runs `loop` sends its model
|
|
81
95
|
calls to the gate at `NYLORUN_GATES_URL`; in a container it refuses to start
|
|
82
96
|
without it and `NYLORUN_GATES_TOKEN`. That token is core's credential: the gate
|
|
83
97
|
accepts only it for vault writes and token signing. Model, HTTP tool and
|
|
@@ -89,16 +103,16 @@ is refused with `409 run_stale`. A `gates` process needs only
|
|
|
89
103
|
(`NYLORUN_GATES_LISTEN_HOST`, `NYLORUN_GATES_LISTEN_PORT`, default 4100, and
|
|
90
104
|
`NYLORUN_GATES_ALLOWED_HOSTS`) and the Host's `tenant/` directory, which it
|
|
91
105
|
never writes; it serves the database's one Tenant. A session's MCP servers and
|
|
92
|
-
HTTP tools get their credentials from its
|
|
106
|
+
HTTP tools get their credentials from the vault of its Environment only: a `bearer` token
|
|
93
107
|
or a `headers` map per URL, sent to an MCP gateway instead when it has `via`,
|
|
94
108
|
with the session owner in its identity header
|
|
95
|
-
([DEPLOYMENT.md](
|
|
109
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)); the credential resolver is gone
|
|
96
110
|
(protocol 10), and a process that still sets a `NYLORUN_RESOLVER_*` variable logs
|
|
97
111
|
`resolver_removed` and ignores it. With `egress`
|
|
98
112
|
(`--service gates,keys,egress`, for pod sandboxes) the same process runs
|
|
99
113
|
egress-gate on `NYLORUN_EGRESS_LISTEN_HOST`:`NYLORUN_EGRESS_LISTEN_PORT`
|
|
100
114
|
(default `0.0.0.0:4200`): a CONNECT proxy that admits a pod's egress token and
|
|
101
|
-
tunnels only to the hosts its
|
|
115
|
+
tunnels only to the hosts its Environment allows (`network.allowedHosts`), on 443 or 80, never to a
|
|
102
116
|
private address. `NYLORUN_PACKING` (`combined` or `split`) is logged at startup.
|
|
103
117
|
|
|
104
118
|
The Host has one listener, which serves both APIs. Host work runs on the machine
|
|
@@ -155,9 +169,12 @@ with `415 unsupported_media_type`, except an artifact upload (`POST /v1/artifact
|
|
|
155
169
|
presents a trusted issuer's token; keys (application and management)
|
|
156
170
|
are refused from browsers before they are looked up. The Runtime sends no CORS headers, and answers
|
|
157
171
|
`OPTIONS` with `204` and `Allow` only: the operator's reverse proxy answers preflights
|
|
158
|
-
([DEPLOYMENT.md](
|
|
172
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#calling-the-runtime-from-browsers-and-apps)). Missing or
|
|
159
173
|
unsupported protocol → `426` before authentication, unless the request sends neither
|
|
160
|
-
`Nylorun-Protocol` nor `Authorization` (the Host serves
|
|
174
|
+
`Nylorun-Protocol` nor `Authorization` (the Host serves protocol 11 only; protocol 11 replaces
|
|
175
|
+
sandbox resources, the Tenant's default sandbox, inline sandboxes and user and installation
|
|
176
|
+
vaults with Environments, each one sandbox and one vault, under `/v1/environments`, and
|
|
177
|
+
removes the Management API's vault routes, so a protocol 10 client gets `426`; protocol 10 removes
|
|
161
178
|
the MCP OAuth connect, the vault's `oauth` credentials and the credential resolver, and adds
|
|
162
179
|
`headers` credentials, `via` and the identity header, manifest v6 and
|
|
163
180
|
`POST /v1/tenant/mcp/preview`; protocol 9 makes the Runtime API an OAuth 2.1 resource server: a missing or rejected credential is `401
|
|
@@ -168,8 +185,7 @@ the Admin API, and those old paths answer `404` with no alias; protocol 7 remove
|
|
|
168
185
|
derived principals, whose routes answer `404`; protocol 6 adds file and folder artifacts and message `parts`; at each turn's end the
|
|
169
186
|
Runtime exports `/workspace/outputs` of the session's sandbox as a version of its
|
|
170
187
|
`outputs` folder, read at `/v1/artifacts/{id}/versions/{n|latest}/tree`, `/files/{path}`,
|
|
171
|
-
`/diff?from=` and `/zip`).
|
|
172
|
-
another Tenant (or malformed) and a Tenant that
|
|
188
|
+
`/diff?from=` and `/zip`). Clients name no Tenant (since protocol 5). A Tenant that
|
|
173
189
|
could not be opened → opaque `404` with identical body. A path or method no route serves is `404 Route not found`
|
|
174
190
|
once the caller is known.
|
|
175
191
|
|
|
@@ -193,13 +209,13 @@ https://cdn.jsdelivr.net/npm/@nylorun/runtime@<version>/dist/management-openapi.
|
|
|
193
209
|
|
|
194
210
|
Each document has described tags, in the order a developer uses them. The Runtime API's are
|
|
195
211
|
grouped (`x-tagGroups`): Agents (Agents, Definition files), Sessions (Sessions API, AG-UI,
|
|
196
|
-
A2A),
|
|
212
|
+
A2A), Environments, Artifacts and Service (health, readiness, `/v1/me`, the JWKS and the
|
|
197
213
|
document; the `/openapi.json` alias is served but not listed). The Management API's are
|
|
198
|
-
Tenant, Application keys, Models,
|
|
214
|
+
Tenant, Application keys, Models, Credential coverage, MCP servers, Signing keys and Settings. Each operation's
|
|
199
215
|
`security` says which credentials it takes (application key and trusted issuer's token in
|
|
200
216
|
the Runtime API; management key in the Management API), and its
|
|
201
217
|
`x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields say
|
|
202
|
-
who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/openapi/` holds the committed snapshots: a change to a route changes
|
|
218
|
+
who may call it. Event streams are `text/event-stream` with an `itemSchema`. `runtime/server/openapi/` holds the committed snapshots: a change to a route changes
|
|
203
219
|
them (`node scripts/build-openapi.mjs --write`), and `check-package` fails until they are
|
|
204
220
|
updated.
|
|
205
221
|
|
|
@@ -237,18 +253,22 @@ const runtime = await startEphemeralRuntime({
|
|
|
237
253
|
|
|
238
254
|
The package's own tests run every Session Store on Postgres: `npm test` gives each
|
|
239
255
|
test file a database on the Docker test stack (`test/stack/compose.yaml`) and starts
|
|
240
|
-
the stack when it is down; see [CONTRIBUTING.md](
|
|
256
|
+
the stack when it is down; see [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
241
257
|
|
|
242
258
|
Save agents with `PUT /v1/agents/{agentId}` using the application principal (`saveAgent`
|
|
243
259
|
in `@nylorun/agents`). The Runtime runs the agent from its manifest and never calls your
|
|
244
260
|
code during a session: a definition with a tool that would (`tool({ run })`, a flow's tool
|
|
245
261
|
stage) is refused. Your services are reached as tools: HTTP tools, POSTed from the Runtime,
|
|
246
|
-
so their URLs must be reachable from it, and remote MCP servers.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
(
|
|
250
|
-
and
|
|
251
|
-
|
|
262
|
+
so their URLs must be reachable from it, and remote MCP servers. Each URL gets a
|
|
263
|
+
credential in the vault of the Environment a session names (`PUT
|
|
264
|
+
/v1/environments/{id}/secrets/{name}`), the operator previews an MCP server's tools
|
|
265
|
+
(`nylorun mcp inspect --environment <id>`),
|
|
266
|
+
and the manifest says which tools are enabled or deferred
|
|
267
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#mcp-servers-and-http-tools)). Which calls wait for a
|
|
268
|
+
person, or are refused, is the Tenant's tool policy
|
|
269
|
+
([DEPLOYMENT.md](../../guides/DEPLOYMENT.md#tool-policy)). Model gateway
|
|
270
|
+
and sandbox backend are Tenant configuration (the Tenant's own vault, Tenant
|
|
271
|
+
settings and seed), not Host process env.
|
|
252
272
|
|
|
253
273
|
## Session behaviour
|
|
254
274
|
|
|
@@ -259,11 +279,12 @@ definition/session APIs.
|
|
|
259
279
|
Postgres transactions persist session checkpoints, command receipts, individual
|
|
260
280
|
effects, waits and the record of session events — in the Tenant's database; history is
|
|
261
281
|
read from Durable Streams (S2). An advance owns its session through a lease with
|
|
262
|
-
an epoch; a Worker that takes over after a crash marks in-flight effects
|
|
263
|
-
`uncertain
|
|
282
|
+
an epoch; a Worker that takes over after a crash marks in-flight model effects
|
|
283
|
+
`uncertain`, and sends an interrupted MCP or HTTP tool call again with the same key (R5). Vault ciphertext needs that Tenant's own KEK.
|
|
264
284
|
|
|
265
|
-
|
|
266
|
-
Tenant owns each
|
|
285
|
+
A session that names an Environment of kind `virtual` or `pod` gets Runtime-executed
|
|
286
|
+
sandbox tools; the agent declares nothing. The Tenant owns each Environment; backend
|
|
287
|
+
names are prefixed `nylorun-<tenant-id>-`.
|
|
267
288
|
|
|
268
289
|
## Local Project workflow
|
|
269
290
|
|
|
@@ -293,9 +314,53 @@ Definitions have no `agent.run()`; applications use `@nylorun/agents`.
|
|
|
293
314
|
## Session reads
|
|
294
315
|
|
|
295
316
|
Hosts advertising `session-reads` serve pinned session manifests, recorded usage, model-call
|
|
296
|
-
pages and opt-in session/history/
|
|
317
|
+
pages and opt-in session/history/Environment pagination. `calls-export` adds the Management API's
|
|
297
318
|
unfiltered, resumable model ledger export (`GET /v1/tenant/calls/model`, a management key) in
|
|
298
|
-
safe transaction order.
|
|
319
|
+
safe transaction order. `tool-calls` adds `GET /v1/sessions/{id}/calls/tool`: each tool call of
|
|
320
|
+
the session from the effect journal, with its input, status and sends. Existing list/history
|
|
299
321
|
requests without `limit` retain their response shapes. These are public APIs for Studio,
|
|
300
322
|
CLI and custom clients; clients never need database access. See the
|
|
301
323
|
[API contracts, authorization, SDK examples and Studio handoff](https://github.com/nylorun/agents/blob/main/runtime/docs/session-reads.md).
|
|
324
|
+
|
|
325
|
+
## Artifact pages
|
|
326
|
+
|
|
327
|
+
Hosts advertising `artifact-reads` accept `GET /v1/artifacts?limit=50`, returning
|
|
328
|
+
`{ artifacts: ArtifactListItem[], nextCursor: string | null }`. An explicit `limit`
|
|
329
|
+
of 1–200 opts into paging; without it the existing `{ artifacts: ArtifactView[] }`
|
|
330
|
+
list remains oldest first. Pages order by `(createdAt DESC, artifactId DESC)` and
|
|
331
|
+
contain metadata only: id, kind, name, current content type, latest version number,
|
|
332
|
+
optional session and labels, and creation/update timestamps. Use the existing
|
|
333
|
+
artifact detail, numbered version and download APIs for history and bytes.
|
|
334
|
+
|
|
335
|
+
Paged reads accept equality filters `sessionId`, `kind=file|folder` and repeated
|
|
336
|
+
`label=key=value` (all labels must match). `sessionId` includes only that session's
|
|
337
|
+
artifacts, excluding Tenant-owned artifacts. An application key acting as itself
|
|
338
|
+
sees every artifact; subjects and issuer tokens see only artifacts of their owned
|
|
339
|
+
sessions and permitted agents. Missing or inaccessible session filters return 404.
|
|
340
|
+
Management keys cannot use this Runtime API route.
|
|
341
|
+
|
|
342
|
+
Send `nextCursor` as `cursor` with the same normalized filters until it is null.
|
|
343
|
+
Cursors are bound to the Tenant, route and filters; invalid or mismatched cursors
|
|
344
|
+
return 400 (`cursor_invalid` or `cursor_mismatch`). Each page reapplies current
|
|
345
|
+
authorization and is an independent read: it is not a frozen snapshot. Newer
|
|
346
|
+
inserts require a fresh traversal; deleted rows disappear and versions/metadata
|
|
347
|
+
may change between pages. Creation time and ID remain the paging position.
|
|
348
|
+
|
|
349
|
+
These pages reuse the separate bounded, read-only Drizzle pool (four connections,
|
|
350
|
+
two-second statement timeout). Migration `0023_artifact_pages` adds only the
|
|
351
|
+
creation-order index; the existing session index also serves session pages in
|
|
352
|
+
reverse order. No data backfill, protocol bump or direct client database access is
|
|
353
|
+
required. Studio can discover `artifact-reads` through `/health` before enabling
|
|
354
|
+
a tenant-wide table; `@nylorun/agents` exposes the same API as `artifacts.page()`.
|
|
355
|
+
|
|
356
|
+
[Recorded query plans](docs/artifact-pages-evidence.json) use a disposable local
|
|
357
|
+
Postgres 17 fixture: the public SDK drained 10,006 artifacts in 201 pages of at
|
|
358
|
+
most 50 rows, without gaps or duplicates, in 796 ms. First/deep page queries used
|
|
359
|
+
backward index scans with a tuple index condition on the deep page (0.014/0.028 ms).
|
|
360
|
+
These are fixture timings, not production throughput estimates. Reproduce after
|
|
361
|
+
building dependencies with:
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
ARTIFACT_READS_EVIDENCE="$PWD/runtime/server/docs/artifact-pages-evidence.json" \
|
|
365
|
+
npm test --workspace @nylorun/runtime -- test/http/artifact-pages.test.ts
|
|
366
|
+
```
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type DurableExecution, type SandboxSignal, type StuckInvocation, type Wake, type WorkerHandlers } from "../../execution/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* How long one send through the ingress keeps retrying while the ingress is unreachable,
|
|
4
|
+
* overloaded, or answers 404 because a just-registered service has not reached it yet. The
|
|
5
|
+
* outbox's grace (`OUTBOX_GRACE_MS` in `tenant/sweep.ts`) is longer.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SEND_RETRY_MS = 10000;
|
|
2
8
|
export interface RestateTimeouts {
|
|
3
9
|
/**
|
|
4
10
|
* How long an invocation may run without journal progress before Restate
|
|
@@ -60,8 +66,8 @@ export interface RestateExecutionOptions {
|
|
|
60
66
|
/**
|
|
61
67
|
* Overwrite a deployment already registered at `workerAdvertisedUrl`, so a
|
|
62
68
|
* restarted Worker with changed code takes effect. Default true, which suits
|
|
63
|
-
* a stable URL on a developer machine.
|
|
64
|
-
* versioned URL
|
|
69
|
+
* a stable URL on a developer machine. A Host in a container advertises a
|
|
70
|
+
* versioned URL without it (`workerDeployment` in `infra/execution.ts`, §14.6).
|
|
65
71
|
*/
|
|
66
72
|
forceRegistration?: boolean;
|
|
67
73
|
/** How long `start` keeps retrying registration while Restate comes up. Default 60000. */
|
|
@@ -88,6 +94,7 @@ export declare class RestateExecution implements DurableExecution {
|
|
|
88
94
|
private readonly inflight;
|
|
89
95
|
private readonly names;
|
|
90
96
|
private readonly ingressUrl;
|
|
97
|
+
private readonly ingress;
|
|
91
98
|
private readonly adminUrl;
|
|
92
99
|
private readonly sweepIntervalMs;
|
|
93
100
|
constructor(options: RestateExecutionOptions);
|
|
@@ -123,7 +130,12 @@ export declare class RestateExecution implements DurableExecution {
|
|
|
123
130
|
private runTracked;
|
|
124
131
|
private staleSweepMs;
|
|
125
132
|
private serviceOptions;
|
|
126
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* One-way send through the ingress (`@restatedev/restate-sdk-clients`). Every send carries
|
|
135
|
+
* an idempotency key, so the client's retries (`SEND_RETRY_MS`), and a send repeated after
|
|
136
|
+
* its answer was lost, start one invocation: the request's own key where it has one (an
|
|
137
|
+
* outbox row's, a timer's time), or one made for this call.
|
|
138
|
+
*/
|
|
127
139
|
private send;
|
|
128
140
|
/** Registers the Worker endpoint with the admin API, retrying until Restate is up. */
|
|
129
141
|
private register;
|