# Getting started

Install AML and resolve a credential-free workflow before connecting a real Agent provider.
Canonical: https://agent-markup-language.com/docs/getting-started/
Documentation index: https://agent-markup-language.com/docs/
Complete documentation: https://agent-markup-language.com/docs/llms.txt

A deterministic Agent provider runs AML without model credentials, an agent executable, Docker, or a remote account. Agent providers control how sessions run; the same components can use a [live provider](https://agent-markup-language.com/docs/providers/agents/) once its executable and credentials are configured.

## Prerequisites

- Node.js **26 or newer**
- an ESM project (`"type": "module"` in `package.json`)
- TypeScript if you author `.tsx`

**Caution — AML is ESM-only**

CommonJS `require()` is not a supported package entrypoint. Use ESM imports and a TSX-capable runner or compile the
project before running it.

If a coding agent will author or review the workflow, optionally install the [AML authoring skill](https://agent-markup-language.com/docs/agent-skill/). The skill teaches the agent the current public API and safety boundaries; it does not replace the SDK installation below.

## 1. Create a project

**npm**

```sh title="Terminal"
mkdir aml-first-workflow
cd aml-first-workflow
npm init -y
npm pkg set type=module
npm install @aml-jsx/sdk
npm install --save-dev typescript vite-node
```

**pnpm**

```sh title="Terminal"
mkdir aml-first-workflow
cd aml-first-workflow
pnpm init
pnpm add @aml-jsx/sdk
pnpm add --save-dev typescript vite-node
```

Add `"type": "module"` to `package.json`.

**Note — pnpm may require build-script approval**

pnpm 11 can stop the install with `ERR_PNPM_IGNORED_BUILDS` when it blocks transitive native build scripts. Run `pnpm
approve-builds`, approve the dependencies named by pnpm, then run `pnpm install` again. This is pnpm's installation
policy; AML does not execute those scripts at runtime.

Create `tsconfig.json`:

```json title="tsconfig.json"
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2024",
    "strict": true,
    "skipLibCheck": true,
    "jsx": "react-jsx",
    "jsxImportSource": "@aml-jsx/sdk"
  }
}
```

The automatic JSX runtime turns AML elements into AML nodes. React is not installed or involved. `skipLibCheck` skips
checking upstream declaration files loaded by the bundled provider adapters while preserving strict checks for your
project source.

Add one repeatable command to the generated `package.json`:

```json title="package.json"
{
  "type": "module",
  "scripts": {
    "aml": "vite-node src/first-workflow.tsx"
  }
}
```

Keep the dependencies added by your package manager when you merge this snippet into the generated file.

## 2. Evaluate your first tree

Create `src/first-workflow.tsx`:

```tsx title="src/first-workflow.tsx"
import { Agent, AmlRuntime } from "@aml-jsx/sdk"
import { DeterministicAgentProvider } from "@aml-jsx/sdk/testing"

const agentProvider = new DeterministicAgentProvider({
  name: "first-workflow",
  respond: request => ({ text: `Resolved: ${request.prompt}` }),
})

const runtime = new AmlRuntime({ agentProvider })

const result = await runtime.evaluate(<Agent system="Answer in one sentence.">Explain what AML evaluates.</Agent>)

console.log(result)
```

Run it with the script you just added:

```sh title="Terminal"
npm run aml
# or: pnpm aml
```

Expected output:

```text title="Output"
Resolved: Explain what AML evaluates.
```

1. JSX creates an AML node tree; it does not run `<Agent />` during rendering.
2. `AmlRuntime.evaluate()` validates and resolves the tree.
3. [`<Agent />`](https://agent-markup-language.com/docs/reference/primitives/agent/) builds a normalized request from its system text, prompt, capabilities, and effective scopes.
4. The selected provider returns an `AgentResponse`; the runtime returns its text from the root.
5. The runtime releases acquired resources in reverse order.

The [`DeterministicAgentProvider`](https://agent-markup-language.com/docs/reference/testing/#deterministicagentprovider) deliberately echoes a known result and records calls. It is part of the public testing entrypoint, making it useful for tutorials, examples, and workflow tests—not just AML's own test suite.

## 3. Compose an agent team

An AML component can combine several agents into a reusable unit. Here two specialists receive the same change description in parallel. Their results become input to a release editor, which starts after both specialists finish.

`AML.Component<{ change: string }>` declares the component's input and lets it return any AML value. Import `AML` as a type. When a component accepts nested content, declare it with [`AML.PropsWithChildren` or `AML.PropsWithRequiredChildren`](https://agent-markup-language.com/docs/ast/#optional-and-required-children).

Replace `src/first-workflow.tsx` with this credential-free example:

```tsx title="src/first-workflow.tsx"
import { type AML, Agent, AmlRuntime, Block, Parallel } from "@aml-jsx/sdk"
import { DeterministicAgentProvider } from "@aml-jsx/sdk/testing"

const ReleaseBrief: AML.Component<{ change: string }> = ({ change }) => {
  return (
    <Agent name="release-editor" system="Write a release brief from the supplied findings.">
      <Parallel>
        <Block tag="user-impact">
          <Agent name="impact-specialist" system="Explain the user impact.">
            {change}
          </Agent>
        </Block>
        <Block tag="rollout-checks">
          <Agent name="rollout-specialist" system="List checks before rollout.">
            {change}
          </Agent>
        </Block>
      </Parallel>
    </Agent>
  )
}

const provider = new DeterministicAgentProvider({
  respond(request) {
    if (request.system === "Explain the user impact.") {
      return { text: "Users can export their invoices as CSV." }
    }
    if (request.system === "List checks before rollout.") {
      return { text: "Check account permissions and CSV escaping." }
    }
    return { text: request.prompt }
  },
})

const runtime = new AmlRuntime({ agentProvider: provider })
console.log(await runtime.evaluate(<ReleaseBrief change="Add CSV export to the invoice list." />))
console.log(`Agent sessions: ${provider.calls.length}`)
```

Run `npm run aml`. The editor fixture echoes the resolved specialist sections, followed by `Agent sessions: 3`. The output contains:

```text
<user-impact>
Users can export their invoices as CSV.
</user-impact>

<rollout-checks>
Check account permissions and CSV escaping.
</rollout-checks>
Agent sessions: 3
```

Each `<Block>` adds blank lines around its section, so adjacent Blocks produce four newline characters between tags. JSX children otherwise concatenate without automatic separators. The fixture supplies known answers; it demonstrates composition, not live reasoning.

`<Parallel>` starts independent branches and preserves their authored output order. Each `<Agent>` is a separate session. Its result flows upward; its tools and system instructions do not transfer to its parent. The runtime default supplies a provider to every Agent that omits a `provider` prop.

To run this team live, keep `ReleaseBrief`, replace the fixture provider using the next section, and remove the `provider.calls` diagnostic. The same pattern is available from a repository checkout as `npm run example -- agent-team`.

## 4. Connect a real Agent

The authored node remains the same. Replace the contents of `src/first-workflow.tsx` with one complete tab below, then run the same `aml` script.

**Note — There is no universal default provider**

Start with the coding harness and model account you already operate. The Codex tab is the shortest path when you have
an OpenAI API key; choose OpenCode for its model catalog and native tool policy, or Pi for its extensible Tool and MCP
path. The provider comparison explains the trade-offs in detail.

**Codex**

```tsx title="src/first-workflow.tsx"
import { Agent, AmlRuntime, codexAgent } from "@aml-jsx/sdk"

const apiKey = process.env.OPENAI_API_KEY
if (!apiKey) throw new Error("OPENAI_API_KEY is required")

const runtime = new AmlRuntime({
  agentProvider: codexAgent({
    apiKey,
    model: "gpt-5.6-luna",
    reasoningEffort: "low",
  }),
})
const result = await runtime.evaluate(<Agent>Describe this project in three bullets.</Agent>)

console.log(result)
```

Install and verify the ACP adapter version exercised by this repository, then supply a credential through your shell or secret manager:

```sh
npm install --global @agentclientprotocol/codex-acp@1.4.0 @openai/codex@0.147.0
codex-acp --help
codex --version
export OPENAI_API_KEY="your-development-key"
npm run aml
```

Do not commit the key or place it in a Workspace. See the [Codex guide](https://agent-markup-language.com/docs/providers/agents/codex/) for executable overrides, `CODEX_API_KEY`, model precedence, permissions, and Sandbox compatibility.

**OpenCode**

```tsx title="src/first-workflow.tsx"
import { Agent, AmlRuntime, opencodeAgent } from "@aml-jsx/sdk"

const apiKey = process.env.OPENCODE_API_KEY
if (!apiKey) throw new Error("OPENCODE_API_KEY is required")

const runtime = new AmlRuntime({
  agentProvider: opencodeAgent({
    env: { OPENCODE_API_KEY: apiKey },
    model: "opencode-go/deepseek-v4-flash",
  }),
})
const result = await runtime.evaluate(<Agent>Describe this project in three bullets.</Agent>)

console.log(result)
```

Install and verify the OpenCode executable, then configure a model provider:

```sh
npm install --global opencode-ai@1.18.30
opencode --version
export OPENCODE_API_KEY="your-development-key"
npm run aml
```

The OpenCode configuration passes the model credential through `env`. An interactive `opencode auth login` alone is insufficient for AML's default isolated session: AML gives each invocation a private `XDG_DATA_HOME`. To use file-based login, stage the required credentials in a dedicated data directory and pass its path through [`env.XDG_DATA_HOME`](https://agent-markup-language.com/docs/providers/agents/opencode/#install-and-verify). Select a model available to that account.

**Pi**

```tsx title="src/first-workflow.tsx"
import { Agent, AmlRuntime, piAgent } from "@aml-jsx/sdk"

const apiKey = process.env.OPENCODE_API_KEY
if (!apiKey) throw new Error("OPENCODE_API_KEY is required")

const runtime = new AmlRuntime({
  agentProvider: piAgent({
    command: "pi-acp",
    env: { OPENCODE_API_KEY: apiKey },
    model: "opencode-go/deepseek-v4-flash",
    piCommand: "pi",
  }),
})
const result = await runtime.evaluate(<Agent>Describe this project in three bullets.</Agent>)

console.log(result)
```

Install and verify the ACP bridge and Pi CLI:

```sh
npm install --global pi-acp@0.0.33 @earendil-works/pi-coding-agent@0.84.2
pi --version
command -v pi-acp
export OPENCODE_API_KEY="your-development-key"
npm run aml
```

This follows the repository smoke path with DeepSeek V4 Flash through OpenCode Go. If you select another Pi model provider, change the model identifier and forward that provider's credential instead. The [Pi guide](https://agent-markup-language.com/docs/providers/agents/pi/) explains when AML generates a wrapper and how to install `pi-mcp-adapter` for Tools, authored MCP servers, or structured output.

**Tip — Provider selection is lexical too**

Set a default on `AmlRuntime`, or pass `provider` directly to an `<Agent />` when one subtree needs a different model
harness. A direct prop wins over the runtime default. Compare the built-in profiles in [Choose an
Agent](/docs/providers/agents/).

## 5. Add files and command execution when needed

[`<File />`](https://agent-markup-language.com/docs/reference/primitives/file/) writes through the nearest active [`<Workspace />`](https://agent-markup-language.com/docs/reference/primitives/workspace/) or [`<Sandbox />`](https://agent-markup-language.com/docs/reference/primitives/sandbox/) filesystem. [`<Script />`](https://agent-markup-language.com/docs/reference/primitives/script/) can run directly on the trusted host, but this example deliberately uses a fresh Workspace directory and scopes command execution to that materialization.

```tsx title="src/resources.tsx"
import { mkdtemp } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { AmlRuntime, File, Sandbox, Script, Workspace, localSandbox, localWorkspace } from "@aml-jsx/sdk"

const directory = await mkdtemp(join(tmpdir(), "aml-guide-"))
const runtime = new AmlRuntime({
  sandboxProvider: localSandbox(),
  workspaceProvider: localWorkspace({ directory }),
})

const output = await runtime.evaluate(
  <Workspace id="guide" load={false} save={false}>
    <File path="brief.txt">Inspect this isolated guide directory.</File>
    <Sandbox access="read-write">
      <Script command="node" args={["-e", "console.log('workspace ready')"]} />
    </Sandbox>
  </Workspace>
)

console.log(output)
```

Run `npx vite-node src/resources.tsx`. Expected output is `workspace ready`. This example creates a temporary directory; remove `directory` with `rm(directory, { recursive: true, force: true })` from `node:fs/promises` after inspecting its contents. A real local Workspace writes directly to its directory even with `save={false}`; that flag controls revision publication, not rollback.

**Danger — Local Sandbox is trusted host execution**

[`localSandbox()`](https://agent-markup-language.com/docs/providers/sandboxes/local/) starts ordinary host processes with your application's
privileges. AML validates logical Workspace paths, but it does not isolate the process, environment, network, or host
filesystem. Use only trusted commands and read the [Sandbox security guidance](https://agent-markup-language.com/docs/production/security/).

## Optional: run a workflow with the CLI

The application above constructs `AmlRuntime` because it owns the process. For a standalone workflow file, install the experimental CLI beside the SDK:

```sh title="Terminal"
npm install --save-dev @aml-jsx/cli
```

Create `src/cli-workflow.tsx`. Put the provider directly on [`<Agent />`](https://agent-markup-language.com/docs/reference/primitives/agent/), export the tree, and let the CLI create and instrument the runtime:

```tsx title="src/cli-workflow.tsx"
import { Agent } from "@aml-jsx/sdk"
import { DeterministicAgentProvider } from "@aml-jsx/sdk/testing"

const provider = new DeterministicAgentProvider({
  name: "cli-workflow",
  respond: request => ({ text: `Resolved: ${request.prompt}` }),
})

export default <Agent provider={provider}>Explain what AML evaluates.</Agent>
```

Run the exported workflow:

```sh title="Terminal"
npx aml run ./src/cli-workflow.tsx
```

Expected output on standard output:

```text title="Output"
Resolved: Explain what AML evaluates.
```

**Caution — Do not create a second runtime inside a CLI workflow**

Export the AML tree itself. Calling `new AmlRuntime().evaluate(...)` inside the exported function creates a separate
nested evaluation, so the CLI cannot trace, cancel, or configure the Agent work it was intended to own. The complete
export, environment, and output contract is in the [CLI guide](https://agent-markup-language.com/docs/cli/).

## Where to go next

**Understand evaluation**

Learn post-order resolution, lexical scopes, concurrency, and cleanup in [Core concepts](https://agent-markup-language.com/docs/concepts/).

**Build from a recipe**

Copy a complete workflow from the [Cookbook](https://agent-markup-language.com/docs/cookbook/), with expected output and provider requirements.

**Choose infrastructure**

Compare the complete [provider catalog](https://agent-markup-language.com/docs/providers/) across Agent, Sandbox, and Workspace responsibilities.

**Prepare production**

Review isolation, secrets, persistence, cancellation, and telemetry in [Production](https://agent-markup-language.com/docs/production/).

**Run workflow files**

Use the [experimental CLI](https://agent-markup-language.com/docs/cli/) when an exported AML tree should run without application entry-point glue.

**Work with a coding agent**

Install and verify the [AML authoring skill](https://agent-markup-language.com/docs/agent-skill/) before asking an agent to generate or review AML
code.
