Creating Agents
An agent is a model in a ReAct loop: it is given a goal and a set of tools,
emits a JSON tool call, sees the result, and repeats until it emits a final
answer or runs out of steps. agent runs that loop and returns the final
answer as a string.
Prerequisites
An agent needs a provider. Without one, every call fails immediately and says so:
agent("list the files here")
# error[E_UNKNOWN]: No AI provider configured.
# Set AETHER_AI environment variable to: irongate, openai, ollama, or compat
Running an agent
agent(<goal>, [tools...], [max_steps], [dry_run])
Only the goal is required. It is a task, not a persona — the system prompt is supplied by the loop, and the goal is the user turn.
agent("Find the three largest files under src/")
Tools are named individually or as an array. They are ordinary builtins:
agent("Find every .log file over 1 MB", "ls", "find", "stat")
agent("Summarise what this project builds", ["cat", "ls", "grep"])
After the tools come two optional positional arguments: an integer step limit (default 8) and a boolean dry-run flag.
# Twenty steps, and do not actually execute the tool calls.
agent("Reorganise the test fixtures", ["ls", "mv"], 20, true)
The record form
The same call can be written as a record, which is easier to build
programmatically. Exactly four keys are read — goal, tools, max_steps and
dry_run — and anything else is ignored:
agent({
goal: "Summarise the open TODOs",
tools: ["grep", "cat"],
max_steps: 12,
dry_run: false
})
A record with no goal is refused with agent config requires {goal: String}.
Each call starts fresh
agent builds a new dialogue every time — a system prompt and your goal — and
returns a string. There is no session, no conversation history, and no reset
builtin, so a second call knows nothing about the first. To carry context
forward, put it in the next goal:
let plan = agent("Break down building a CLI todo app into steps")
let code = agent("Implement this plan: " + plan, ["write", "cat"])
This is also how multiple agents are composed; see Agent Swarms for the coordinated form.
Which tools an agent may run
Naming a tool in the call does not by itself permit it. Shell-command execution
is default-deny: with AGENT_ALLOW_CMDS unset, no command is allowed, and the
refusal says exactly that.
export AGENT_ALLOW_CMDS=ls,cat,grep,git
The list is read once, when the security configuration is first built, so
export it before starting ae. Setting it from inside a running shell with
env_set or set_env takes effect only if no command has been validated yet.
Anything outside the list is refused by name, and both the allowed and the refused attempts are written to the security audit log.
MCP tools
agent_with_mcp takes a goal and an array of MCP tool names, for agents that
should reach tools served over the Model Context Protocol rather than builtins:
agent_with_mcp("Check the deployment status", ["k8s_get_pods", "k8s_logs"])
Rate limiting
agent is capped at 10 calls per minute per process. Exceeding it fails with
Agent rate limit exceeded rather than queuing.
Security
An agent runs real commands. Beyond AGENT_ALLOW_CMDS:
ae --agentputs the shell in default-deny mode, gating destructive effect classes behind approval.ae --workspace <dir>confines writes and destructive operations to that directory.dry_runlets you watch the loop’s intent without executing it.
See Security & Auth.