Skip to content

Build your first agent

By the end of this tutorial you will have built a working agent from an empty directory: run it offline with no API key, give it a sandboxed tool, serve it over HTTP, and add a deterministic check. It takes about ten minutes.

  • A Rust toolchain (to build the maiden binary).
  • The maiden repository checked out.

Build the binary once:

Terminal window
cargo build --release -p maiden

That produces target/release/maiden. The examples below call it as maiden; either add target/release to your PATH or substitute the full path.

An agent is a directory. Make one with the two files every agent needs:

Terminal window
mkdir -p my-agent

Create my-agent/agent.toml:

[agent]
model = "openai/gpt-4o-mini"
instructions_file = "instructions.md"
max_turns = 6
temperature = 0.2

Create my-agent/instructions.md:

You are a concise assistant. When a tool is available, prefer using it over
guessing, and quote what it returns.

You don’t need an API key to see the loop work. The --mock flag swaps in a scripted provider that behaves deterministically:

Terminal window
maiden run my-agent "say hello" --mock
No tools available.

The agent ran end to end — loaded the (empty) thread, drove one turn, and answered. It has nothing to do yet because it has no tools. Let’s fix that.

A tool is a WASI-P2 WebAssembly component in tools/. Authoring one is its own topic (Add a sandboxed tool); for now, borrow the structured-echo tool that ships with the examples:

Terminal window
mkdir -p my-agent/tools
cp examples/echo-agent/tools/structured-echo.wasm my-agent/tools/

The tool name comes from the file: structured-echo.wasm is the tool structured-echo. Run again:

Terminal window
maiden run my-agent "echo the phrase hello" --mock
Tool returned: {"echoed":"from-mock","nested":{"ok":true},"timestamp":1700000000}

The mock provider called the tool and quoted its output. The tool ran inside the WASM sandbox with a timeout and a memory ceiling — you didn’t have to configure any of that.

Drop --mock to run against the model in agent.toml. That model is an OpenAI model, so set your key:

Terminal window
export OPENAI_API_KEY="sk-..."
maiden run my-agent "echo the phrase hello, then explain what you did"

Now the real model decides when to call structured-echo and writes the answer in its own words. To use Anthropic instead, change model in agent.toml — see Choose a provider.

Instead of one-shot runs, serve the agent over HTTP. Each thread is checkpointed after every turn, so conversations survive restarts.

Terminal window
maiden serve my-agent --port 8080

In another terminal:

Terminal window
curl -s localhost:8080 -d '{"thread":"t1","message":"echo hello"}'

Send a second message on the same thread and it continues the conversation. Kill the server, start it again, and thread t1 resumes from disk. To stream the answer token by token, see Serve over HTTP and stream.

Evals are the deterministic smoke test that your agent still boots, calls its tools, and answers. Create my-agent/evals/echo.toml:

prompt = "echo the phrase hello"
expect = ["echoed"]

Run it:

Terminal window
maiden eval my-agent --mock
ok echo
[maiden] 1/1 eval(s) passed

maiden eval exits non-zero if any case fails, so this line drops straight into CI. See Write and run evals for more.

You’ve touched every core piece. To go deeper: