# Runtime and evaluation

Choose AML runtime defaults, budgets, cancellation, and result handling.
Canonical: https://agent-markup-language.com/docs/reference/runtime/
Documentation index: https://agent-markup-language.com/docs/
Complete documentation: https://agent-markup-language.com/docs/llms.txt

`AmlRuntime` owns one complete AML evaluation. It resolves the JSX tree, creates lexical context, schedules provider calls, propagates cancellation, enforces limits, emits lifecycle events, and releases resources. A runtime instance can be reused; evaluation state belongs to each call.

```tsx
import { Agent, AmlRuntime } from "@aml-jsx/sdk"
import { DeterministicAgentProvider } from "@aml-jsx/sdk/testing"

const agentProvider = new DeterministicAgentProvider({
  respond: request => ({ text: `Summary: ${request.prompt}` }),
})

const runtime = new AmlRuntime({
  agentProvider,
  maxConcurrentAgents: 4,
  maxAgentCalls: 32,
})

const answer = await runtime.evaluate(<Agent>Summarize the repository.</Agent>)
```

## Runtime options

Choose the provider defaults and safety budgets at construction time. The input options are captured; mutating the original object later does not change the runtime. Every numeric limit is a non-negative safe integer. A limit of `0` disables that limit.

| Option                |         Default | Decide this when…                                                                                                               |
| --------------------- | --------------: | ------------------------------------------------------------------------------------------------------------------------------- |
| `agentProvider`       |               — | `<Agent />` nodes should use one default Agent provider.                                                                        |
| `sandboxProvider`     |               — | Outer `<Sandbox />` nodes should use one default execution provider.                                                            |
| `workspaceProvider`   |               — | The evaluation may use one top-level durable `<Workspace />`.                                                                   |
| `cwd`                 | `process.cwd()` | Local `<Include src>`, `<File src>`, and `<Skill src>` reads plus host `<Script />` execution need a controlled base directory. |
| `system`              |               — | Every `<Agent />` should receive a shared system instruction.                                                                   |
| `toolPrefix`          |         `"aml"` | JavaScript Tools bridged through MCP need a stable model-facing prefix; an empty string also selects `"aml"`.                   |
| `allowedTools`        |    unrestricted | The application must allow only named Tool capabilities.                                                                        |
| `allowedMcpServers`   |    unrestricted | The application must allow only named MCP servers.                                                                              |
| `trace`               |               — | The application needs immutable, metadata-only-by-default trace events.                                                         |
| `onTraceError`        |               — | Trace consumer failures need a dedicated policy.                                                                                |
| `maxAgentCalls`       |            `32` | Total Agent sessions must be bounded.                                                                                           |
| `maxConcurrentAgents` |             `4` | Parallel Agent work must be bounded.                                                                                            |
| `maxTurnsPerAgent`    |            `16` | Follow-ups and authored `<Agent />` inputs must be bounded.                                                                     |
| `maxDepth`            |            `16` | Recursive semantic JSX needs a depth budget.                                                                                    |

`toolPrefix` becomes the invocation-owned MCP server name used to qualify JavaScript Tools. For example, `toolPrefix: "review"` exposes `review_get_pull_request` in OpenCode and `review-get_pull_request` in Copilot. AML throws when the prefix conflicts with another MCP server granted to the same Agent rather than silently changing the configured name.

## Evaluation options and cancellation

`runtime.evaluate(value, options?)` returns a promise. Its only current per-call option is a caller-owned `AbortSignal`:

```tsx
const controller = new AbortController()

const result = await runtime.evaluate(<Agent>Review the changed files.</Agent>, {
  signal: controller.signal,
})
```

AML checks cancellation before work and across asynchronous provider and cleanup boundaries. Evaluation settles only after owned Sandbox and Workspace resources have completed cleanup. If cancellation and cleanup both fail, AML preserves both causes in an `AggregateError`.

`ProcessSignalCancellation` is the optional Node process bridge for application-owned entry points. Construction listens for `SIGINT` and `SIGTERM`; the first signal aborts its `signal`, a second signal forces exit, and `cleanupDeadlineMs` bounds graceful cleanup before forced exit. The deadline defaults to 10 seconds and must be a non-negative safe integer. Read `exitCode` after evaluation settles to preserve `130` or `143`, then call `dispose()` to remove both listeners and cancel the deadline timer. Importing `@aml-jsx/sdk` alone never installs signal listeners.

Use one instance for the process shutdown boundary, not one per evaluation. Combine `cancellation.signal` with request-specific cancellation through `AbortSignal.any()` when a service has multiple active requests. [`Runtime lifecycle`](https://agent-markup-language.com/docs/runtime/#process-signals) has the complete entry-point pattern.

Evaluation is post-order: child components resolve before their containing `<Agent />` plan is sent to a provider. Follow-ups share the Agent session history and count toward `maxTurnsPerAgent`.

## Component-local evaluation

The `evaluate` export is available only inside an active function component. It is the explicit **evaluate to collect** operation: the component pauses, AML resolves the supplied nested value, and TypeScript receives that result before the component continues and returns its next AML value. By contrast, returning JSX without evaluating it is **return to compose**: the function body ends and the returned subtree resolves naturally as the component's output. [The component mental model](https://agent-markup-language.com/docs/concepts/#component-bodies-descend-results-ascend) explains the downward component-execution and upward result-flow directions.

Component-local `evaluate()` inherits the current lexical context, Sandbox, Workspace, trace parent, limits, and cancellation. It does not create a new runtime or provider session, and detached work is rejected after its AML evaluation domain closes.

| Call                              | Use it when…                                                                                                   |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `runtime.evaluate(tree, options)` | The application is starting a root run and owns provider defaults, cancellation, events, and the final result. |
| `evaluate(value, schema?)`        | An active AML function component needs to resolve a nested value inside the current lexical evaluation domain. |

Do not use the component helper as a general top-level shortcut. Do not create another `AmlRuntime` inside a component just to evaluate a child; that would discard the active scopes, limits, trace ancestry, and cancellation relationship.

Use a Standard Schema only when the component has one structured `<Agent />` result to validate:

```tsx
const report = await evaluate(<Agent>Return the report.</Agent>, ReportSchema)
```

The schema remains application-owned. AML derives the provider-facing JSON Schema from it and retains the authoritative validator. On the shared ACP path, AML validates every `aml_submit_result` candidate immediately so the Agent can correct invalid output in the same turn. The first valid candidate wins; later candidates are ignored. AML still enforces the schema at the final application boundary before returning the value.

Use `<Agent schema={ReportSchema}>` instead when a nested Agent should validate its own boundary and contribute canonical JSON text to the surrounding AML tree. An Agent `schema` prop and the second `evaluate()` argument are alternative schema owners and cannot be combined on the same Agent.

## Parallel branches

[`<Parallel>`](https://agent-markup-language.com/docs/reference/primitives/parallel/) uses component-local `evaluate()` to start independent AML branches
inside the current evaluation domain. Branches inherit the active Context, Sandbox, Workspace, trace ancestry, limits,
and cancellation. Their output stays isolated until every branch settles, then renders in authored order.

`maxConcurrentAgents` remains the only Agent-session concurrency limit. `<Parallel>` adds no second scheduler. Use
manual `Promise.all(evaluate(...))` when component code needs individual or schema-inferred results instead of direct
text composition.

## Errors and lifecycle events

`EvaluationError` identifies invalid authored AML values and evaluator invariants. Provider failures, schema failures, capability validation, cancellation, and cleanup failures retain their original causes where possible. Catch `EvaluationError` when you specifically need to distinguish an AML authoring/runtime failure from an upstream failure. `ParallelError` reports one or more failed [`<Parallel>`](https://agent-markup-language.com/docs/reference/primitives/parallel/#failure-semantics) branches through ordered `{ branchIndex, cause }` entries.

`start` and `finish` listeners are awaited. Trace sinks are dispatched according to their own policy and do not delay workflow completion:

```ts
runtime.on("start", event => {})
runtime.once("finish", event => {})
const unsubscribe = runtime.on("trace", sink)
unsubscribe()
```

The finish event reports `status: "ok" | "error"`, `runId`, `signal`, and an optional `error`.

Agent providers using AML's retained-session lifecycle produce actual `agent.session`, ordered `agent.turn`, and `agent.cleanup` spans. Built-in ACP profiles additionally emit prompt, update, cancellation, stop, usage, and opaque process lifecycle events. This is ACP-boundary telemetry: one Agent turn may contain several provider-internal model requests. [`captureContent`](https://agent-markup-language.com/docs/observability/#content-capture-and-redaction) is a per-sink AML policy and is not part of ACP.

## Choosing budgets

- Keep the defaults for bounded, interactive workflows.
- Raise `maxAgentCalls` and `maxTurnsPerAgent` only when the workflow intentionally uses repeated Agent work.
- Set `maxConcurrentAgents` to the capacity of the provider, Sandbox, and downstream APIs—not just the host CPU.
- Treat `allowedTools` and `allowedMcpServers` as application policy. Provider credentials and network policy still belong to the deployment boundary.

For end-to-end lifecycle patterns, see [Runtime lifecycle](https://agent-markup-language.com/docs/runtime/). For provider-specific defaults and credentials, see the [provider catalog](https://agent-markup-language.com/docs/providers/).

Sources: [`AmlRuntime`](https://github.com/we-are-singular/aml/blob/main/sdk/src/core/aml-runtime.ts), [`ProcessSignalCancellation`](https://github.com/we-are-singular/aml/blob/main/sdk/src/core/process-signal-cancellation.ts), [`evaluate`](https://github.com/we-are-singular/aml/blob/main/sdk/src/core/evaluate.ts), [`EvaluationError`](https://github.com/we-are-singular/aml/blob/main/sdk/src/core/evaluation-error.ts), and [`AmlEventSubscriber`](https://github.com/we-are-singular/aml/blob/main/sdk/src/core/aml-event-subscriber.ts).
