Browse the documentation

AEL 1.0.0

Your first agent, end to end

This tutorial takes one agent written in AEL, the Agent Engineering Language, through every step of the command line, from the health check to a native program you run. The agent summarizes customer support requests for a support team, as in A first agent, and this tutorial adds what that page leaves out: where its system prompt can come from, how its model is bound, how its budget is set, and how you test, evaluate and build it.

A console agent needs no server, so nothing here installs or starts one.

Step 1: check the toolchain

The AEL 1.0.0 build for Linux, macOS or Windows comes with target management and a health check. Start with the health check:

ael doctor

ael doctor reports the toolchain, the installed targets and anything missing, each finding with the action that fixes it. It never prints a credential, a token or a secret value, even when it tests a model connection.

Step 2: create the project

One command creates a ready-to-build project:

ael init support

ael init writes the three root files, main.ael, pack.ael and metadata.ael, and never overwrites a file that is already there. Add the three files of the agent:

support/
  main.ael                       # the program's entry point
  pack.ael                       # packages; none needed here
  metadata.ael                   # the project's name
  prompts/summarize.md           # the system prompt
  src/
    nodes/summarize.node.ael     # the node: one business step
    agents/support.agent.ael     # the agent: the workflow

Agents, nodes, edges, configuration and hooks each live in their own named file, so the project's structure reads at a glance.

Step 3: write the typed input

The agent takes a typed request instead of loose text. A structure and a check of your own decide what a request may hold before any model is called. These declarations open the node's file, src/nodes/summarize.node.ael, and the check is one of its private helpers:

struct Request {
    priority: u8,
    words: u32,
}

enum RequestError {
    TooLong(u32),
    UnknownPriority(u8),
}

fn check_request(request: Request) -> Result<Request, RequestError> {
    if request.words > 600 {
        return Result::Err(RequestError::TooLong(request.words));
    }
    if request.priority > 3 {
        return Result::Err(RequestError::UnknownPriority(request.priority));
    }
    return Result::Ok(request);
}

The check returns a typed error that names what is wrong, and the agent's typed output goes through the checks of Typed inputs and outputs before it reaches your code.

Step 4: choose where the prompt comes from

The system prompt is the agent's business policy. Here it is a file, prompts/summarize.md:

You summarize customer support requests for the support team.
Write two or three sentences: what the customer wants, and anything urgent.
Use only facts that are in the request.

The node, in the same file, names it, together with its model binding and its attempts:

# src/nodes/summarize.node.ael (continued)
node summarize(request: Request) -> Summary {
    config {
        prompt: file("prompts/summarize.md");
        model: binding("primary_model");
        max_attempts: 2;
    }
    return write_summary(request);
}

fn write_summary(request: Request) -> Summary {
    # A private helper: it sends the request to the model.
}

The body of write_summary is a placeholder, as A first agent explains. You write an agent's business policy as a system prompt (inline, from a file, from the environment or from configuration), or run it on its input alone with no system prompt:

SourceWhen to use it
InlineA short policy that changes only with the code.
FileA policy you review like code, packaged with the build, as in this tutorial.
EnvironmentA policy that the place where the program runs sets, through one variable you name.
ConfigurationA policy chosen through your configuration layers, or kept behind a secret reference.

You can list several sources in the order you want them tried. When none of them gives a usable prompt, the node runs input-only: the model receives the request alone. If your agent must never run without its policy, require a system prompt, and the run fails with a "system prompt unavailable" error instead. System prompts has the full table of outcomes.

Step 5: bind the model

The source names only the binding, primary_model. Your configuration or your deployment says what it means: the kind of API, the endpoint's address, the model, a reference to the credentials and the parameters a call may set. A built-in client for OpenAI-compatible APIs comes with the language, and you choose local or cloud models: local model servers, optional packages for major cloud providers, and clients for inference endpoints you run yourself.

  • Keep every secret value out of your source and your prompt files, which are packaged with your build: the binding holds a reference to the credentials, never the credentials, and AEL never writes a credential into compiled files or logs.
  • A missing binding, an invalid endpoint or missing credentials fail the run with a model configuration error before any model call.
  • ael config explain shows where each value of the binding comes from.

Models and providers describes bindings in full.

Step 6: set the budget

Every run of the agent draws on one budget, and the agent's own limits are explicit. Retries, output repair, timeouts and cancellation draw on one shared budget, so nested retries cannot multiply cost, and token, time, CPU, memory and concurrency budgets apply across every child agent.

  • max_attempts: 2 above allows the first attempt and at most one retry, both on the same budget.
  • A limit you leave out is inherited from the level above; it never means "no limit".
  • The tightest limit wins: the deployment's, the tenant's, the agent's and the run's.

Typed, reusable configuration is locked into a snapshot for each run, so a change to the binding or a limit applies to the runs that start afterwards. Budgets, retries and validation lists every kind of limit.

Step 7: check, test and evaluate

ael check
ael test
ael eval --suite support-regression
  • ael check checks the whole project: file names and roles, the prompt file's path, the types from main to the node, and the binding's name. Its diagnostics point at your source.
  • ael test runs your tests with scripted or recorded model responses, so they need no network and no credentials.
  • ael eval runs an evaluation suite against a prompt or model change and compares it with a baseline. Prompts are treated as releasable business logic, with evaluations, recorded-model regression tests, drift checks, and promotion with one-step rollback.

Step 8: build and run

ael build --target <target> -o build/support --release
ael run

ael build writes a native executable for the target you name; Supported targets lists them. ael run checks, builds and runs the program on the machine you work on, through the same steps.

main starts the agent, as in A first agent, whose agent file this tutorial keeps, with Request and Summary as its types:

# main.ael
main main() {
    let request: Request = read_request();
    let summary: Summary = support(request);
    print_summary(summary);
}

read_request and print_summary are private helpers of main.ael with placeholder bodies, like the helpers of A first agent.

Time and token usage is reported by default on every channel, and on the command line the summary goes to standard error, so the agent's answer on standard output stays unchanged for pipes and scripts. You can turn off each field without disabling the limits behind it.

Honest limits

  • The helpers on this page are placeholders: the library calls that send a request to a model and read the console are described on Model client and Console and logging, not written out here.
  • An evaluation describes how the agent did on its cases. It is not proof of how a model behaves in every case, and model output is not reproducible run for run.
  • ael doctor reports what it finds on your machine; a model endpoint that answers today can still change on the provider's side.