# Capabilities (/docs/build/capabilities)



Add a named capability and attach it with `.use()`. The following snippet
assumes an application-owned `orders` service; construct that client in the
host.

```ts
import { Agent, capability, tool } from "@nylorun/agents";
import { z } from "zod";

type SupportInfo = { accountId?: string };
type OrderService = {
  find(
    orderNumber: string,
    options: { signal: AbortSignal; accountId?: string },
  ): Promise<{ orderNumber: string; status: string }>;
};

export function supportCapability(orders: OrderService) {
  const findOrder = tool({
    name: "find_order",
    description: "Find an order by its public order number.",
    input: z.object({ orderNumber: z.string() }),
    output: z.object({ orderNumber: z.string(), status: z.string() }),
    effects: "read",
    async run({ orderNumber }, context) {
      return orders.find(orderNumber, {
        signal: context.signal,
        accountId: context.info?.accountId,
      });
    },
  });

  return capability({
    id: "support",
    instructions: "Ask for an order number before using find_order.",
    tools: [findOrder],
  });
}

export const assistant = Agent({ id: "assistant", name: "Assistant" })
  .use(supportCapability(orders))
  .build();
```

Construct `orders` and other service clients in the application, then close over
them in the capability. Per-session host data is `info` on the Runtime session
(tools read `context.info`). Top-level `tools` and `instructions` on
`Agent({ ... })` are first-class and compile as capability id `"agent"`.
`tools` on either surface can include another `Agent` — see
[Subagents](/docs/build/subagents).

## Understand hooks [#understand-hooks]

Durable definitions use scoped hooks. They can add instructions, tools, or
state, or deny / approve / retry / block a candidate. They cannot set `model`
— Runtime owns model resolution.

| Contribution           | Declarative field           | Dynamic API                        |
| ---------------------- | --------------------------- | ---------------------------------- |
| Instructions           | `instructions`              | `before("turn"\|"step")` → `Patch` |
| Tools                  | `tools`                     | `before("turn"\|"step")` → `Patch` |
| Session memory         | tools write `context.state` | `before` reads `state`             |
| After a model call     | —                           | `after("step")` → `Decision`       |
| After the final answer | —                           | `after("turn")` → `TurnDecision`   |

`capability({ model })` is deprecated. Durable manifests reject arbitrary
middleware closures. Use [hooks](/docs/build/hooks).

Helpers such as `mcp()`, `sandbox()`, `skills()`, and `plugin()` each return
one capability. See those pages for the options they accept.

## Manage ordering and ownership [#manage-ordering-and-ownership]

The application owns resources. Create clients in the host, close over them
from tools, and shut them down in the application. There is no capability
`state` factory.

Capabilities are applied in `.use()` order. Use stable, unique ids and
separate declarations for independent concerns.

The published catalog row is `capabilities[]` with `id`, `type`
(`agent` or `agent-plugin`), optional instructions, tools, skills, MCP
servers, sandbox, and `hooks: { at, scope }[]`.

## Next step [#next-step]

<Cards>
  <Card title="MCP" description="Declare MCP servers on the agent." href="/docs/build/mcp" />

  <Card title="Sessions" description="Open a Runtime session and send a command." href="/docs/run/sessions" />
</Cards>


# Chain (/docs/build/chain)



`Chain` runs a non-empty list of agents, tools, or nested workflows in order.
Each step receives the preceding step's output unless a slot supplies an
`input` function.

```ts
import { Chain } from "@nylorun/agents/define";

export const report = Chain({
  id: "report",
  steps: [
    researcher,
    {
      run: analyst,
      input: ({ results }) =>
        `Summarize: ${results.researcher.findings}`,
    },
    {
      run: publish,
      input: ({ value }) => ({ summary: value.summary }),
    },
  ],
});
```

## Values and results [#values-and-results]

* `value` is the immediately preceding step's output.
* `results` contains earlier outputs keyed by step id.
* `input` is the original workflow input.

A direct step keeps its runnable id. Set `id` on a slot when the same runnable
appears more than once or when you want a stable result key:

```ts
{
  id: "final-review",
  run: reviewer,
  input: ({ value, input, results }) => ({ value, input, results }),
}
```

The chain output is the final step's output. Step ids must be unique among
siblings. Runtime checkpoints every node boundary, so a resumed chain does not
repeat completed effects.

See the shipped [`chain` example](https://github.com/nylorun/agents/tree/main/examples/agents/chain),
then learn how to [route with Switch](/docs/build/switch).


# Examples (/docs/build/examples)



The generated registry contains one **Order assistant** with a `lookup_order`
tool:

```sh
npm create @nylorun/agent my-agent
cd my-agent
npx nylorun up
npx @nylorun/cli tenant create
npm run dev
```

The [Agents repository examples](https://github.com/nylorun/agents/tree/main/examples/agents)
show instructions, tools, hooks, MCP, sandbox, skills, plugins, subagents, and
media-oriented agents. Copy definitions and capabilities into your own
`agents/` tree; do not copy credentials or generated data.

## Workflow examples [#workflow-examples]

Six runnable examples demonstrate the workflow primitives:

| Example                                                                                    | Pattern                                                     |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| [`chain`](https://github.com/nylorun/agents/tree/main/examples/agents/chain)               | Run nodes in order and pass output through slots.           |
| [`switch`](https://github.com/nylorun/agents/tree/main/examples/agents/switch)             | Choose one branch from input.                               |
| [`parallel`](https://github.com/nylorun/agents/tree/main/examples/agents/parallel)         | Run independent branches concurrently.                      |
| [`map`](https://github.com/nylorun/agents/tree/main/examples/agents/map)                   | Apply a node to every input item.                           |
| [`loop`](https://github.com/nylorun/agents/tree/main/examples/agents/loop)                 | Repeat a body until its condition is satisfied.             |
| [`ship-feature`](https://github.com/nylorun/agents/tree/main/examples/agents/ship-feature) | Combine agents and control-flow nodes in a larger workflow. |

Start a new Studio session after changing a definition. Existing sessions stay
pinned to the manifest with which they started unless an input supplies a turn
manifest.

## Next step [#next-step]

Learn to [add a typed tool](/docs/build/tools), [compose workflows](/docs/build/workflows),
or inspect execution in [Studio](/docs/studio).


# Hooks (/docs/build/hooks)



Hooks intercept a turn or a model step. Register them on the builder or inside
a capability. There are no compatibility aliases for the old names.

```ts
import { Agent } from "@nylorun/agents";

export const support = Agent({
  id: "support",
  instructions: "Help with orders.",
})
  .before("turn", ({ info }) => ({
    instructions: [`Tenant ${info?.tenantId ?? "unknown"}`],
  }))
  .after("step", ({ text, toolCalls, attempt }) => {
    if (toolCalls.length === 0 && !text) {
      return attempt < 2 ? { retry: "Answer or call a tool." } : { block: "Empty step." };
    }
    return {};
  })
  .after("turn", ({ text, attempt }) => {
    if (!text?.trim()) {
      return attempt < 2 ? { retry: "Give a short final answer." } : {};
    }
    return {};
  })
  .build();
```

| Registration          | When it runs                                                        | Return         |
| --------------------- | ------------------------------------------------------------------- | -------------- |
| `.before("turn", fn)` | Once per turn; the `Patch` applies to every model call in that turn | `Patch`        |
| `.before("step", fn)` | Every model call                                                    | `Patch`        |
| `.after("step", fn)`  | After every model call, before tools or text take effect            | `Decision`     |
| `.after("turn", fn)`  | After the final answer                                              | `TurnDecision` |

`before("turn")` is issued only in a turn's first segment. In a Runtime, every
capability registered at one hook point runs in a **single** executor action,
so a hook point costs one round trip per turn or per model call.

Hooks may run more than once when a delivery is retried: an expired hook claim
is offered again instead of becoming uncertain. Keep side effects in tools.

## Patch, Decision, and TurnDecision [#patch-decision-and-turndecision]

A `Patch` can add `instructions`, toggle `capabilities` or `tools`, merge
`state`, or `block` the model call. It cannot set `model`.

A `Decision` can replace `text`, `deny` or `approve` proposed tool calls,
`retry` with feedback, or `block`.

A `TurnDecision` can replace final `text` (plain-text agents) or `output`
(agents with an `outputSchema`), `retry`, or `block`.

`retry` now retries. From `after("step")` it denies the proposed tool calls
with the feedback, or sends a text answer back with the feedback as a message;
from `after("turn")` it sends the final answer back. The engine does not cap
retries: bound them with `attempt`, for example
`attempt < 2 ? { retry: "…" } : { block: "…" }`.

Capability form:

```ts
capability({
  id: "policy",
  before: { turn: ({ info }) => ({ instructions: [`Tenant ${info?.tenantId}`] }) },
  after: { step: ({ text }) => ({}), turn: ({ text }) => ({}) },
})
```

The published manifest lists each hook as `{ at, scope }` on
`capabilities[].hooks`. Action kind is `hook` with
`{ at, scope, capabilityIds }`. Rebuild agents after upgrading; Runtime
cancels leftover old hook actions and fails in-flight schema 3 turns. Start
new sessions.

See [Capabilities](/docs/build/capabilities) and the
[0.15 → 0.17 migration](/docs/compatibility#breaking-changes).

## Next step [#next-step]

<Cards>
  <Card title="Capabilities" description="Bundle tools with instructions and hooks." href="/docs/build/capabilities" />

  <Card title="Sessions" description="Open a Runtime session and send a command." href="/docs/run/sessions" />
</Cards>


# Agent (/docs/build)



An agent is a definition: an id, instructions, and the tools the model may
call. You export it from `agents/index.ts`; the Runtime picks the model.

```ts title="agents/assistant/agent.ts"
import { Agent, tool } from "@nylorun/agents";
import { z } from "zod";

const lookupOrder = tool({
  name: "lookup_order",
  description: "Look up a sample order by ID. Try demo-123.",
  input: z.object({ orderId: z.string() }),
  output: z.object({ orderId: z.string(), status: z.string() }),
  async run({ orderId }) {
    return { orderId, status: orderId === "demo-123" ? "shipped" : "not found" };
  },
});

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Help with orders. Always use lookup_order for order questions.",
  tools: [lookupOrder],
}).build();
```

* `id` is the stable identifier sessions use. `name` is the label Studio shows.
* `instructions` tell the model what to do. See [Instructions](/docs/build/instructions).
* `tools` are typed functions that run in your process. See [Tools](/docs/build/tools).

Do not put `model` on `Agent({})`. The Tenant's model settings choose it; see
[Models](/docs/run/models).

## Add capabilities [#add-capabilities]

Each row is the basic usage only. Open a page for the details. Add only what
this agent uses.

| Capability    | Basic usage                                | Page                                     |
| ------------- | ------------------------------------------ | ---------------------------------------- |
| Instructions  | `instructions: "…"`                        | [Instructions](/docs/build/instructions) |
| Tools         | `tools: [lookupOrder]`                     | [Tools](/docs/build/tools)               |
| Hooks         | `.before("turn", …)` / `.after("step", …)` | [Hooks](/docs/build/hooks)               |
| Capabilities  | `.use(capability({ id, … }))`              | [Capabilities](/docs/build/capabilities) |
| MCP           | `.use(mcp({ … }))`                         | [MCP](/docs/build/mcp)                   |
| Sandbox       | `.use(sandbox())`                          | [Sandbox](/docs/build/sandbox)           |
| Skills        | `.use(skills("./assistant-skills"))`       | [Skills](/docs/build/skills)             |
| Agent Plugins | `.use(plugin("./plugins/github"))`         | [Plugins](/docs/build/plugins)           |

## Compose agents [#compose-agents]

| Pattern   | Basic usage                                   | Page                               |
| --------- | --------------------------------------------- | ---------------------------------- |
| Subagents | `tools: [lookupOrder, researcher]`            | [Subagents](/docs/build/subagents) |
| Loop      | `Loop({ id, run, verify, decide })`           | [Loop](/docs/build/loop)           |
| Chain     | `Chain({ id, steps: [researcher, analyst] })` | [Chain](/docs/build/chain)         |
| Switch    | `Switch({ id, on, cases, default })`          | [Switch](/docs/build/switch)       |
| Map       | `Map({ id, over, each })`                     | [Map](/docs/build/map)             |
| Parallel  | `Parallel({ id, branches: { a, b } })`        | [Parallel](/docs/build/parallel)   |
| Workflows | Export any of the above like an agent         | [Workflows](/docs/build/workflows) |

## Hooks [#hooks]

Patch a turn or decide after a model call:

```ts
export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Help with orders.",
  tools: [lookupOrder],
})
  .before("turn", ({ info }) => ({
    instructions: [`Tenant ${info?.tenantId ?? "unknown"}`],
  }))
  .build();
```

`before("turn"|"step")`, `after("step"|"turn")`, `Patch` / `Decision` /
`TurnDecision`, and retry bounds are on [Hooks](/docs/build/hooks).

## Capabilities [#capabilities]

Bundle instructions, tools, and hooks, then attach them with `.use()`:

```ts
import { Agent, capability } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
}).use(
  capability({
    id: "support",
    instructions: "Ask for an order number before using find_order.",
    tools: [findOrder],
  }),
);
```

`.use()` returns a **new** snapshot. Ordering, ownership, catalog rows, and
helpers that return one capability are on
[Capabilities](/docs/build/capabilities).

## MCP [#mcp]

Declare MCP servers the Runtime discovers:

```ts
import { Agent, mcp } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Use the available tools.",
}).use(
  mcp({
    github: {
      name: "github",
      type: "streamable-http",
      url: "https://mcp.example.com/github",
    },
  }),
);
```

Transports, capability ids, snapshots, and `McpError` are on
[MCP](/docs/build/mcp).

## Sandbox [#sandbox]

Give the agent Runtime-executed computer tools:

```ts
import { Agent, sandbox } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  instructions: "Analyse the data the user gives you. Use Python.",
}).use(sandbox());
```

Images, network presets, backends, idle, and `sandbox.*` events are on
[Sandbox](/docs/build/sandbox).

## Skills [#skills]

Load an Agent Skills catalog folder:

```ts
import { Agent, skills } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  tools: [lookupOrder],
}).use(skills("./assistant-skills"));
```

Folder layout, the agentskills.io spec, `load_skill` /
`read_skill_resource`, and build-copy are on [Skills](/docs/build/skills).

## Agent Plugins [#agent-plugins]

Attach an Agent Plugin package as one capability:

```ts
import { Agent, plugin } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Use the plugin tools and skills.",
}).use(plugin("./plugins/github"));
```

`loadPlugin`, diagnostics, `pluginRoot`, and plugin MCP/skills are on
[Plugins](/docs/build/plugins).

## Subagents [#subagents]

Put an agent in `tools` so the model can delegate a self-contained task:

```ts
const researcher = Agent({
  id: "researcher",
  description:
    "Investigates an order's history. Returns a short summary with the ids it relied on.",
  instructions: "Investigate one question about one order.",
  tools: [searchOrders, readTicket],
});

export const assistant = Agent({
  id: "assistant",
  instructions:
    "For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
  tools: [lookupOrder, researcher],
});
```

`{ task: string }`, required `description`, `ctx.agent`,
`history({ agent })`, `delegation.*` events, and one-level / non-interactive
limits are on [Subagents](/docs/build/subagents).

## Loop [#loop]

Run, verify, and retry until the output passes:

```ts
import { Loop } from "@nylorun/agents/define";

export const polish = Loop({
  id: "polish",
  run: coder,
  verify: ({ output }) =>
    output.answer?.trim() ? { pass: true } : { pass: false, feedback: "Answer is empty." },
  decide: ({ output, verdict, iteration }) => {
    if (verdict.pass) return { output };
    if (iteration >= 3) throw new Error("Still empty after 3 tries");
    return { input: verdict.feedback };
  },
});
```

Iteration limits and verifier agents are on [Loop](/docs/build/loop).

## Chain [#chain]

Run steps in order; each step receives the previous output:

```ts
import { Chain } from "@nylorun/agents/define";

export const report = Chain({ id: "report", steps: [researcher, analyst] });
```

Slots, `value`, and `results` are on [Chain](/docs/build/chain).

## Switch [#switch]

Run exactly one branch chosen from the input:

```ts
import { Switch } from "@nylorun/agents/define";

export const route = Switch({
  id: "route",
  on: (ticket) => ticket.kind,
  cases: { bug: bugAgent, billing: billingAgent },
  default: generalAgent,
});
```

Deterministic keys and slots are on [Switch](/docs/build/switch).

## Map [#map]

Run one node for every item in a list, concurrently:

```ts
import { Map } from "@nylorun/agents/define";

export const write = Map({ id: "write", over: (plan) => plan.sections, each: sectionWriter });
```

Ordering and `index` are on [Map](/docs/build/map).

## Parallel [#parallel]

Run a fixed set of named branches on the same input:

```ts
import { Parallel } from "@nylorun/agents/define";

export const checks = Parallel({
  id: "checks",
  branches: { security: securityReviewer, style: styleReviewer },
});
```

Result keys are on [Parallel](/docs/build/parallel).

## Workflows [#workflows]

`Loop`, `Chain`, `Switch`, `Map`, and `Parallel` nest into one workflow. A
workflow is exported, saved, and run exactly like an agent: same registry,
same session client, same executor. See [Workflows](/docs/build/workflows).

## Details [#details]

`.build()` returns the assembled facade. You can export the builder; generated
projects call `.build()` so the registry holds a `BuiltAgent`. Invalid tool
schemas, duplicate tool names, or other incompatible contributions raise
`AgentBuildError`.

* `description` is optional catalog metadata. It is required when this agent is
  used as a tool; see [Subagents](/docs/build/subagents).
* `outputSchema` is optional and validates completed engine output.
* `tools` and `instructions` on `Agent({ ... })` compile as capability id
  `"agent"`.
* `JSON.stringify(agent)` is the public manifest (`manifestSchemaVersion: 4`).
  Bindings stay local; `getBinding()` is not a wire format.
  `Agent.from(manifest, implementations)` reconstructs a definition and rejects
  schema 3.

<Callout title="Keep runtime concerns outside the agent">
  Construct service clients in the application and close over them from tools.
  Do not put API keys, database handles, HTTP routing, or deployment policy in
  an agent manifest.
</Callout>

Full signatures are in [Reference → define](/docs/reference/agents/define).

## Next step [#next-step]

<Cards>
  <Card title="Tools" description="Give the agent a typed function it can call." href="/docs/build/tools" />

  <Card title="Examples" description="Copy a working agent or workflow." href="/docs/build/examples" />

  <Card title="Call it from code" description="Create a session and send input." href="/docs/run/sessions" />
</Cards>


# Instructions (/docs/build/instructions)



Instructions are what the model reads before it answers. A generated agent
sets them on `Agent({ ... })`:

```ts
export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions:
    "Help with orders. Always use lookup_order for order questions.",
}).build();
```

`instructions` accepts one string or an ordered list. A string is normalized
to a one-element array at build. Top-level instructions compile as capability
id `"agent"`. Named capabilities can add their own:

```ts
capability({
  id: "support",
  instructions: "Ask for an order number before using find_order.",
  tools: [findOrder],
})
```

## Dynamic instructions [#dynamic-instructions]

A `before("turn")` or `before("step")` hook can return a `Patch` with extra
`instructions`. Turn-scoped patches apply to every model call in that turn;
step-scoped patches apply to that model call only.

```ts
export const support = Agent({
  id: "support",
  instructions: "Help with orders.",
})
  .before("turn", ({ info }) => ({
    instructions: [`Tenant ${info?.tenantId ?? "unknown"}`],
  }))
  .build();
```

`info` is host identity from the Runtime session. It is not model-visible
unless a hook copies it into instructions. See [Hooks](/docs/build/hooks).

Do not put secrets in instructions, manifests, or hook patches that become
history.

## Next step [#next-step]

<Cards>
  <Card title="Tools" description="Give the model a typed host-owned function." href="/docs/build/tools" />

  <Card title="Hooks" description="Patch a turn or decide after a model call." href="/docs/build/hooks" />
</Cards>


# Loop (/docs/build/loop)



`Loop` repeats three durable stages: `run`, `verify`, and `decide`. The decision
either returns final `output` or supplies the next `input`.

```ts
import { Loop } from "@nylorun/agents/define";

export const polish = Loop({
  id: "polish",
  run: coder,
  verify: ({ output }) => {
    const text = output.answer ?? "";
    if (text.trim().length >= 8) return { pass: true };
    return {
      pass: false,
      feedback: "Answer must be at least eight characters. Try again.",
    };
  },
  decide: ({ output, verdict, iteration }) => {
    if (verdict.pass) return { output };
    if (iteration >= 3) throw new Error("Still too short after 3 tries");
    return { input: verdict.feedback };
  },
});
```

`run` may be an agent, tool, workflow, or slot. `verify` may be a function,
tool, or verifier agent. A verifier agent must declare an output schema that
accepts both verdict shapes:

```ts
{ pass: true }
{ pass: false, feedback: "What to improve" }
```

`decide` receives the current `output`, `verdict`, `iteration`, original
`input`, and prior `history`. Always define a terminal condition: return an
output, cap attempts, or throw a useful error. Runtime checkpoints each
iteration and Studio shows its node status and history.

See the shipped [`loop` example](https://github.com/nylorun/agents/tree/main/examples/agents/loop)
or return to the [Workflows overview](/docs/build/workflows) to register and
run the composed workflow.


# Map (/docs/build/map)



`Map` derives a list with `over`, runs `each` once per item concurrently, and
returns the outputs in the original item order. An empty list returns `[]`.

```ts
import { Chain, Map } from "@nylorun/agents/define";

export const digest = Chain({
  id: "digest",
  steps: [
    planner,
    Map({
      id: "write",
      over: (plan) => plan.sections,
      each: sectionWriter,
    }),
    {
      run: merge,
      input: ({ value }) => ({ parts: value }),
    },
  ],
});
```

`over` is durable workflow logic. Keep it deterministic and return a JSON-safe
array. `each` can be an agent, tool, nested workflow, or slot. A slot can use
`index` while shaping each item's input:

```ts
Map({
  id: "write",
  over: (plan) => plan.sections,
  each: {
    run: sectionWriter,
    input: ({ value, index }) => ({ title: value, position: index }),
  },
})
```

Use `Map` for data-driven fan-out. Use [Parallel](/docs/build/parallel) when
branches have different names or behavior known at definition time.

See the shipped [`map` example](https://github.com/nylorun/agents/tree/main/examples/agents/map),
then use [Loop](/docs/build/loop) for repeat-until-accepted work.


# MCP (/docs/build/mcp)



Declare MCP servers with `mcp(...)`. The map shape matches Agent Plugins
`mcpServers`: each key must equal that server's `name`.

```ts
import { Agent, mcp } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Assistant",
  instructions: "Use the available tools.",
}).use(
  mcp({
    github: {
      name: "github",
      type: "streamable-http",
      url: "https://mcp.example.com/github",
    },
  }),
);
```

Transports follow [Agent Plugins MCP servers](https://agent-plugins.org/plugin-authors/mcp-servers):
`stdio`, `streamable-http`, and `sse`. Pass `{ id: "…" }` as the second
argument to override the default capability id `"mcp"`.

```ts
mcp(
  {
    local: {
      name: "local",
      type: "stdio",
      command: "npx",
      args: ["-y", "@example/mcp-server"],
    },
  },
  { id: "docs-mcp" },
)
```

Runtime discovers tools on the session's first turn and pins them in
`mcpSnapshot`. The snapshot contains no secrets. A later session PUT may
replace `vaultIds` and `credentialSelections`; `agentId`, `ownerUserId`, and
`info` stay the creation identity.

URL-bound credentials for outbound MCP calls belong in an end-user
[vault](/docs/run/vault) with the same `ownerUserId` as the session. The host
vault is not attachable to a session.

A mismatched key and `name` throws `McpError` (`mcp.name-mismatch`). An empty
map throws `mcp.empty`.

## Next step [#next-step]

<Cards>
  <Card title="Sandbox" description="Give the agent an isolated computer." href="/docs/build/sandbox" />

  <Card title="Vault" description="Store host and end-user credentials." href="/docs/run/vault" />
</Cards>


# Parallel (/docs/build/parallel)



`Parallel` sends the same input to a fixed object of named branches and runs
them concurrently. It returns an object keyed by those branch names.

```ts
import { Chain, Parallel } from "@nylorun/agents/define";

export const review = Chain({
  id: "review",
  steps: [
    Parallel({
      id: "checks",
      branches: {
        security: securityReviewer,
        style: styleReviewer,
        tests: testAuditor,
      },
    }),
    {
      run: summarizer,
      input: ({ value }) =>
        `Summarize these reviews:\n${JSON.stringify(value, null, 2)}`,
    },
  ],
});
```

Use `Parallel` when the number and names of branches are known while defining
the workflow. Branch names become stable result keys and manifest paths. Each
branch can be an agent, tool, nested workflow, or slot.

The example above produces a value shaped like:

```ts
{
  security: securityResult,
  style: styleResult,
  tests: testResult,
}
```

All branches share the workflow session sandbox. Cancelling the parent
cancels active branches; completed node effects remain checkpointed.

See the shipped [`parallel` example](https://github.com/nylorun/agents/tree/main/examples/agents/parallel).
Use [Map](/docs/build/map) instead when the branch count comes from input data.


# Plugins (/docs/build/plugins)



Read an [Agent Plugin](https://agent-plugins.org/) package and attach it with
`.use(plugin(directory))`:

```ts
import { Agent, plugin } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Assistant",
  instructions: "Use the plugin tools and skills.",
}).use(plugin("./plugins/github"));
```

`plugin(directory)` calls `loadPlugin` and returns one capability:

* `type: "agent-plugin"`
* `id` is the plugin name
* optional `pluginRoot` (stored beside the definition; not part of the manifest hash)
* optional skills (`load_skill` / `read_skill_resource`)
* optional `mcpServers`

`loadPlugin` is the lower-level reader if you want the loaded package and
diagnostics before attaching it. Capability diagnostics stay on the returned
object; they are not serialized into the public manifest.

MCP servers inside a plugin use the same map shape as [`mcp()`](/docs/build/mcp).
Runtime discovers those tools on the first turn and pins them on the session.

## Next step [#next-step]

<Cards>
  <Card title="MCP" description="Declare MCP servers without a plugin package." href="/docs/build/mcp" />

  <Card title="Run" description="Register the agent and start a session." href="/docs/run" />
</Cards>


# Sandbox (/docs/build/sandbox)



Give an agent an isolated computer with `sandbox()`:

```ts
import { Agent, sandbox } from "@nylorun/agents";

export const analyst = Agent({
  id: "analyst",
  instructions: "Analyse the data the user gives you. Use Python.",
}).use(sandbox());
```

The model gets `bash`, `read`, `write`, `edit`, `grep`, and `glob` on a Linux
machine with a persistent `/workspace`. These tools run in the Runtime, not in
your process, so sandbox-only agents need no connected executor. The agent
declares what it needs; the Runtime decides where it runs.

Every option is optional plain data:

```ts
.use(sandbox({
  image: "python:3.13",
  network: { preset: "dev", allow: ["api.example.com"] },
  resources: { cpus: 2, memory: "2GiB" },
  idle: "15m",
}))
```

| Option                      | Default                              | Meaning                                            |
| --------------------------- | ------------------------------------ | -------------------------------------------------- |
| `image`                     | Runtime default (`python:3.13-slim`) | Any OCI image.                                     |
| `network.preset`            | `"dev"`                              | `"none"`, `"dev"`, or `"open"`.                    |
| `network.allow`             | —                                    | Extra hosts, for example `api.example.com`.        |
| `resources.cpus` / `memory` | Runtime default                      | Compute budget. Memory is a size such as `"2GiB"`. |
| `idle`                      | Runtime default                      | Stop compute when idle; files persist.             |

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`, `scope`, …) throw a `SandboxError` that says so.

Runtime owns one virtual sandbox per session, created on the first sandbox tool
call and reattached with its files after idle stops or restarts. The backend is
an emulated shell in the Runtime process, not a VM security boundary. Set
`NYLORUN_SANDBOX=auto` or `virtual`; both select the virtual backend.
`GET /v1/tenant/sandbox` and `nylo doctor sandbox` report it.

Tool calls emit `sandbox.state` and `sandbox.exec` session events. A sandbox
call interrupted by a crash becomes uncertain and is never re-run. Virtual
workspaces live in the Tenant's `sandboxes/` directory.

Pass `{ id: "…" }` as the second argument to override the default capability id
`"sandbox"`. An agent may declare only one sandbox capability.

## Next step [#next-step]

<Cards>
  <Card title="Skills" description="Load an Agent Skills catalog." href="/docs/build/skills" />

  <Card title="Run" description="Start Runtime and inspect sandbox status." href="/docs/run" />
</Cards>


# Skills (/docs/build/skills)



Load Agent Skills from a local catalog folder with `skills(path)`:

```ts
import { Agent, skills } from "@nylorun/agents";

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Use lookup_order for orders.",
  tools: [lookupOrder],
}).use(skills("./assistant-skills"));
```

Each subdirectory under the catalog must contain a `SKILL.md` with YAML
frontmatter (`name`, `description`) per [Agent Skills](https://agentskills.io/home).
Supporting files (for example `references/`) are available through
`read_skill_resource` after `load_skill`.

```text
assistant-skills/
  lookup-order/SKILL.md
  refund/
    SKILL.md
    references/policy.md
```

The helper sets both the manifest skill catalog and the on-disk skill records
so you do not duplicate content. The engine injects:

| Tool                  | Input            | Role                                            |
| --------------------- | ---------------- | ----------------------------------------------- |
| `load_skill`          | `{ name }`       | Skill instructions and the resource list        |
| `read_skill_resource` | `{ name, path }` | A supporting file, when the skill has resources |

Capability id defaults to the catalog directory basename. Override it with
`skills("./assistant-skills", { id: "order-skills" })`.

`plugin()` can also contribute skills from an Agent Plugin package. See
[Plugins](/docs/build/plugins).

## Next step [#next-step]

<Cards>
  <Card title="Plugins" description="Load an Agent Plugin package." href="/docs/build/plugins" />

  <Card title="Capabilities" description="How named bundles attach to an agent." href="/docs/build/capabilities" />
</Cards>


# Subagents (/docs/build/subagents)



Put an agent in another agent's `tools` to let the model delegate to it.

```ts
import { Agent } from "@nylorun/agents";
import { z } from "zod";

const researcher = Agent({
  id: "researcher",
  description:
    "Investigates an order's history. Returns a short summary with the ids it relied on.",
  instructions:
    "Investigate one question about one order. Be exhaustive, then be brief.",
  tools: [searchOrders, readTicket],
  outputSchema: z.object({
    summary: z.string(),
    evidence: z.array(z.string()),
  }),
});

const support = Agent({
  id: "support",
  instructions:
    "For anything needing more than two lookups, delegate to researcher with a complete, self-contained task.",
  tools: [lookupOrder, refundOrder, researcher],
});
```

The tool is named after the child's `id` and takes `{ task: string }`. The
child's `description` (required) is what the parent's model reads. 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.

`connectAgents({ agents: [support] })` serves both. Children share the
session's sandbox, keep their own tools, hooks, skills, and MCP servers, and
start 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. 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).

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.

Delegate when the parent should keep the answer. When a specialist should own
the rest of the conversation, switch capabilities with a `before("step")`
patch instead.

See [Compatibility](/docs/compatibility) for the tested package set.

## Next step [#next-step]

<Cards>
  <Card title="Tools" description="Ordinary host-owned functions." href="/docs/build/tools" />

  <Card title="Events" description="delegation.started and history({ agent })." href="/docs/run/events" />

  <Card title="Workflows" description="Compose explicit durable control flow." href="/docs/build/workflows" />
</Cards>


# Switch (/docs/build/switch)



`Switch` computes a key from its input and runs exactly one matching case. Add
`default` when inputs may produce keys outside the declared cases.

```ts
import { Chain, Switch } from "@nylorun/agents/define";

export const support = Chain({
  id: "support",
  steps: [
    triager,
    Switch({
      id: "route",
      on: (ticket) => ticket.kind,
      cases: {
        bug: bugAgent,
        billing: billingAgent,
      },
      default: generalAgent,
    }),
  ],
});
```

`on` is durable workflow logic: keep it deterministic and derive the key only
from its input. Case names become manifest paths, so use stable names and do
not use the reserved key `default`.

Each case may be an agent, tool, workflow, or slot. A slot can reshape the
selected branch's input:

```ts
cases: {
  bug: {
    run: bugAgent,
    input: ({ value }) => `Investigate: ${value.summary}`,
  },
}
```

The switch output is the selected branch's output. Studio shows the chosen
case and leaves unselected branches inactive.

See the shipped [`switch` example](https://github.com/nylorun/agents/tree/main/examples/agents/switch),
then learn how to [run fixed branches in Parallel](/docs/build/parallel).


# Tools (/docs/build/tools)



A tool exposes a host-owned function to the model. Prefer `input` / `output` /
`run`. A plain `return` is a completed result. Legacy `inputSchema` / `execute`
and tagged `{ kind: "completed" }` outcomes still work.

```ts
import { Agent, tool } from "@nylorun/agents";
import { z } from "zod";

const lookupOrder = tool({
  name: "lookup_order",
  description: "Look up a sample order by ID. Try demo-123.",
  input: z.object({ orderId: z.string() }),
  output: z.object({ orderId: z.string(), status: z.string() }),
  async run({ orderId }) {
    return { orderId, status: orderId === "demo-123" ? "shipped" : "not found" };
  },
});

export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  tools: [lookupOrder],
}).build();
```

`input` validates model-generated arguments before execution. Add `output` when
downstream code relies on a specific completed result. Runtime checks a
successful tool result against that output schema and stores a mismatch as a
failed outcome. A valid schema is not authorization: check the current user in
the host service.

## Execution context [#execution-context]

Forward `context.signal` to network clients so cancellation stops in-flight
work. Context also includes `idempotencyKey`, optional `redelivery`, `state`,
`session.id`, and optional `agent` (`{ id, path, delegationId? }` — the root
agent, or the child when this call belongs to a [delegation](/docs/build/subagents)).
Durable wait helpers (`ask`, `approve`, `sleep`, `waitFor`,
`step`) exist on the context; automatic timer and event wakeups are not in this
release.

Code-only fields (never serialized into the manifest):

* `effects`: `"read" | "idempotent" | "write"`
* `approval`: return a prompt string (or `true`) to pause before `run`

## Connected execution versus the engine [#connected-execution-versus-the-engine]

The default path is a **connected executor**: the CLI (or `connectAgents`)
claims actions from Runtime and runs your implementations in the application
process.

| Behavior           | Connected executor (`connectAgents`)                                                                                               | In-process engine (`harness/run`)                                         |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Invalid tool input | Failed result `tool.invalid-input`. The message is a JSON string of issues. There is no engine `details: { phase, issues }` shape. | Failed result `tool.invalid-arguments` with `details: { phase, issues }`. |
| `context.progress` | No-op. Progress transport is not implemented on this path.                                                                         | Emits a `tool.progress` observation when a listener is present.           |

Document the mode you are using. Do not treat engine-only validation detail or
live progress as part of the starter workflow.

## Ownership [#ownership]

Construct service clients in the application and close over them from tools.
Per-session host data is `info` on the Runtime session (tools read
`context.info`). Definitions do not create clients, supply credentials, or
authorize external calls.

Tools belong in a capability so their ownership is visible. Use separate
capabilities when tools depend on different services, credentials, or policies.

Sandbox tools (`bash`, `read`, `write`, `edit`, `grep`, `glob`) run in Runtime,
not in your executor. See [Sandbox](/docs/build/sandbox).

Put a built `Agent` in `tools` to let the model delegate a self-contained
task. The child becomes a tool named after its `id` and takes `{ task: string }`.
See [Subagents](/docs/build/subagents).

## Next step [#next-step]

<Cards>
  <Card title="Hooks" description="Patch a turn or decide after a model call." href="/docs/build/hooks" />

  <Card title="Subagents" description="Put an agent in another agent's tools." href="/docs/build/subagents" />
</Cards>


# Workflows (/docs/build/workflows)



A workflow is a registered runnable with `kind: "workflow"`. It uses the same
Tenant session API as an agent, but its manifest describes a graph of agent,
tool, function, and verification nodes.

```ts title="agents/report/agent.ts"
import { Agent, Chain, tool } from "@nylorun/agents/define";
import { z } from "zod";

const researcher = Agent({
  id: "researcher",
  name: "Researcher",
  description: "Gathers concise findings.",
  instructions: "Research the topic and return { findings }.",
  outputSchema: z.object({ findings: z.string() }),
}).build();

const publish = tool({
  name: "publish",
  description: "Records finished findings.",
  input: z.object({ findings: z.string() }),
  output: z.object({ published: z.boolean() }),
  async run() {
    return { published: true };
  },
});

export const report = Chain({
  id: "report",
  steps: [
    researcher,
    {
      run: publish,
      input: ({ value }) => value,
    },
  ],
});
```

Export the workflow from the registry exactly as you export an agent:

```ts title="agents/index.ts"
import { report } from "./report/agent.js";

export const agents = [report];
```

## Primitives [#primitives]

| Primitive                          | Behavior                                                                         |
| ---------------------------------- | -------------------------------------------------------------------------------- |
| [`Loop`](/docs/build/loop)         | Runs a node, verifies its output, and either returns or supplies the next input. |
| [`Chain`](/docs/build/chain)       | Runs steps in order; each result becomes the next step's value.                  |
| [`Switch`](/docs/build/switch)     | Computes a key and runs the matching case or default branch.                     |
| [`Map`](/docs/build/map)           | Runs one node per item concurrently and returns ordered results.                 |
| [`Parallel`](/docs/build/parallel) | Runs a fixed object of named branches concurrently.                              |

Primitives nest, so a `Chain` can contain a `Map` whose `each` node is a
`Loop`. Runtime persists each node boundary and resumes from completed effects
instead of repeating them.

## Slots and data [#slots-and-data]

A direct node receives the current value. Wrap it in a slot to give the node a
stable `id` or reshape its input:

```ts
{
  id: "summarize",
  run: summarizer,
  input: ({ value, results, input }) => ({
    current: value,
    earlier: results.researcher,
    original: input,
  }),
}
```

`value` is the preceding node output, `results` contains completed keyed nodes,
and `input` is the workflow's original turn input. Functions used for slot
inputs, switch keys, map expansion, and loop decisions run as durable `fn` or
`verify` executor actions.

## Sessions and manifests [#sessions-and-manifests]

Use `saveAgent`, `createSession`, `input`, `observe`, `pending`, `approve`, and
`cancel` exactly as for agents. Text agents accept `content`; workflows can
also accept structured `data`. Saving a workflow first saves the agents it
references.

Agent sessions can carry a turn-only `message.manifest`; see
[Sessions](/docs/run/sessions#turn-manifests). Workflow sessions retain their
workflow pin. Studio renders the workflow manifest tree, live node states,
iterations, and links to child agent sessions.

All nodes in one workflow share the session sandbox. Cancelling the parent
cancels active child work.

## Examples [#examples]

See the shipped [`chain`, `switch`, `parallel`, `map`, `loop`, and
`ship-feature`](/docs/build/examples#workflow-examples) examples.

## Next step [#next-step]

Start with [Chain](/docs/build/chain), or open [Sessions](/docs/run/sessions)
to run a workflow and [Studio](/docs/studio) to inspect its manifest and
node progress.


# App server (/docs/deploy/app-server)



Your **app server** is your own application. In production it does two jobs:

1. **Runs your tools.** `connectAgents` registers agents and executes their
   tools when the Runtime asks.
2. **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 [#build-and-start]

```sh
npm run build
npm start          # node dist/src/main.js
```

Set 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](/docs/deploy/docker#connect-your-app-server). |
| `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`](/docs/reference/admin).

### How the connection is found [#how-the-connection-is-found]

`createClient()` and `connectAgents()` use exactly one complete source, in this
order:

1. Explicit `{ url, key, tenant }` options.
2. `NYLORUN_RUNTIME_URL`, `NYLORUN_SERVER_KEY` or `NYLORUN_EXECUTOR_KEY`, and
   `NYLORUN_TENANT`.
3. The nearest `.nylorun/link.json` plus `credentials.json`.

Sources never mix. If any variable is set, all of them must be, or the call
fails with `connection_missing`.

## Executors [#executors]

The compiled app connects every exported agent and workflow:

```ts title="src/main.ts"
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](/docs/run/executors).

## Serve people [#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:

```ts
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 sessions
```

`as()` 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](/docs/reference/agents/client#as).

### Serve a chat UI with AG-UI [#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:

```ts
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](https://github.com/nylorun/agents/tree/main/examples/src/ag-ui).
Routes, approvals, and limitations are in
[Reference → ag-ui](/docs/reference/agents/ag-ui).

### Rules for an app server [#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 forward `Origin`;
  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](/docs/deploy/docker#connect-your-app-server).

## Next step [#next-step]

<Cards>
  <Card title="Docker" description="Run the Runtime and connect your app server to it." href="/docs/deploy/docker" />

  <Card title="Sessions" description="Everything a session client can do." href="/docs/run/sessions" />

  <Card title="Agents SDK reference" description="createClient, as(), connectAgents, and the AG-UI handler." href="/docs/reference/agents" />
</Cards>


# Nylorun Cloud (/docs/deploy/cloud)



<Callout title="Coming soon" type="info">
  Nylorun Cloud is not available in this release. Early-access applications are
  reviewed manually, with no guaranteed approval timeline.
</Callout>

Nylorun intends the hosted Runtime to share the same execution foundation and
session protocol as the Runtime you run with Docker. That is a direction, not
a current deployment or migration guarantee.

## What to use now [#what-to-use-now]

Run the Runtime with [Docker](/docs/deploy/docker): `npx nylorun up`, create a
Tenant with `npx @nylorun/cli tenant create`, and connect your app with
`npm run dev` or `npm start`.

Local Postgres schemas, S2 streams, project credentials, and Console
organization keys do not become Cloud credentials.

## Connect [#connect]

Not yet. There is no Cloud Runtime URL or key to connect an app server to.

## Next step [#next-step]

<Cards>
  <Card title="Docker" description="The supported way to run the Runtime." href="/docs/deploy/docker" />

  <Card title="Deploy" description="Compare the Runtime options." href="/docs/deploy" />
</Cards>


# Docker (/docs/deploy/docker)



`npx nylorun up` runs the Runtime as one Docker Compose project named
`nylorun`. This is the supported deployment in this release.

```sh
npx nylorun up        # start, or leave running if already up
npx nylorun status    # service, port, and readiness state
npx nylorun down      # stop containers and keep data
```

## Services [#services]

| Service    | Role                                                 |
| ---------- | ---------------------------------------------------- |
| `postgres` | Session Store; one `tenant_<id>` schema per Tenant.  |
| `restate`  | Durable session execution, wakes, and Tenant sweeps. |
| `s2`       | s2-lite streams for session history and SSE.         |
| `runtime`  | Tenant and Admin HTTP APIs and workers.              |
| `studio`   | Local dashboard and trusted proxy.                   |

## Images [#images]

Runtime and Studio ship as multi-architecture images (`linux/amd64`,
`linux/arm64`) tagged with their package versions:

| Image                                       | Notes                         |
| ------------------------------------------- | ----------------------------- |
| `ghcr.io/nylorun/runtime:<runtime version>` | `--role api`, `worker`, `all` |
| `ghcr.io/nylorun/studio:<studio version>`   | Not published to npm          |

`nylorun up` runs the versions its release pins beside the official Postgres,
Restate, and s2-lite images. Tags are never moved and there is no `latest`
tag. Override the pins with `NYLORUN_RUNTIME_IMAGE` and `NYLORUN_STUDIO_IMAGE`,
for example with a local build. The local stack runs the Runtime with the
combined `all` role.

## Ports [#ports]

| Service    | Default          |
| ---------- | ---------------- |
| Runtime    | `127.0.0.1:8787` |
| Studio     | `127.0.0.1:4161` |
| Restate UI | `127.0.0.1:9070` |

The first start stores configured or free ports in `stack/.env`. Change them
there with `NYLORUN_PORT`, `NYLORUN_STUDIO_PORT`, and `NYLORUN_RESTATE_PORT`.

## Container configuration [#container-configuration]

* Database: `NYLORUN_DATABASE_URL`
* Restate: `NYLORUN_RESTATE_*`
* S2: `NYLORUN_S2_*`
* Listener: `NYLORUN_LISTEN_HOST`, `NYLORUN_LISTEN_PORT`,
  `NYLORUN_ALLOWED_HOSTS`, and `NYLORUN_PUBLIC_URL`

`GET /ready` checks Postgres, Restate, and S2 and returns `503` while a required
dependency is unavailable. `GET /health` reports version and protocol
compatibility.

## Storage [#storage]

Two things hold all state, and they belong together:

1. **The Host root** (`NYLORUN_HOME` or `~/.nylorun`): identity, the admin key,
   stack configuration, and per-Tenant files.
2. **Docker volumes**: each Tenant's Postgres schema, S2 session streams,
   Restate state, and workspaces.

```text
~/.nylorun/
  host.json
  host-credentials.json     # admin key
  stack/
    compose.yaml
    .env                    # mode 0600
    restate-identity.pem
  tenants/<id>/
    vault-kek
    plugin-data/
    logs/
    home/
    tmp/
    sandboxes/
  trash/
```

* Keep the Host root private and persistent. Keep each project's
  `.nylorun/link.json` and `credentials.json` private too.
* Back up and restore the Host root and the volumes **together**; credentials
  and database state are not independent.
* `nylorun down` stops containers and keeps data. `nylorun reset` deletes all
  volumes and every Tenant.
* Run one stack per machine.

SQLite Tenants from earlier releases are not migrated. On first start they move
to `trash/<id>-sqlite-<time>/` and the Runtime logs
`sqlite_tenant_moved_to_trash`. Copy out anything you need and recreate the
Tenant with `nylo tenant create`.

## Connect your app server [#connect-your-app-server]

| Where your app server runs               | `NYLORUN_RUNTIME_URL`                                   |
| ---------------------------------------- | ------------------------------------------------------- |
| Same machine                             | `http://localhost:<port>` (the URL `nylorun up` prints) |
| Container on the stack's Compose network | `http://runtime:4000`                                   |
| Another machine                          | Needs a reverse proxy; not part of this release         |

* Never publish the Runtime, Studio, or Restate ports beyond loopback.
* Keep Studio for operators: loopback or an SSH tunnel.

## Next step [#next-step]

<Cards>
  <Card title="App server" description="Configure your app and serve your users." href="/docs/deploy/app-server" />

  <Card title="Troubleshooting" description="Stack, connection, and Studio failures." href="/docs/troubleshooting" />
</Cards>


# Deploy (/docs/deploy)



A deployment has two independent halves:

* **Your app server** is your code. It registers agents, runs their tools, and
  serves your users.
* **The Runtime** runs sessions, calls the model, and stores history.

Deploy them separately. Your app finds the Runtime through three environment
variables.

## Your app server [#your-app-server]

```sh
npm run build
eval "$(npx @nylorun/cli env)"   # or set the three NYLORUN_* variables yourself
npm start
```

Environment, executors, and serving signed-in users are covered on
[App server](/docs/deploy/app-server).

## The Runtime [#the-runtime]

| Where                   | Status            | Page                                  |
| ----------------------- | ----------------- | ------------------------------------- |
| One machine, Docker     | Available         | [Docker](/docs/deploy/docker)         |
| Self-host on Kubernetes | Not yet available | [Kubernetes](/docs/deploy/kubernetes) |
| Nylorun Cloud           | Not yet available | [Nylorun Cloud](/docs/deploy/cloud)   |

This release supports one shape: the Docker stack from `npx nylorun up` on a
single machine, with your app server on the same machine or on the stack's
Compose network.

Remote ingress, TLS, server Compose or Helm deployment, replicas, hosted
executors, backups and migrations, crash-recovery qualification, and deployment
automation are not part of this release. Do not reuse the old Hono, Worker,
Vercel, or exported-fetch recipes.

## Next step [#next-step]

<Cards>
  <Card title="App server" description="Configure, connect, and serve your users." href="/docs/deploy/app-server" />

  <Card title="Docker" description="Services, ports, storage, and backups for the Runtime." href="/docs/deploy/docker" />
</Cards>


# Kubernetes (/docs/deploy/kubernetes)



<Callout title="Not yet available" type="info">
  There is no Helm chart or supported Kubernetes deployment in this release.
  Replicas, remote ingress, TLS, and backups for a cluster deployment are not
  qualified.
</Callout>

## What to use now [#what-to-use-now]

Run the Runtime with [Docker](/docs/deploy/docker) on one machine, and run your
[app server](/docs/deploy/app-server) on the same machine or on the stack's
Compose network.

## What exists today [#what-exists-today]

These pieces exist, but they are not a supported cluster deployment:

* The Runtime image `ghcr.io/nylorun/runtime:<version>` accepts
  `--role api`, `--role worker`, or `--role all`.
* `GET /ready` returns `503` until Postgres, Restate, and S2 are reachable.
* The container is configured with `NYLORUN_DATABASE_URL`,
  `NYLORUN_RESTATE_*`, `NYLORUN_S2_*`, and the listener variables listed on
  [Docker](/docs/deploy/docker#container-configuration).

## Next step [#next-step]

<Cards>
  <Card title="Docker" description="The supported way to run the Runtime." href="/docs/deploy/docker" />

  <Card title="Nylorun Cloud" description="The planned hosted Runtime." href="/docs/deploy/cloud" />
</Cards>


# Use with AI agents (/docs/ai-agents)



Nylorun publishes its documentation through a public, read-only MCP server and
plain Markdown endpoints. Use them to give a coding agent current Nylorun
context without copying pages into a prompt.

## Connect an MCP client [#connect-an-mcp-client]

The MCP endpoint is `https://docs.nylorun.com/mcp`. It requires no credentials
and exposes three documentation tools:

| Tool         | Purpose                                     |
| ------------ | ------------------------------------------- |
| `list_pages` | List the public documentation page tree.    |
| `get_page`   | Read one documentation page as Markdown.    |
| `search`     | Search across public documentation content. |

### Codex [#codex]

```sh
codex mcp add nylorun-docs --url https://docs.nylorun.com/mcp
codex mcp list
```

### Claude Code [#claude-code]

```sh
claude mcp add --transport http --scope user nylorun-docs https://docs.nylorun.com/mcp
claude mcp list
```

### Gemini CLI [#gemini-cli]

```sh
gemini mcp add --transport http nylorun-docs https://docs.nylorun.com/mcp
gemini mcp list
```

To hand an agent everything at once, use the **Copy prompt** box on the
[Quickstart](/docs).

After connecting, ask the client to search the Nylorun docs before it answers a
Nylorun question. For consistent project behavior, add this instruction to
`AGENTS.md` or the equivalent instruction file used by your client:

```md title="AGENTS.md"
Use the Nylorun Docs MCP server whenever you need current information about
Nylorun APIs, configuration, runtime behavior, or compatibility.
```

## Read the Markdown directly [#read-the-markdown-directly]

Agents that do not support MCP can use the Markdown interfaces instead:

| URL                                | Content                              |
| ---------------------------------- | ------------------------------------ |
| [`/llms.txt`](/llms.txt)           | Compact index of documentation pages |
| [`/llms-full.txt`](/llms-full.txt) | Complete public documentation corpus |
| `/docs/<page>.md`                  | One documentation page               |

For example, [`/docs/build/sandbox.md`](/docs/build/sandbox.md) returns the
Sandbox guide as Markdown. Clients may also request a normal documentation URL
with `Accept: text/markdown`.

## Read-only boundary [#read-only-boundary]

This MCP server only lists, searches, and reads the content published on this
site. It has no credentials, cannot execute Nylorun APIs, and does not expose
Nylorun's internal repository documentation.

To configure outbound MCP servers that a Nylorun agent can use during a
session, see [MCP](/docs/build/mcp).


# Compatibility (/docs/compatibility)



<Callout title="Pre-1.0: expect breaking changes" type="warn">
  Nylorun is in beta. APIs, specs, and on-disk formats change quickly, and any
  release may break them. Upgrade packages as a family and read the breaking
  changes below.
</Callout>

The `latest` and `beta` channels currently resolve to the same release family:

| Package or tool                    | Version       |
| ---------------------------------- | ------------- |
| `nylorun`                          | `0.1.0-beta`  |
| `@nylorun/cli`                     | `0.4.0-beta`  |
| `@nylorun/agents`                  | `0.7.0-beta`  |
| `@nylorun/admin`                   | `0.3.0-beta`  |
| `@nylorun/core`                    | `0.6.0-beta`  |
| `@nylorun/harness`                 | `0.19.1-beta` |
| `@nylorun/runtime` / Runtime image | `0.11.0-beta` |
| `@nylorun/create-agent`            | `0.10.0-beta` |

Studio ships only as the image pinned by `nylorun`. `@nylorun/runtime` is a
library and container source, not a global CLI install.

The family uses manifest schema 4 and protocol 2 with required features
`runtime-tenants`, `admin-status`, and `studio-principal`. A Runtime may also
advertise optional features such as `tenant-fixture-model`,
`subject-headers`, and `transcript-events`.

## Upgrading [#upgrading]

1. Upgrade every Nylorun package in your project together.
2. Read the breaking changes for each release you skip.
3. Run `npm run check` and `npm run build`.
4. Restart the stack with `npx nylorun@latest up` and check `npx nylorun status`.
5. Start `npm run dev`, then run one session in Studio.

Clients negotiate protocol and features through `/health`. Package version
equality is not the wire check; an incompatible Runtime fails with
`IncompatibleRuntimeError` before any authenticated work.

## Breaking changes [#breaking-changes]

| Release    | What broke                                                                                                                                          |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Runtime V1 | Docker stack replaces the installed Runtime; `nylorun` / `nylo` split; Postgres and S2 replace SQLite; Studio is a container; virtual sandbox only. |
| 0.19       | Runtime Hosts and Tenants; protocol 2; client connection resolution; workflows.                                                                     |
| 0.17       | Scoped hooks; manifest schema 4; persistent Runtime; vault-stored models; sandbox.                                                                  |
| 0.15       | Agents SDK and SQLite Runtime replace Hono, `agent.run()`, and the old AG-UI adapter.                                                               |

Release notes for every version are on
[GitHub releases](https://github.com/nylorun/agents/releases).

## Next step [#next-step]

<Cards>
  <Card title="Quickstart" description="Start a new project on the current release." href="/docs" />

  <Card title="Troubleshooting" description="Fix an incompatible Runtime or missing history." href="/docs/troubleshooting" />
</Cards>


# Troubleshooting (/docs/troubleshooting)



Start with the smallest failing layer: stack, Tenant link, application process,
then Studio.

## Standard recovery [#standard-recovery]

```sh
npx nylorun up
npx @nylorun/cli tenant create
npm run dev
```

Node 24+, a running Docker engine, and Docker Compose v2 are required. Run
`npx nylorun doctor` to check them.

## Stack does not start [#stack-does-not-start]

* Inspect `npx nylorun status --json`; exit codes 3, 4, and 7 distinguish
  Docker, Compose, and stack-readiness failures.
* A port conflict can be fixed by changing `NYLORUN_PORT`,
  `NYLORUN_STUDIO_PORT`, or `NYLORUN_RESTATE_PORT` in `<Host root>/stack/.env`.
* Follow Runtime startup with `npx nylorun logs runtime -f`.
* `/ready` returns `503` while Postgres, Restate, or S2 is unavailable.

## No Runtime connection found [#no-runtime-connection-found]

Clients need a complete explicit destination, a complete three-variable
environment destination, or `.nylorun/link.json` plus `credentials.json`.
Partial environment configuration blocks Project-link fallback.

Run `npx @nylorun/cli env` to print the linked connection. Use `nylo tenant
create` for a missing link or `nylo tenant use <name-or-id>` for the wrong
Tenant. `nylo` exits 6 when no Runtime answers.

## Studio login fails [#studio-login-fails]

A login token is single-use and expires after two minutes. Run `npx nylorun
studio` for a fresh URL. Studio sessions end when the container restarts.

## Runtime is incompatible [#runtime-is-incompatible]

Clients in this release require the `studio-principal` protocol feature. An
older Host fails as `incompatible_host`. Upgrade with:

```sh
npx nylorun down
npx nylorun@latest up
```

Tenants created before Studio principals must be recreated.

## History disappeared after upgrade [#history-disappeared-after-upgrade]

SQLite Tenants are moved to `~/.nylorun/trash/<id>-sqlite-<time>/` and are not
imported. Look for `sqlite_tenant_moved_to_trash`, preserve anything needed,
and create a new Tenant. See [Docker → Storage](/docs/deploy/docker#storage).

## Tenant is quarantined [#tenant-is-quarantined]

The possible codes are `kek-missing`, `corrupt`, `schema-too-new`,
`migration-failed`, `envelope-invalid`, `open-timeout`, and `open-failed`.
Inspect `nylorun logs runtime`; do not delete Host data to conceal the cause.

## App server gets 403 or 404 [#app-server-gets-403-or-404]

* `403` with `scope_required`: the call needs a scope the subject was not
  given. Add it to `client.as(subject, { scopes })`. Tenant reset, config
  seed, executors, actions, and sandbox tool routes are never reachable with
  a subject; call them without `as()`.
* `403` from an executor key: only an application key can act for a subject.
* `404` for a session that exists: it belongs to another subject. The Runtime
  answers the same as for a missing session.
* AG-UI `502` with `runtime_feature_missing`: the Runtime lacks
  `transcript-events` or `subject-headers`. Upgrade the stack with
  `npx nylorun up`.

See [App server](/docs/deploy/app-server#serve-people).

## Still blocked? [#still-blocked]

Open a focused issue in the
[Agents repository](https://github.com/nylorun/agents/issues) with the Node
and Docker versions, package versions, redacted `nylorun status --json`, the
failing command, and the smallest relevant Runtime log excerpt. Never include
admin, application, executor, Studio, or provider keys.


# Admin (/docs/reference/admin)



`@nylorun/admin` is the operator client for Host status and Tenant lifecycle.
Application backends that only run agents use `@nylorun/agents` instead.

```ts
import { createAdmin, deriveStudioToken } from "@nylorun/admin";

const admin = createAdmin();
const status = await admin.status();
const { tenant, applicationKey } = await admin.createTenant({ name: "my-app" });
const studioKey = deriveStudioToken(process.env.NYLORUN_ADMIN_KEY!, tenant.id);

await admin.listTenants();
await admin.getTenant(tenant.id);
await admin.deleteTenant(tenant.id, { activeWork: "refuse" });
```

Persist the returned `applicationKey`: the Host stores only its hash and cannot
return the cleartext value later. `createTenant` generates its Tenant id,
application principal, key, Studio credential hash, and idempotency key locally.
`deriveStudioToken(adminKey, tenantId)` derives the per-Tenant Studio principal
key without storing it.

## Connection resolution [#connection-resolution]

`createAdmin()` resolves in this order:

1. Explicit `createAdmin({ url, key })`.
2. `NYLORUN_ADMIN_URL` plus `NYLORUN_ADMIN_KEY`.
3. Local `host.json` and `host-credentials.json` under `NYLORUN_HOME` or `~/.nylorun`.

Use `createAdmin({ home })` to select another local Host root. On POSIX, local
Host credentials must be owned by the current user and not be group- or
world-readable.

## Methods [#methods]

| Method                             | Admin route                    | Purpose                                                            |
| ---------------------------------- | ------------------------------ | ------------------------------------------------------------------ |
| `status()`                         | `GET /v1/admin/status`         | Read Host version, protocol, readiness, and Tenant summary.        |
| `listTenants()`                    | `GET /v1/admin/tenants`        | List known Tenant envelopes and state.                             |
| `getTenant(id)`                    | `GET /v1/admin/tenants/:id`    | Inspect one Tenant.                                                |
| `createTenant({ name })`           | `POST /v1/admin/tenants`       | Create a Tenant and its first application principal.               |
| `deleteTenant(id, { activeWork })` | `DELETE /v1/admin/tenants/:id` | Refuse, drain, or cancel active work according to operator policy. |

Host shutdown is Host-private and is not part of the shared Admin client.

## Errors and compatibility [#errors-and-compatibility]

The client checks `/health` once and sends `Nylorun-Protocol`. Incompatible
Hosts fail before an admin operation. Failures are `AdminError` instances with
a registry `code`; the package re-exports `ERROR_CODES`, `PROTOCOL_FEATURES`,
and `compareVersions`.

See [Runtime reference](/docs/reference/runtime-api) for the Tenant API and
[Environment](/docs/reference/environment) for connection variables.


# ag-ui (/docs/reference/agents/ag-ui)



```ts
import { createAgUiHandler, toNodeListener } from "@nylorun/agents/ag-ui";
```

Serves the Tenant's agents to any [AG-UI](https://docs.ag-ui.com) client:
`@ag-ui/client`'s `HttpAgent`, CopilotKit, or a desktop renderer. Your server
signs people in; the handler maps each AG-UI thread to one session per person,
agent, and thread. This entry point adds `@ag-ui/core`; nothing else imports
it. Guide: [App server → Serve people](/docs/deploy/app-server#serve-people).

## `createAgUiHandler` [#createaguihandler]

```ts
createAgUiHandler(options: AgUiHandlerOptions): AgUiHandler
```

| Option     | Type                                                      | Required | Description                                                                      |
| ---------- | --------------------------------------------------------- | -------- | -------------------------------------------------------------------------------- |
| `agents`   | `(BuiltAgent \| BuiltWorkflow \| string)[]`               | Yes      | Agents this endpoint may run. Nothing else is reachable.                         |
| `subject`  | `(request: Request) => string \| undefined \| Promise<…>` | Yes      | The signed-in person. `undefined` answers `401`; there is no anonymous mode.     |
| `basePath` | `string`                                                  | No       | Where the handler is mounted, e.g. `/api/agui`. Default `/`.                     |
| `client`   | `AgentsClient \| Promise<AgentsClient>`                   | No       | Application client. Default `createClient()`.                                    |
| `scopes`   | `SubjectScope[]`                                          | No       | Default `["sessions:own"]`. Add `"vaults:own"` when `session()` attaches vaults. |
| `session`  | `(subject, agentId) => AgUiSessionOptions`                | No       | Per-session `info`, `vaultIds`, or `credentialSelections`. Keep `info` stable.   |

**Returns** an `AgUiHandler`:

| Member                                 | Description                                               |
| -------------------------------------- | --------------------------------------------------------- |
| `fetch(request)`                       | One bound entry point that routes by method and path.     |
| `run(request, agentId)`                | `POST`: `RunAgentInput` in, AG-UI server-sent events out. |
| `history(request, agentId, threadId)`  | `GET`: the thread's messages as AG-UI `Message[]`.        |
| `reattach(request, agentId, threadId)` | `GET`: the rest of a run after a dropped connection.      |
| `cancel(request, agentId, threadId)`   | `POST`: cancel the thread's running turn.                 |

```ts
const agui = createAgUiHandler({
  basePath: "/api/agui",
  agents: [support],
  subject: async (request) => (await getSignedInUser(request))?.id,
});

createServer(toNodeListener(agui)).listen(3000);
// Next.js, Hono, Bun, Deno, Workers: export or mount agui.fetch directly.
```

## Routes [#routes]

| Method and path under `basePath`             | Operation                                                                  |
| -------------------------------------------- | -------------------------------------------------------------------------- |
| `POST /{agentId}`                            | Run: `RunAgentInput` in, server-sent events out.                           |
| `GET /{agentId}/threads/{threadId}/messages` | History as `Message[]`, usable as `HttpAgent`'s `initialMessages`.         |
| `GET /{agentId}/threads/{threadId}/events`   | Reattach from `Last-Event-ID` (or `?cursor=`); `204` when nothing is left. |
| `POST /{agentId}/threads/{threadId}/cancel`  | Cancel the running turn.                                                   |

## `toNodeListener` [#tonodelistener]

```ts
toNodeListener(handler: { fetch }): (req, res) => void
```

Adapts the handler to `node:http`. Closing the connection stops reading; it
never cancels the turn.

## Behavior [#behavior]

* Each AG-UI message id is the command's idempotency key, so a retried run
  replays the same turn.
* An approval ends the run with an AG-UI interrupt (`reason: "tool_approval"`).
  Resume with
  `runAgent({ resume: [{ interruptId, status: "resolved", payload: { approved: true } }] })`.
  Other interactions answer with `payload` as the response.
* Every event that ends a group carries the Runtime cursor as its SSE `id`.
  `HttpAgent` does not reconnect by itself; call the reattach route with the
  last id you received.
* The handler calls the Runtime as each person with `client.as()`, so the
  Runtime keeps one person out of another's threads.

## Errors [#errors]

| Status | Code                      | When                                                                                                 |
| ------ | ------------------------- | ---------------------------------------------------------------------------------------------------- |
| `401`  | none                      | `subject` returned `undefined`.                                                                      |
| `400`  | `invalid_request` or none | Invalid `RunAgentInput`, frontend tools, a non-user last message, or a request the Runtime rejected. |
| `404`  | none                      | Unknown agent, or a thread the person does not own.                                                  |
| `409`  | `session_busy`            | The thread is busy or waiting on an open interrupt.                                                  |
| `409`  | `session_conflict`        | The thread's session was opened with other `session()` parameters.                                   |
| `500`  | `subject_invalid`         | The subject is not a valid Runtime subject.                                                          |
| `502`  | `runtime_feature_missing` | The Runtime lacks `transcript-events` or `subject-headers`.                                          |
| `502`  | `runtime_incompatible`    | Protocol mismatch with the Runtime.                                                                  |
| `502`  | `runtime_error`           | Any other Runtime failure.                                                                           |

A busy or paused thread may also end a streaming run with `RUN_ERROR` code
`session_busy`.

## Limitations [#limitations]

* Assistant text arrives once per model step; there is no token streaming.
* No reasoning, state, activity, or subagent events. An agent used as a tool
  shows only its result.
* Frontend tools in `RunAgentInput.tools` are rejected with `400`.
* One text part per user message. Earlier messages cannot be edited or
  regenerated.
* Browsers always go through your server. Never let one call the Runtime.


# client (/docs/reference/agents/client)



```ts
import { createClient, resolveConnection } from "@nylorun/agents/client";
```

Guides: [Sessions](/docs/run/sessions), [Events](/docs/run/events),
[App server](/docs/deploy/app-server).

## `createClient` [#createclient]

```ts
createClient(destination: Destination): AgentsClient
createClient(): Promise<AgentsClient>
```

Creates a Tenant API client. With a destination it returns synchronously. With
no argument it resolves the connection (environment, then the nearest project
link) and returns a promise.

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `url` | `string \| undefined` | No | Runtime URL. |
| `key` | `string \| undefined` | No | Application or executor key. |
| `tenant` | `string \| undefined` | No | Tenant id. |
| `fetch` | `((input: RequestInfo \| URL, init?: RequestInit) => Promise<Response>) \| undefined` | No | Custom fetch implementation. |

`url`, `key`, and `tenant` are optional in the type. When a destination is
passed, each one must be set here or in `NYLORUN_RUNTIME_URL`,
`NYLORUN_SERVER_KEY`, or `NYLORUN_TENANT`.

**Throws** `ConnectionError` (`connection_missing`) when no complete source is
found. The first call checks `/health`; version skew throws
`IncompatibleRuntimeError`.

```ts
const client = createClient({
  url: process.env.NYLORUN_RUNTIME_URL!,
  key: process.env.NYLORUN_SERVER_KEY!,
  tenant: process.env.NYLORUN_TENANT!,
});
```

## `resolveConnection` [#resolveconnection]

```ts
resolveConnection(options?: { url?, tenant?, key?, cwd? }): Promise<ResolvedConnection>
```

Returns `{ url, tenant, key, role, source }` using the same order as
`createClient()`: explicit options, then environment, then project link.
Sources never mix. `role` is `"executor"` when `NYLORUN_EXECUTOR_KEY` is used.

## `AgentsClient` [#agentsclient]

| Method                                                                                             | HTTP                           | Returns           |
| -------------------------------------------------------------------------------------------------- | ------------------------------ | ----------------- |
| `as(subject, { scopes? })`                                                                         | none                           | `AgentsClient`    |
| `saveAgent(runnable, options)`                                                                     | `PUT /v1/agents/:id`           | saved definition  |
| `listAgents()`                                                                                     | `GET /v1/agents`               | agent summaries   |
| `createSession(options)`                                                                           | `PUT /v1/sessions/:id`         | `SessionClient`   |
| `listSessions()`                                                                                   | `GET /v1/sessions`             | session summaries |
| `session(id)`                                                                                      | none                           | `SessionClient`   |
| `hostFeatures()`                                                                                   | `GET /health`                  | `string[]`        |
| `createVault` / `listVaults` / `getVault` / `deleteVault`                                          | `/v1/vaults/…`                 | vault info        |
| `createCredential` / `listCredentials` / `getCredential` / `rotateCredential` / `deleteCredential` | `/v1/vaults/:id/credentials/…` | credential info   |

### `createSession` [#createsession]

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `agentId` | `string` | Yes | Agent or workflow id. |
| `ownerUserId` | `string` | Yes | The person who owns the session. Set by your server. |
| `id` | `string \| undefined` | No | Session id. Generated when omitted. |
| `info` | `Record<string, unknown> \| undefined` | No | Session info available to hooks and tools. |
| `vaultIds` | `readonly string[] \| undefined` | No | End-user vaults to attach. |
| `credentialSelections` | `readonly { serverName: string; credentialId: string; }[] \| undefined` | No | Which credentials to use. |
| `sandbox` | `{ session: string; } \| undefined` | No | Share another session's sandbox. |
| `requestId` | `string \| undefined` | No | Request id sent with the `PUT`. Generated when omitted. |

### `as` [#as]

```ts
client.as(subject: string, options?: { scopes?: SubjectScope[] }): AgentsClient
```

Returns a copy of the client that acts for one person. Every call, event
streams included, sends `Nylorun-Subject` and `Nylorun-Scopes`, and the Runtime
limits it to those scopes and to the subject's own sessions and vaults.

| Parameter        | Type             | Default            | Description                                                             |
| ---------------- | ---------------- | ------------------ | ----------------------------------------------------------------------- |
| `subject`        | `string`         | none               | 1–200 visible ASCII characters; spaces only inside. `host` is reserved. |
| `options.scopes` | `SubjectScope[]` | `["sessions:own"]` | What the subject may do.                                                |

| 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                                |

**Throws** `TypeError` for an invalid subject or scope, and `Error` when the
client already acts for a subject. At request time, another person's session
or vault answers `404`; a route outside the scopes answers `403`
(`scope_required`); an executor key answers `403`. No scope reaches Tenant
reset, config seed, executors, actions, or sandbox tool routes; call those
without `as()`. Requires the Runtime feature `subject-headers`.

## `SessionClient` [#sessionclient]

| Method                                                 | Description                                                                        |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
| `input(value, { idempotencyKey })`                     | Start a turn. Strings become `message.content`; other JSON becomes `message.data`. |
| `approve(interactionId, approved, { idempotencyKey })` | Resolve an approval.                                                               |
| `respond(interactionId, value, { idempotencyKey })`    | Resolve a request for input.                                                       |
| `cancel({ idempotencyKey, reason? })`                  | Cooperatively cancel the active turn.                                              |
| `history({ cursor?, agent?, signal? })`                | Page canonical history (`GET /v1/sessions/:id/items`).                             |
| `observe({ cursor?, follow?, signal? })`               | Async iterator over live events. `follow` merges linked workflow child sessions.   |
| `inspect()`                                            | Snapshot of status, waits, outstanding actions, and uncertainty.                   |
| `pending()`                                            | Open interactions.                                                                 |
| `command(command)`                                     | Send a raw session command.                                                        |

Every command needs a stable `idempotencyKey`. Retrying with changed content
under the same key answers `409`. `history({ agent })` filters by
`payload.agent.delegationId` or `payload.agent.path`.

## Errors [#errors]

| Error                      | When                                                    |
| -------------------------- | ------------------------------------------------------- |
| `ConnectionError`          | No complete connection source (`connection_missing`).   |
| `IncompatibleRuntimeError` | Protocol or required-feature mismatch with the Runtime. |
| `RuntimeError`             | Any other non-2xx Runtime response; has `status`.       |

All error codes are in [Errors](/docs/reference/errors).


# define (/docs/reference/agents/define)



```ts
import { Agent, tool, capability, Chain, Loop } from "@nylorun/agents/define";
import type { BuiltAgent, AgentRef, AgentTool } from "@nylorun/agents/define";
```

Everything here is also exported from `@nylorun/agents`, except the
`AgentRef` and `AgentTool` types. Definitions have no `.run()`; the Runtime
runs them. Guides: [Agent](/docs/build), [Workflows](/docs/build/workflows).

## `Agent` [#agent]

```ts
Agent(options: AgentOptions): AgentBuilder
```

Starts an agent definition. Do not pass `model`; the Tenant chooses it.

| Parameter              | Type                                       | Required | Description                                                                       |
| ---------------------- | ------------------------------------------ | -------- | --------------------------------------------------------------------------------- |
| `options.id`           | `string`                                   | Yes      | Stable identifier used by manifests and sessions.                                 |
| `options.name`         | `string`                                   | No       | Human-readable label.                                                             |
| `options.description`  | `string`                                   | No       | Catalog description. Required when the agent is used as a tool.                   |
| `options.instructions` | `string \| readonly string[]`              | No       | Initial model instructions.                                                       |
| `options.tools`        | `readonly (ToolDefinition \| AgentTool)[]` | No       | Top-level tools (capability id `"agent"`). An agent here becomes a subagent tool. |
| `options.outputSchema` | `ToolSchemaSource`                         | No       | Contract for completed engine output.                                             |
| `options.metadata`     | `JsonObject`                               | No       | Opaque JSON.                                                                      |

**Returns** an `AgentBuilder`.

`Agent.from(manifest, implementations)` reconstructs a built agent from a
manifest and **rejects schema 3**.

```ts
export const assistant = Agent({
  id: "assistant",
  name: "Order assistant",
  instructions: "Help with orders.",
  tools: [lookupOrder],
}).build();
```

## `AgentBuilder` [#agentbuilder]

| Method                                  | Parameters                       | Returns        | Description                                       |
| --------------------------------------- | -------------------------------- | -------------- | ------------------------------------------------- |
| `use(declaration)`                      | capability or helper result      | `AgentBuilder` | Adds a named capability. Returns a new snapshot.  |
| `before(scope, fn)`                     | `"turn" \| "step"`, `BeforeHook` | `AgentBuilder` | Hook that returns a `Patch`.                      |
| `after(scope, fn)`                      | `"step" \| "turn"`, `AfterHook`  | `AgentBuilder` | Hook that returns a `Decision` or `TurnDecision`. |
| `build()`                               | none                             | `BuiltAgent`   | Assembles the agent.                              |
| `id` / `name` / `manifest` / `toJSON()` | none                             | identity       | `toJSON()` is `manifestSchemaVersion: 4`.         |

**Throws** `AgentBuildError` from `build()` for invalid tool schemas, duplicate
tool names, or incompatible contributions.

`BuiltAgent` is a type-only facade (`id`, `name`, `manifest`). `AgentTool` is
`Pick<BuiltAgent, "id" | "manifest" | "getBinding">`: a built agent or builder
placed in another agent's `tools`. See [Subagents](/docs/build/subagents).

## `tool` [#tool]

```ts
tool(options: ToolOptions): ToolDefinition
```

| Field              | Type                                | Required | Description                                             |
| ------------------ | ----------------------------------- | -------- | ------------------------------------------------------- |
| `name`             | `string`                            | Yes      | Tool name the model sees.                               |
| `description`      | `string`                            | No       | Model-facing text. Strongly recommended.                |
| `input` / `output` | Zod or JSON schema                  | `input`  | Argument and result schemas.                            |
| `run`              | `(input, ctx) => output`            | Yes      | Implementation. A plain `return` is a completed result. |
| `approval`         | `boolean \| string`                 | No       | Code-only. Pauses for approval before `run`.            |
| `effects`          | `"read" \| "idempotent" \| "write"` | No       | Code-only side-effect class.                            |

`inputSchema`, `outputSchema`, and `execute` are accepted aliases. `run`
receives a `ToolExecutionContext`; `ctx.agent` is an optional `AgentRef`
`{ id, path, delegationId? }` identifying the root or delegated agent.

Throw `new ToolError(code, message)` inside `run` to produce a failed tool
result. See [Tools](/docs/build/tools).

## `capability` [#capability]

```ts
capability({
  id: "support",
  instructions: "…",
  tools: [findOrder],
  before: { turn?: fn, step?: fn },
  after: { step?: fn, turn?: fn },
})
```

Bundles instructions, tools, and hooks for `.use()`. `tools` accepts
`ToolDefinition | AgentTool`. `capability({ model })` is deprecated. Durable
manifests reject arbitrary middleware closures. See
[Capabilities](/docs/build/capabilities).

## Helpers [#helpers]

| Helper                       | Default capability id | Notes                                                                              |
| ---------------------------- | --------------------- | ---------------------------------------------------------------------------------- |
| `mcp(servers, { id? })`      | `"mcp"`               | Keys must equal each server `name`. Transports: `stdio`, `streamable-http`, `sse`. |
| `sandbox(options?, { id? })` | `"sandbox"`           | Options: `image`, `network`, `resources`, `idle`.                                  |
| `skills(directory, { id? })` | catalog basename      | Injects `load_skill` / `read_skill_resource`.                                      |
| `plugin(directory)`          | plugin name           | `type: "agent-plugin"`. Also `loadPlugin`.                                         |

Built-in tools:

* **Sandbox:** `bash`, `read`, `write`, `edit`, `grep`, `glob`, executed by the
  Runtime. Calling the SDK stubs in-process throws `sandbox.runtime-only`.
* **Skills:** `load_skill` `{ name }`; `read_skill_resource` `{ name, path }`
  when resources exist.
* **MCP:** names come from the discovered server and are pinned in
  `mcpSnapshot`.

## Workflows [#workflows]

| Builder                              | Page                             |
| ------------------------------------ | -------------------------------- |
| `Loop({ id, run, verify, decide })`  | [Loop](/docs/build/loop)         |
| `Chain({ id, steps })`               | [Chain](/docs/build/chain)       |
| `Switch({ id, on, cases, default })` | [Switch](/docs/build/switch)     |
| `Map({ id, over, each })`            | [Map](/docs/build/map)           |
| `Parallel({ id, branches })`         | [Parallel](/docs/build/parallel) |

Each returns a `BuiltWorkflow` that is exported, saved, and run like an agent.
Invalid graphs throw `WorkflowBuildError`.


# executor (/docs/reference/agents/executor)



```ts
import { connectAgents } from "@nylorun/agents/executor";
```

Guides: [Executors](/docs/run/executors), [App server](/docs/deploy/app-server#executors).

## `connectAgents` [#connectagents]

```ts
connectAgents(options: ConnectOptions): AgentConnection
```

Saves every definition, registers an executor for each runnable, and opens an
authenticated event stream. When the Runtime offers an action, the executor
claims it, runs your implementation, and returns the result. It runs agent
tools and hooks, and workflow tool, `fn`, and `verify` nodes. Sandbox tools
run in the Runtime instead.

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `agents` | `readonly (AgentSource \| BuiltWorkflow<JsonValue, JsonValue>)[]` | Yes | Every runnable to serve. Agents used as tools are served with their parent. |
| `runtime` | `Destination \| undefined` | No | `{ url, tenant, key }`. Default: environment, then project link. |
| `implementationVersion` | `string \| undefined` | No | Default `NYLORUN_IMPLEMENTATION_VERSION`, then `"dev"`. |
| `application` | `AgentsClient \| undefined` | No | Use an existing application client instead of resolving one. |
| `onError` | `((error: unknown) => void) \| undefined` | No | Receives connection and execution errors. |

**Returns** an `AgentConnection`:

| Name | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| `ready` | `Promise<void>` | Yes | Settles after registration, discovery, and the authenticated stream are up. |
| `close` | `() => Promise<void>` | Yes | Stops subscriptions and leases. Running user code receives an `AbortSignal`. |

**Throws** (via `ready`) `ConnectionError` when no connection is found, and
`IncompatibleRuntimeError` on version skew.

```ts
const connection = connectAgents({ agents, onError: console.error });
await connection.ready;
```

## Credentials [#credentials]

With an application key, `PUT /v1/executors` registers a derived token for each
`(application key, Tenant, runnable id)`. Tokens are never stored. Claims send
only `{ requestId, implementationVersion }`. With `NYLORUN_EXECUTOR_KEY`, the
executor uses that pre-provisioned, single-runnable credential.

## Delegation limits [#delegation-limits]

Agents used as tools cannot declare `approval`. `ctx.ask`, `ctx.approve`,
`ctx.sleep`, and `ctx.waitFor` inside a delegated agent fail with
`delegation.interaction-unsupported`.

## `deriveExecutorToken` [#deriveexecutortoken]

Computes the same derived executor token the SDK registers. Most apps never
call it directly.


# Agents SDK (/docs/reference/agents)



<div className="api-package">
  @nylorun/agents · 0.7
</div>

```sh
npm install @nylorun/agents zod
```

`@nylorun/agents` is the only Nylorun package your app needs in production. It
depends on `@nylorun/core` and not on the Harness. Import from the root for
convenience, or from a focused entry point:

| Import path                | Use it to                                            | Page                                        |
| -------------------------- | ---------------------------------------------------- | ------------------------------------------- |
| `@nylorun/agents/define`   | Define agents, tools, capabilities, and workflows.   | [define](/docs/reference/agents/define)     |
| `@nylorun/agents/client`   | Save agents, create sessions, act for a person.      | [client](/docs/reference/agents/client)     |
| `@nylorun/agents/executor` | Register agents and run their tools in your process. | [executor](/docs/reference/agents/executor) |
| `@nylorun/agents/ag-ui`    | Serve agents to AG-UI chat clients from your server. | [ag-ui](/docs/reference/agents/ag-ui)       |

```ts
import { Agent, tool, createClient, connectAgents } from "@nylorun/agents";
```

The root re-exports all of them except `/ag-ui`, which adds `@ag-ui/core` and
is imported only from its own path.

Guides: [Build](/docs/build), [Run](/docs/run), [App server](/docs/deploy/app-server).


# CLI (/docs/reference/cli)



<div className="api-package">
  nylorun 0.1 · @nylorun/cli 0.4
</div>

There are three command-line tools. Run each with `npx`; generated projects do
not install them.

| Command                           | Package                 | Use it to                                               | Page                                   |
| --------------------------------- | ----------------------- | ------------------------------------------------------- | -------------------------------------- |
| `npx nylorun <command>`           | `nylorun`               | Start, stop, and inspect the local Runtime stack.       | [nylorun](/docs/reference/cli/nylorun) |
| `npx @nylorun/cli <command>`      | `@nylorun/cli` (`nylo`) | Create and select Tenants, configure models, print env. | [nylo](/docs/reference/cli/nylo)       |
| `npm create @nylorun/agent <dir>` | `@nylorun/create-agent` | Create a new project.                                   | [below](#npm-create-nylorunagent)      |

## `npm create @nylorun/agent` [#npm-create-nylorunagent]

```text
npm create @nylorun/agent <directory> [--yes]
```

Writes and installs a new project. It does not start the stack or the
application; see the [Quickstart](/docs).

| Flag                       | Description                         |
| -------------------------- | ----------------------------------- |
| `--yes`                    | Answer yes to installation prompts. |
| `--no-open`, `--no-studio` | Deprecated no-ops, still accepted.  |

## Moved commands [#moved-commands]

| Removed spelling         | Replacement                                    |
| ------------------------ | ---------------------------------------------- |
| `nylorun dev`            | `nylo tenant create` once, then `npm run dev`. |
| `nylorun tenant …`       | `nylo tenant …`                                |
| `nylorun configure`      | `nylo configure`                               |
| `nylorun status --env`   | `nylo env`                                     |
| `nylorun doctor sandbox` | `nylo doctor sandbox`                          |
| `nylorun runtime …`      | `nylorun up`, `down`, `status`, or `logs`      |

Moved commands exit 2 and print their replacement.


# nylo (/docs/reference/cli/nylo)



```text
npx @nylorun/cli <tenant|configure|env|doctor> [flags]
```

The `@nylorun/cli` package provides the `nylo` command. It talks to a running
Runtime; start the stack first with `npx nylorun up`.

## `tenant create` [#tenant-create]

```text
nylo tenant create [name]
```

Creates a Tenant, links the current project (`.nylorun/`), and seeds its model
from `.env` (`MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`, optional
`MODEL_PROVIDER_BASE_URL`).

## `tenant current` [#tenant-current]

```text
nylo tenant current
```

Prints the Tenant this project is linked to.

## `tenant list` [#tenant-list]

```text
nylo tenant list [--json]
```

Lists every Tenant on the Runtime.

## `tenant use` [#tenant-use]

```text
nylo tenant use <name-or-id>
```

Links the current project to an existing Tenant.

## `tenant status` [#tenant-status]

```text
nylo tenant status [--json]
```

Shows the linked Tenant's health.

## `tenant reset` / `tenant delete` [#tenant-reset--tenant-delete]

```text
nylo tenant reset
nylo tenant delete
```

`reset` clears selected Tenant data. `delete` removes the Tenant. Removing
`.nylorun/` only unlinks the project; it never deletes a Tenant.

## `configure` [#configure]

```text
nylo configure
```

Interactive wizard that sets the linked Tenant's provider, model, and key. It
does not send a test request; verify in Studio.

## `env` [#env]

```text
nylo env
```

Prints shell exports for the linked project:

```sh
eval "$(npx @nylorun/cli env)"
```

## `doctor sandbox` [#doctor-sandbox]

```text
nylo doctor sandbox [--json]
```

Checks the linked Tenant's sandbox.

## Exit codes [#exit-codes]

| Exit      | Meaning                                    |
| --------- | ------------------------------------------ |
| 0         | Success.                                   |
| 1         | General or configuration failure.          |
| 2         | Usage error or command moved to `nylorun`. |
| 6         | Runtime is not reachable.                  |
| 130 / 143 | Interrupted by SIGINT / SIGTERM.           |


# nylorun (/docs/reference/cli/nylorun)



```text
npx nylorun <up|down|start|stop|status|logs|studio|reset|doctor> [flags]
```

`nylorun` manages only the local Docker stack. `nylorun stack <command>` remains
a hidden alias.

## `up` / `start` [#up--start]

```text
nylorun up [--no-studio]
```

Creates the stack configuration on first run, then starts the stack, or leaves
it running. Prints the Runtime URL and a Studio login URL.

| Flag          | Description          |
| ------------- | -------------------- |
| `--no-studio` | Do not start Studio. |

## `down` / `stop` [#down--stop]

```text
nylorun down
```

Stops the containers and keeps all volumes and Tenants.

## `status` [#status]

```text
nylorun status [--json]
```

Shows service, port, and readiness state.

| Flag     | Description              |
| -------- | ------------------------ |
| `--json` | Machine-readable output. |

## `logs` [#logs]

```text
nylorun logs [service] [-f] [--tail <n>]
```

Reads stack logs, or one service's logs (`runtime`, `studio`, `postgres`,
`restate`, `s2`).

| Flag         | Description                 |
| ------------ | --------------------------- |
| `-f`         | Follow new output.          |
| `--tail <n>` | Show only the last n lines. |

```sh
npx nylorun logs runtime -f
```

## `studio` [#studio]

```text
nylorun studio [--no-open]
```

Mints a single-use Studio login, valid for two minutes, and opens it.

| Flag        | Description                       |
| ----------- | --------------------------------- |
| `--no-open` | Print the URL without opening it. |

## `reset` [#reset]

```text
nylorun reset [--yes]
```

Deletes every stack volume and every Tenant. This cannot be undone.

| Flag    | Description            |
| ------- | ---------------------- |
| `--yes` | Skip the confirmation. |

## `doctor` [#doctor]

```text
nylorun doctor [--json]
```

Checks Node, Docker, Compose v2, and stack health.

## Exit codes [#exit-codes]

| Exit      | Meaning                           |
| --------- | --------------------------------- |
| 0         | Success.                          |
| 1         | General failure.                  |
| 2         | Usage error or removed command.   |
| 3         | Docker is missing or unavailable. |
| 4         | Compose v2 is missing.            |
| 7         | Stack did not become ready.       |
| 130 / 143 | Interrupted by SIGINT / SIGTERM.  |


# Environment (/docs/reference/environment)



## Project and client [#project-and-client]

| Variable                         | Reader                      | Purpose                                     |
| -------------------------------- | --------------------------- | ------------------------------------------- |
| `NYLORUN_RUNTIME_URL`            | Agents SDK, Admin, `nylo`   | Runtime base URL.                           |
| `NYLORUN_TENANT`                 | Agents SDK, `nylo`          | Tenant id.                                  |
| `NYLORUN_SERVER_KEY`             | Agents SDK, `nylo`          | Application credential.                     |
| `NYLORUN_EXECUTOR_KEY`           | Agents SDK                  | Pre-provisioned scoped executor credential. |
| `NYLORUN_ADMIN_URL`              | Admin, `nylo tenant create` | Admin API URL.                              |
| `NYLORUN_ADMIN_KEY`              | Admin, `nylo tenant create` | Admin bearer key.                           |
| `NYLORUN_HOME`                   | Both CLIs, Admin            | Host root; default `~/.nylorun`.            |
| `NYLORUN_IMPLEMENTATION_VERSION` | Agents SDK                  | Executor implementation version.            |

## Tenant creation [#tenant-creation]

| Variable                    | Purpose                                                              |
| --------------------------- | -------------------------------------------------------------------- |
| `MODEL_PROVIDER`            | Provider id for an empty Tenant.                                     |
| `MODEL`                     | Model id.                                                            |
| `MODEL_PROVIDER_API_KEY`    | Provider credential.                                                 |
| `MODEL_PROVIDER_BASE_URL`   | Custom OpenAI-compatible endpoint.                                   |
| `NYLORUN_DEV_MODEL=fixture` | Makes `nylo` skip interactive model setup; Runtime does not read it. |
| `NYLORUN_SANDBOX`           | `auto` or `virtual`; `auto` selects the virtual backend.             |

## Local stack [#local-stack]

| Variable                | Purpose                            |
| ----------------------- | ---------------------------------- |
| `NYLORUN_RUNTIME_IMAGE` | Override the pinned Runtime image. |
| `NYLORUN_STUDIO_IMAGE`  | Override the pinned Studio image.  |
| `NYLORUN_STACK_PROJECT` | Override the Compose project name. |

`stack/.env` stores `NYLORUN_PORT`, `NYLORUN_STUDIO_PORT`, and
`NYLORUN_RESTATE_PORT` after the first start.

## Runtime container [#runtime-container]

| Family   | Variables                                                                                   |
| -------- | ------------------------------------------------------------------------------------------- |
| Database | `NYLORUN_DATABASE_URL`                                                                      |
| Restate  | `NYLORUN_RESTATE_*`                                                                         |
| S2       | `NYLORUN_S2_*`                                                                              |
| Listener | `NYLORUN_LISTEN_HOST`, `NYLORUN_LISTEN_PORT`, `NYLORUN_ALLOWED_HOSTS`, `NYLORUN_PUBLIC_URL` |

Never commit `.env`, Project credentials, Host credentials, or provider keys.


# Errors and contracts (/docs/reference/errors)



<div className="api-package">
  @nylorun/agents/define · beta
</div>

```ts
import { isHarnessError } from "@nylorun/agents/define";
import { run, bindingFromAgent } from "@nylorun/harness/run";

try {
  await run({
    binding: bindingFromAgent(agent.build()),
    input: "Run the task",
    onModelCall: adapter,
  });
} catch (error) {
  if (isHarnessError(error)) console.error(error.code, error.details);
}
```

`HarnessError` and JSON contracts are exported from `@nylorun/agents/define`.
They are not `@nylorun/harness` root exports. Recording failures reject
`run()`. They do not emit a session-only observation path. SDK HTTP failures
use `RuntimeError` from `@nylorun/agents`.

## Runtime Client errors [#runtime-client-errors]

```ts
import {
  ConnectionError,
  IncompatibleRuntimeError,
  RuntimeError,
} from "@nylorun/agents";
```

| Error                      | Meaning                                                                                                        |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `ConnectionError`          | No complete explicit, environment, or Project-link connection was found. Its code is `connection_missing`.     |
| `IncompatibleRuntimeError` | The Host protocol range or features do not satisfy this client. Inspect `compatibility`, `host`, and `remedy`. |
| `RuntimeError`             | An HTTP request failed; inspect `status` and parsed `body`.                                                    |

`@nylorun/admin` throws `AdminError` with a registry `code`. Both client
packages expose compatibility information before sending authenticated work.

## Shared operational error codes [#shared-operational-error-codes]

`ERROR_CODES` is exported by `@nylorun/agents`, `@nylorun/admin`, and
`@nylorun/core/compatibility`:

| Family           | Codes                                                                     |
| ---------------- | ------------------------------------------------------------------------- |
| Request boundary | `not_found`, `host_rejected`, `origin_rejected`, `unsupported_media_type` |
| Compatibility    | `protocol_unsupported`, `incompatible_host`                               |
| Tenant lifecycle | `tenant_conflict`, `active_work`                                          |
| Connection       | `connection_missing`                                                      |

Use the code for control flow and the message for operator context. A missing
or inaccessible Tenant is intentionally an opaque `404`, not a disclosure of
Tenant state.

`ConnectionError` directs local users to `npx nylorun up` and
`npx @nylorun/cli tenant create`.

## Shared JSON contracts [#shared-json-contracts]

**Access:** `@nylorun/agents/define` → `JsonPrimitive`, `JsonValue`, `JsonObject`,
`DeferredOutcome`, `ContextItem`, `Tripwire`

| Type              | Shape or fields                                                           | Description                                          |
| ----------------- | ------------------------------------------------------------------------- | ---------------------------------------------------- |
| `JsonPrimitive`   | `string`, `number`, `boolean`, or `null`                                  | JSON scalar.                                         |
| `JsonValue`       | Primitive, object, or readonly JSON-value array                           | JSON-only boundary.                                  |
| `JsonObject`      | String-keyed JSON values                                                  | Configuration, context, metadata.                    |
| `DeferredOutcome` | `kind: "deferred"`, optional `token`                                      | Defers a tool operation for host-managed settlement. |
| `Patch`           | `instructions?`, `capabilities?`, `tools?`, `state?`, `block?`, `active?` | Closed-world before-hook result. No `model`.         |
| `Decision`        | `text?`, `deny?`, `approve?`, `retry?`, `block?`                          | After-step result.                                   |
| `TurnDecision`    | `text?`, `output?`, `retry?`, `block?`                                    | After-turn result.                                   |

## Diagnostics and manifests [#diagnostics-and-manifests]

**Access:** `@nylorun/agents` → `AgentManifest`

**Access:** `@nylorun/agents/define` → `BuildDiagnostic`, `CapabilityManifest`

| Type                 | Name                                  | Required | Description                                           |
| -------------------- | ------------------------------------- | -------- | ----------------------------------------------------- |
| `BuildDiagnostic`    | `code`                                | Yes      | Programmatic invalid-build code.                      |
| `BuildDiagnostic`    | `message`                             | Yes      | Human-readable diagnostic.                            |
| `AgentManifest`      | `manifestSchemaVersion`               | Yes      | Published value is `4`.                               |
| `AgentManifest`      | `id`                                  | Yes      | Public built-agent identity.                          |
| `AgentManifest`      | `name`, `description`, `outputSchema` | No       | Catalog and output contract.                          |
| `CapabilityManifest` | `id`, `type`                          | Yes      | `type` is `"agent"` or `"agent-plugin"`.              |
| `CapabilityManifest` | `hooks`                               | No       | `{ at, scope }[]` (`before`/`after` × `turn`/`step`). |

Two different version numbers exist: manifest **4** versus core
`DEFINITION_SCHEMA_VERSION` **2** (definition-hash metadata). Do not document
the latter as the public manifest field.

## Observability contracts [#observability-contracts]

**Access:** `@nylorun/agents/define` → `Observer`, `ObserveEvent`

`run({ onEvent })` wraps those observations in `ExecutionEvent`
(`@nylorun/harness`). They are **not** Runtime SSE types. See
[Events](/docs/reference/events).

## `HarnessError` [#harnesserror]

**Access:** `@nylorun/agents/define` → `HarnessError`, `HarnessErrorCode`,
`isHarnessError`

| Name                    | Type                  | Required | Description                                 |
| ----------------------- | --------------------- | -------- | ------------------------------------------- |
| `code`                  | `HarnessErrorCode`    | Yes      | Stable machine-readable error code.         |
| `message`               | `string`              | Yes      | Human-readable explanation.                 |
| `details`               | `HarnessErrorDetails` | Yes      | Frozen scalar details map.                  |
| `isHarnessError(error)` | `unknown`             | Yes      | Narrows an unknown error to `HarnessError`. |

Use `code` rather than matching an error message.

| Error family     | Codes                                                                                                                                                                                                                                                                    |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Agent            | `agent.build-failed`, `agent.lifecycle-sealed`                                                                                                                                                                                                                           |
| Execution        | `execution.invalid-state`, `execution.invalid-input`, `execution.incompatible`, `execution.record-failed`                                                                                                                                                                |
| Context          | `context.invalid-item`, `context.invalid-item-type`, `context.invalid-order`, `context.invalid-reason`, `context.invalid-slot`                                                                                                                                           |
| Configuration    | `configuration.duplicate-tool-name`, `configuration.invalid`, `configuration.invalid-instructions`, `configuration.invalid-order`, `configuration.invalid-reason`, `configuration.invalid-slot`, `configuration.invalid-tools`, `configuration.model-selection-conflict` |
| Interaction      | `interaction.invalid`, `interaction.missing-resume`, `interaction.uncorrelated-resume`                                                                                                                                                                                   |
| Input and output | `input.invalid-content`, `output.invalid`, `output.invalid-schema`                                                                                                                                                                                                       |
| JSON             | `json.invalid-data`, `json.invalid-object`                                                                                                                                                                                                                               |
| Model            | `model.candidate-missing`, `model.adapter-invalid-options`, `model.adapter-invalid-response`, `model.invalid-candidate`, `model.invalid-directive`, `model.unsupported-content`, `model.unsupported-output-schema`                                                       |
| Response         | `response.invalid-replacement`                                                                                                                                                                                                                                           |
| Tool             | `tool.invalid`, `tool.invalid-arguments`, `tool.invalid-output`, `tool.invalid-name`, `tool.invalid-schema`, `tool.invalid-tool-result`, `tool.unregistered`, `tool.invalid-binding`                                                                                     |
| Sandbox          | `sandbox.runtime-only` (stub execute), `sandbox.unsupported`, `sandbox.unknown-option` (`SandboxError`)                                                                                                                                                                  |

Connected execution also uses `tool.invalid-input` on failed argument
validation. That code is an executor outcome, not this `HarnessError` table.

A delegated agent's `ctx.ask`, `ctx.approve`, `ctx.sleep`, or `ctx.waitFor`
fails as a tool result with `delegation.interaction-unsupported`. Nested
delegation fails the parent build with a named diagnostic. See
[Subagents](/docs/build/subagents).


# Events (/docs/reference/events)



<div className="api-package">
  @nylorun/core/contracts · 0.5
</div>

Wire shape:

```ts
{
  eventId: string;
  sessionId: string;
  turnId: string;
  cursor: string;
  createdAt: string;
  type: string;
  payload: JsonValue;
}
```

History: `GET /v1/sessions/:id/items` returns `{ items, cursor }`. Optional
`?agent=` keeps events whose `payload.agent.delegationId` or
`payload.agent.path` matches (`session.history({ agent })`). Live SSE:
`GET /v1/sessions/:id/events`. `SessionClient.observe` parses JSON `data` and
ignores SSE events named `heartbeat` or `ready`. The server also sends
`: keepalive` comments.

## Runtime `LiveEvent.type` [#runtime-liveeventtype]

| `type`                 | When                                                                                            |
| ---------------------- | ----------------------------------------------------------------------------------------------- |
| `command.message`      | User message accepted                                                                           |
| `command.approve`      | Approval command                                                                                |
| `command.respond`      | Response command                                                                                |
| `command.cancel`       | Cancel command                                                                                  |
| `turn.completed`       | Durable success (`output` in payload)                                                           |
| `turn.paused`          | Waiting on interactions                                                                         |
| `turn.failed`          | Failure                                                                                         |
| `turn.cancelled`       | Cancel (`reason`)                                                                               |
| `turn.waiting`         | Waiting on remote work                                                                          |
| `turn.uncertain`       | Uncertain turn                                                                                  |
| `action.pending`       | Tool or hook action offered                                                                     |
| `action.claimed`       | Executor claimed                                                                                |
| `action.completed`     | Result applied                                                                                  |
| `action.uncertain`     | Expired non-hook claim                                                                          |
| `effect.uncertain`     | Lost or cancelled effect                                                                        |
| `sandbox.state`        | Sandbox lifecycle                                                                               |
| `sandbox.exec`         | Sandbox exec audit                                                                              |
| `delegation.started`   | An agent used as a tool began. Payload includes `agent: { id, path, delegationId }` and `task`. |
| `delegation.completed` | That child settled. Payload includes `agent` plus `status` and `outcome`.                       |
| `node.started`         | A workflow node began; includes manifest `path`, `key`, kind, and iteration.                    |
| `node.completed`       | A workflow node completed with its path and output.                                             |
| `node.failed`          | A workflow node failed with a curated error.                                                    |
| `node.agent`           | An agent node created or reused a linked child session.                                         |

Harness-internal observations (`tool.started`, `model.requested`, …) are **not**
wire events. Do not treat `onEvent` from `@nylorun/harness/run` as Runtime SSE.

Guide: [Run: events](/docs/run/events).


# Glossary (/docs/reference/glossary)



| Term                                                                      | Meaning                                                                                                                                   |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| <span id="agent">**Agent**</span>                                         | A definition: id, instructions, tools, and capabilities. See [Build](/docs/build).                                                        |
| <span id="app-server">**App server**</span>                               | Your server that signs people in, holds the Tenant key, and calls the Runtime for each person. See [App server](/docs/deploy/app-server). |
| <span id="capability">**Capability**</span>                               | A named bundle of instructions, tools, and hooks attached with `.use()`.                                                                  |
| <span id="durable-streams">**Durable Streams**</span>                     | S2 event history and live session streams.                                                                                                |
| <span id="durable-session-execution">**Durable Session Execution**</span> | Restate-backed wakes and serialized session advancement.                                                                                  |
| <span id="executor">**Executor**</span>                                   | Your process that runs tools, hooks, workflow functions, and verification nodes. See [Executors](/docs/run/executors).                    |
| <span id="host-root">**Host root**</span>                                 | `NYLORUN_HOME` or `~/.nylorun`: Host identity, credentials, stack files, and Tenant files.                                                |
| <span id="project">**Project**</span>                                     | Your application. See [Your project](/docs/project).                                                                                      |
| <span id="project-link">**Project link**</span>                           | `.nylorun/link.json` and `credentials.json`, connecting a Project to a Tenant.                                                            |
| <span id="runtime">**Runtime**</span>                                     | The service that runs sessions, calls the model, and stores history. Also called the Runtime Host.                                        |
| <span id="scope">**Scope**</span>                                         | What a subject may do, e.g. `sessions:own`. See [as()](/docs/reference/agents/client#as).                                                 |
| <span id="session">**Session**</span>                                     | One durable conversation or workflow run inside a Tenant. See [Sessions](/docs/run/sessions).                                             |
| <span id="session-store">**Session Store**</span>                         | Postgres state for sessions, checkpoints, effects, and definitions.                                                                       |
| <span id="studio">**Studio**</span>                                       | The local dashboard. See [Studio](/docs/studio).                                                                                          |
| <span id="studio-principal">**Studio principal**</span>                   | Per-Tenant credential Studio derives from the Host admin key.                                                                             |
| <span id="subject">**Subject**</span>                                     | The person an app server calls the Runtime for, sent as `Nylorun-Subject`.                                                                |
| <span id="tenant">**Tenant**</span>                                       | An isolated Postgres schema, S2 basin, Host directory, vault, principals, and sandboxes.                                                  |
| <span id="turn">**Turn**</span>                                           | One input and everything the agent does until it completes, pauses, fails, or is cancelled.                                               |
| <span id="workflow">**Workflow**</span>                                   | A runnable graph built from Loop, Chain, Switch, Map, and Parallel. See [Workflows](/docs/build/workflows).                               |


# Harness (/docs/reference/harness)



<div className="api-package">
  @nylorun/harness/run · beta
</div>

## Advanced: in-process engine [#advanced-in-process-engine]

```ts
import { run, bindingFromAgent } from "@nylorun/harness/run";

const result = await run({
  binding: bindingFromAgent(agent.build()),
  input,
  state,
  onModelCall: adapter,
  info,
  signal,
  onEvent,
  record,
});
```

`run()` is the public engine surface. It returns `Promise<RunResult>`.
Application definitions have no `.run()`. The `/engine` specifier and
hosted-execution names are gone; use `/run` and the durable names below.

This page is advanced host guidance. The taught application path is
[sessions](/docs/run/sessions) plus a connected executor.

## `BoundRunOptions` and `RunBinding` [#boundrunoptions-and-runbinding]

**Access:** `@nylorun/harness/run` → `run`, `RunBinding`, `BoundRunOptions`,
`bindingFromAgent`

| Name          | Type                                               | Required | Description                                             |
| ------------- | -------------------------------------------------- | -------- | ------------------------------------------------------- |
| `binding`     | `RunBinding<Info>`                                 | Yes      | Manifest plus local implementations.                    |
| `input`       | `ExecutionInput`                                   | Yes      | User message or a pause continuation.                   |
| `onModelCall` | `ModelAdapter`                                     | Yes      | Model adapter for this invocation.                      |
| `state`       | `ExecutionState`                                   | No       | Prior returned state. Omit to start empty.              |
| `info`        | `Info`                                             | No       | Host identity for hooks and tools.                      |
| `signal`      | `AbortSignal`                                      | No       | Cooperative cancellation. Abort settles as `cancelled`. |
| `onEvent`     | `(event: ExecutionEvent) => void \| Promise<void>` | No       | Live observations (not Runtime SSE).                    |
| `record`      | `(state: ExecutionState) => void \| Promise<void>` | No       | Awaited persistence barrier.                            |

`bindingFromAgent(agent)` builds a `RunBinding` from an authored `BuiltAgent`.
Bindings contain local functions and are not a wire format.

Statuses: `completed` | `paused` | `cancelled` | `failed`. Each returns
serializable `state`.

## Durable execution [#durable-execution]

**Access:** `@nylorun/harness/run` → `runDurable`, `createDurableCheckpoint`,
`DurableHost`, `DurableCheckpoint`, `DurableResult`

Rename map (no aliases): `runHosted` → `runDurable`,
`createHostedCheckpoint` → `createDurableCheckpoint`,
`EngineHost` → `DurableHost`, `EngineBinding` → `RunBinding`.

Durable execution reconstructs progress from a checkpoint and individually
journaled effect outcomes. Engine version is **`hosted-2`**. Checkpoint
version is `1`. Protocol version is `1`. Manifest schema is **4**.

A `hook` effect runs one hook point for every capability that registered it.
Effect ids use stable names (`before-turn`, `before-step:<stepId>`,
`after-step:<stepId>`, `after-turn:<stepId>`).

## Model adapters [#model-adapters]

Custom hosts supply `onModelCall`. Translators for provider wire formats live
on `@nylorun/harness` for host authors. Application users configure the
provider in the [Tenant vault](/docs/run/models), not on the agent.

Guide: [HOST\_CONTRACT.md](https://github.com/nylorun/agents/blob/main/harness/HOST_CONTRACT.md).


# Reference (/docs/reference)



<div className="api-package">
  latest · agents 0.7 / runtime 0.11 / harness 0.19 · protocol 2
</div>

<Callout title="Pre-1.0">
  Public APIs and specs change quickly before 1.0, and any release may break
  them. Check the published versions on [Compatibility](/docs/compatibility).
</Callout>

Guides teach a task; these pages list every option. Start with the Agents SDK:
most apps never call the HTTP API directly.

| Spec                                       | What it is                                         | Use it when                                  |
| ------------------------------------------ | -------------------------------------------------- | -------------------------------------------- |
| [Agents SDK](/docs/reference/agents)       | `@nylorun/agents`: define, client, executor, ag-ui | You write agents or call them from your app. |
| [CLI](/docs/reference/cli)                 | `nylorun`, `nylo`, and `npm create`                | You run the stack or manage Tenants.         |
| [Runtime API](/docs/reference/runtime-api) | The Runtime's HTTP routes and headers              | You call the Runtime without the SDK.        |
| [Admin SDK](/docs/reference/admin)         | `@nylorun/admin`: Host status and Tenant lifecycle | You provision Tenants from code.             |
| [Events](/docs/reference/events)           | Every `LiveEvent` type                             | You render or react to session events.       |
| [Errors](/docs/reference/errors)           | Machine-readable error codes                       | You handle failures.                         |
| [Environment](/docs/reference/environment) | Process variables                                  | You configure an app or container.           |
| [Harness](/docs/reference/harness)         | In-process `run` / `runDurable`                    | You embed the engine directly.               |
| [Glossary](/docs/reference/glossary)       | Every Nylorun term                                 | A word on another page is unfamiliar.        |

## Packages [#packages]

| Package            | Role                                                                           |
| ------------------ | ------------------------------------------------------------------------------ |
| `nylorun`          | Manages only the local Docker stack.                                           |
| `@nylorun/cli`     | Provides `nylo`: Tenants, project links, and models.                           |
| `@nylorun/agents`  | Defines runnables and provides the Tenant client, executor, and AG-UI handler. |
| `@nylorun/admin`   | Manages Hosts and Tenants.                                                     |
| `@nylorun/runtime` | Library and source of the Runtime container. It has no CLI binary.             |
| `@nylorun/core`    | Shared wire contracts and compatibility constants.                             |
| Studio             | Ships only as the `ghcr.io/nylorun/studio` image, not an npm package.          |

Versions are listed on [Compatibility](/docs/compatibility).


# Runtime API (/docs/reference/runtime-api)



<div className="api-package">
  @nylorun/runtime 0.11 · protocol 2
</div>

<Callout title="Most apps use the SDK">
  The [Agents SDK client](/docs/reference/agents/client) wraps these routes,
  headers, and compatibility checks. Generated per-endpoint pages are coming.
</Callout>

## Compatibility and readiness [#compatibility-and-readiness]

`GET /health` is unauthenticated and reports Runtime version, Host id, protocol
range, and required features:

```json
{
  "status": "ok",
  "service": "nylorun-runtime",
  "version": "0.11.0-beta",
  "protocol": {
    "min": 2,
    "max": 2,
    "features": ["runtime-tenants", "admin-status", "studio-principal"]
  }
}
```

Hosts may also advertise optional `tenant-fixture-model`. `GET /ready` checks
Postgres, Restate, and S2 and returns `503` with dependency checks until all
required services are available.

## Headers [#headers]

| Header             | Sent on                | Value                                                                     |
| ------------------ | ---------------------- | ------------------------------------------------------------------------- |
| `Authorization`    | Every request          | `Bearer <key>`                                                            |
| `Nylorun-Protocol` | Every request          | `2`                                                                       |
| `Nylorun-Tenant`   | Tenant requests        | The Tenant id. Admin requests omit it.                                    |
| `Nylorun-Subject`  | App-server requests    | The person the call is for. See [as()](/docs/reference/agents/client#as). |
| `Nylorun-Scopes`   | With `Nylorun-Subject` | Space-separated scopes, e.g. `sessions:own`.                              |

Subject headers require the optional feature `subject-headers`. Only an
application key may send them. The Runtime refuses browser requests that carry
`Origin`.

## Host routes [#host-routes]

| Method       | Path                      | Purpose                                                          |
| ------------ | ------------------------- | ---------------------------------------------------------------- |
| `GET`        | `/health`                 | Liveness and compatibility.                                      |
| `GET`        | `/ready`                  | Postgres, Restate, and S2 readiness.                             |
| `GET`        | `/v1/admin/status`        | Host and Tenant aggregate status.                                |
| `GET/POST`   | `/v1/admin/tenants`       | List or create Tenants. Creation accepts `studioCredentialHash`. |
| `GET/DELETE` | `/v1/admin/tenants/:id`   | Inspect or delete a Tenant.                                      |
| `POST`       | `/v1/admin/host/shutdown` | Host-private shutdown route.                                     |

Application principal id `studio` is reserved. `PUT /v1/tenant/config/seed`
accepts `fixtureModel: true` when `tenant-fixture-model` is available.

## Tenant routes [#tenant-routes]

| Method           | Path                               | Purpose                                                                 |
| ---------------- | ---------------------------------- | ----------------------------------------------------------------------- |
| `GET`            | `/v1/tenant`                       | Tenant status; `checks.store`, plus optional `execution` and `streams`. |
| `PUT/GET`        | `/v1/agents/:id`, `/v1/agents`     | Save or list definitions.                                               |
| `PUT/GET`        | `/v1/sessions/:id`, `/v1/sessions` | Create, inspect, or list sessions.                                      |
| `POST`           | `/v1/sessions/:id/commands`        | Input, approval, response, cancellation, or executor result.            |
| `GET`            | `/v1/sessions/:id/items`           | Canonical history.                                                      |
| `GET`            | `/v1/sessions/:id/events`          | Canonical SSE history.                                                  |
| CRUD             | `/v1/vaults/*`                     | End-user vaults and credentials.                                        |
| `GET/PUT`        | `/v1/tenant/model*`                | Model credentials, selection, and catalog.                              |
| `GET`            | `/v1/tenant/sandbox`               | Virtual sandbox status.                                                 |
| `PUT/GET/DELETE` | `/v1/executors*`                   | Executor registration and administration.                               |

Executor routes provide SSE discovery, action listing, claims, heartbeats, and
`action_result` commands. Executor credentials are scoped to one runnable and
cannot call vault, model, definition-management, or Admin routes.

## Ephemeral embedding [#ephemeral-embedding]

`startEphemeralRuntime()` creates an in-memory Host and Tenant for tests and
controlled embedding. Nothing survives `close()`; use the Docker stack for
durable work.

Authoritative implementation details are in the
[Runtime source](https://github.com/nylorun/agents/tree/main/runtime).


# Control (/docs/run/control)



Cancel the active turn from a trusted backend or Studio:

```ts
await session.cancel({ idempotencyKey, reason: "user stopped" });
```

Studio exposes the same cancel. Cancellation is cooperative: forward
`context.signal` from tools so in-flight HTTP work can stop.

On the explicit engine path, pass `signal` into `run()`. Abort settles as
`cancelled` — a result, not a throw.

Cancelled in-flight customer **tool** work can become **uncertain**. Runtime
does not automatically repeat it. Expired **hook**, workflow `fn`, and
workflow `verify` claims can be offered again.

Cancellation ends the current turn; a later message can start another turn from
the state preceding the cancelled turn.

## Approvals and responses [#approvals-and-responses]

The session client can `approve` or `respond` to a correlated interaction.
Tools can request those pauses through `approval`, `ask`, or `approve` on the
execution context.

Studio does **not** ship an approvals UI in this release. Drive those commands
from a trusted backend if you use them. A delegated agent's tools cannot
declare `approval`, and `ctx.ask`, `ctx.approve`, `ctx.sleep`, or `ctx.waitFor`
inside it fail with `delegation.interaction-unsupported`. Keep those pauses on
the parent. See [Subagents](/docs/build/subagents).

## Sleep, events, and steps [#sleep-events-and-steps]

Durable wait helpers exist on the tool context:

| Helper              | Current behavior                                                                               |
| ------------------- | ---------------------------------------------------------------------------------------------- |
| `ask` / `approve`   | Pause for a correlated response or approval.                                                   |
| `sleep` / `waitFor` | Return an inspectable deferred outcome. Automatic timer and event wakeups are not implemented. |
| `step`              | Memoize a value after a persisted action outcome. Interrupted actions stay uncertain.          |

Do not document these helpers as a working scheduler, cron, or exactly-once
external-effects system. Recorded progress does not guarantee that every
interrupted external operation can be safely repeated.

See [Storage](/docs/deploy/docker#storage).

## Next step [#next-step]

<Cards>
  <Card title="Vault" description="Store Tenant model and end-user credentials." href="/docs/run/vault" />

  <Card title="Run" description="Start Runtime and open Studio." href="/docs/run" />
</Cards>


# Events (/docs/run/events)



Read history and subscribe to live events:

```ts
const page = await session.history();
for (const item of page.items) {
  console.log(item.type, item.payload);
}

for await (const event of session.observe({ cursor: page.cursor ?? undefined })) {
  console.log(event.type, event.payload);
}
```

Runtime stores that canonical history with the session. Studio's Events
inspector reads `GET /v1/sessions/:id/items` and live
`GET /v1/sessions/:id/events`. The SDK exposes the same records.

Each record includes `eventId`, `sessionId`, `turnId`, `cursor`, `createdAt`,
`type`, and `payload`. The `cursor` is an opaque position, not an array index.
Pass it to `history({ cursor })` or `observe({ cursor })` to continue.
`history({ agent })` keeps events whose `payload.agent.delegationId` or
`payload.agent.path` matches — one child invocation, or every concurrent child
that shares a path such as `support/researcher`. `Last-Event-ID` is accepted
on the SSE route. `observe` skips SSE frames named `heartbeat` or `ready`.

`session.inspect()` returns status, waits, outstanding actions, and
uncertainty. It is a snapshot, not a live stream.

Canonical Runtime types include `command.*`, `turn.*`, `action.*`,
`effect.uncertain`, `sandbox.*`, `delegation.*`, and workflow `node.*` events.
The full table is in
[Reference: events](/docs/reference/events).

When an agent is used as a tool, Runtime emits `delegation.started` (payload
includes `agent` and `task`) and `delegation.completed` (`agent`, `status`,
`outcome`). Action events for child work carry `payload.agent`. See
[Subagents](/docs/build/subagents).

Workflow sessions emit `node.started`, `node.completed`, and `node.failed` for
manifest paths. Agent nodes also emit `node.agent` with the child session id;
Studio uses those records for workflow-to-agent links and its live node tree.

## What you can rely on [#what-you-can-rely-on]

* History is the Tenant-owned record. Studio restores it for the selected session.
* Live SSE is the same canonical event stream, not a second log format.
* Keep keys, cookies, headers, and raw provider payloads out of events and
  manifests.

Studio shows completed assistant text and tool inputs/results. Token streaming
is not part of this release.

Engine `onEvent` observations (`ExecutionEvent` from `@nylorun/harness`) are a
separate in-process surface for custom hosts. They are not the Runtime history
API.

## Next step [#next-step]

<Cards>
  <Card title="Control" description="Cancel a turn and understand wait limits." href="/docs/run/control" />

  <Card title="Event reference" description="Canonical LiveEvent types." href="/docs/reference/events" />
</Cards>


# Executors (/docs/run/executors)



The generated production entry connects every exported runnable:

```ts
import { connectAgents } from "@nylorun/agents";
import { agents } from "../agents/index.js";

await connectAgents({ agents }).ready;
```

With no explicit Runtime option, `connectAgents` resolves environment and then
reads the Project link directly. The generated watcher needs no CLI wrapper.

Runtime persists session work and offers actions. The executor claims matching
actions, runs your implementation, and returns the result. It executes agent
tools and hooks plus workflow tool, `fn`, and `verify` nodes. Sandbox tools run
inside the Tenant Runtime instead.

## Credentials [#credentials]

An application key saves definitions and registers executors through
`PUT /v1/executors`. The SDK derives a separate token for each
`(application key, Tenant, runnable id)` and uses it for claims. Derived tokens
are never stored in the Project.

* Unchanged registration is idempotent and keeps open streams.
* Re-registering a changed implementation reconnects its executor.
* `GET /v1/executors` lists summaries but never tokens.
* `DELETE /v1/executors/:agentId` removes the registration.

Executor credentials authorize SSE discovery, action listing, claims,
heartbeats, and `action_result` commands for their single runnable id. They
cannot call vault, model, definition-management, or Admin routes, and cannot
broaden their scope.

Use an application key for normal `connectAgents` provisioning.
Supply `NYLORUN_EXECUTOR_KEY` only when an operator has separately provisioned
that scoped credential, and pass a complete destination:

```ts
const connection = connectAgents({
  agents,
  runtime: {
    url: process.env.NYLORUN_RUNTIME_URL!,
    tenant: process.env.NYLORUN_TENANT!,
    key: process.env.NYLORUN_EXECUTOR_KEY!,
  },
});

await connection.ready;
```

`implementationVersion` defaults to `NYLORUN_IMPLEMENTATION_VERSION`, then
`dev`. `connection.ready` settles after definition registration, discovery,
and authenticated executor SSE. `connection.close()` stops subscriptions and
leases; running user code receives an `AbortSignal`.

Stopping a Worker or closing a Tenant does not cancel the interrupted turn;
the next advance resumes from its checkpoint. Takeover marks in-flight effects
`uncertain`. Only user cancellation ends the turn as `cancelled`. Interrupted
external tool work becomes uncertain rather than being repeated.
Expired hook, workflow function, and verification claims may be offered again;
keep irreversible side effects in tools and make them idempotent.

## Next step [#next-step]

<Cards>
  <Card title="Deploy your app server" description="Run executors with explicit Runtime settings." href="/docs/deploy/app-server#executors" />

  <Card title="Events" description="Read history and stream live progress." href="/docs/run/events" />
</Cards>


# Run (/docs/run)



Running an agent involves four pieces:

* **The stack**: `npx nylorun up` runs the Runtime and Studio in Docker.
* **A Tenant**: the isolated space your project is linked to.
* **Your app**: registers agents and runs their tools (`npm run dev`).
* **Sessions**: one conversation or workflow run, stored durably by the Runtime.

## Call your agent from code [#call-your-agent-from-code]

From any code in your linked project:

```ts
import { createClient } from "@nylorun/agents";

const client = await createClient();
const session = await client.createSession({
  agentId: "assistant",
  ownerUserId: "user-123",
});

await session.input("Look up order demo-123", { idempotencyKey: crypto.randomUUID() });

for await (const event of session.observe()) {
  console.log(event.type, event.payload);
  if (["turn.completed", "turn.paused", "turn.failed", "turn.cancelled"].includes(event.type)) break;
}
```

The stream stays open; stop reading when the turn completes, pauses, or fails.

`createClient()` finds the Runtime through the project link, so this works
locally with no configuration. Studio uses the same calls.

## Topics [#topics]

| Topic     | Basic usage                                      | Page                             |
| --------- | ------------------------------------------------ | -------------------------------- |
| Models    | `npx @nylorun/cli configure`                     | [Models](/docs/run/models)       |
| Sessions  | `client.createSession({ agentId, ownerUserId })` | [Sessions](/docs/run/sessions)   |
| Executors | `await connectAgents({ agents }).ready`          | [Executors](/docs/run/executors) |
| Events    | `for await (const e of session.observe()) …`     | [Events](/docs/run/events)       |
| Control   | `session.cancel({ idempotencyKey })`             | [Control](/docs/run/control)     |
| Vault     | `client.createSession({ …, vaultIds })`          | [Vault](/docs/run/vault)         |

## Day-to-day commands [#day-to-day-commands]

```sh
npx nylorun status                   # is the stack healthy?
npx nylorun logs runtime -f          # follow Runtime logs
npx @nylorun/cli tenant current      # which Tenant is this project linked to?
npx @nylorun/cli tenant use <name>   # switch Tenant
```

Every command and flag is in [Reference → CLI](/docs/reference/cli).

`npm run dev` is plain `tsx watch`. Saving a file restarts only your app;
`connectAgents` re-registers the agents. Start a new session after changing a
definition: existing sessions stay pinned to the definition they started with.

## Next step [#next-step]

<Cards>
  <Card title="Sessions" description="Create, continue, and inspect sessions." href="/docs/run/sessions" />

  <Card title="Events" description="Read history and stream live progress." href="/docs/run/events" />

  <Card title="Deploy" description="Run your app and the Runtime outside your laptop." href="/docs/deploy" />
</Cards>


# Models (/docs/run/models)



From a linked Project, configure its Tenant:

```sh
npx nylorun up
npx @nylorun/cli configure
```

The wizard resolves the Project link and writes the provider, model, and key
through `PUT /v1/tenant/model`. It does not make a test model request; send a
message in Studio to verify provider access.

Applications do not put provider secrets or a `model` instance in an agent
manifest. The Tenant Runtime selects its active provider from the Tenant vault.

## One-time environment seed [#one-time-environment-seed]

`nylo tenant create` can seed an empty Tenant vault from:

```dotenv
MODEL_PROVIDER=custom
MODEL=your-model-id
MODEL_PROVIDER_API_KEY=your-key
MODEL_PROVIDER_BASE_URL=https://your-provider.example/v1
```

`MODEL_PROVIDER_BASE_URL` is required only for a custom OpenAI-compatible
provider. After the first write, the vault is authoritative; later `.env`
edits do not replace it. Use `nylo configure` or Studio Model Settings.

Studio can store multiple provider credentials and switch the active provider
and model through `/v1/tenant/model/selection`. Reads return metadata, never
the secret.

`NYLORUN_DEV_MODEL=fixture` only makes `nylo` skip interactive setup. Seed the
Tenant with `fixtureModel: true` to use the credential-free fixture.

<Callout title="Keep credentials in the Tenant" type="warn">
  Do not put provider keys in definitions, manifests, session records, Studio
  configuration, Project links, or source control.
</Callout>

For provider failures, confirm `nylo tenant current`, inspect the selected
model in Studio, check provider access/quota, and read
`nylorun logs runtime`.

## Next step [#next-step]

Read [Vault](/docs/run/vault) for encryption and end-user credentials, or open
[Studio](/docs/studio) to switch the selection.


# Sessions (/docs/run/sessions)



From code running inside a linked Project, resolve its connection and create a
session:

```ts
import { createClient } from "@nylorun/agents";

const client = await createClient();
const session = await client.createSession({
  agentId: "assistant",
  ownerUserId: authenticatedUser.id,
});

await session.input("Look up order demo-123", {
  idempotencyKey: requestId,
});
```

Zero-argument `createClient()` resolves environment variables and then the
nearest Project link. A deployed backend can pass the destination explicitly:

```ts
const client = createClient({
  url: process.env.NYLORUN_RUNTIME_URL!,
  key: process.env.NYLORUN_SERVER_KEY!,
  tenant: process.env.NYLORUN_TENANT!,
});
```

Trusted servers supply `ownerUserId`. Application credentials are never
browser credentials. Retry the same semantic command with the same
`idempotencyKey`; changed content under the same key conflicts with `409`.

## Agent and workflow input [#agent-and-workflow-input]

Strings become `message.content`; any other JSON value becomes `message.data`.
Structured data is useful for workflow inputs:

```ts
await session.input({ repository: "nylorun/harness", pullRequest: 117 }, {
  idempotencyKey: requestId,
});
```

Both agents and workflows use `createSession`, `input`, `observe({ follow })`,
`pending`, `approve`, `respond`, `cancel`, and `history`. Studio uses the same
surface. A workflow session includes its manifest tree, node progress, loop
iterations, and links to child agent sessions.

## Turn manifests [#turn-manifests]

An agent message may include an optional `manifest` variant through the raw
`session.command()` API. Runtime validates that it is compatible with the
session's pinned agent manifest and applies it only to that turn. The session's
original pin remains unchanged. Use this for controlled per-turn instruction
or capability changes, not arbitrary identity or schema replacement.

## Continue and inspect [#continue-and-inspect]

Use `client.session(sessionId)` to reattach. `session.history({ agent })`
filters delegated-agent events by path or delegation id. `createSession` also
accepts optional `id`, `info`, `vaultIds`, and `credentialSelections`.

Sessions are isolated by Tenant. Checkpoints live in Postgres and event history
lives in S2, not in the Project's `.nylorun/` directory. Start a new session
after changing definitions or implementations.

## Commands [#commands]

| Helper                                              | Purpose                                              |
| --------------------------------------------------- | ---------------------------------------------------- |
| `session.input(value, options)`                     | Start a text-agent or structured workflow turn.      |
| `session.approve(interactionId, approved, options)` | Resolve a correlated approval.                       |
| `session.respond(interactionId, value, options)`    | Resolve a correlated request for input.              |
| `session.cancel(options)`                           | Cooperatively cancel the active turn and child work. |

## Next step [#next-step]

Connect implementations with [Executors](/docs/run/executors) or inspect the
session in [Studio](/docs/studio).


# Vault (/docs/run/vault)



Every Tenant has its own encrypted vault.

| Scope            | Owner             | Stores                                                        |
| ---------------- | ----------------- | ------------------------------------------------------------- |
| **Tenant model** | Tenant operators  | Provider credentials and the active provider/model selection. |
| **End-user**     | One `ownerUserId` | URL-bound credentials for outbound calls such as MCP.         |

Secret values use AES-256-GCM under a key-encryption key stored with the Tenant
inside the Host root. Tenant runtimes do not read ambient Host environment for
vault configuration. API reads return metadata only.

## Tenant model vault [#tenant-model-vault]

`nylo tenant create` can seed a provider and write it to
`PUT /v1/tenant/model`. `nylo configure` targets the Tenant selected by the
Project link. Studio Model Settings uses the same Tenant routes.

One-time `MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`, and optional
`MODEL_PROVIDER_BASE_URL` values can seed an empty Tenant. After that, use the
vault APIs rather than treating `.env` as live storage.

## End-user vaults [#end-user-vaults]

Create and attach vaults from a trusted backend:

```ts
const vault = await client.createVault({
  ownerUserId: authenticatedUser.id,
  idempotencyKey,
});

await client.createSession({
  agentId: "assistant",
  ownerUserId: authenticatedUser.id,
  vaultIds: [vault.id],
});
```

A session can attach only vaults with the same `ownerUserId`. End-user vaults
cannot access the Tenant model credential. Creates and rotations require an
idempotency key; secret values are omitted from reads.

Tenant deletion removes its vault data. Removing a Project link does not.

## Next step [#next-step]

Use vault credentials with [MCP](/docs/build/mcp), or inspect the selected
provider in [Studio](/docs/studio).


# Quickstart (/docs)



<CopyPrompt />

You need Node 24+, Docker with Compose v2, and a model-provider key. No Nylorun
account is required. On Windows, use WSL2 with Docker Desktop's WSL integration.
Docker Desktop, OrbStack, and Colima all work.

```sh
node --version
docker compose version
```

<TerminalCommand command="npx nylorun up" />

<Steps>
  <Step>
    ### Start the Runtime [#start-the-runtime]

    Run the command above from any directory. It starts the local stack
    (Runtime, Studio, and their databases) and prints the Runtime and Studio
    URLs. Check it at any time with `npx nylorun status`.
  </Step>

  <Step>
    ### Create a project [#create-a-project]

    In another terminal:

    ```sh
    npm create @nylorun/agent my-agent
    cd my-agent
    ```

    This is an ordinary TypeScript application with one agent, **Order
    assistant**. It does not start or own the Runtime.
  </Step>

  <Step>
    ### Give the project a Tenant [#give-the-project-a-tenant]

    Copy `.env.example` to `.env` and set a model:

    ```dotenv title=".env"
    MODEL_PROVIDER=openai
    MODEL=your-model-id
    MODEL_PROVIDER_API_KEY=your-key
    ```

    Then create an isolated Tenant and link the project to it:

    ```sh
    npx @nylorun/cli tenant create
    ```
  </Step>

  <Step>
    ### Connect the agent [#connect-the-agent]

    ```sh
    npm run dev
    ```

    `src/main.ts` calls `connectAgents`, which registers the agent with the
    Runtime and runs its tools in this process.
  </Step>

  <Step>
    ### Try it [#try-it]

    ```sh
    npx nylorun studio
    ```

    Select **Order assistant** and ask **Look up order demo-123**. The agent
    calls `lookup_order` and answers `shipped`.
  </Step>
</Steps>

<Callout title="Your setup is working" type="success">
  The Runtime is healthy, your project has its own Tenant, and Studio ran your
  agent. If a step failed, see [Troubleshooting](/docs/troubleshooting): the
  [stack does not start](/docs/troubleshooting#stack-does-not-start), [no
  Runtime connection found](/docs/troubleshooting#no-runtime-connection-found),
  or [Studio login fails](/docs/troubleshooting#studio-login-fails).
</Callout>

## How it works [#how-it-works]

Your app defines agents and runs their tools. The Runtime runs sessions,
calls the model, and stores history.

<ArchitectureFlow />

| Term         | Meaning                                                                            |
| ------------ | ---------------------------------------------------------------------------------- |
| **Runtime**  | The service that runs sessions. `npx nylorun up` starts it locally in Docker.      |
| **Tenant**   | An isolated space inside the Runtime: its own sessions, model settings, and vault. |
| **Project**  | Your application. `.nylorun/` links it to one Tenant.                              |
| **Executor** | The part of your app that runs your tools when the Runtime asks.                   |
| **Studio**   | The local dashboard for chatting with agents and inspecting sessions.              |

Every other term is in the [Glossary](/docs/reference/glossary).

## What keeps running [#what-keeps-running]

The Runtime is independent of any one project:

```sh
npx nylorun status     # inspect the stack
npx nylorun logs       # read service logs
npx nylorun down       # stop containers and keep all data
npx nylorun up         # start them again
```

Saving a file in `agents/` restarts only your project. `connectAgents`
re-registers the agent; the Runtime and existing sessions stay up. Stopping
`npm run dev` disconnects your tools but leaves the Tenant and its history.

## What next [#what-next]

| I want to…                      | Go to                                          |
| ------------------------------- | ---------------------------------------------- |
| Change the agent or add a tool  | [Build → Tools](/docs/build/tools)             |
| Call my agent from my backend   | [Run → Sessions](/docs/run/sessions)           |
| Stream progress to a UI         | [Run → Events](/docs/run/events)               |
| Put agents in front of my users | [Deploy → App server](/docs/deploy/app-server) |
| Chain agents into a workflow    | [Build → Workflows](/docs/build/workflows)     |
| Ship to a server                | [Deploy](/docs/deploy)                         |

<Cards>
  <Card title="Studio" description="Chat with the agent, read its history, and follow live events." href="/docs/studio" />

  <Card title="Your project" description="See the generated files, scripts, and where state lives." href="/docs/project" />
</Cards>


# Your project (/docs/project)



`npm create @nylorun/agent` writes an ordinary TypeScript application.
Agents live under `agents/`; `src/main.ts` connects them to the Runtime.

```text
my-agent/
  agents/
    assistant/agent.ts       # the Order assistant and its lookup_order tool
    index.ts                 # exports every agent and workflow
  src/main.ts                # connectAgents({ agents })
  package.json
  tsconfig.json
  .env.example
  .nylorun/
    .gitignore               # contains *
    link.json                # Runtime URL, Host id, Tenant id
    credentials.json         # application key; mode 0600
```

```ts title="src/main.ts"
import { connectAgents } from "@nylorun/agents";
import { agents } from "../agents/index.js";

await connectAgents({ agents }).ready;
```

This process is your **executor**. `connectAgents` registers the agents with
the Tenant, then waits for the Runtime to ask it to run a tool. Your tools
always run here, in your code, with your dependencies. It does not serve HTTP;
to put agents in front of people, add an
[app server](/docs/deploy/app-server).

## Scripts [#scripts]

| Script  | Command                                           | Role                                           |
| ------- | ------------------------------------------------- | ---------------------------------------------- |
| `dev`   | `tsx watch --env-file-if-exists=.env src/main.ts` | Watch and reconnect the application.           |
| `build` | copy assets and run `tsc`                         | Produce `dist/agents/` and `dist/src/main.js`. |
| `start` | `node dist/src/main.js`                           | Connect the compiled application.              |
| `check` | `tsc --noEmit`                                    | Typecheck without emitting files.              |

Production dependencies are `@nylorun/agents` and `zod`. Development
dependencies are `tsx`, `typescript`, and `@types/node`. The project does not
install the CLI, Studio, or Runtime; run them with `npx`.

## Where state lives [#where-state-lives]

| Location                                   | Contents                                                          | Commit? |
| ------------------------------------------ | ----------------------------------------------------------------- | ------- |
| Project `.nylorun/`                        | Tenant link and application key.                                  | No      |
| Host root (`NYLORUN_HOME` or `~/.nylorun`) | `host.json`, `host-credentials.json`, `stack/`, and Tenant files. | No      |
| Docker volumes                             | Postgres schemas, S2 streams, Restate state, and workspaces.      | No      |
| `.env`                                     | One-time model seed and application configuration.                | No      |

Sessions and history live in the Runtime's Docker volumes, not in your
project. Deleting `.nylorun/` unlinks the project without deleting its Tenant;
run `nylo tenant create` or `nylo tenant use` to link again.

Outside your machine, replace the link with three environment variables:
`NYLORUN_RUNTIME_URL`, `NYLORUN_SERVER_KEY`, and `NYLORUN_TENANT`. See
[App server](/docs/deploy/app-server).

## Next step [#next-step]

<Cards>
  <Card title="Build" description="Change the agent and add tools." href="/docs/build" />

  <Card title="Call it from code" description="Create a session and send input from your backend." href="/docs/run/sessions" />

  <Card title="Serve your users" description="Put agents behind your own sign-in." href="/docs/deploy/app-server" />
</Cards>


# Studio (/docs/studio)



Studio starts with the stack at `http://localhost:4161`. Open the login URL
that `nylorun up` printed, or mint a fresh one:

```sh
npx nylorun studio
```

## Your first session [#your-first-session]

With `npm run dev` running in your project:

1. Pick your project's Tenant.
2. Select **Order assistant** and start a session.
3. Ask **Look up order demo-123**.

The agent calls the `lookup_order` tool in your project process, which returns
`shipped`, and the Runtime records the whole turn. Open the session's events to
see the model step, the tool action, and its result.

If the agent does not appear, check that `npm run dev` is running and connected
to the same Tenant (`npx @nylorun/cli tenant current`).

## What you can do [#what-you-can-do]

Studio lists each Tenant's agents, workflows, and sessions. From there you can
send input, restore history, follow live events, cancel a turn, and change the
Tenant's model settings.

Tenant pages live under `/tenants/<id>/…`; `/` shows a Tenant picker. Studio
can inspect every Tenant on the machine's Runtime.

## Logging in [#logging-in]

The login token is single-use and valid for two minutes. It sets an
`HttpOnly`, `SameSite=Strict` session cookie. Restarting the Studio container
ends Studio sessions; run `npx nylorun studio` again to log in.

## Security boundary [#security-boundary]

Studio is a local development tool for operators, not a production dashboard.

* Studio derives a separate Studio key for each Tenant from the Runtime's admin
  key. No Runtime or provider credential reaches the browser.
* Its proxy accepts only `localhost` or `127.0.0.1`, checks `Origin`, and sends
  no CORS headers. Reach it on another machine through an SSH tunnel.

Tenants created before Studio keys were introduced must be recreated. The
hosted dashboard, pairing fragments, `--local-ui`, and `nylorun-studio` no
longer exist.

## Next step [#next-step]

<Cards>
  <Card title="Your project" description="See the generated files, scripts, and where state lives." href="/docs/project" />

  <Card title="Build" description="Change the agent and add tools." href="/docs/build" />
</Cards>
