# Sub-agents Source: https://docs.withaster.dev/agents Fan work out to named personas with the agent tool, or run them headless with aster run. Inside a turn, the agent can delegate self-contained tasks to sub-agents. Each starts with a fresh context and reports back; the parent weighs the report rather than trusting it. ## The roster | Agent | Role | | -------------- | ------------------------------------------------------------- | | `scout` | Read-only recon, fast: where does X live, how does Y work | | `cartographer` | Read-only architecture mapping: how subsystems connect | | `sentinel` | Skeptical review; tries to refute a claim before reporting it | | `forge` | Applies a specific, well-described change to files | | `scribe` | Writes and updates docs so they match the code | | `prism` | Synthesis: deduplicates collector reports into one result | ## Custom agents A directory with an `AGENT.md` is a custom agent: frontmatter (name, tools, edit permission) plus the system prompt as the body. See `docs/SWARM.md` in the main repository for the format. ## Limits Under `agents:` in aster.yaml: `max_concurrent`, `max_per_turn`, `agent_timeout_secs`, and `collector_model` (the model that synthesizes fan-out reports). ## Headless runs ```sh theme={null} aster run sentinel "review yesterday's commits on main" [--notify] [--cwd DIR] [--schedule NAME] ``` `aster run` executes one agent task and exits, `--notify` sends the result where the agent's config says to, and `--schedule NAME` attaches the run to a [schedule](/schedules). `aster run --help` mentions `aster agents`; that command does not exist, the roster above is the list. # Browser Source: https://docs.withaster.dev/browser The chat UI served from localhost. ```sh theme={null} aster serve ``` Serves the chat UI at `http://localhost:4187` and opens it. Off loopback, a token is required in the URL, so an exposed port is not an open door. # Chat Source: https://docs.withaster.dev/chat The TUI, keys, slash commands, one-shot mode, and streaming. `aster` in a repository opens the chat TUI. Everything else is optional flags on top of it. ```sh theme={null} aster # TUI in the current repo aster "why does the build fail?" # one-shot: print and exit (--print / -p) echo "explain this repo" | aster # piped stdin is the prompt aster --continue # resume this repo's latest session aster --resume [ID] # pick a session, or resume one by id aster --session ID "next question" # persist into a named session aster --messages-json history.json # caller owns the transcript (- for stdin) aster --compact --messages-json h.json # fold history into a summary, print it aster --stream # NDJSON events out, approval replies in aster --no-tools # plain chat, no read/search/edit aster --no-mcp # start instantly; /mcp connects later aster --allow-edits # let a scripted turn edit files aster --permission-mode plan|manual|auto|edit|yolo ``` Chat, review, and fix also take `--model` and `--effort off|low|medium|high|xhigh|max|ultra`. ## Keys | Key | Does | | ----------- | ----------------------------------------------------------- | | `enter` | Send; while a turn runs it queues the message | | `esc` | Interrupt the running turn; `esc esc` quits | | `shift+tab` | Step to the next permission mode | | `ctrl+j` | Newline without sending | | `ctrl+o` | The `/switch` panel | | `@` | Mention a repo file; images and documents attach as content | | `↑ ↓` | Move the cursor, then step through past messages | ## Slash commands | Command | Does | | --------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `/switch` | Thinking, mode, effort, model, provider in one panel | | `/model [id]`, `/m` | Switch model or open the picker | | `/provider [id\|url]`, `/p` | Switch endpoint, then pick a model | | `/mode [name]` | plan, manual, auto, edit, yolo | | `/effort [level]` | Set or cycle the reasoning budget | | `/thinking` | Print the model's thinking in full or not | | `/yolo` | Toggle yolo (guardrails off, red theme) | | `/mom [resume]` | Show the [mom.yaml](/configuration#mom-yaml-model-routing) routing state; `resume` re-arms it after a manual `/model` | | `/resume`, `/r` | Reopen a saved session | | `/clear`, `/c` | Start fresh | | `/compact` | Fold earlier turns into a summary | | `/status` | Session, model, context, token usage | | `/diff`, `/d` | Uncommitted changes | | `/mcp` | MCP servers and their tools; connects them if `--no-mcp` skipped it | | `/skills` | Pick a skill to load | | `/memory` | What Aster remembers here | | `/remember ` | Save a fact to memory | | `/theme` | Open the theme picker; previews live as you arrow through | | `/welcome` | Show or hide the session header, saved to `ui.welcome` | | `/goal ` | Loop turns until a judge model says the condition is met | | `/help`, `/quit` | The list above; exit | Installed skills that declare a command also appear as slash commands, for example `/write-tests`. ## Themes `/theme` opens a picker that previews live as you arrow through; Enter saves the choice to `ui.theme` in aster.yaml. Built-ins include `dark`, `light`, `midnight`, `forest`, `dracula`, `catppuccin`, `nord`, `gruvbox`, `solarized`, `synthwave`, `github-dark`, `monokai`, `one-dark`, `aster-ocean`, `aster-ember`, and `aster-orchid`. Custom themes are YAML files dropped into `~/.aster/themes/` or `.aster/themes/`; they appear in the picker on the next launch. Sixteen built-in palettes, switchable live with `/theme` in the session. Each swatch below uses the exact background and accent colors from the theme itself.
❯ default
the default warm dark palette
❯ aster-ocean
the signature aster deep-teal dark
❯ aster-ember
warm charcoal, burning orange
❯ aster-orchid
violet dark, magenta accent
❯ dracula
purple and pink on dark slate
❯ catppuccin
warm pastels on mauve
❯ nord
cool blues on slate
❯ gruvbox
earthy amber and orange
❯ solarized
muted teal and blue
❯ synthwave
neon pink to cyan
❯ github-dark
GitHub's calm blue dark palette
❯ monokai
classic Sublime/Monokai Pro palette
❯ one-dark
Atom One Dark blues and mint
❯ midnight
deep blue dark palette
❯ forest
muted green dark palette
❯ light
for bright terminals
## Streaming for tools `--stream` emits one JSON object per line. The desktop app and the VS Code extension both consume this format, and it is stable output, not a log: ```sh theme={null} aster --stream "summarize the failing test" ``` Approval prompts arrive as events on stdout; reply on stdin to approve or deny, which is how headless front-ends gate edits. # CLI commands Source: https://docs.withaster.dev/cli Every aster subcommand, with the flags that matter. Every subcommand takes `--json` (before or after it) and turns errors into `{"ok":false,"error":…}`. Chat, review, and fix take `--model` and `--effort off|low|medium|high|xhigh|max|ultra`. `aster --help` is the source of truth. ## Chat and sessions | Command | Does | | --------------------- | ----------------------------------------------------- | | `aster` | Chat TUI in the current repo | | `aster [PROMPT]` | One-shot: print the answer and exit (`--print`, `-p`) | | `aster --continue` | Resume this repo's latest session | | `aster --resume [ID]` | Pick a session, or resume one by id | | `aster --stream` | NDJSON events out, approval replies in | | `aster sessions …` | List, show, rename, delete, prune, import | ## Setup and status | Command | Does | | ---------------------- | ------------------------------------------------------------------- | | `aster init` | Wizard: provider + key into `~/.aster` (`--local`, `--yes --force`) | | `aster login [TARGET]` | GitHub, or a provider, via browser | | `aster logout` | Drop every stored login | | `aster status` | What the next turn runs with | | `aster upgrade` | Swap in the latest release (`--version`, `--force`) | | `aster announce` | Undismissed release notes as JSON | ## Config, keys, providers, models | Command | Does | | ------------------------------------------- | ------------------------------------- | | `aster config` | Form in a terminal, table when piped | | `aster config list/get/set/unset/path/edit` | Read and write settings | | `aster config providers/models/model` | Catalog and selection | | `aster key list/get/set/unset/path` | API keys in `.env` files | | `aster provider list/use` | Endpoints | | `aster model list/use/recommended/router` | Models and the auto tier | | `aster mom check/route` | Validate and preview mom.yaml routing | ## Work | Command | Does | | --------------------------------------------------- | --------------------------------------------- | | `aster review` | Review a diff, range, or PR | | `aster fix` | Patch findings from review JSON | | `aster run AGENT TASK` | One headless agent run | | `aster web search/extract/crawl/sitemap/screenshot` | Web as Markdown | | `aster memory …` | Inspect and edit memory | | `aster remember TEXT` | Save one fact to memory | | `aster learn` | Score the last turn, refine the learned skill | | `aster sessions …` | Session housekeeping | ## Extensions | Command | Does | | ----------------- | --------------------------------------------------- | | `aster skills …` | List, add, find, use, bundled, update, remove, init | | `aster plugins …` | Add, list, remove, validate | | `aster bots …` | Install and list published specialist bots | | `aster mcp …` | List, enable, disable, import, remove, login | ## Automation | Command | Does | | ------------------------------------ | ----------------- | | `aster cron install/list/remove/run` | Scheduled runs | | `aster remind MSG WHEN` | One-shot reminder | | `aster remote telegram` | Telegram bot | | `aster serve` | Browser UI | | `aster acp` | Zed agent server | # Configuration Source: https://docs.withaster.dev/configuration The complete aster.yaml reference. Precedence, every section, keys, providers, models, and mom.yaml routing. Configuration is yaml, layered: ```sh theme={null} ./aster.yaml # project, wins ~/.aster/aster.yaml # global built-in defaults # lose ``` The project file wins per key, not per section: a key it leaves unset falls through to the global file, then to defaults. Lists merge instead of replacing: permission rules, MCP tool filters, and MCP servers union across both files, the permission mode takes the stricter of the two, and schedules merge by name so a repo can override one cadence without dropping the rest. A malformed yaml file is an error, never silently skipped. Precedence for the things that can also come from elsewhere: **CLI flags > environment variables > aster.yaml > defaults.** Keys never live in yaml. They go in `.env` files (`~/.aster/.env` global, `.env` in the repo local), loaded into the environment at startup. ## Managing from the shell ```sh theme={null} aster config # form in a terminal, table when piped aster config list | get KEY | set KEY VALUE [--global|--local] | unset KEY aster config path | edit [--global|--local] aster config providers | provider [ID --model M] | models [--capabilities] | model [ID] aster key list [--all] | get VAR | set VAR [VALUE] [--stdin] [--local] | unset VAR | path aster provider list aster provider use openai [--model gpt-5.5] # endpoint and model together aster model list [--capabilities] | use ID | recommended aster model router [--tier cheap|balanced|strong] ``` `aster config list` prints each setting with the layer it came from, which is the fastest way to answer "why is this value what it is". ## Sections ### Model and endpoint The model, endpoint, and reasoning effort live under `review:`. The name is historical; every command reads them, chat included. `aster init`, `aster model use`, and `aster provider use` all write here. ```yaml theme={null} review: model: anthropic/claude-sonnet-4.5 # or auto, see below base_url: https://openrouter.ai/api/v1 effort: medium # off|low|medium|high|xhigh|max|ultra ``` Any OpenAI-compatible endpoint works: point `base_url` at it and set a key var named for it. A key var named for the endpoint (`ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`) beats the shared `ASTER_API_KEY`. `ASTER_MODEL` and `ASTER_BASE_URL` outrank this file. `model: auto` picks from OpenRouter's live benchmark rankings instead of a fixed id (OpenRouter endpoints only). The tier comes from `ASTER_ROUTER_TIER` (`cheap` | `balanced` | `strong`, default `balanced`), picks are cached for a day in `~/.aster/model-rankings.json`, and `aster model router` shows what each tier resolves to. ### `permissions` See [Permissions](/permissions) for the full language. ```yaml theme={null} permissions: mode: edit allow: ["Bash(cargo test:*)"] ask: ["Bash(git push:*)"] deny: ["Edit(infra/**)"] use_default_rules: true additional_directories: ["~/Downloads"] allow_credentials: ["gh:~/.config/gh"] ``` ### `agent` ```yaml theme={null} agent: max_tool_rounds: 60 # ASTER_MAX_TOOL_ROUNDS command_timeout_secs: 300 # ASTER_COMMAND_TIMEOUT compact_budget_chars: 192000 # ASTER_COMPACT_BUDGET max_output_tokens: 8000 # ASTER_MAX_TOKENS; 0 lifts the cap language: English # ASTER_LANGUAGE; unset follows the user learn: true # score finished tasks, refine learned skills ``` ### `review` See [Review](/review#configuration) for every key. ### `mcp` See [MCP servers](/mcp) for the full section. ```yaml theme={null} mcp: servers: {} context_tokens: 100000 inventory_percent: 1.5 tools: allow: [] deny: [] ``` ### `schedules` See [Schedules](/schedules). ```yaml theme={null} schedules: - name: nightly-review cron: "0 9 * * *" agent: sentinel task: "review yesterday's commits on main" notify: true ``` ### `agents` Sub-agent limits: `max_concurrent`, `max_per_turn`, `agent_timeout_secs`, `collector_model`. See [Sub-agents](/agents). ### `ui` ```yaml theme={null} ui: theme: dark # any built-in or custom theme name welcome: true # session header on launch ``` ### `mom` The switch and the file it reads; the routing policy itself lives in [mom.yaml](#mom-yaml-model-routing). ```yaml theme={null} mom: enabled: true # whether a mom.yaml may route each turn manifest: mom.yaml # which file to read ``` ### `providers` ```yaml theme={null} providers: catalog_url: https://… # JSON of model ids per provider, pulled by `aster provider refresh` ``` Only model ids travel over this: endpoints and key vars ship in the binary and are never taken from a fetched file. ### `mom.yaml`: model routing per turn A `mom.yaml` in the repo root or `~/.aster/` routes each turn to a model by intent. Entries describe intent (`power`, `thinking`, `prefer`), `switch` rules fire on conditions like `planning`, `stuck`, `looping`, `model-down`, and a cheap router judges the rest. Switches show as `mom:` notes in chat. ```sh theme={null} aster mom check # validate, show what each entry resolves to aster mom route "refactor the auth module" # which entry the router would pick ``` Spec: `specs/mom.md` in the main repository. Logs: `~/.aster/logs/mom-router.jsonl` and `mom-switches.jsonl`. ## Environment variables See [Environment variables](/env) for the full list. The two that outrank the yaml file: `ASTER_MODEL` and `ASTER_BASE_URL`, both reported by `aster status` when set. # Desktop Source: https://docs.withaster.dev/desktop The Tauri app wrapping the same agent. The Tauri app in `desktop/` wraps the same agent with a native window. It shells out to the `aster` binary on PATH, so `make install` keeps it current. # Environment variables Source: https://docs.withaster.dev/env Every ASTER_* variable, what it does, and where it sits in the precedence order. Environment variables sit between CLI flags and aster.yaml: **CLI flags > environment > aster.yaml > defaults**. Keys are usually set in `.env` files rather than the shell; both work the same once loaded. ## Model and provider | Variable | Does | | ---------------------------------------------------------- | ------------------------------------------------------------- | | `ASTER_API_KEY` | Shared fallback key; a var named for the endpoint beats it | | `ASTER_BASE_URL` | Override the endpoint; outranks aster.yaml | | `ASTER_MODEL` | Override the model; outranks aster.yaml | | `ASTER_EFFORT` | Reasoning budget: off, low, medium, high, xhigh, max, ultra | | `ASTER_REASONING_EFFORT` | Alias for `ASTER_EFFORT` | | `ASTER_MAX_TOKENS` | Completion cap; `off` lifts the cap | | `ASTER_SEED` | Sampling seed; `off` disables fixing it | | `ASTER_TIMEOUT_SECS` | Per-request timeout | | `ASTER_MAX_RETRIES` | Retry count for failed requests | | `ASTER_DEADLINE_SECS` | Whole-turn deadline | | `ASTER_VISION_MODEL` | Model used to describe images for models that cannot see them | | `ASTER_PRICE_PROMPT_PER_M`, `ASTER_PRICE_COMPLETION_PER_M` | Manual pricing for cost display | | `ASTER_PROMPT_CACHE` | `off` disables explicit prompt-cache breakpoints | | `ASTER_CATALOG_URL` | Override the model catalog URL | ## Agent behavior | Variable | Does | | ---------------------------- | -------------------------------------------------------------- | | `ASTER_MAX_TOOL_ROUNDS` | Cap on tool rounds per turn (default 60) | | `ASTER_COMMAND_TIMEOUT` | Per-command timeout in seconds (default 300) | | `ASTER_COMPACT_BUDGET` | Context budget that triggers compaction (default 192000 chars) | | `ASTER_LANGUAGE` | Language every reply is written in; unset follows the user | | `ASTER_GOAL_MAX_TURNS` | Cap for `/goal` loops (default 20) | | `ASTER_ROUTER_TIER` | `cheap`, `balanced`, or `strong` for `model: auto` | | `ASTER_AGENT_MAX_CONCURRENT` | Sub-agents running at the same time | | `ASTER_AGENT_MAX_PER_TURN` | Sub-agent fan-out allowed per turn | | `ASTER_AGENT_TIMEOUT` | Per-sub-agent timeout in seconds | | `ASTER_COLLECTOR_MODEL` | Model that merges sub-agent reports | | `ASTER_LEARN` | `0` turns off skill learning | ## Web and browser | Variable | Does | | ------------------------------------------- | -------------------------------------------------------- | | `ASTER_WEB_SEARCH` | `1` gives the agent web search by default | | `FIRECRAWL_API_KEY` | Enables `web crawl` | | `CONTEXT_DEV_API_KEY` | Enables `crawl`, `sitemap`, `screenshot` | | `CLOUDFLARE_API_ID`, `CLOUDFLARE_API_TOKEN` | Alternative keys for `crawl` | | `ASTER_NO_BROWSER` | `1`: `open_preview` prints the URL instead of opening it | | `ASTER_WEBMCP_CDP_URL` | Chrome remote debugging URL for the webmcp server | ## Review | Variable | Does | | -------------------------- | ----------------------------------------------------------------------------------------- | | `ASTER_VERIFY_MODEL` | Override `review.verify_model` | | `ASTER_HYPOTHESIS_MODEL` | Override `review.hypothesis_model` | | `ASTER_VERIFY_CONCURRENCY` | Parallel verifier count | | `ASTER_ANALYZERS` | Analyzers for review, comma separated: `semgrep`, `ast-grep`; empty means the model alone | | `ASTER_ASTGREP_RULES` | Extra ast-grep rules path | | `ASTER_REPO` | Repo name shown in review reports when none is detected | ## Runtime and misc | Variable | Does | | -------------------------------------------- | ------------------------------------- | | `ASTER_TELEGRAM_TOKEN`, `ASTER_REMOTE_USERS` | [Remote control](/remote) | | `ASTER_EDITOR` | Editor opened by config/edit commands | | `ASTER_UI_DIR` | Serve a custom UI directory | | `ASTER_MCP_EXTRA` | Extra MCP servers config | | `ASTER_NO_UPDATE_CHECK` | `1` skips the release check | | `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry export | `.env.example` in the main repository lists them with notes. `aster key list --all` shows which are set and where they came from. # Fix Source: https://docs.withaster.dev/fix Turn review findings into patches, preview them, apply them. `aster fix` consumes review findings and produces patches: ```sh theme={null} aster review --json | aster fix # dry run: preview the patches aster review --json | aster fix --apply # write them aster fix --findings-json findings.json --apply --repo-root . ``` The dry run prints each patch against its finding; `--apply` writes them to the working tree, where your normal git flow takes over. Findings below the confidence floor are skipped, and anything it cannot patch cleanly is left for you with a note. # Getting started Source: https://docs.withaster.dev/getting-started Install Aster, connect a model, and run your first turn in a repository. ## Install ```sh theme={null} curl -fsSL https://withaster.dev/install | sh ``` The installer downloads the binary for your platform and puts `aster` on your `PATH`. Inspect [the script](https://withaster.dev/install) first if you prefer to read what runs. From source instead, with Rust 1.85+: ```sh theme={null} cargo install --path crates/aster-cli ``` Upgrade later with `aster upgrade`, or pin a version with `aster upgrade --version 0.4.0 --force`. ## Sign in Run `aster init` in any directory. The wizard walks through: 1. **Provider and model.** Pick from the catalog, sign in through the browser, or paste an API key. Keys are written to `~/.aster/.env`, never to yaml. 2. **Scope.** Global (`~/.aster`) by default, or `--local` to keep the config in the repository. 3. **Web keys, optional.** Search and page extraction work without a key; crawl and screenshots need one. `aster init --yes --force` skips the questions and writes defaults. `aster status` shows what the next turn will use. Provider sign-in is also available on its own: ```sh theme={null} aster login # GitHub, for --pr and --comment aster login codex # ChatGPT subscription, no API key needed aster login openrouter aster login zai ``` `aster login codex` runs on a ChatGPT subscription against `https://chatgpt.com/backend-api/codex`. Aster translates OpenAI's Responses API in both directions, so models and tools behave as they do anywhere else. ## First turn In a repository: ```sh theme={null} cd your-repo aster ``` The TUI prints a session header (model, provider, skills) and a prompt. Start with a question: ```text theme={null} where does the retry logic live? ``` Aster reads files, searches, and answers with file and line references. Then ask for a change: ```text theme={null} add a timeout to that call and a test for it ``` In the default `edit` mode, edits land in your working tree and commands run in a sandbox. Press `esc` to interrupt a turn, `shift+tab` to change the permission mode, and `esc esc` to quit. ## Everyday flow ```sh theme={null} aster # chat in the current repo aster --continue # resume this repo's latest session aster --resume # pick a session aster "why does the build fail?" # one-shot: print the answer and exit echo "explain this repo" | aster # piped stdin is the prompt ``` Outside a repository, chat still works, with less to look at. ## What to read next * [Chat](/chat): keys, slash commands, and one-shot mode. * [Permissions](/permissions): the five modes and the rule language. * [Configuration](/configuration): every `aster.yaml` key. # Goals Source: https://docs.withaster.dev/goals A slash command that loops turns until a separate judge model agrees the condition is met. `/goal ` in chat sets a target and keeps the turn loop running until it is met: ```text theme={null} /goal all tests pass and clippy is clean ``` After each turn, a separate cheap judge model rules the condition `met`, `not_yet`, or `impossible`. The worker model never certifies itself; a different model holds the yardstick. The judge's verdict is shown in the transcript, and the loop stops on `met` or `impossible`. `ASTER_GOAL_MAX_TURNS` caps the loop, default 20. `/goal` with no argument clears the current goal. # Headless and scheduled Source: https://docs.withaster.dev/headless One agent on one task, no terminal attached. `aster run "task"` runs one agent on one task with no terminal attached, and `aster cron install` puts every schedule in `aster.yaml` into the OS scheduler. Both resolve the same config as every other surface. # Introduction Source: https://docs.withaster.dev/index Aster is an open-source coding agent that reads your code, edits files, runs commands, and reviews changes, with any model you choose. Aster is a coding agent that lives in your terminal. It reads your code, answers questions about it, edits files, runs commands, and reviews your changes. It works with any OpenAI-compatible provider: OpenRouter, OpenAI, Groq, Anthropic, Z.ai, or a model running on your own machine. Aster running in the terminal, showing the session header and prompt One binary does all of it: ```sh theme={null} aster ``` That opens a chat bound to the repository you are in. Ask where the retry logic lives, ask for a small change and a test, or hand it a diff to review. Everything Aster does is visible: edits are gated by a permission mode you control, commands run in a sandbox, and every session is saved under `~/.aster`. ## For AI agents Every page is machine-readable. [llms.txt](/llms.txt) indexes the whole site with descriptions, [llms-full.txt](/llms-full.txt) is all of it in one file, and appending `.md` to any page URL returns clean markdown. There is also an MCP server at `/mcp` (tools: `search_docs`, `get_page`) and a [SKILL.md](/SKILL.md) for coding agents. ## What it does * **Chats about your code** with the full repository in reach: file search, content search, language-server navigation, and document reading. * **Edits and runs** under a permission mode from `plan` (look, do not touch) to `yolo` (no rules, no sandbox), with a rule language for everything between. * **Reviews diffs** with a hypothesize-then-verify pipeline that tries to refute each finding before reporting it, and posts the result to a PR on request. * **Remembers** across sessions: project facts always in context, named blocks read on demand. * **Extends** with skills, plugins, and MCP servers, and fans work out to sub-agents. * **Runs on a schedule** or over Telegram, headless, with the same config. ## Where to go next * [Getting started](/getting-started) installs Aster and connects a model. * [Chat](/chat) covers the TUI, keys, and slash commands. * [Permissions](/permissions) explains the modes and the rule language. * [Configuration](/configuration) is the complete `aster.yaml` reference. ## Where things live | Path | What it is | | --------------------------- | -------------------------------------------------- | | `~/.aster/aster.yaml` | Global configuration | | `aster.yaml` in a repo root | Project configuration, layered over the global one | | `~/.aster/.env` | API keys, never in yaml | | `~/.aster/sessions/` | Saved sessions | | `~/.aster/logs/` | Provider and router logs | Aster is open source at [github.com/Zfinix/aster](https://github.com/Zfinix/aster). These docs are open too, at [github.com/Zfinix/aster-docs](https://github.com/Zfinix/aster-docs). # MCP servers Source: https://docs.withaster.dev/mcp Connect Model Context Protocol servers, local or remote, with progressive injection and per-tool filters. Aster speaks the [Model Context Protocol](https://modelcontextprotocol.io). Servers are declared under `mcp.servers` in aster.yaml: ```yaml theme={null} mcp: servers: github: command: npx args: ["-y", "@modelcontextprotocol/server-github"] env: GITHUB_TOKEN: ${GITHUB_TOKEN} docs: url: https://docs.example.com/mcp headers: Authorization: Bearer ${DOCS_TOKEN} ``` `command` + `args` + `env` spawn a local server over stdio; `url` connects to a remote one over streamable HTTP (`type: sse` for the deprecated HTTP+SSE binding, `http` accepted as an alias). `disabled: true` skips a server without deleting its config. Two other sources add servers after the yaml is read: `.mcp.json` in the repo root and `~/.aster/mcp.json` are read natively, and installed plugins contribute theirs as `/`. Both only fill names `aster.yaml` did not already define, so the yaml always wins a collision. ## Progressive injection Tool inventories can be huge, so Aster does not paste them all into context. The inventory is measured against a budget (`mcp.context_tokens` times `mcp.inventory_percent`, default 1.5% of 100k). Within budget, tools are listed in the prompt. Above it, the prompt lists servers only, and the model discovers tools with a search call. Either way the agent finds the tool; the difference is how much context it costs. ## Filtering tools ```yaml theme={null} mcp: tools: allow: [] # empty means every tool deny: - "web/crawl" - "browser/*" ``` Globs match against the `server/tool` id, and `deny` wins over `allow`. The filter runs after every server has listed, so it covers the built-in `web` server as well as third-party ones. ## Managing from the shell ```sh theme={null} aster mcp list [--no-connect] aster mcp enable NAME | server/tool aster mcp disable NAME | server/tool # globs allowed aster mcp import [--from claude|codex|cursor|opencode|hermes] aster mcp remove [NAME] aster mcp login NAME # OAuth for a remote server ``` `/mcp` in chat shows the same picture and connects servers if the session started with `--no-mcp`. ## WebMCP `mcp.webmcp` bridges tools a web page registers in your own Chrome. Start Chrome with `--remote-debugging-port=9222` and the page's tools appear as MCP tools. # Memory Source: https://docs.withaster.dev/memory Durable memory in two layers. Project facts always in context, named blocks read on demand. Aster remembers across sessions in two layers: * **Project memory** is a short list of facts kept in context on every turn. Small, always visible, for the things that shape every reply. * **Named blocks** are longer-lived notes with a title. Only the title and a one-line description are listed in context; the agent reads the full body with `recall` when it becomes relevant. The agent writes with `remember`, reads with `recall`, and retracts with `forget`, all on its own during a turn. You can manage the same store from the shell: ```sh theme={null} aster memory [list] aster memory add "we deploy from release, never main" aster memory add "prefers terse replies" --title tone aster memory show tone aster memory remove tone ``` ## What belongs there Lasting facts: conventions, preferences, workflow rules, environment quirks. Not conversation history; sessions already persist, and `/compact` summarizes them when they grow long. ## In chat `/memory` prints what Aster remembers in this project, both layers. Ask it to remember something in plain language and it writes the block itself; correct it later and it updates or retracts the block. # Telegram, iMessage, Photon Source: https://docs.withaster.dev/messaging Set up each messaging channel and drive the agent from your phone. `aster remote` bridges a messaging channel to the agent, so the agent in your repository is reachable from your phone. The remote session uses the same `aster.yaml`, API keys, and permission rules as the terminal on that machine. Pick one channel: * **Telegram**: works anywhere, free, only needs a bot token. Long-polling, so no public URL, port, or tunnel. * **iMessage**: free and local, macOS only. Aster reads and sends through Messages.app on your Mac. * **Photon**: bridges iMessage through a Photon agent server with signed webhooks, for when you already run Photon. All three take `--mode`, which sets how much the agent may do without asking: `manual` (default) asks before every edit and command, `auto` applies your permission rules without prompting. `plan`, `edit`, and `yolo` are also accepted. See [Permissions](/permissions). ## Telegram 1. **Create a bot.** Message [@BotFather](https://t.me/BotFather), send `/newbot`, and follow the prompts. It gives you a token that looks like `123456789:AAH…`. 2. **Start the bridge.** From the repository you want the agent to work in: ```sh theme={null} ASTER_TELEGRAM_TOKEN=123456789:AAH… aster remote telegram ``` Or pass the token directly with `--token`. 3. **Allow your account.** Until you allow at least one user, the bridge ignores every message. Send your bot any message, then restart with your Telegram user id: ```sh theme={null} aster remote telegram --token … --user 123456789 ``` `--user` is repeatable, or set `ASTER_REMOTE_USERS=123,456` for several. 4. **Message the bot.** Replies stream into the chat as they run. Approval prompts arrive as inline buttons you tap. Long output is sent as a document instead of flooding the chat. Notes: * Transport is long-polling against Telegram. The machine running the bridge only makes outbound requests, so nothing to expose. * One bot token serves one poller. Two processes polling the same token split updates randomly, so the bot answers from whichever wins the race. ## iMessage 1. **Grant Full Disk Access.** Aster reads Messages.app's `chat.db` to see inbound messages. Give your terminal app Full Disk Access in System Settings, Privacy & Security, or the database is unreadable. 2. **Start the bridge.** macOS only. From the repository you want the agent to work in: ```sh theme={null} aster remote imessage --sender +15551234567 ``` `--sender` is an iMessage handle, email or phone, and is repeatable. Or set `ASTER_REMOTE_USERS=+15551234567,you@me.com`. 3. **Allow your handle.** With no senders allowed, the bridge ignores every message. Message the chat once, then restart with your handle as above. 4. **Message the agent.** Aster polls the chat every few seconds and replies through Messages.app as plain texts: no markdown tables or long code blocks. Approval prompts arrive as plain questions; you answer yes/no/always in text. ## Photon Photon's macOS agent server watches iMessage and POSTs signed webhooks to Aster; replies go back through Photon's send API. 1. **Run Photon** and get the webhook signing secret from its dashboard. 2. **Point Photon's webhook at Aster.** The listener binds `127.0.0.1` on port `8799` by default, so the webhook URL is `http://127.0.0.1:8799` unless you change `--port`. 3. **Start the bridge:** ```sh theme={null} aster remote photon --secret --sender +15551234567 ``` Or set `ASTER_PHOTON_SECRET` and `ASTER_REMOTE_USERS` instead of the flags. 4. **Message the agent.** Same plain-text replies and yes/no/always approvals as the native iMessage bridge. ## Environment variables | Variable | Purpose | | ---------------------- | ------------------------------------------------------------- | | `ASTER_TELEGRAM_TOKEN` | Bot token for the Telegram remote | | `ASTER_PHOTON_SECRET` | Webhook signing secret for the Photon remote | | `ASTER_REMOTE_USERS` | User ids or senders allowed to talk to Aster, comma separated | Whoever can message the bot or chat can run the agent with your keys, so always set `--user` / `--sender` and keep the default `manual` mode until you trust the setup. # One-shot CLI Source: https://docs.withaster.dev/one-shot-cli Same agent, no interactive session. ```sh theme={null} aster -p "why is this test failing?" aster review aster fix --findings-json findings.json --apply ``` Same agent, no interactive session. Plain text out by default when piped, `--json` for machine-readable output. This is the form scripts and CI use. See the [CLI reference](/cli) for every flag. # Permissions Source: https://docs.withaster.dev/permissions Five permission modes and one rule language that gates every edit, read, and command. One setting gates what the agent may write, read, and run. Change it with `shift+tab` in chat, `--permission-mode` per run, or `permissions.mode` in aster.yaml. ## The modes | Mode | Does | | -------- | ------------------------------------------------------------------------------------------- | | `plan` | Explores and presents a plan. Never edits or runs | | `manual` | Asks before every edit and command. Needs the TUI or `--stream`; headless runs deny instead | | `auto` | Edits and runs, pausing on the built-in risky-command list | | `edit` | Like auto, but commands are trusted; only a rule stops one. **Default** | | `yolo` | No rules, no sandbox. Asks once before enabling | No mode allows something a looser one refuses, so stepping up the ladder only ever adds freedom. ## Rules `allow`, `ask`, and `deny` hold rules in one language: | Rule | Matches | | ---------------------- | ---------------------------------------- | | `Edit()` | Writing a path | | `Read()` | Reading a path | | `Bash(:*)` | A command line starting with `` | | `Bash()` | That command line exactly | | `Edit`, `Read`, `Bash` | Everything that tool does | ```yaml theme={null} permissions: mode: edit allow: ["Bash(cargo test:*)", "Edit(src/**)", "Read(**/.env)"] ask: ["Edit(migrations/**)", "Bash(git push:*)"] deny: ["Bash(npm publish:*)", "Edit(infra/**)"] ``` A `Bash` rule is matched against the command line **and every command inside a shell script it carries**, so `Bash(sudo:*)` still fires on `bash -lc "cargo build && sudo make install"`. Leading environment assignments are ignored, a shell nested in a shell is followed a few levels down, and a prefix ends at a word boundary, so `Bash(rm:*)` does not also match `rmdir`. ## Precedence 1. `deny` 2. `ask` 3. `allow` 4. the built-in rules 5. `mode` User rules come before the built-ins, so a single `allow` entry overrides one of them without disabling the rest. To read `.env` files but keep every other secret protected: ```yaml theme={null} permissions: allow: ["Read(**/.env)"] ``` ## Built-in rules **Ask before writing**, since anything here runs as code later: `.git/**`, `**/.git/**`, `.github/workflows/**`, `.husky/**`. **Ask before running, in every mode but `edit`**: privilege escalation (`sudo`, `doas`, `su`), destructive filesystem operations (`rm`, `rmdir`, `dd`, `mkfs`, `shred`), permission and process control (`chmod`, `chown`, `chgrp`, `kill`, `killall`, `pkill`), system control (`shutdown`, `reboot`, `halt`, `systemctl`, `launchctl`), and network egress (`curl`, `wget`, `nc`, `ssh`, `scp`, `rsync`). Pausing on these is the whole difference between `auto` and `edit`; an `ask` rule of your own fires in `edit` too. **Refuse to read**, since a secret cannot be taken back out of the model's context: `**/.env`, `**/.env.*`, `**/*.pem`, `**/*.key`, `**/id_rsa*`, `**/*.p12`, `**/*.pfx`, `**/credentials.json`, `**/secrets.*`. `use_default_rules: false` drops all three sets at once. ## Sandbox Outside yolo, commands run in a sandbox: filesystem writes are limited to the repository and temp directories, secrets are stripped from the environment, and the network is reachable with per-domain approval. Two keys tune the edges: ```yaml theme={null} permissions: additional_directories: ["~/Downloads"] # readable outside the repo, no prompt allow_credentials: ["gh:~/.config/gh"] # preauthorize one credential dir for one command ``` The sandbox denies `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gh`, and `~/.kube` by default and prompts when a command needs one; an `allow_credentials` entry skips the prompt for that command-and-directory pair only. `~/Library/Keychains` is never grantable. ## Headless runs A prompt needs a front-end that can answer. Headless runs (`-p`, `--json`) have none, so anything that reaches `ask` is refused there. Pre-approve it with an `allow` rule, or run with `--stream` and answer approvals on stdin. # Plugins Source: https://docs.withaster.dev/plugins Agent Plugins packages, skills plus MCP servers, install unchanged. Plugins are [Agent Plugins](https://github.com/openai/agent-plugins) packages: a bundle of skills and MCP servers that installs as one unit. Aster reads the format unchanged. A plugin's MCP servers appear under names like `/`, and its skills appear in `/skills`. ```sh theme={null} aster plugins add owner/repo | ./dir [-p] [--plugin name] [--all] [-y] [-l] [--force] aster plugins list [-p|-g] aster plugins remove name [--purge] [-y] aster plugins validate [./dir] ``` `-p` installs into the project instead of globally. `--purge` on remove also deletes the plugin's cached skills and servers. ## Where they land Installed plugins live under the skills root next to regular skills, and their server contributions merge into the MCP catalog. Names `aster.yaml` already defines win a collision; a plugin only fills gaps. # Review Source: https://docs.withaster.dev/review Hypothesize, retrieve evidence, verify by refutation. Findings with file, line, severity, and confidence. `aster review` reviews a diff: the current branch, a range, a PR, or stdin. ```sh theme={null} aster review # current branch aster review --range main..HEAD git diff HEAD~1 | aster review --diff - aster review --pr 42 [--repo owner/repo] [--token T] aster review --pr 42 --comment [-y] # post inline PR comments aster review --tui # browse findings aster review --json # data for CI aster review --stream # NDJSON progress for UIs aster review -i "src/**/*.rs" -x "**/*.lock" --min-confidence 0.6 aster review --no-index # faster, less evidence aster review --effort high ``` ## How it works 1. **Hypothesize.** A model reads the diff and forms candidate findings. 2. **Retrieve.** A local symbol index pulls the code around each candidate, so the verifier sees context, not just the hunk. 3. **Verify.** A second pass tries to refute each finding. What survives is reported; what is refuted is dropped. 4. **Shape.** Findings carry file, line, severity, category, title, suggestion, and confidence. The refutation pass is the point: a finding that cannot be refuted after an honest attempt is worth your time. `--no-index` skips the symbol index for speed at the cost of evidence. ## Configuration Under `review:` in aster.yaml: | Key | Does | | ---------------------------------- | -------------------------------------------------------------- | | `model` | The review model; `auto` picks from OpenRouter's live rankings | | `hypothesis_model`, `verify_model` | Override per stage | | `base_url` | Custom endpoint | | `min_confidence` | Drop findings below this (0 to 1) | | `max_diff_bytes` | Refuse diffs larger than this | | `analyzers` | `[semgrep, ast-grep]`, run alongside the model pass | | `astgrep_rules` | Extra ast-grep rule paths | | `effort` | Reasoning budget for the review | | `web_search` | Let the review search the web | | `focus_areas` | Steer attention, e.g. security or error handling | | `include`, `exclude` | Glob filters over the diff | `model: auto` tiers with `ASTER_ROUTER_TIER=cheap|balanced|strong`; `aster model router` shows the current pick. Environment overrides: `ASTER_VERIFY_MODEL`, `ASTER_HYPOTHESIS_MODEL`, `ASTER_VERIFY_CONCURRENCY`. ## In CI `--json` prints findings as data; exit code reflects severity, so a plain `aster review --json` step fails the build on a critical finding. The `--pr --comment` pair posts findings inline on the PR. See [Fix](/fix) for consuming the JSON. # Schedules Source: https://docs.withaster.dev/schedules Cron runs, reminders, and where their output goes. ## Schedules Declare schedules in aster.yaml: ```yaml theme={null} schedules: - name: nightly-review cron: "0 9 * * *" # five fields, local time agent: sentinel task: "review yesterday's commits on main" notify: true ``` ```sh theme={null} aster cron install | list | remove NAME | run NAME # launchd on macOS, cron on Linux ``` `aster cron install` registers a launcher with your system scheduler; the launcher invokes `aster cron run ` at the scheduled time. Each run is a session like any other, visible in `aster sessions --all`. `notify: true` sends the result through the agent's notification channel. ## Reminders ```sh theme={null} aster remind "stand up" "in 30m" # or "in 2h", "at 18:00" ``` Reminders are one-shot schedules: the same launcher fires, the message arrives, nothing else runs. # Sessions Source: https://docs.withaster.dev/sessions Every chat is saved under ~/.aster. List, resume, rename, prune, and import from other agents. Every chat, ACP thread, and scheduled run is a session under `~/.aster`. Sessions are what `--continue` and `--resume` reopen, and what `/compact` folds. ```sh theme={null} aster sessions [list] [--all] # this repo, or every project aster sessions show ID aster sessions rename ID "title" aster sessions delete ID aster sessions prune [--keep N] [--older-than DAYS] # empties always go aster sessions import [--from claude|codex|cursor|opencode|hermes] [--dry-run] ``` ## Resuming Inside chat, `/resume` (or `/r`) reopens a saved session. From the shell, `aster --continue` resumes this repository's latest session and `aster --resume ID` picks one. `aster --session ID "question"` persists into a named session, creating it when it does not exist. ## Importing `aster sessions import --from claude` (also `codex`, `cursor`, `opencode`, `hermes`) pulls history from another agent's local storage so it is searchable and resumable here. `--dry-run` reports what it would bring in without writing. ## Housekeeping `aster sessions prune` deletes empty sessions always, and with `--keep N` or `--older-than DAYS` trims the rest. `aster sessions delete ID` removes one. # Skills Source: https://docs.withaster.dev/skills Folders with a SKILL.md that teach Aster a workflow. Install from GitHub, local dirs, or other agents. A skill is a folder with a `SKILL.md`: instructions for one workflow, loaded when it becomes relevant. Titles are indexed into context; bodies load on demand, so ten skills cost almost nothing until one matches. Five core skills ship built in and listed. Eleven more about the agent's own conduct are internal and never shown. A skill installed under `/internal//` is internal too. Ten optional skills are bundled off by default. ## Install ```sh theme={null} aster skills add owner/repo # from GitHub aster skills add ./dir # from a local folder aster skills add # wizard listing skills found on this machine aster skills find react [--owner org] # search GitHub, install interactively aster skills add claude-code # import from another agent's format ``` Useful flags: `-p` project scope instead of global, `-s name` one skill from a repo, `--all` everything found, `-y` no prompts, `--force` overwrite. ## Manage ```sh theme={null} aster skills list [-p|-g] aster skills use owner/repo@skill # print a skill without installing aster skills bundled [name…] [--force] # list or turn on optional built-ins aster skills update [name…] aster skills remove [name…] [--all] [-y] aster skills init my-skill # scaffold my-skill/SKILL.md ``` ## Using skills `/skills` in chat lists installed skills and loads one. The agent also loads skills on its own when a task matches a description. Skills that declare a command appear as slash commands, for example `/write-tests`. ## Writing your own `aster skills init my-skill` scaffolds the folder. A `SKILL.md` is frontmatter (name, description) plus a markdown body of instructions. Keep the description concrete: it is what the agent matches tasks against. Install it right after writing with `aster skills add ./my-skill --all --yes --force`. # Terminal TUI Source: https://docs.withaster.dev/terminal-tui The full chat experience in your terminal. ```sh theme={null} aster ``` The full chat experience in your terminal: streamed replies, tool calls with diffs, plan approvals, themes, and slash commands. `shift+tab` cycles permission modes. See [Chat](/chat) for everything the session supports. # Troubleshooting Source: https://docs.withaster.dev/troubleshooting When something fails: what to run first, and where the logs are. ## Start here ```sh theme={null} aster status # what will run, and from where tail -5 ~/.aster/logs/provider-errors.jsonl | jq # every failed provider request aster config list # each setting and its source aster mcp list --no-connect # servers without spawning them ``` `aster status` answers most "why is it using that model" questions: it prints the resolved provider, model, mode, and which layer each came from. ## Common failures **Auth error from the provider.** The key in the environment is missing, expired, or for the wrong endpoint. `aster key list --all` shows what is set; `aster login ` or `aster key set VAR` fixes it. A var named for the endpoint (`OPENROUTER_API_KEY`) beats the shared `ASTER_API_KEY`, so a stale endpoint-specific var can override a fresh shared one. **"Command not allowed" in a script.** A permission rule reached `ask` in a headless run, where nothing can answer. Add an `allow` rule for it, or run with `--stream` and reply to approvals on stdin. See [Permissions](/permissions#headless-runs). **MCP server will not connect.** `aster mcp list --no-connect` shows the config without spawning. Check the command exists, the env vars it needs are set, and `disabled` is not true. `/mcp` in chat retries a connection. **Session will not resume.** `aster sessions list` shows what is on disk; `aster sessions show ID` prints one. If a session is from another machine, import it with `aster sessions import --from …`. **Review finds nothing.** Check `--min-confidence` (try lowering it), the `include`/`exclude` globs, and `--no-index` to rule out the symbol index. `aster review --stream` shows the pipeline working stage by stage. **Model switches mid-session.** That is [mom.yaml](/configuration#mom-yaml-model-routing) routing by intent. `/mom` shows the current state; `/mom resume` re-arms it after a manual `/model`. ## Logs | Path | What | | ------------------------------------- | ------------------------------------------------ | | `~/.aster/logs/provider-errors.jsonl` | Every failed provider request with the raw error | | `~/.aster/logs/mom-router.jsonl` | Routing decisions | | `~/.aster/logs/mom-switches.jsonl` | Model switches | ## Still stuck [Open an issue](https://github.com/Zfinix/aster/issues) with the output of `aster status` and the relevant lines from `provider-errors.jsonl`. Redact keys first; the log never contains them, but your shell history might. # VS Code and Cursor Source: https://docs.withaster.dev/vscode The extension embeds the chat in a sidebar, tab, or window. The extension embeds the chat in a sidebar, tab, or window. * `cmd+shift+a` opens Aster, `cmd+alt+k` the command menu, `cmd+alt+n` a new session, `cmd+alt+r` reopens a session * `alt+a` sends the current selection as an @-mention * Review findings land in the Problems pane * Settings: `aster.binaryPath`, `aster.minConfidence`, `aster.publishDiagnostics`, `aster.extraArgs` # Web Source: https://docs.withaster.dev/web Search, extract, crawl, sitemaps, and screenshots, as CLI commands and as the agent's web tools. Aster ships a built-in `web` MCP server. Five operations, usable from the shell and callable by the agent during a turn: ```sh theme={null} aster web search "query" [--limit 5] [--region us-en] [--safesearch strict] aster web extract https://… aster web crawl https://… [--max-pages 100] [--max-depth N] [--url-regex R] [--main-content-only] [--follow-subdomains] [--no-pdfs] [--stop-after-ms 80000] aster web sitemap docs.rs [--max-links 500] [--url-regex R] aster web screenshot https://… [--full-page] ``` Search and extract return clean Markdown, made for reading by a model. ## Keys | Operation | Needs | | ----------------------- | --------------------------------------------------------------------- | | `search`, `extract` | Nothing | | `crawl` | `FIRECRAWL_API_KEY`, or `CONTEXT_DEV_API_KEY`, or the Cloudflare pair | | `sitemap`, `screenshot` | `CONTEXT_DEV_API_KEY` | `aster key list --all` shows every web key you have set and what each one buys. Keys live in `.env` files like every other key; `aster key set CONTEXT_DEV_API_KEY` writes one. ## In chat The same five are the agent's `web/search`, `web/extract`, `web/crawl`, `web/sitemap`, and `web/screenshot` tools. Ask it to look something up and it picks one; results arrive as Markdown in the turn. `mcp.tools.deny` can filter them like any other MCP tool. # Zed Source: https://docs.withaster.dev/zed Aster as a custom agent server via the Agent Client Protocol. `aster acp` speaks the Agent Client Protocol. Register it in Zed's `settings.json` as a custom agent server: ```json theme={null} { "agent": { "custom_servers": { "aster": { "command": "aster", "args": ["acp", "--permission-mode", "edit"] } } } } ``` Flags: `--permission-mode`, `--model`, `--no-mcp`, `--trace`.