@nylorun/agents 0.9.0-beta → 0.10.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 +131 -0
- package/README.md +287 -69
- package/dist/a2a/handler.d.ts +59 -0
- package/dist/a2a/handler.js +178 -0
- package/dist/a2a/index.d.ts +8 -0
- package/dist/a2a/index.js +7 -0
- package/dist/access.d.ts +101 -0
- package/dist/access.js +111 -0
- package/dist/action-handler.d.ts +64 -0
- package/dist/action-handler.js +283 -0
- package/dist/ag-ui/handler.d.ts +12 -4
- package/dist/ag-ui/handler.js +87 -281
- package/dist/ag-ui/index.d.ts +2 -1
- package/dist/ag-ui/index.js +2 -1
- package/dist/browser.d.ts +97 -0
- package/dist/browser.js +277 -0
- package/dist/client.d.ts +3 -230
- package/dist/client.js +2 -357
- package/dist/connection.d.ts +0 -1
- package/dist/connection.js +3 -9
- package/dist/delivery-token.d.ts +57 -0
- package/dist/delivery-token.js +150 -0
- package/dist/execute-action.d.ts +6 -0
- package/dist/execute-action.js +24 -0
- package/dist/http.d.ts +31 -1
- package/dist/http.js +74 -14
- package/dist/index.d.ts +9 -7
- package/dist/index.js +3 -3
- package/dist/sandbox/client.d.ts +4 -5
- package/dist/sandbox/client.js +4 -8
- package/dist/sandbox/index.d.ts +0 -2
- package/dist/sandbox/index.js +0 -1
- package/dist/served-definitions.d.ts +20 -0
- package/dist/served-definitions.js +100 -0
- package/dist/session-client.d.ts +282 -0
- package/dist/session-client.js +375 -0
- package/dist/sse.js +12 -0
- package/dist/studio-embed.d.ts +9 -0
- package/dist/studio-embed.js +8 -0
- package/dist/token-endpoint.d.ts +23 -0
- package/dist/token-endpoint.js +30 -0
- package/package.json +15 -8
- package/dist/ag-ui/history.d.ts +0 -7
- package/dist/ag-ui/history.js +0 -60
- package/dist/ag-ui/sse.d.ts +0 -9
- package/dist/ag-ui/sse.js +0 -7
- package/dist/ag-ui/translate.d.ts +0 -38
- package/dist/ag-ui/translate.js +0 -222
- package/dist/derived-credentials.d.ts +0 -5
- package/dist/derived-credentials.js +0 -17
- package/dist/executor.d.ts +0 -20
- package/dist/executor.js +0 -417
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
+
## 0.10.0-beta
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- c121144: **Definitions no longer declare a sandbox.** `.sandbox()` on ReAct and flow agents, and the `sandbox`, `SandboxError`, `SandboxOptions` and `SandboxCapability` exports, are removed. Open the session with one instead, `createSession({ sandbox: { … } })`, or set the Tenant's default; see `MIGRATION.md`.
|
|
8
|
+
|
|
9
|
+
- **Build and registration.** A capability that still carries `sandbox` fails the build (`sandbox.in-definition`), and `PUT /v1/agents/:id` refuses such a definition with a `400` that names it. Definitions stored before this release keep their sandbox.
|
|
10
|
+
- **Trees.** `sandbox.mismatch` and `workflow.sandbox-mismatch` are gone, with the workflow manifest's derived `sandbox`: a tree shares the sandbox its session was opened with.
|
|
11
|
+
- **Executors.** The action claim reports whether the session has a sandbox (`ActionClaim.sandbox`), and `ctx.sandbox` follows it.
|
|
12
|
+
|
|
13
|
+
- 9546ac7: **Protocol 4: every session event is typed, on the `nylorun.event/2` envelope.**
|
|
14
|
+
|
|
15
|
+
- **The catalog.** `EVENT_CATALOG` in `@nylorun/core/contracts` lists every event type the Runtime writes, with its payload schema, its schema version and its source. `SessionEventSchema` is their union, discriminated on `type`; `parseSessionEvent` types a known event and returns an unknown one as the bare envelope.
|
|
16
|
+
- **The envelope.** Events carry `schema`, `seq`, `epoch`, `runId`, `incarnation`, `schemaVersion`, `source`, `evidence`, `visibility`, `retention` and an optional `trace`. `createdAt` is renamed `time`. The envelope is no longer strict, so later fields never break a client.
|
|
17
|
+
- **Validated writes.** `Tx.event` is typed by the catalog, and both Session Stores check each event against it before it commits (`InvalidEventError`). Workflow `action.pending` payloads may carry `path` and `key`.
|
|
18
|
+
- **OpenAPI.** Each event type is a component (`MessageAssistantEvent`, …), `SessionEvent` is their union, and the session SSE and history responses refer to them.
|
|
19
|
+
- **Clients.** `@nylorun/agents` reads events with `parseSessionEvent`, so a newer Runtime's event types reach your code instead of failing the stream. Studio reads `time`.
|
|
20
|
+
|
|
21
|
+
See `MIGRATION.md`.
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- c7614a4: **A2A: serve agents to other agents (v1, gateway mode).** The Runtime answers [A2A](https://a2a-protocol.org) 1.0 over JSON-RPC for the Tenant's agents (optional Host feature `a2a-endpoint`), and the app server publishes them with `createA2aHandler`.
|
|
26
|
+
|
|
27
|
+
- **Runtime.** `POST /v1/a2a/agents/:agent` acts for a subject with `sessions:own`. It serves `SendMessage` (blocking up to 5 minutes, or `returnImmediately`), `GetTask` and `CancelTask`. A context is one session per subject, agent and `contextId`, and a task is one turn. A question pauses the task as `TASK_STATE_INPUT_REQUIRED` until the caller replies on the same task. The `messageId` is the idempotency key. `ListTasks`, streaming, push notifications and the extended card answer with their A2A errors, and approvals cannot be answered over A2A yet. `GET /v1/a2a/agents/:agent/card` returns the card built from the manifest, without interfaces. `observeSession` lets in-process readers join a session's shared event feed.
|
|
28
|
+
- **Agents SDK.** `@nylorun/agents/a2a`: `createA2aHandler({ agents, subject, publicUrl, card })` forwards each partner's JSON-RPC request to the Runtime as that partner's subject, and serves the Agent Card with its own URL, provider and security schemes. It loads no A2A package. `Transport.forward` returns the Runtime's raw response.
|
|
29
|
+
|
|
30
|
+
- 5f23047: **Action endpoints: `createActionHandler` (preview; needs a Runtime with the `action-endpoints` feature to register).** An application serves its agents' tools, hooks and workflow functions from one HTTP handler, which the Runtime will call, in place of `connectAgents`.
|
|
31
|
+
|
|
32
|
+
- `createActionHandler({ agents })` returns `{ fetch, node, register }`. `fetch` is a web-standard handler and `node` serves `node:http` and Express. Each request is verified before any code runs: its delivery token (`Nylorun-Signature`) must be an ES256 token signed by the Tenant's signing key, for this Tenant, this URL, this Action and generation, and the exact body. The Action then runs through the same code executors use, with the request's signal as `ctx.signal`. The answer is the tagged outcome (`Nylorun-Outcome: 1`). An agent or tool it does not serve answers `404`, and a workflow Action for another version of the workflow answers `409`. A token signed with a key the handler has not seen yet (just after a rotation) answers `503`, which the Runtime retries.
|
|
33
|
+
- A process that only serves Actions needs no key: set `runtime: { url, tenant }` and it reads the Tenant's public keys, or pass them in `jwks`. Verification uses WebCrypto; the SDK takes no JWT dependency.
|
|
34
|
+
- `register({ url })` saves the definitions (`saveDefinitions: false` skips it), registers the URL for every served agent, and pings each one through the Runtime. It refuses a Runtime without `action-endpoints` before sending anything.
|
|
35
|
+
- `ctx.sandbox` calls back with the delivery token. `createActionSandbox` accepts no claim for this case, and `Transport.withKey` copies a transport with another bearer, sharing its compatibility check.
|
|
36
|
+
|
|
37
|
+
- 4282d5f: **Breaking: executors are removed; protocol 3.** The Runtime delivers every Action (tool, hook, `fn`, `verify`) to the Action endpoint an agent registers. It no longer offers Actions for executors to claim. See `MIGRATION.md`, "Action endpoints replace executors".
|
|
38
|
+
|
|
39
|
+
- **Core.** `PROTOCOL_VERSION` is 3 and a Host serves only protocol 3. `action-endpoints` is a protocol feature instead of an optional Host feature. Removed:
|
|
40
|
+
|
|
41
|
+
- the executor, claim and Action-list schemas;
|
|
42
|
+
- `action_result` from `SessionCommandSchema`;
|
|
43
|
+
- the `claimed` Action status, `claimId` and `leaseExpiresAt`.
|
|
44
|
+
|
|
45
|
+
New: `ActionResultReceiptSchema`, the receipt of `POST /v1/actions/:id/result`, which is `AcceptedResponse` without `requestId`.
|
|
46
|
+
|
|
47
|
+
- **Runtime.** Removed:
|
|
48
|
+
|
|
49
|
+
- `PUT`/`GET /v1/executors`, `DELETE /v1/executors/:agentId`, `GET /v1/executors/connect`;
|
|
50
|
+
- `GET /v1/actions`, `POST /v1/actions/:id/claim`, and the executor form of `POST /v1/actions/:id/heartbeat`;
|
|
51
|
+
- the `tenant/work` stream;
|
|
52
|
+
- claim expiry;
|
|
53
|
+
- executor credentials.
|
|
54
|
+
|
|
55
|
+
`POST /v1/actions/:id/sandbox/:tool` takes the delivery token only. Tenant summaries report `inFlightDeliveries` instead of `connectedExecutors`. Tenant status lists each agent's endpoint and has an `endpoints` check instead of `executors`.
|
|
56
|
+
|
|
57
|
+
Postgres migration 6 drops the `executors` table. Actions an executor had claimed are handled like lost deliveries: a tool becomes `uncertain`, and a hook, `fn` or `verify` is delivered again.
|
|
58
|
+
|
|
59
|
+
- **Agents.** Removed: `connectAgents`, the `@nylorun/agents/executor` subpath, derived executor keys and `NYLORUN_EXECUTOR_KEY`. Serve agents with `createActionHandler` and call `register({ url })`.
|
|
60
|
+
- **CLI.** Executor keys are gone from Project credentials. `nylo tenant endpoints` shows each endpoint and its health.
|
|
61
|
+
|
|
62
|
+
- 82d95ef: **Action endpoints: background tools, CLI and Studio.**
|
|
63
|
+
|
|
64
|
+
- **Background tools (core, agents).** `tool({ …, background: true })` marks a tool that runs longer than an endpoint's timeout. The option is code-only and never serialized into the manifest. `createActionHandler` answers its delivery at once with `202`, runs the tool, heartbeats on the deadline the Runtime returns (each time with the newest delivery token), and posts the outcome. A heartbeat answered `409` (cancelled, lost or sent again) aborts the tool's `ctx.signal`, and nothing is posted. The new `waitUntil` option hands the background work to platforms that end a request's work with its response.
|
|
65
|
+
- **CLI.** `nylo tenant endpoints [--json]` lists each agent's Action endpoint and how it is doing, and `nylo tenant endpoints ping <agent>` pings one through the Runtime.
|
|
66
|
+
- **Studio.** Shows `action.delivered` ("Action delivered") and `action.delivery_failed` ("Delivery failed", with the endpoint's error and when it retries).
|
|
67
|
+
|
|
68
|
+
- e28b8a9: **New projects serve their tools as an Action endpoint.** Everything Nylorun ships now uses Action endpoints instead of executors. `connectAgents` still works, but it is deprecated.
|
|
69
|
+
|
|
70
|
+
- **Starter (`create-agent`).** `src/main.ts` serves the agents with `createActionHandler` on `http://localhost:3001/nylorun/actions` (`PORT`, `NYLORUN_ACTIONS_URL`) and registers it. `npm run dev` and `npm start` work as before. The README explains which URL the Runtime must reach.
|
|
71
|
+
- **Local stack (`nylorun`).** The Runtime container sets `NYLORUN_ENDPOINT_LOOPBACK=docker-host` and maps `host.docker.internal` to the Docker host, so a `localhost` endpoint on the developer's machine is reachable, on Linux Docker Engine too.
|
|
72
|
+
- **Examples.** The AG-UI and browser-direct apps serve their Action endpoint at `/nylorun/actions` beside their other routes, and `register(origin)` replaces `connection.ready`.
|
|
73
|
+
- **Docs.** The README, `DEPLOYMENT.md` and the `@nylorun/agents` README describe Action endpoints.
|
|
74
|
+
|
|
75
|
+
- 50d0fb5: **AG-UI in the Runtime.** The Runtime serves AG-UI itself at `/v1/ag-ui/agents/:agent` (optional Host feature `ag-ui-endpoint`), and pages reach it directly with a subject token. `@nylorun/agents` no longer contains or depends on any AG-UI package.
|
|
76
|
+
|
|
77
|
+
- **Runtime.** `POST /v1/ag-ui/agents/:agent` runs (a `RunAgentInput` in, server-sent AG-UI events out); `GET …/threads/:thread/messages`, `GET …/threads/:thread/events` (reattach) and `POST …/threads/:thread/cancel`. For a person named by a subject token (limited to the role's agents) or by subject headers; an application key alone is `400`. A thread's session (`sessionIdFor`, unchanged, so existing threads keep their sessions) is created on its first run with `forwardedProps.nylorun.session` (`vaultIds`, `credentialSelections`, and `info` from app servers only) and never changed afterwards. Limits and a busy session are streamed as `RUN_ERROR`. A stream opened with a token ends at the token's expiry or revocation with `CUSTOM nylorun.stream_closed`. The translation moves here from `@nylorun/agents`, and `@ag-ui/core` becomes a Runtime dependency.
|
|
78
|
+
- **Agents SDK.** `createAgUiHandler` keeps its options and routes and forwards each request to the Runtime acting for the signed-in person; it now needs a Runtime with `ag-ui-endpoint` (`502 runtime_feature_missing` otherwise). `session()` options apply when a thread's session is created; whatever the browser sends in `forwardedProps.nylorun` is replaced. `@ag-ui/core` is no longer a dependency. `@nylorun/agents/browser` adds `agUi(agentId)`, a `{ url, fetch }` for `HttpAgent` that adds the key and a current token and reattaches a run the Runtime ended at token expiry, so the agent sees one run, and `agUiHistory()`. The transport gains `forward()`, which returns the Runtime's response as it is.
|
|
79
|
+
|
|
80
|
+
- 50d0fb5: **Browser access: web pages and apps call the Runtime with a publishable key.** A page ships a publishable key and gets subject tokens from its app server; the Runtime answers it directly, with CORS (optional Host feature `browser-access`).
|
|
81
|
+
|
|
82
|
+
- **Publishable keys.** `nr_pub_<tenantId>_…`, sent in `Nylorun-Key`, name the Tenant and one app, with an origin allowlist (exact origins, or `http://localhost:*` and `http://127.0.0.1:*` for development; none for native apps). `GET`/`POST /v1/access/publishable-keys`, `PUT`/`DELETE …/:id`. A key alone grants the policy's `anon` role, at most the public agent list, and reaches no session or vault. Postgres migration 4.
|
|
83
|
+
- **Host.** With browser access on, requests with an `Origin` reach Tenant routes; `/health`, `/ready` and admin routes still refuse them. Preflights for browser routes (agents, sessions, vaults, AG-UI, JWKS) are answered from the route alone and grant no credentials; the actual request must carry a publishable key whose allowlist names the origin, and only then do responses (JSON, errors, `401`, `429`, event streams) carry CORS headers. A disallowed origin or unknown key gets the opaque `404`. Tenant and executor keys sent with an `Origin` are refused before they are looked up. `Nylorun-Tenant` may be left out when `Nylorun-Key` names the Tenant; both must agree when both are sent. Browser access is on in the stack (`NYLORUN_BROWSER_ACCESS=off` turns it off) and off for a Host started from `host.json` unless `browserAccess` is true.
|
|
84
|
+
- **JWKS.** `GET /v1/access/jwks` is readable by any caller that reaches the Tenant.
|
|
85
|
+
- **Agents SDK.** `@nylorun/agents/browser`: `createBrowserClient({ url, publishableKey, token })` keeps subject tokens in memory, refreshes them a minute before expiry or after `401 token_expired`, one fetch at a time, and creates sessions and vaults owned by the token's subject; it loads no Node module. `createTokenEndpoint()` is the app server's token route. `client.access.publishableKeys` manages keys. The transport accepts a `token` source and a `publishableKey`, and event streams the Runtime ends at token expiry reconnect at once. The Tenant API client classes move to a module with no Node imports; `@nylorun/agents` and `/client` export the same names.
|
|
86
|
+
- **CLI.** `nylo access keys list|create|set-origins|revoke`.
|
|
87
|
+
|
|
88
|
+
- c121144: **Choose a session's sandbox when you open it.** `createSession({ sandbox })` (`PutSessionRequest.sandbox`) now takes `false`, `{ session }` to share, or an inline sandbox: `image`, `network.allow` and `resources`. Omit it for the Tenant's default. The agent definition needs no `.sandbox()`: the Runtime resolves the request against the Tenant's limits, pins it on the session and adds the six sandbox tools to that session only (capability `nylorun.sandbox`). The registered definition is unchanged.
|
|
89
|
+
|
|
90
|
+
- **Tenant configuration.** `GET /v1/tenant/sandbox` adds `config`; `PUT /v1/tenant/sandbox` sets `default` (`none`, `virtual` or an inline sandbox) and `limits` (`network` ceiling, `resources` maximum, `defaultResources`, `idle`). Unset, a Tenant's default is `none` and its ceiling is the package registries and code hosts of today's `dev` preset. `SeedTenantConfigRequest.sandbox.config` seeds it.
|
|
91
|
+
- **Checks at open.** A request outside the limits is a `400` that lists every problem. An inline sandbox from a caller acting for a subject is a `403`; such callers get the Tenant default or `false`. An `image` is refused: the virtual sandbox has no images. The sandbox is fixed for the session's life: a different one on a later `PUT` is a `409`.
|
|
92
|
+
- **Trees inherit.** The agent sessions of a flow agent, the agents a session uses as tools, and sessions attached with `{ session }` use the sandbox the session was opened with, and get its tools.
|
|
93
|
+
- **Compatible.** An agent that declares `.sandbox()` keeps working as before; opening its session with a `sandbox` value is a `400` that says to remove `.sandbox()` from the agent.
|
|
94
|
+
|
|
95
|
+
- 9d52189: **Studio embedding contract.** `@nylorun/core/contracts` defines the contract between Studio and an app that embeds it in an iframe, and `@nylorun/agents/studio-embed` re-exports it for Studio's web app and embedders such as Babai Desktop.
|
|
96
|
+
|
|
97
|
+
- `StudioEmbedMessageSchema`: every `postMessage` between the two, on the envelope `{ type: "nylorun.studio", protocol, kind }`. Kinds: `ready`, `init`, `token.refresh`, `theme.changed`, `navigate`, `session`, `token.expiring`, `route.changed`, `open.external`, `open.babai`, `error`. `STUDIO_EMBED_PROTOCOLS` is `[1]`.
|
|
98
|
+
- `StudioLoginTokenRequestSchema` and `StudioLoginTokenResponseSchema` for `POST /_studio/login-tokens` (now with optional `tenant` and `subject`), and `StudioSessionRequestSchema` and `StudioSessionResponseSchema` for `POST /_studio/sessions`.
|
|
99
|
+
- `parseFrameAncestors` and `isFrameAncestor` validate `NYLORUN_STUDIO_FRAME_ANCESTORS`: exact origins only, no wildcards, keywords, scheme-only entries or paths.
|
|
100
|
+
|
|
101
|
+
- 50d0fb5: **Subject tokens: a person's own credential for the Runtime.** An app server mints a short-lived token for one signed-in person, and their app calls the Runtime directly (optional Host feature `subject-tokens`). Requests with application keys and subject headers are unchanged.
|
|
102
|
+
|
|
103
|
+
- **Runtime.** `POST /v1/tokens` (application key only) mints an ES256 JWT for a subject and a role, valid 60–900 seconds. The Tenant API accepts it as a bearer and resolves its scopes and agents from the role on every request. Forged, foreign or malformed tokens get the opaque `404`; an expired token, a revoked subject, a revoked key or a removed role gets `401 token_expired` with `WWW-Authenticate`. Tokens carry only `agents:read`, `sessions:own` and `vaults:own`; they may not set session `info`, send `message.manifest` or store OAuth refresh credentials, and `GET /v1/agents` shows them `{ agentId, name, description }` of their role's agents only.
|
|
104
|
+
- **Access policy.** `GET`/`PUT /v1/access/policy`: roles with token scopes, an agent allowlist and limits (`turnsPerHour`, `concurrentTurns`, answered with `429 limit_exceeded` and `Retry-After`). Without roles nothing is minted.
|
|
105
|
+
- **Signing keys.** Per Tenant, the private key sealed with the vault KEK: `GET /v1/access/signing-keys`, `POST …/rotate` (refused while the previous key may still verify live tokens; `force` for incidents), `POST …/:kid/revoke`, `GET /v1/access/jwks`. A Tenant with signing keys and no KEK is quarantined `kek-missing`.
|
|
106
|
+
- **Revocation.** `POST /v1/access/revocations` ends a subject's tokens; their open event streams end with `event: nylorun.closed` on every process. A stream opened with a token also ends when the token expires.
|
|
107
|
+
- Postgres migration 3 adds `signing_keys`, `subject_epochs`, `subject_usage` and an index on the session owner and status. New error codes `token_expired` and `limit_exceeded`.
|
|
108
|
+
- **Agents SDK.** `client.tokens.create()`, `client.access.getPolicy()`/`putPolicy()`/`revokeSubject()`/`jwks()` and `client.access.signingKeys.list()`/`rotate()`/`revoke()`, each checking the Host feature first.
|
|
109
|
+
- **CLI.** `nylo access policy get|set|init`, `nylo access signing-keys list|rotate|revoke`, `nylo access revoke <subject>` and `nylo access token` for trying the API.
|
|
110
|
+
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
- Pin core to the tested release.
|
|
114
|
+
- Updated dependencies [c7614a4]
|
|
115
|
+
- Updated dependencies [679c488]
|
|
116
|
+
- Updated dependencies [c85cd9e]
|
|
117
|
+
- Updated dependencies [4282d5f]
|
|
118
|
+
- Updated dependencies [82d95ef]
|
|
119
|
+
- Updated dependencies [f48f12f]
|
|
120
|
+
- Updated dependencies [50d0fb5]
|
|
121
|
+
- Updated dependencies [50d0fb5]
|
|
122
|
+
- Updated dependencies [c121144]
|
|
123
|
+
- Updated dependencies [6ab4c59]
|
|
124
|
+
- Updated dependencies [6ab4c59]
|
|
125
|
+
- Updated dependencies [2ab8ed1]
|
|
126
|
+
- Updated dependencies [c121144]
|
|
127
|
+
- Updated dependencies [9546ac7]
|
|
128
|
+
- Updated dependencies [9546ac7]
|
|
129
|
+
- Updated dependencies [9d52189]
|
|
130
|
+
- Updated dependencies [50d0fb5]
|
|
131
|
+
- Updated dependencies [18468d9]
|
|
132
|
+
- @nylorun/core@0.9.0-beta
|
|
133
|
+
|
|
3
134
|
## 0.9.0-beta
|
|
4
135
|
|
|
5
136
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,44 +1,59 @@
|
|
|
1
1
|
# @nylorun/agents
|
|
2
2
|
|
|
3
|
-
Tenant API client package: definition authoring, a session client, and
|
|
4
|
-
|
|
3
|
+
Tenant API client package: definition authoring, a session client, and the
|
|
4
|
+
Action endpoint that runs your tools (`createActionHandler`). Depends only on `@nylorun/core` among Nylorun
|
|
5
5
|
packages. A developer application's production tree should contain only this
|
|
6
6
|
package and `@nylorun/core` from Nylorun. Vocabulary:
|
|
7
7
|
[runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
|
|
8
8
|
|
|
9
|
-
## Application entry (preferred)
|
|
9
|
+
## Application entry (preferred): an Action endpoint
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
// src/main.ts
|
|
13
|
-
import {
|
|
13
|
+
import { createServer } from "node:http";
|
|
14
|
+
import { createActionHandler } from "@nylorun/agents";
|
|
14
15
|
import { agents } from "../agents/index.js";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
const url = process.env.NYLORUN_ACTIONS_URL ?? "http://localhost:3001/nylorun/actions";
|
|
18
|
+
const actions = createActionHandler({ agents, url });
|
|
19
|
+
createServer(actions.node).listen(3001); // or actions.fetch in Hono, Next.js, Workers, Bun
|
|
20
|
+
await actions.register({ url });
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
The Runtime delivers each tool call, hook and workflow function of these agents
|
|
24
|
+
to `url`, signed with a short-lived **delivery token** that the handler checks
|
|
25
|
+
(Tenant, URL, Action, generation and body) before any code runs. `register`
|
|
26
|
+
saves the definitions, registers the URL and pings it through the Runtime. A
|
|
27
|
+
process that only serves Actions needs no key: pass `runtime: { url, tenant }`
|
|
28
|
+
and it reads the Tenant's public keys. Mark a long tool
|
|
29
|
+
`tool({ …, background: true })`: the handler answers at once, heartbeats and
|
|
30
|
+
posts the result. `npx @nylorun/cli tenant endpoints` shows each endpoint and
|
|
31
|
+
how its deliveries are doing.
|
|
32
|
+
|
|
33
|
+
The URL must be one the Runtime can reach: `localhost` on the local stack
|
|
34
|
+
(its Runtime runs in Docker and maps `localhost` to this machine), a public URL
|
|
35
|
+
in production, or a tunnel (ngrok, Cloudflare Tunnel) for a remote Runtime.
|
|
36
|
+
|
|
37
|
+
`connectAgents` and executors were removed in protocol 3: mount
|
|
38
|
+
`createActionHandler` instead (see [MIGRATION.md](../MIGRATION.md)). The handler
|
|
39
|
+
finds the Runtime through the three `NYLORUN_*` variables or the Project link
|
|
40
|
+
that `npx @nylorun/cli tenant create` writes; with neither, `register` fails
|
|
41
|
+
with `connection_missing` and names those steps. See [MIGRATION.md](../MIGRATION.md#runtime-clients-and-admin-api-breaking-beta)
|
|
26
42
|
for upgrading from `nylorun serve`.
|
|
27
43
|
|
|
28
44
|
## Connection resolution
|
|
29
45
|
|
|
30
|
-
`resolveConnection` / `createClient()` / `
|
|
46
|
+
`resolveConnection` / `createClient()` / `createActionHandler({ agents })`:
|
|
31
47
|
|
|
32
48
|
1. Explicit `{ url, tenant, key }`
|
|
33
|
-
2. Environment — if any of `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT
|
|
34
|
-
`NYLORUN_SERVER_KEY`
|
|
35
|
-
|
|
36
|
-
3. Project link — `.nylorun/link.json` + `credentials.json` (application role)
|
|
49
|
+
2. Environment — if any of `NYLORUN_RUNTIME_URL`, `NYLORUN_TENANT` or
|
|
50
|
+
`NYLORUN_SERVER_KEY` is set, all three must be present
|
|
51
|
+
3. Project link — `.nylorun/link.json` + `credentials.json` (the application key)
|
|
37
52
|
|
|
38
53
|
Sources never mix. Partial environment fails with `connection_missing`.
|
|
39
54
|
|
|
40
55
|
```ts
|
|
41
|
-
import { Agent,
|
|
56
|
+
import { Agent, createActionHandler, createClient, tool } from "@nylorun/agents";
|
|
42
57
|
import { z } from "zod";
|
|
43
58
|
|
|
44
59
|
const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
@@ -67,19 +82,22 @@ const session = await client.createSession({
|
|
|
67
82
|
await session.input("Look up item 123", { idempotencyKey: requestId });
|
|
68
83
|
const history = await session.history();
|
|
69
84
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
85
|
+
// A process that only serves Actions holds no key: it reads the Tenant's public keys.
|
|
86
|
+
const actions = createActionHandler({
|
|
72
87
|
agents: [assistant],
|
|
73
88
|
implementationVersion: "app-1",
|
|
74
89
|
runtime: {
|
|
75
|
-
url: process.env.NYLORUN_RUNTIME_URL
|
|
76
|
-
|
|
77
|
-
tenant: process.env.NYLORUN_TENANT,
|
|
90
|
+
url: process.env.NYLORUN_RUNTIME_URL!,
|
|
91
|
+
tenant: process.env.NYLORUN_TENANT!,
|
|
78
92
|
},
|
|
93
|
+
url: "https://app.example.com/nylorun/actions",
|
|
79
94
|
onError: console.error,
|
|
80
95
|
});
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
app.post("/nylorun/actions", actions.node);
|
|
97
|
+
// A deploy step with the application key registers the URL once:
|
|
98
|
+
await createActionHandler({ agents: [assistant], client }).register({
|
|
99
|
+
url: "https://app.example.com/nylorun/actions",
|
|
100
|
+
});
|
|
83
101
|
```
|
|
84
102
|
|
|
85
103
|
```sh
|
|
@@ -121,28 +139,22 @@ const assistant = Agent({ id: "assistant", name: "Assistant" })
|
|
|
121
139
|
|
|
122
140
|
Each key names a server; its `name` defaults to the key and, when given, must equal it. Repeated `.mcp()` calls add servers to the same capability. Transports follow [Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers): `stdio`, `streamable-http`, and `sse`. Attach an Agent Plugin package with `.plugin(path)`.
|
|
123
141
|
|
|
124
|
-
Give
|
|
142
|
+
Give a session a sandbox when you open it. The agent declares nothing, so the same agent runs with or without one, in any Tenant:
|
|
125
143
|
|
|
126
144
|
```ts
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
const session = await client.createSession({
|
|
146
|
+
agentId: "analyst",
|
|
147
|
+
ownerUserId,
|
|
148
|
+
sandbox: {
|
|
149
|
+
network: { allow: ["pypi.org", "files.pythonhosted.org"] }, // no egress unless you list hosts
|
|
150
|
+
resources: { cpus: 2, memory: "2GiB" },
|
|
151
|
+
},
|
|
152
|
+
});
|
|
132
153
|
```
|
|
133
154
|
|
|
134
|
-
The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a
|
|
155
|
+
The model gets `bash`, `read`, `write`, `edit`, `grep` and `glob` on a persistent `/workspace`. These tools run in the Runtime, not in your process, so sandbox-only agents need no Action endpoint. The Runtime decides where the sandbox runs: today an emulated shell in the Runtime process, which is not a VM boundary and takes no `image`.
|
|
135
156
|
|
|
136
|
-
|
|
137
|
-
.sandbox({
|
|
138
|
-
image: "python:3.13", // any OCI image; default python:3.13-slim
|
|
139
|
-
network: { preset: "dev", allow: ["api.example.com"] }, // "none" | "dev" (default) | "open"
|
|
140
|
-
resources: { cpus: 2, memory: "2GiB" },
|
|
141
|
-
idle: "15m", // stop compute when idle; files persist
|
|
142
|
-
})
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
The `dev` preset allows package registries and code hosts. Private networks, loopback, the host and cloud metadata endpoints are always blocked. Options from the full design that are not in this version (`setup`, `files`, `secrets`, `mount`, `onStart`, …) throw a `SandboxError` that says so.
|
|
157
|
+
`sandbox` takes `false` for none, `{ session }` to share another session's sandbox, or an inline sandbox as above; omit it for the Tenant's default. The Runtime checks it 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. A caller acting for a user (`app.as(...)`) can't define one inline; it gets the Tenant's default or `false`. Private networks, loopback, the host and cloud metadata endpoints are always blocked.
|
|
146
158
|
|
|
147
159
|
Add an agent with `.subagents()` to let the model delegate to it:
|
|
148
160
|
|
|
@@ -167,9 +179,9 @@ const support = Agent({ id: "support" })
|
|
|
167
179
|
.subagents(researcher);
|
|
168
180
|
```
|
|
169
181
|
|
|
170
|
-
The tool is named after the agent's `id` and takes `{ task: string }`; the agent's `description` (required) is what the parent's model reads to decide when to delegate. The child starts with a fresh context: it sees its own instructions and the task, nothing of the parent's conversation, and only its final text (or `outputSchema` result) comes back. It keeps its own tools, hooks, skills and MCP servers, served by the
|
|
182
|
+
The tool is named after the agent's `id` and takes `{ task: string }`; the agent's `description` (required) is what the parent's model reads to decide when to delegate. The child starts with a fresh context: it sees its own instructions and the task, nothing of the parent's conversation, and only its final text (or `outputSchema` result) comes back. It keeps its own tools, hooks, skills and MCP servers, served by the Action endpoint you already mount for the parent (`createActionHandler({ agents: [support] })` serves both), shares the session's sandbox, and starts with empty `ctx.state`. Tools can read `ctx.agent` (`{ id, path, delegationId }`). Several delegation calls in one model response run in parallel. An empty answer, a failure (with the child's last text marked as evidence) or a cancelled child reaches the parent's model as a failed tool result, never as success. The Runtime emits `delegation.started` and `delegation.completed`; `session.history({ agent })` filters by `delegationId` (one child invocation) or by path such as `support/researcher` (every concurrent child that shares that path).
|
|
171
183
|
|
|
172
|
-
A flow agent can be a subagent too: `.subagents(researchFlow)` with a `description` like any other. Its manifest is inlined in the parent's, so it is saved with the parent and served by the parent's
|
|
184
|
+
A flow agent can be a subagent too: `.subagents(researchFlow)` with a `description` like any other. Its manifest is inlined in the parent's, so it is saved with the parent and served by the parent's Action endpoint. When the model calls it, the flow runs on the Runtime in its own linked session (a fresh one per call, linked from the parent's with a `node.agent` event at `support/<flow id>`), its agents in theirs, and the flow's output comes back as the tool result; a failed flow is a failed tool result. Cancelling the parent cancels the flow. Flow agents as subagents run on the Runtime only, not in a local `run()`. An approval asked for inside the flow waits on the flow's own session.
|
|
173
185
|
|
|
174
186
|
Delegate when the parent should keep the answer. When a specialist should own the rest of the conversation, switch capabilities with a `.beforeModel()` patch instead. This version is one level deep and non-interactive: a delegated agent cannot use agents as tools, its tools cannot declare `approval` (keep those on the parent), and `ctx.ask`, `ctx.approve`, `ctx.sleep` or `ctx.waitFor` inside it fail with `delegation.interaction-unsupported`. Delegation is not an approval boundary; approvals live on tools.
|
|
175
187
|
|
|
@@ -247,10 +259,10 @@ Name the stages you may reorder.
|
|
|
247
259
|
`Chain`, `Switch`, `Parallel`, `Map` and `Loop` still build workflow manifest v1
|
|
248
260
|
directly and run as before; a flow agent can't be a child of them.
|
|
249
261
|
|
|
250
|
-
The agents in a flow share one sandbox:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
built-ins via `session.sandbox` (application) or `ctx.sandbox` (
|
|
262
|
+
The agents in a flow share one sandbox: open the flow's session with it,
|
|
263
|
+
`createSession({ …, sandbox: { … } })`, and every agent, tool step and `verify` in the
|
|
264
|
+
flow uses it. Share it with another session with `sandbox: { session }`, and call
|
|
265
|
+
built-ins via `session.sandbox` (application) or `ctx.sandbox` (a tool).
|
|
254
266
|
Observe with `session.observe({ follow: true })` to merge linked agent streams;
|
|
255
267
|
`pending()` lists waits across the tree. Studio renders the manifest tree and
|
|
256
268
|
live node status. Source examples:
|
|
@@ -263,23 +275,32 @@ Use `session.observe({ cursor, signal })` for resumable canonical events, `sessi
|
|
|
263
275
|
|
|
264
276
|
Connection defaults follow `resolveConnection` (options → environment → Project
|
|
265
277
|
link). Implementation version defaults from `NYLORUN_IMPLEMENTATION_VERSION`,
|
|
266
|
-
then `dev`.
|
|
267
|
-
|
|
268
|
-
Agents do not hash the manifest, and an in-flight action
|
|
278
|
+
then `dev`. `register` saves the definitions and registers one endpoint per
|
|
279
|
+
served agent with the application key; serving deliveries needs no key.
|
|
280
|
+
Agents do not hash the manifest, and an in-flight action is still delivered after
|
|
269
281
|
the registered digest changes. Model selection and model credentials belong to
|
|
270
282
|
the Tenant.
|
|
271
283
|
|
|
272
|
-
After registration,
|
|
284
|
+
After registration, the Runtime POSTs each Action to the endpoint with a delivery token signed by the Tenant's key; the handler verifies it before any code runs and answers with the outcome. A background tool answers `202`, heartbeats with the newest token and posts its result. A cancel aborts the request, which reaches the tool as `ctx.signal`; JavaScript cannot forcibly terminate a function that ignores its signal. The host makes lost in-flight deliveries uncertain instead of automatically repeating external effects.
|
|
273
285
|
|
|
274
|
-
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use `before`/`after` hooks. The
|
|
286
|
+
Tools receive state, info, identity, resume, signal, approval/response helpers and memoized `step`. Step outcomes survive a persisted wait result; they do not establish exactly-once external effects after an unacknowledged crash. `sleep` and `waitFor` currently return inspectable deferred outcomes; automatic timer/event wakeups remain runtime implementation work. Remote `onModelCall` convenience and progress-event transport are not supplied in this pass. Arbitrary middleware closures are rejected for durable definitions; use `before`/`after` hooks. The Action endpoint runs every capability registered at a hook point in one action. Agent definitions have no `.run()`; explicit local execution is available through `@nylorun/harness/run`.
|
|
275
287
|
|
|
276
288
|
## AG-UI
|
|
277
289
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
290
|
+
The Runtime serves the Tenant's agents to any [AG-UI](https://docs.ag-ui.com)
|
|
291
|
+
client (`@ag-ui/client`'s `HttpAgent`, CopilotKit, a desktop renderer) at
|
|
292
|
+
`/v1/ag-ui/agents/{agentId}` (optional feature `ag-ui-endpoint`). Each AG-UI
|
|
293
|
+
thread is one session per person, agent and thread. There are two ways in:
|
|
294
|
+
|
|
295
|
+
- **From your own server**, with `@nylorun/agents/ag-ui` (below): your server
|
|
296
|
+
signs people in and the handler forwards each request to the Runtime acting
|
|
297
|
+
for that person. The browser never sees the Runtime.
|
|
298
|
+
- **From the page itself**, with `@nylorun/agents/browser`'s `agUi()` and a
|
|
299
|
+
subject token ([In the browser](#in-the-browser)). No chat traffic passes
|
|
300
|
+
through your server.
|
|
301
|
+
|
|
302
|
+
Both reach the same threads: one started through the handler continues from
|
|
303
|
+
the page.
|
|
283
304
|
|
|
284
305
|
```ts
|
|
285
306
|
import { createServer } from "node:http";
|
|
@@ -305,9 +326,10 @@ createServer(toNodeListener(agui)).listen(3000);
|
|
|
305
326
|
|
|
306
327
|
`run`, `history`, `reattach` and `cancel` are also on the handler for your own
|
|
307
328
|
routing. The default `client` is `createClient()`; pass `client` to use another
|
|
308
|
-
one, and `session(subject, agentId)` to add the person's `vaultIds
|
|
309
|
-
`credentialSelections`
|
|
310
|
-
|
|
329
|
+
one, and `session(subject, agentId)` to add the person's `vaultIds`,
|
|
330
|
+
`credentialSelections` or `info`. They apply when the thread's session is
|
|
331
|
+
created, on its first run; later runs keep them. Whatever the browser sends in
|
|
332
|
+
`forwardedProps.nylorun` is replaced.
|
|
311
333
|
|
|
312
334
|
- Each AG-UI message id is the command's idempotency key: a retried run replays
|
|
313
335
|
the same turn instead of starting a second one.
|
|
@@ -325,9 +347,9 @@ identity).
|
|
|
325
347
|
`scopes` defaults to `["sessions:own"]`; add `"vaults:own"` when `session()`
|
|
326
348
|
attaches the person's vaults. A `subject` the Runtime cannot name (see below)
|
|
327
349
|
answers `500` (`subject_invalid`).
|
|
328
|
-
- The handler needs a Runtime with the optional
|
|
329
|
-
|
|
330
|
-
|
|
350
|
+
- The handler needs a Runtime with the optional feature `ag-ui-endpoint` and
|
|
351
|
+
answers `502` (`runtime_feature_missing`) without it. It loads no AG-UI
|
|
352
|
+
package itself: the Runtime does the protocol work.
|
|
331
353
|
|
|
332
354
|
A complete web backend with a test is in
|
|
333
355
|
[examples/src/ag-ui](../examples/README.md#an-agent-in-your-web-app-ag-ui).
|
|
@@ -336,8 +358,71 @@ Limitations: assistant text arrives once per model step (no token streaming);
|
|
|
336
358
|
no reasoning, state, activity or subagent events, and an agent used as a tool
|
|
337
359
|
shows only its result; frontend tools in `RunAgentInput.tools` are rejected
|
|
338
360
|
(`400`); one text part per user message; earlier messages cannot be edited or
|
|
339
|
-
regenerated.
|
|
340
|
-
|
|
361
|
+
regenerated. A browser reaches the Runtime only with a subject token and a
|
|
362
|
+
publishable key, never with a Tenant key.
|
|
363
|
+
|
|
364
|
+
## A2A
|
|
365
|
+
|
|
366
|
+
`@nylorun/agents/a2a` serves the Tenant's agents to other agents over
|
|
367
|
+
[A2A](https://a2a-protocol.org) 1.0 (JSON-RPC) from your own server: the
|
|
368
|
+
gateway. Your server authenticates each partner and names the subject its tasks
|
|
369
|
+
belong to. The handler forwards each request to the Runtime, which speaks A2A,
|
|
370
|
+
so the handler loads no A2A package and parses no message.
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
import { createServer } from "node:http";
|
|
374
|
+
import { createA2aHandler, toNodeListener } from "@nylorun/agents/a2a";
|
|
375
|
+
import support from "./agents/support.js";
|
|
376
|
+
|
|
377
|
+
const a2a = createA2aHandler({
|
|
378
|
+
basePath: "/a2a",
|
|
379
|
+
agents: [support], // nothing else is reachable
|
|
380
|
+
// undefined → 401; return { subject, agents } to give a partner fewer agents
|
|
381
|
+
subject: (request) => partnerFor(request.headers.get("x-partner-key"))?.subject,
|
|
382
|
+
publicUrl: "https://api.example.com/a2a", // for the card; default: the request's origin
|
|
383
|
+
card: {
|
|
384
|
+
provider: { organization: "Example Inc.", url: "https://example.com" },
|
|
385
|
+
securitySchemes: {
|
|
386
|
+
partnerKey: { apiKeySecurityScheme: { location: "header", name: "X-Partner-Key" } },
|
|
387
|
+
},
|
|
388
|
+
securityRequirements: [{ schemes: { partnerKey: { list: [] } } }],
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
createServer(toNodeListener(a2a)).listen(3000);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
| Method and path under `basePath` | Operation |
|
|
396
|
+
| --- | --- |
|
|
397
|
+
| `GET /{agentId}/.well-known/agent-card.json` | The Agent Card: name, description and one skill from the manifest, with this endpoint's URL and your provider and security schemes. Public; `subject` is not called |
|
|
398
|
+
| `POST /{agentId}` | One A2A JSON-RPC request, answered by the Runtime |
|
|
399
|
+
|
|
400
|
+
What the Runtime answers (Host feature `a2a-endpoint`):
|
|
401
|
+
|
|
402
|
+
- `SendMessage` waits until the task completes, fails, is canceled or asks
|
|
403
|
+
for input, for at most 5 minutes (then it returns `WORKING`); with
|
|
404
|
+
`configuration.returnImmediately` it returns at once and the partner polls
|
|
405
|
+
`GetTask`. `CancelTask` works on a running task.
|
|
406
|
+
- A `contextId` is one session per partner, agent and context; a task is one
|
|
407
|
+
turn. A message without `taskId` starts a task (one at a time per context).
|
|
408
|
+
A question from the agent pauses the task as `TASK_STATE_INPUT_REQUIRED`
|
|
409
|
+
with the question as the status message; the partner answers with a message
|
|
410
|
+
on the same `taskId`.
|
|
411
|
+
- The `messageId` is the idempotency key: a retried message returns the same
|
|
412
|
+
task.
|
|
413
|
+
- Text parts, or a single data part, in; the turn's output as the artifact
|
|
414
|
+
`output` (text, or JSON as a data part). Files are
|
|
415
|
+
`ContentTypeNotSupportedError`.
|
|
416
|
+
- Clients must send `A2A-Version: 1.0` (header or `?A2A-Version=`).
|
|
417
|
+
- Not yet: `ListTasks`, streaming and `SubscribeToTask`
|
|
418
|
+
(`UnsupportedOperationError`; the card says `streaming: false`), push
|
|
419
|
+
notifications, the extended card, and approvals. An agent that pauses for an
|
|
420
|
+
approval shows `INPUT_REQUIRED`, refuses a reply, and can be canceled, so do
|
|
421
|
+
not publish agents with approval-gated tools yet.
|
|
422
|
+
- The handler calls the Runtime as the partner's subject with the scope
|
|
423
|
+
`sessions:own` only, so one partner never reaches another's tasks. It sends
|
|
424
|
+
no header the partner chose except `A2A-Version` and `A2A-Extensions`. Without
|
|
425
|
+
`a2a-endpoint` on the Runtime it answers `502` (`runtime_feature_missing`).
|
|
341
426
|
|
|
342
427
|
## Acting for a person (app servers)
|
|
343
428
|
|
|
@@ -370,10 +455,143 @@ compatibility check, so calling `as()` per request is cheap.
|
|
|
370
455
|
| `agents:write` | Saving agents; listing agents, models and providers |
|
|
371
456
|
| `tenant:settings` | The Tenant's status, model provider and sandbox settings |
|
|
372
457
|
|
|
373
|
-
No scope reaches Tenant reset, config seed,
|
|
458
|
+
No scope reaches Tenant reset, config seed, Action endpoints, actions or the sandbox
|
|
374
459
|
tool routes; call those without `as()`. A subject is 1–200 visible ASCII
|
|
375
460
|
characters (spaces only inside) and `host` is reserved. Your server must drop
|
|
376
461
|
any `Nylorun-*` header its own clients send, and only an application key can act
|
|
377
|
-
for a subject
|
|
462
|
+
for a subject.
|
|
463
|
+
|
|
464
|
+
## Minting subject tokens (app servers)
|
|
465
|
+
|
|
466
|
+
To let a person's browser or app call the Runtime directly, your server mints a
|
|
467
|
+
short-lived **subject token** for them instead of carrying their requests
|
|
468
|
+
(optional feature `subject-tokens`). What a token may do comes from the Tenant's
|
|
469
|
+
access policy, not from the caller:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { createClient } from "@nylorun/agents";
|
|
473
|
+
|
|
474
|
+
const app = createClient(); // the Tenant key, on the server only
|
|
475
|
+
|
|
476
|
+
// Once: roles, their agents and limits (or `nylo access policy init`).
|
|
477
|
+
await app.access.putPolicy({
|
|
478
|
+
version: 1,
|
|
479
|
+
roles: {
|
|
480
|
+
user: {
|
|
481
|
+
scopes: ["sessions:own", "agents:read"],
|
|
482
|
+
agents: ["support"],
|
|
483
|
+
limits: { turnsPerHour: 60, concurrentTurns: 2 },
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
anon: { scopes: [], agents: [] },
|
|
487
|
+
tokens: { maxTtlSeconds: 600 },
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Per signed-in person, from a same-origin POST route:
|
|
491
|
+
const { token, expiresAt } = await app.tokens.create({
|
|
492
|
+
subject: `app:${user.id}`,
|
|
493
|
+
role: "user",
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The client sends it as `Authorization: Bearer <token>` with `Nylorun-Tenant`.
|
|
498
|
+
Tokens carry only `sessions:own`, `vaults:own` and `agents:read`, live at most
|
|
499
|
+
15 minutes, and see only `{ agentId, name, description }` of their role's
|
|
500
|
+
agents. A token that expired or was revoked answers `401` with
|
|
501
|
+
`code: "token_expired"`: fetch a new one and retry. Starting more turns than the
|
|
502
|
+
role allows answers `429` with `code: "limit_exceeded"` and `Retry-After`.
|
|
503
|
+
|
|
504
|
+
| Call | Does |
|
|
505
|
+
| --- | --- |
|
|
506
|
+
| `app.access.revokeSubject(subject)` | Ends every token minted so far for the person and their open event streams |
|
|
507
|
+
| `app.access.signingKeys.rotate()` | Rotates the Tenant's signing keys without signing anyone out (`{ force: true }` for incidents) |
|
|
508
|
+
| `app.access.signingKeys.list()`, `.revoke(kid)`, `app.access.jwks()` | Inspect and retire keys; the public keys |
|
|
509
|
+
|
|
510
|
+
Keep tokens in memory on the client, never in `localStorage`, and serve the
|
|
511
|
+
minting route without CORS. `nylo access …` does the same from the terminal.
|
|
512
|
+
|
|
513
|
+
`createTokenEndpoint` is that route, ready to mount (Next.js, Hono or any
|
|
514
|
+
`Request` → `Response` handler):
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import { createTokenEndpoint } from "@nylorun/agents";
|
|
518
|
+
|
|
519
|
+
export const POST = createTokenEndpoint({
|
|
520
|
+
client: app,
|
|
521
|
+
role: "user",
|
|
522
|
+
subject: (request) => userFromCookie(request)?.id,
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## In the browser
|
|
527
|
+
|
|
528
|
+
`@nylorun/agents/browser` calls the Runtime from a web page or an app with a
|
|
529
|
+
publishable key and the token route above (optional feature `browser-access`).
|
|
530
|
+
It loads no Node module.
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
import { createBrowserClient } from "@nylorun/agents/browser";
|
|
534
|
+
|
|
535
|
+
const nylo = createBrowserClient({
|
|
536
|
+
url: "https://runtime.example.com",
|
|
537
|
+
publishableKey: "nr_pub_tn_…", // app.access.publishableKeys.create({ name, origins })
|
|
538
|
+
token: () => fetch("/api/nylorun/token", { method: "POST" }).then((r) => r.json()),
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const session = await nylo.createSession({ agentId: "support" }); // owned by the token's subject
|
|
542
|
+
await session.input("Where is my order?", { idempotencyKey: crypto.randomUUID() });
|
|
543
|
+
for await (const event of session.observe()) console.log(event.type);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
For a chat UI, `agUi()` gives `@ag-ui/client`'s `HttpAgent` a `fetch` with the
|
|
547
|
+
key and a current token:
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
import { HttpAgent } from "@ag-ui/client";
|
|
551
|
+
|
|
552
|
+
const { url, fetch } = nylo.agUi("support");
|
|
553
|
+
const agent = new HttpAgent({
|
|
554
|
+
url,
|
|
555
|
+
fetch,
|
|
556
|
+
threadId,
|
|
557
|
+
initialMessages: await nylo.agUiHistory("support", threadId),
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
When the Runtime ends a run's stream because the token expired or was revoked,
|
|
562
|
+
that `fetch` reattaches from the last event with a new token, so the agent sees
|
|
563
|
+
one run. A person's token may attach their own vaults to a new thread
|
|
564
|
+
(`forwardedProps: { nylorun: { session: { vaultIds } } }`) but not set `info`.
|
|
565
|
+
|
|
566
|
+
The client keeps the token in memory, fetches a new one a minute before it
|
|
567
|
+
expires or when the Runtime answers `401 token_expired`, and never asks for two
|
|
568
|
+
at once. Event streams that the Runtime ends at token expiry or revocation
|
|
569
|
+
reconnect from their last event with a new token. Create a publishable key per
|
|
570
|
+
app with the origins that serve it (`http://localhost:*` for development);
|
|
571
|
+
requests from other origins get the opaque `404`.
|
|
572
|
+
|
|
573
|
+
The SDK depends only on core within the Nylorun packages; installing it does not install harness or any AG-UI or A2A package. Use `/define`, `/client`, `/ag-ui`, `/a2a` or `/browser` for focused imports, or the root for convenience. Studio uses `/client`. See [the adopted host contract](../harness/HOST_CONTRACT.md).
|
|
574
|
+
|
|
575
|
+
## Embedding Studio (desktop apps)
|
|
576
|
+
|
|
577
|
+
`@nylorun/agents/studio-embed` is the contract between Studio and an app that
|
|
578
|
+
shows it in an iframe, such as Babai. It exports the message schema
|
|
579
|
+
(`StudioEmbedMessageSchema`, envelope `{ type: "nylorun.studio", protocol, kind }`),
|
|
580
|
+
the login-token and session schemas, `STUDIO_EMBED_PROTOCOLS` and
|
|
581
|
+
`parseFrameAncestors`.
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import { StudioEmbedMessageSchema } from "@nylorun/agents/studio-embed";
|
|
585
|
+
|
|
586
|
+
window.addEventListener("message", (event) => {
|
|
587
|
+
if (event.source !== frame.contentWindow || event.origin !== studioOrigin) return;
|
|
588
|
+
const message = StudioEmbedMessageSchema.safeParse(event.data);
|
|
589
|
+
if (!message.success) return;
|
|
590
|
+
if (message.data.kind === "ready") frame.contentWindow!.postMessage(init, studioOrigin);
|
|
591
|
+
});
|
|
592
|
+
```
|
|
378
593
|
|
|
379
|
-
The
|
|
594
|
+
The embedder's backend mints the login token with the admin key
|
|
595
|
+
(`mintStudioLoginToken` in `@nylorun/admin`) and the page passes it to Studio in
|
|
596
|
+
`init`. No key reaches the page. Studio's origin allowlist is
|
|
597
|
+
`NYLORUN_STUDIO_FRAME_ANCESTORS`.
|