Skip to main content
Build an agent by adding one thing at a time to the same call. Every step below is POST /sessions/{id}albus sessions run in the CLI, sessions.run_session in Python, sessions.runSession in TypeScript — with a little more in the agent object. Each step shows the previous code with the new lines marked +, and says what the addition changes about how the agent behaves. The examples use the Python SDK and, alongside it, the JSON agent file the CLI takes with --agent-file. The field names are the same in both.

1. Run a simple agent

The smallest agent is a model, a system prompt, and a user prompt. No tools: the model answers from what it knows, and the reply is the whole result.
Three names matter here. id is the session: a conversation you name, which Albus creates on the first run and continues on every later one, so the next prompt to support-1234 sees this one. agent_name groups runs into an agent, and the agent object is its configuration. Albus supplies the model — no provider key of your own is needed until you bring your own. system_prompt is prefixed with the current date and time, so the model can answer time-sensitive questions; omit it and a server default applies.

2. Make the run retry-safe

Add an invocation_key. The call becomes idempotent: a network failure, a 504 from a long wait, or a lost response costs nothing, because retrying the same key re-attaches to the same run instead of starting another.
Over HTTP the same value travels as the Idempotency-Key header; in TypeScript it is invocationKey. It is unique within your organization, and it is also how you read the run back later — GET /traces/{invocation_key}. The effective key always comes back in the Idempotency-Key response header, even when you let the server generate one. Choosing a session id. Ids match ^[0-9a-zA-Z._:-]+$, 2–100 characters. Use something meaningful from your own system — the ticket (support-1234), the pull request (pr-987), the job (nightly:2026-08-11) — so you can find the session again without storing a mapping, and so the conversation naturally continues where the work does. Choosing an invocation key. Derive it from the thing that must happen exactly once: the event id that triggered the run, or the session id plus a turn counter (support-1234-triage-1). Never a timestamp or a random value generated on each retry — that is a new invocation every time, which is what the key exists to prevent. Reusing a key re-attaches even to a failed run, so use the same key to recover a lost response and a new key to genuinely run again.

3. Add web search and a terminal

Add the two built-in tools. The agent stops answering from memory alone: it can look things up on the web and run commands in a persistent Linux sandbox, and the model decides when to call each. Runs get longer and better grounded.
tools is an object of tool blocks: include a block to offer that tool, omit it to withhold it. web_search shows up in traces as WEB_SEARCH and terminal as TERMINAL. In the CLI, --tool web_search --tool terminal builds the same object when you are not using --agent-file. What each tool does and what it costs is in Built-in tools. Tool calls take steps. max_steps caps how many model steps a run may take before it stops; add it when the agent has tools that could keep it busy.

4. Limit the tools the agent can see

Take a tool away. The model never learns the tool exists, so it cannot be talked into using it — the agent becomes a web researcher that cannot execute anything.
The tools object is an allowlist: what is not in it does not exist for the model. Omit tools entirely and the agent has no built-in tools at all, as in step 1. The same idea applies to MCP servers in the next step, where allowed_tools picks which of a server’s tools the model is offered — and a prompt that says “do not run commands” is not a substitute for either: the model can ignore instructions, but it cannot call a tool it was never given.

5. Add a secret and an MCP server

Give the agent your own tools through an MCP server, authenticated with a secret. The agent can now act on your systems — here, search GitHub issues — and the credential never appears in the configuration, the audit log, or a revision. First store the credential. The value is the full header value the server expects, Bearer included:
Reads return the value masked to its last three characters; no operation returns a secret’s value. Names are path-like (/-separated segments of [a-zA-Z0-9_-]), so group them the way you would files. Then reference it from the agent as albus.sh/secrets/<name>:
Albus connects to the server for the run, discovers its tools, offers them to the model, and dispatches the calls; nothing runs on your machine. The server must speak Streamable HTTP at a public http(s) URL — stdio servers need an HTTP front. A reference is resolved when the run starts, not when the request is validated, so a reference to a secret that does not exist fails the invocation rather than the request — create the secret first. Rotating is albus secrets update: references keep working, the next run picks up the new value, and the configuration is unchanged.

6. Configure memory

Add a memory group. What the agent learns now outlives the session: every invocation configured with the same group reads the same memories, whichever session it runs in, and writes new ones at the points you list.
generation is when memories get written: end_of_invocation after the invocation answers, agent whenever the agent decides mid-run that something is worth keeping. A group is yours to scope — one per customer, per team, per agent. Without memory, a session still remembers its own last 1000 messages; memory is what carries knowledge between sessions. Read and prune a group with GET /memories?group=support and DELETE /memories?group=support, or in the console under Memories. More in Built-in tools.

7. Read the trace and iterate

Every invocation is recorded as a trace: the invocation, each step, the model call in each step, and the tool calls it requested, as spans with their inputs, outputs, timings, and token usage. It is how you find out what the agent actually did — and the fastest loop for improving a prompt or a tool.
Python
Span ids read as a path: inv5 is the fifth invocation of the session, inv5.attempt1.step1.tool1 the first tool call the first step requested. A model_call span’s input is exactly what the model was given and its output what it said, including the tool calls it asked for; a tool_call span’s input is the arguments and its output the tool’s result. Long payloads are shortened with an [omitted: …] marker and carry their full byte count and SHA-256. Read a trace with questions like these:
  • Did the agent call the tool you expected? If WEB_SEARCH fires where github__search_issues should have, the tool’s description (from the MCP server) or your system_prompt did not make the choice obvious. Name the tool and when to use it in the prompt, or tighten allowed_tools.
  • Did it get what it needed from the tool? Look at the tool_call output. A tool that returns 40 KB of JSON for a one-line answer wastes steps and tokens; a tool that returns an error the model cannot act on gets retried. Fix the tool’s shape, not the prompt.
  • Where did the steps go? Count step spans and read usage on each model_call. Loops of the same tool call with slightly different arguments are a sign the task is underspecified; a max_steps stop is a sign the budget or the prompt is wrong.
  • Why did it fail? A FAILED span carries error; the invocation carries a failure. A retried invocation has more than one attempt — pass attempts=all to see the superseded ones, whose tool calls still had their effects.
GET /traces lists invocations across sessions, newest first, filtered by agent_name, agent_revision, status, session_id, since, and untilstatus=FAILED on an agent name is the first thing to look at when a job goes wrong. Spans are kept for 90 days from the invocation’s start. The console shows the same thing under Traces, with each invocation at /traces/<invocation_key>. Change the prompt or a tool, run again with a new invocation key, compare the traces. Which brings us to what “change” means.

8. Every change is a revision

You never create an agent: you name one on a run, and Albus records the configuration. Every distinct agent object under one agent_name becomes a revision, identified by a hash of the configuration — so each step in this guide created a new revision of support-triage, and running the same configuration again reuses the existing one.
agents get returns the current revision’s full configuration plus every revision, newest first; agents revision returns one revision’s exact model, prompt, tools, MCP servers, and memory. In the console, Agents lists them, and each revision lives at /agents/<name>/revisions/<revision>. Every session response carries session.agent_revision, every trace and audit event carries agent_revision, and GET /traces?agent_name=…&agent_revision=… lists every invocation that ran one configuration. That is the link from an answer you are unhappy with back to the configuration that produced it:
Keep agent_name stable for one job (support-triage, pr-reviewer) rather than per run or per customer, so the revision list reads as the agent’s history; session ids are where per-run identity belongs. Record the revision next to your own results and you will always know what produced them. A revision is configuration only — credentials are references, so rotating a secret does not create one.

9. The audit log

The audit log is the security record of a session: an immutable, time-ordered account of every invocation, model call, and tool call — arguments and results included — and how each run ended. Where a trace is for understanding and iterating, the audit log is for answering what did this agent do, on whose request, with which configuration.
Every event carries session_id, the invocation_key it belongs to, the agent_revision that ran, event_time, and a payload shaped by its type. Model output, tool output, and tool arguments are stored up to 32 KiB each, alongside the complete value’s byte count, SHA-256 digest, and a truncation flag (contentBytes, contentSha256, contentTruncated), so a stored value can be checked against the original. Secrets never appear: references are resolved at run time and the values are not written anywhere the API returns.
Page with --after/--limit (default 100), passing the response’s next_cursor as the next after. The log is per-session; for the same events across sessions, filtered by agent, revision, status, or time, use traces.

Clean up

Where next