The agent tool lets a chat turn fan work out to named specialists that run in
parallel and report back. Each sub-agent starts with a fresh context and cannot
see the parent conversation, so every task must be self-contained. The parent
weighs the reports; it does not treat them as verified truth.
The system prompt teaches one dispatch rule: when work is divisible, split it
into distinct, non-overlapping slices and send them as one agent call with
one task per slice, so the batch covers the whole. A job that splits cleanly is
never handed to a single agent, and when the user asks to spin up or use
agents, the model fans out even for work it could do itself.
The roster
Six built-in personas ship in the binary, grouped by category. Each is a
directory under crates/aster-agents/builtins/<name>/ holding one AGENT.md.
| Persona | Category | Edits | Role |
|---|---|---|---|
| Scout | recon | no | Fast read-only reconnaissance: "where does X live", "how does Y work", answered from repository evidence. |
| Cartographer | recon | no | Architecture mapping: traces a flow end to end, names module boundaries, recommends where a change should land. |
| Sentinel | review | no | Skeptical review: tries to refute a suspected defect before reporting it, and only accepts one with a concrete failure scenario. |
| Forge | build | yes | Applies a specific, well-described change with the minimal edit. Edits stay policy-gated and may prompt for approval. |
| Scribe | docs | yes | Writes or updates documentation so it matches what the code actually does. Touches docs only, never code. |
| Prism | synthesis | no | The expensive pass: merges raw collector reports, resolves conflicts by reading the repo, spot-checks claims, outputs one curated result. |
The intended shape for broad work is collectors first, synthesis second: fan
several cheap Scouts (or Cartographers, Sentinels) out in one call, then pass
their raw reports to Prism in a second call. Prism runs on the session model;
collectors run on the cheaper agents.collector_model when one is configured.
The agent index the model sees is generated from the registry and grouped by category, so custom agents slot into the same listing.
How a batch runs
One agent tool call carries a tasks array of {agent, task} pairs.
Dispatch works through crates/aster-cli/src/agents.rs:
- One call runs at most
agents.max_per_turntasks (default 24). Overflow is deferred, not lost: the tool result names the count and tells the model to re-send the rest in followingagentcalls, so large jobs run in waves. The cap is per call, and it protects report quality: the result budget divides across the batch, so each wave keeps readable reports, whilemax_concurrentbounds true parallelism anyway. - The whole batch is announced up front with one
runningstatus event per task, so UIs can show progress totals immediately. - Tasks run concurrently, bounded by
agents.max_concurrent(default 8), each underagents.agent_timeout_secs(default 300s). A timeout or error fails that task alone; the rest keep running. - Each completion emits a
doneorerrorstatus event as it lands. The tool result the model sees preserves input order regardless of completion order, and caps each report so the combined result fits the 24k-char tool budget.
An unknown agent name fails its task with unknown agent: <name> instead of
poisoning the batch.
What a sub-agent gets
A sub-agent is the same user in the same session, minus the conversation:
- Its
AGENT.mdbody as the system prompt, and only the tools its frontmatter allows. Without atoolslist it gets the read-only set (read_file,list_files,search_files,find_files,read_skill). - The parent's policy and credential approvals. Out-of-repo write grants are not inherited; a sub-agent asks for its own.
- Its model resolves as: the definition's
model, elseagents.collector_model, else the session model. - A round budget of its own (
max_rounds, default 8), and noagenttool: swarms do not nest. - No skills, no memory, no MCP servers. The task text is its whole world.
Stream events
The CLI's --stream output carries two swarm event types, consumed by the
editors and the TUI:
{"type": "agent_status", "call_id": "…", "agent": "scout", "task": "…",
"status": "running" | "done" | "error", "report": "…", "error": "…",
"done": 1, "total": 5}
Identity within a call is agent plus task, because one batch may run the
same persona several times with different tasks. The running events for the
whole batch arrive before any work starts; report rides on done and
error on error.
{"type": "agent_activity", "call_id": "…", "agent": "scout", "task": "…",
"line": "search_files stdio"}
Activity is the live feed of what a running sub-agent is doing. The dispatcher
translates the child's own stream into display lines: each tool call becomes a
name detail line (read_file src/chat.rs), and narration buffers until the
next tool call, then lands as one condensed line. Consumers append lines to
that task's rolling log; the VS Code webview keeps the last 50.
Rendering
The VS Code panel draws each agent call as a card:
- Swarm: a wired graph. An orchestrator node fans out to one node per task; wires are measured off the rendered nodes and carry a flowing dash while that task runs. Each node shows the persona's avatar with a status dot on the rim (blue running, green done, red failed), the capitalized name, and a subtitle that is the latest activity line while running, the task when settled, or the error when failed. Clicking a running node opens its live tail; clicking a finished one opens its report, which can also pop out to a markdown tab.
- Solo: a batch of one skips the graph and header. The card is the node, an always-visible live tail while it works, and the report on click.
Avatars are inline SVG picked by persona name (compass, route, shield, hammer,
pencil, sparkle); unknown agents get the generic mark. The agent tool call's
own row is hidden once its card exists, so the swarm is stated once.
The TUI renders the same status events as per-agent rows with a progress count, and prints the curated report when the swarm settles.
Custom agents
Drop a directory with an AGENT.md in either root:
<repo>/.aster/agents/<name>/AGENT.md(project)~/.local/share/aster/agents/<name>/AGENT.md(global; respectsXDG_DATA_HOME)
Project definitions shadow global ones, and both shadow builtins of the same
name, so overriding Scout is just creating scout/ in the project root.
Malformed definitions are skipped with a warning, never fatal.
The file is YAML frontmatter over a markdown body; the body becomes the sub-agent's system prompt.
---
name: profiler
description: Profiler, the performance specialist. Read-only. Use to find where time is spent and why.
category: recon
tools: [read_file, list_files, search_files, find_files, read_skill]
max_rounds: 10
---
You are Profiler, this project's performance specialist...
| Field | Required | Notes |
|---|---|---|
name |
no | Lowercase letters, digits, hyphens; max 64 chars. Defaults to the directory name. |
description |
yes | Max 1024 chars. This is what the model reads when choosing an agent, so state the persona, whether it edits, and when to use it. |
category |
no | Free-form grouping for the agent index (builtins use recon, review, build, docs, synthesis). Uncategorized agents list under other. |
tools |
no | Tool allowlist. Omit for the read-only set. Include edit_file to let the agent edit; edits stay policy-gated. |
model |
no | Pin a model for this agent. Otherwise agents.collector_model, else the session model. |
max_rounds |
no | Tool rounds before the agent must answer. Default 8. |
verify |
no | Declares the agent's reply should face an adversarial verify pass. Parsed and carried on the definition; the automatic pass is not wired up yet. |
Unknown frontmatter keys are ignored, so newer fields do not break older binaries.
Configuration
Fan-out limits live under the agents key in aster.yaml
(collector_model, max_concurrent, max_per_turn, agent_timeout_secs),
each with an env override. See CONFIG.md.