Skip to content

Runtime and evaluation

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.

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

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.

OptionDefaultDecide this when…
agentProvider<Agent /> nodes should use one default Agent provider.
sandboxProviderOuter <Sandbox /> nodes should use one default execution provider.
workspaceProviderThe evaluation may use one top-level durable <Workspace />.
cwdprocess.cwd()Local <Include src>, <File src>, and <Skill src> reads plus host <Script /> execution need a controlled base directory.
systemEvery <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".
allowedToolsunrestrictedThe application must allow only named Tool capabilities.
allowedMcpServersunrestrictedThe application must allow only named MCP servers.
traceThe application needs immutable, metadata-only-by-default trace events.
onTraceErrorTrace consumer failures need a dedicated policy.
maxAgentCalls32Total Agent sessions must be bounded.
maxConcurrentAgents4Parallel Agent work must be bounded.
maxTurnsPerAgent16Follow-ups and authored <Agent /> inputs must be bounded.
maxDepth16Recursive 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.

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

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

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

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

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

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

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 is a per-sink AML policy and is not part of ACP.

  • 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. For provider-specific defaults and credentials, see the provider catalog.

Sources: AmlRuntime, ProcessSignalCancellation, evaluate, EvaluationError, and AmlEventSubscriber.