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.
Prerequisites
Section titled “Prerequisites”The maiden binary on your PATH, and nothing else — no API key, no Docker,
no Rust toolchain:
curl -fsSL https://maiden.sh/install.sh | shSee Install for the cargo, npm and Docker channels, and for how to verify the download.
Step 1 — Create the folder
Section titled “Step 1 — Create the folder”An agent is a directory, and maiden new writes one that already works:
maiden new my-agentmy-agent/ .gitignore thread state and tool build output agent.toml model and per-tool policy instructions.md the system prompt skills/tone.md extra guidance appended to it tools/greet.wasm a sandboxed tool, already built tools/src/greet/ its source evals/greet.toml a check that passes with or without a keyThat is the whole agent — seven files you can read, diff, and commit. Nothing is hidden in a database or a dashboard, and there is no project to initialize.
Two of them are the ones an agent cannot run without. agent.toml says which
model to call and how far to let it go:
[agent]model = "openai/gpt-5.5"instructions_file = "instructions.md"max_turns = 6And instructions.md is the system prompt, as plain prose:
You are a concise assistant.
When the user asks you to greet, echo, or repeat something, you MUST call the`greet` tool with that text as the `input` argument — do not answer frommemory. After it returns, quote what it returned rather than paraphrasing it.Step 2 — Run it offline
Section titled “Step 2 — Run it offline”You don’t need an API key to see the loop work. The --mock flag swaps in a
scripted provider that behaves deterministically:
maiden run my-agent "greet the world" --mock[maiden] agent 'my-agent' tools [greet][maiden] 2 turn(s)Tool returned: {"echoed":"from-mock"}Two turns: the provider called greet, then wrote an answer quoting what came
back. The tool ran inside the WASM sandbox with a
timeout and a memory ceiling, and you didn’t configure any of it.
The eval that came with the folder passes too:
maiden eval my-agent --mock ok greet[maiden] 1/1 eval(s) passedStep 3 — Change the tool
Section titled “Step 3 — Change the tool”greet.wasm shipped prebuilt so that step 2 worked on a machine with no Rust
toolchain, but its source is right there in tools/src/greet/src/lib.rs. Open
it and you will find three functions: describe tells the model when to reach
for the tool, schema declares the arguments it accepts, and execute does
the work.
Make it shout:
Ok(serde_json::json!({ "echoed": input.to_uppercase() }).to_string())Rebuilding needs cargo-component:
maiden tool build greet --dir my-agentmaiden run my-agent "greet the world" --mock --thread t2Tool returned: {"echoed":"FROM-MOCK"}Note the --thread t2. Without it the run resumes the thread you started in
step 2, and a resumed thread already has its answer — you would see the old
from-mock and think the rebuild failed. That is durability doing its job, and
it is the same reason maiden eval runs on throwaway state.
The tool name comes from the path — tools/greet.wasm is the tool greet.
Rename the file and you have renamed the tool; there is no registry to update.
To write a second one from scratch, see
Add a sandboxed tool.
Step 4 — Use a real model
Section titled “Step 4 — Use a real model”Drop --mock to run against the model in agent.toml. That model is an OpenAI
model, so set your key:
export OPENAI_API_KEY="sk-..."maiden run my-agent "greet the world, then explain what you did"Now a real model decides whether greet is the right tool and writes the answer
in its own words — and skills/tone.md, which was appended to the system
prompt all along, is what keeps it to three sentences. To use Anthropic instead,
change model in agent.toml — see Choose a provider.
Step 5 — Serve it
Section titled “Step 5 — Serve it”Instead of one-shot runs, serve the agent over HTTP. Each thread is checkpointed after every turn, so conversations survive restarts.
maiden serve my-agent --port 8080In another terminal:
curl -s localhost:8080 -d '{"thread":"t1","message":"greet the world"}'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.
Step 6 — Add another eval
Section titled “Step 6 — Add another eval”You ran evals/greet.toml back in step 2. An eval is a prompt plus what must
be true of the run, so adding one is adding a file. Create
my-agent/evals/shouts.toml to pin the change you made in step 3:
prompt = "greet the world"expect = ["FROM-MOCK"]FROM-MOCK is the scripted provider’s own argument, shouted back — so unlike
greet.toml, this case only passes with --mock. That is the tradeoff worth
knowing: assert on answer text and you pin one provider’s wording; assert on
expect_count (as greet.toml does) and the case holds against both.
maiden eval my-agent --mock ok greet ok shouts[maiden] 2/2 eval(s) passedmaiden eval exits non-zero if any case fails, so this line drops straight into
CI. See Write and run evals for more.
Where to go next
Section titled “Where to go next”You’ve touched every core piece. To go deeper:
- Author your own tool → Add a sandboxed tool
- Reach external services → Connect an MCP server
- Compose agents → Subagents
- Understand the loop → Execution model and durability