App server
Build and configure your app, connect its executor, and serve agents to signed-in users.
Your app server is your own application. In production it does two jobs:
- Runs your tools.
connectAgentsregisters agents and executes their tools when the Runtime asks. - Serves your users. It signs people in and calls the Runtime for each person.
The starter project does the first job. Add the second when people use your agents.
Build and start
npm run build
npm start # node dist/src/main.jsSet three variables, or print them for the linked project with
eval "$(npx @nylorun/cli env)":
| Variable | Value |
|---|---|
NYLORUN_RUNTIME_URL | Where your app reaches the Runtime. See Docker. |
NYLORUN_TENANT | The Tenant id. |
NYLORUN_SERVER_KEY | The Tenant's application key. Server-side only. |
The app does not start the stack, Studio, or a file watcher. Start the Runtime
first (npx nylorun up), under the same supervisor if you use one. You can
also create Tenants with @nylorun/admin.
How the connection is found
createClient() and connectAgents() use exactly one complete source, in this
order:
- Explicit
{ url, key, tenant }options. NYLORUN_RUNTIME_URL,NYLORUN_SERVER_KEYorNYLORUN_EXECUTOR_KEY, andNYLORUN_TENANT.- The nearest
.nylorun/link.jsonpluscredentials.json.
Sources never mix. If any variable is set, all of them must be, or the call
fails with connection_missing.
Executors
The compiled app connects every exported agent and workflow:
import { connectAgents } from "@nylorun/agents";
import { agents } from "../agents/index.js";
const connection = connectAgents({
agents,
implementationVersion: process.env.NYLORUN_IMPLEMENTATION_VERSION ?? "app-1",
runtime: {
url: process.env.NYLORUN_RUNTIME_URL!,
tenant: process.env.NYLORUN_TENANT!,
key: process.env.NYLORUN_SERVER_KEY!,
},
onError: console.error,
});
await connection.ready;Restarts and replicas re-register idempotently. Credentials, retries, and uncertain work are on Run → Executors.
Serve people
Your server signs people in and keeps the Tenant key. For each request it
names the person with client.as(). The Runtime then limits the call to that
person's sessions and vaults:
import { createClient } from "@nylorun/agents";
const app = createClient(); // the Tenant key, on the server only
// Per request, after your own sign-in:
const person = app.as(`app:${user.id}`, { scopes: ["sessions:own", "vaults:own"] });
const session = await person.createSession({ agentId: "support", ownerUserId: `app:${user.id}` });
await person.listSessions(); // only this person's sessionsas() sends Nylorun-Subject and Nylorun-Scopes on every call, event streams
included. Nothing is minted or cached, so calling it per request is cheap.
Another person's session answers 404, like a missing one; a route outside the
scopes answers 403 (scope_required).
| Scope | Allows |
|---|---|
sessions:own | The person's own sessions: create, list, read, stream, message, approve, respond, cancel |
vaults:own | The person's own vaults and credentials |
agents:read | Listing the Tenant's agents |
agents:write | Saving agents; listing agents, models, and providers |
tenant:settings | The Tenant's status, model provider, and sandbox settings |
The default is ["sessions:own"]. Full options are in
Reference → client.
Serve a chat UI with AG-UI
For a web or desktop chat UI, mount the AG-UI handler. It maps each AG-UI thread to one session per person, agent, and thread, and calls the Runtime as that person:
import { createServer } from "node:http";
import { createAgUiHandler, toNodeListener } from "@nylorun/agents/ag-ui";
import support from "./agents/support.js";
const agui = createAgUiHandler({
basePath: "/api/agui",
agents: [support], // nothing else is reachable
subject: async (request) => (await getSignedInUser(request))?.id, // undefined → 401
});
createServer(toNodeListener(agui)).listen(3000);
// Next.js, Hono, Bun, Deno, Workers: export or mount agui.fetch directly.Point CopilotKit or @ag-ui/client's HttpAgent at /api/agui/support. A
complete web backend with a test is in the
AG-UI example.
Routes, approvals, and limitations are in
Reference → ag-ui.
Rules for an app server
- Browsers always go through your server. Never let one call the Runtime.
- Drop every
Nylorun-*header your own clients send. Never forwardOrigin; the Runtime refuses browser requests. - Terminate TLS for your clients.
- Keep the admin key and application keys on the server. A server that holds
the admin key can derive its Tenant key instead of storing one
(
deriveTenantKey(adminKey, tenantId, principalId)from@nylorun/admin, for a Tenant created with that derived principal). - Removing a person is your decision: stop acting for them and close their open streams. There is no per-person credential to revoke.
- Keep the Runtime off the network. How your server reaches it is on Docker.