Skip to content

The AML mental model

AML is an execution model for agent workflows written as TypeScript and JSX. It is not a UI renderer, a prompt templating language, or a second markup parser. JSX is the authoring syntax; AmlRuntime is the interpreter that resolves the resulting tree.

Most AML workflows are easier to reason about when you separate three responsibilities:

BoundaryOwnsProvider guides
<Agent />A provider session, prompts, turns, capabilities, and the final resultCodex, GitHub Copilot, GLM, OpenCode, and Pi
<Sandbox />Processes, working directory, filesystem permissions, and cleanupLocal, Docker, Daytona, and Modal
<Workspace />Durable files, revisions, locks, materialization, and publicationLocal directory, filesystem, and S3

<Agent /> may run without an explicit <Sandbox /> according to its provider. An unsandboxed <Script /> uses trusted host execution. <File />, <Include path>, and provider features that require a resource must still have the appropriate active filesystem boundary.

The work needs to…UseDecision rule
ask a model or coding harness to reason, plan, or edit<Agent />The result depends on a provider session rather than deterministic code.
run one trusted local command<Script />The application owns the command and accepts host execution.
run work in a selected or isolated environment<Sandbox />The operation needs a container, remote runtime, or enforcement boundary.
preserve or publish files across evaluations<Workspace />The files need durable identity, locking, revisions, or an explicit save rule.

Compose boundaries only when the task needs them. A text-only Agent does not need an empty Workspace; a deterministic file transform does not become safer or clearer merely because a model performs it.

Use TypeScript for deterministic branching, loops, retries, data transformation, policy checks, and explicit concurrency. Use AML for the semantic tree that scopes <Agent />, capabilities, <Sandbox />, <Workspace />, and their lifecycle.

async function Review({ files }: { files: readonly string[] }) {
const selected = files.filter(file => file.endsWith(".ts"))
if (selected.length === 0) return "No TypeScript files to review."
const findings = await Promise.all(selected.map(file => evaluate(<Agent>Review {file}.</Agent>)))
return <Agent>Synthesize these application-selected findings: {findings}</Agent>
}

Here TypeScript owns selection, the empty case, and concurrency. AML owns each provider call and the final dataflow. This keeps business policy inspectable instead of hiding it inside prompts or inventing JSX primitives for ordinary programming.

Read an AML tree in two directions

Scope flows down

  1. <Workspace />durable files
  2. <Sandbox />process authority
  3. <Agent /><Tool /> · <Mcp /> · prompt
Descendants inherit only the capabilities declared around them.

Results flow up

Specialist ASpecialist BCoordinatorRoot result
Consumers run after the child values used to assemble their input.

AML function components have two distinct moments during one runtime evaluation. When the runtime reaches a component descriptor, it invokes the component function and runs its ordinary TypeScript body from top to bottom. After that function returns an AML value, the runtime resolves the returned subtree and its leaf results flow back upward into their consumers.

This component body does not run when the JSX descriptor is first authored. It runs when AML descends into Review during runtime.evaluate(...):

function Review() {
const policy = selectReviewPolicy()
return <Agent>{policy}</Agent>
}

selectReviewPolicy() finishes before Review returns. The returned <Agent /> session happens afterward as AML resolves the returned value, so that Agent cannot retroactively affect the already computed policy.

Use these two patterns to make the dependency direction explicit:

PatternMeaningUse it when…
Return to compose: return <Bar />The component body finishes and AML naturally resolves <Bar /> as this component’s output.The parent only needs the final rendered value to continue flowing upward.
Evaluate to collect: const bar = await evaluate(<Bar />)The component pauses, AML resolves <Bar /> inside the current evaluation domain, and TypeScript receives its result before continuing.Later TypeScript must inspect, validate, branch on, transform, or name the nested result before authoring the continuation.
async function Review() {
const specialist = await evaluate(<Agent>Find the highest-confidence defect.</Agent>)
const decision = classifyFinding(specialist)
if (decision === "ignore") return "No publishable finding."
return (
<Agent>
Synthesize the accepted evidence.
<Block>{specialist}</Block>
</Agent>
)
}

The await is essential: without it, the local variable is only a Promise and the component has not collected the nested result. Do not call Bar() directly to force this ordering; that invokes an implementation function instead of asking AML to resolve its returned tree. Component-local evaluate() preserves primitive handling and inherits the active Context, Workspace, Sandbox, limits, tracing, and cancellation without starting another root runtime. Resources owned by the nested value finish cleanup before the call resolves and the component continues.

Authored elementExecution locationAuthority and result
Function component and evaluate()AML application processBuilds or resolves AML values using the active lexical scope. It does not create isolation.
<Agent />The selected Agent provider and its ACP/native process; a compatible active Sandbox supplies its cwd and process runtimeOwns model turns and returns an AgentResponse. Agent-native permissions cannot widen the Sandbox.
Callable JavaScript ToolAML application processAn active component may call the value returned by defineTool(); <Tool use> separately grants it to a model. It is not code moved into the Sandbox.
<Mcp />The location chosen by the provider integration and transportA stdio command or remote HTTP URL remains privileged deployment configuration. Do not assume it shares the Tool or Sandbox process.
<Skill />AML application plus invocation-private Agent stagingReads one complete local package, stages it at an Agent-visible path, and registers native discovery or metadata-only fallback without inlining the body.
<Include />AML application for src; nearest active filesystem for pathInspects UTF-8 content and either inlines it or emits size and line metadata with an Agent-visible read instruction when maxBytes is exceeded.
<Script />AML host, or the active Sandbox runtimeDefaults to the runtime or effective Sandbox cwd; an optional cwd resolves from the host cwd or Sandbox root.
<File />Nearest active filesystem: Sandbox guest first, otherwise Workspace materializationReplaces one complete UTF-8 file from resolved children or an application-owned local source.
Workspace load/saveWorkspace provider and its storage serviceMaterializes before descendants, publishes according to save policy, and releases locks/temporary state after the subtree settles.
  • An <Agent provider={...} /> prop wins over AmlRuntime({ agentProvider }) for that <Agent /> only.
  • The outer <Sandbox provider={...} /> selects the lease. Nested <Sandbox /> components reuse it and may narrow access or root, but cannot switch providers or widen authority.
  • The top-level <Workspace provider={...} /> wins over the runtime default. AML supports one top-level <Workspace /> for an evaluation.
  • Workspace paths are logical. Each Sandbox provider must map the Workspace materialization into its host, container, or remote cwd; application code should not guess the provider’s physical path.
  • <File path> and <Include path> use portable paths relative to the nearest active filesystem root. An active Sandbox wins over an enclosing Workspace because its guest is the live execution state.
  • <File src>, <Include src>, and <Skill src> are trusted application reads resolved from AmlRuntimeOptions.cwd; lexical resource scopes do not rewrite those source paths.
  • An unsandboxed <Script /> starts from AmlRuntimeOptions.cwd, which defaults to process.cwd(). Its optional portable cwd resolves from that base; inside a Sandbox it resolves from the active Sandbox root. A cwd is convenience, not confinement.
  • read-only is a requested policy with provider-specific enforcement. It never grants writes, but the exact handling of process execution and mounted files differs by Sandbox.

These are the canonical execution-direction rules. Provider guides add vendor prerequisites and path mappings; they do not redefine the evaluation model.

Sandbox

An ephemeral execution lease. Nested scopes can narrow access, but cannot widen a read-only parent or select another provider. Docker, Daytona, and Modal create that lease from a Sandbox image; Local uses the trusted host.

Workspace

A durable file identity. It materializes before descendants run, publishes according to its save policy, and releases after cleanup.

AML resolves consumers after the values they consume. This is the post-order half of return to compose: a child <Agent /> must finish before the parent session can be assembled.

function Specialist() {
return <Agent>Find concrete correctness risks.</Agent>
}
function Review() {
return (
<Agent system="Synthesize only evidence supplied by the specialists.">
Specialist findings:
<Specialist />
Write the final review.
</Agent>
)
}

The parent receives the child’s final text at the child’s authored position. Ordinary text is prompt content only when it is inside <Agent /> or one of its prompt-bearing descriptors. A child <Agent /> is not an independent side effect that the parent may race; it is a dependency in the parent’s input.

JavaScript evaluates ordinary expressions in authored order. If two JSX values are written as separate synchronous siblings, AML preserves their order in the resulting text. This matters when the second sibling depends on files or external effects produced by the first.

AML does not infer that ordinary sibling <Agent /> components are safe to run concurrently. Wrap independent text-producing branches in <Parallel>:

function Review() {
return (
<Agent>
<Parallel>
<Agent>Review correctness.</Agent>
<Agent>Review maintainability.</Agent>
</Parallel>
</Agent>
)
}

<Parallel> keeps authored branch order even if the second provider call finishes first. It waits for every branch and its cleanup before the parent continues. maxConcurrentAgents limits active provider calls; it does not turn ordinary sequential JSX into concurrent work.

Use Promise.all([evaluate(...), evaluate(...)]) inside an active async component to evaluate to collect several named or typed branch values before authoring the next AML node. Both forms inherit the same evaluation domain, cancellation, resource scopes, budgets, tracing, and Agent scheduler.

<Workspace /> and <Sandbox /> are lexical boundaries. A descendant inherits the active scope while it evaluates, and the boundary releases after its subtree settles.

  1. Enter the outer scope. AML acquires the Workspace and materializes its selected revision, then acquires the Sandbox and establishes its effective access policy. 2. Resolve the subtree. Descendant components, capabilities, scripts, and Agent sessions run inside those resources. 3. Commit or discard. The Workspace applies its save policy (always or success); a provider may reconcile changed files or publish a revision. 4. Release in reverse order. Child Agent sessions and Sandbox leases release before their parent Workspace scope. Cleanup failures remain observable.
<Workspace id="review-42" provider={workspaceProvider}>
<Sandbox access="read-write" provider={sandboxProvider}>
<Agent provider={agentProvider}>Inspect and update the project.</Agent>
</Sandbox>
</Workspace>

Nested <Sandbox /> components reuse the outer lease. They may narrow read-write to read-only, or narrow a root path, but cannot widen permissions. <File /> and <Include path> use that live guest filesystem when nested inside the Sandbox; a read-only lease permits Include reads but rejects File writes.

AML keeps the workflow tree stable while provider factories own vendor behavior: executable discovery, credentials, model names, transport, capability translation, and environment setup. The same <Agent /> tree can use Codex, GitHub Copilot, GLM, OpenCode, or Pi, but their options and prerequisites are not interchangeable. Read the compatibility guide and the relevant provider guide before treating a combination as supported.

Pass an AbortSignal to the root evaluation when the caller owns a deadline or cancellation action:

const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 30_000)
try {
const result = await runtime.evaluate(<Workflow />, { signal: controller.signal })
console.log(result)
} finally {
clearTimeout(timeout)
}

AML propagates cancellation to active provider calls and stops advancing to later frames. It cannot undo external effects already performed by a Tool, Script, provider, or Agent. Treat persistence, network calls, and repository mutations as real effects even when a later frame fails.