# The AML mental model

Learn how AML turns asynchronous JSX into provider-independent agent workflows.
Canonical: https://agent-markup-language.com/docs/concepts/
Documentation index: https://agent-markup-language.com/docs/
Complete documentation: https://agent-markup-language.com/docs/llms.txt

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`](https://agent-markup-language.com/docs/reference/runtime/) is the interpreter that resolves the resulting tree.

## The three boundaries

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

| Boundary                                                 | Owns                                                                   | Provider guides                                                         |
| -------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [`<Agent />`](https://agent-markup-language.com/docs/reference/primitives/agent/)         | A provider session, prompts, turns, capabilities, and the final result | [Codex, GitHub Copilot, GLM, OpenCode, and Pi](https://agent-markup-language.com/docs/providers/agents/) |
| [`<Sandbox />`](https://agent-markup-language.com/docs/reference/primitives/sandbox/)     | Processes, working directory, filesystem permissions, and cleanup      | [Local, Docker, Daytona, and Modal](https://agent-markup-language.com/docs/providers/sandboxes/)         |
| [`<Workspace />`](https://agent-markup-language.com/docs/reference/primitives/workspace/) | Durable files, revisions, locks, materialization, and publication      | [Local directory, filesystem, and S3](https://agent-markup-language.com/docs/providers/workspaces/)      |

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

### Choose the smallest boundary

| The work needs to…                                     | Use                                                      | Decision rule                                                                  |
| ------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------ |
| ask a model or coding harness to reason, plan, or edit | [`<Agent />`](https://agent-markup-language.com/docs/reference/primitives/agent/)         | The result depends on a provider session rather than deterministic code.       |
| run one trusted local command                          | [`<Script />`](https://agent-markup-language.com/docs/reference/primitives/script/)       | The application owns the command and accepts host execution.                   |
| run work in a selected or isolated environment         | [`<Sandbox />`](https://agent-markup-language.com/docs/reference/primitives/sandbox/)     | The operation needs a container, remote runtime, or enforcement boundary.      |
| preserve or publish files across evaluations           | [`<Workspace />`](https://agent-markup-language.com/docs/reference/primitives/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.

### Keep ordinary control flow in TypeScript

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.

```tsx
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:** Workspace durable files → Sandbox process authority → Agent Tools, MCP, and prompt. Descendants inherit only capabilities declared around them.
- **Results flow up:** specialist results resolve before the coordinator that consumes them; the root result resolves last.

## Component bodies descend; results ascend

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(...)`:

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

| Pattern                                                        | Meaning                                                                                                                                  | Use 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. |

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

## What runs where

| Authored element                    | Execution location                                                                                                       | Authority and result                                                                                                                                                                        |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Function component and `evaluate()` | AML application process                                                                                                  | Builds 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 runtime | Owns model turns and returns an `AgentResponse`. Agent-native permissions cannot widen the Sandbox.                                                                                         |
| Callable JavaScript Tool            | AML application process                                                                                                  | An active component may call the value returned by [`defineTool()`](https://agent-markup-language.com/docs/reference/primitives/tool/); `<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 transport                                                            | A 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 staging                                                                    | Reads 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 `path`                                                          | Inspects 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 runtime                                                                                  | Defaults 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 materialization                                      | Replaces one complete UTF-8 file from resolved children or an application-owned local source.                                                                                               |
| Workspace load/save                 | Workspace provider and its storage service                                                                               | Materializes before descendants, publishes according to save policy, and releases locks/temporary state after the subtree settles.                                                          |

### Provider selection and path rules

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

**Agent**

{"A model-facing session. Child "}

[&lt;Agent /&gt;](https://agent-markup-language.com/docs/reference/primitives/agent/)

      {" results become parent input; "}

[&lt;Tool /&gt;](https://agent-markup-language.com/docs/reference/primitives/tool/)

      {", "}

[&lt;Mcp /&gt;](https://agent-markup-language.com/docs/reference/primitives/mcp/)

      {", "}

[&lt;Skill /&gt;](https://agent-markup-language.com/docs/reference/primitives/skill/)

      {", and "}

[&lt;FollowUp /&gt;](https://agent-markup-language.com/docs/reference/primitives/follow-up/)

      {" are scoped to the owning "}

&lt;Agent /&gt;

      {"."}

**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](https://agent-markup-language.com/docs/sandbox-images/); 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.

## Authored order is dataflow

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.

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

### Sequential siblings

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.

### Explicit concurrency

AML does not infer that ordinary sibling `<Agent />` components are safe to run concurrently. Wrap independent
text-producing branches in [`<Parallel>`](https://agent-markup-language.com/docs/reference/primitives/parallel/):

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

## Lexical scope and cleanup

[`<Workspace />`](https://agent-markup-language.com/docs/reference/primitives/workspace/) and [`<Sandbox />`](https://agent-markup-language.com/docs/reference/primitives/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.

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

**Caution — Local is trusted execution**

[`localSandbox()`](https://agent-markup-language.com/docs/providers/sandboxes/local/) runs host processes. It provides the common Sandbox API but is
not a hostile-code isolation boundary. Use a provider with an appropriate isolation model for model-controlled or
untrusted work.

## Provider neutrality has limits

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](https://agent-markup-language.com/docs/compatibility/) and the relevant [provider guide](https://agent-markup-language.com/docs/providers/) before treating a combination as supported.

## Cancellation and failure

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

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

## Where to go next

- [AST and evaluation](https://agent-markup-language.com/docs/ast/) — See the distinction between authored JSX, immutable nodes, and runtime normalization.

- [Runtime lifecycle](https://agent-markup-language.com/docs/runtime/) — Configure domains, limits, cancellation, and lifecycle events.

- [Integrations](https://agent-markup-language.com/docs/integrations/) — Assemble complete Agent, Sandbox, and Workspace stacks.

- [Errors and recovery](https://agent-markup-language.com/docs/errors/) — Classify failures without retrying unsafe or invalid work.
