Introduction
Andromeda is a small async agent framework for local models, plus a terminal assistant built on it, in TypeScript on Bun. Choose Ollama, Docker Model Runner, or llama.cpp as the model engine.
Overview
You can use Andromeda in two ways:
- As an assistant. Run
andromedain any directory and chat with it in a full-screen terminal UI. It reads and edits files, runs commands, searches the web, drives a browser, and generates speech, images and video. Model inference can run locally through Ollama, Docker Model Runner, or llama.cpp. - As an SDK. Import
src/libs/index.tsin TypeScript to build your own agents. An agent combines a model, instructions, tools, and optional memory, voice and planning. The runtime owns the model/tool loop, andAgentRunnerruns many agents under a concurrency limit.
Andromeda is built for two frameworks with the same features: Python (andromeda/) and TypeScript (andromeda-js/, on Bun). You're reading the TypeScript docs; switch at the top of the sidebar, or compare them in Python & TypeScript.
The code has two layers. src/libs/ is the provider-independent framework, and its public API is exported from src/libs/index.ts. src/app/ is the CLI wiring: the terminal UI, slash commands, permissions and the runtime that owns every service.
Install
You need Bun 1.1 or later. The start.sh (macOS/Linux) and start.cmd (Windows) launchers check for Bun, JavaScript packages, browser support, optional media dependencies and Ollama, installing missing pieces where possible. Ollama is the default; Docker Model Runner and llama.cpp are selectable in andromeda.yml.
ollama pull llama3.2:3b # the default model, about 2 GB
To use Docker Model Runner instead, enable it in Docker Desktop, turn on host TCP access on port 12434, and pull a model:
docker model pull ai/smollm2
Then set agent.engine: docker-model-runner and agent.model: ai/smollm2. See Docker Model Runner setup for a complete configuration and embedding model.
From the andromeda-js folder, start with the launcher:
./start.sh # macOS or Ubuntu; runs setup checks, then opens the TUI
./start.sh --plain # line REPL
start.cmd # Windows
On the first run the launcher installs Bun if it is missing, runs bun install, and opens a setup menu (↑/↓ to move, space to toggle, Enter to install, q to skip). Pick Ollama + llama3.2:3b if you have no model server yet; llama.cpp, the Python media backend, Docker, Qdrant, ffmpeg and pdftotext, Chromium and the native engines are optional. Nothing is installed globally: Bun on Windows and other tools go into .tools/ in the checkout, choices are saved in .tools/setup.conf and installs are recorded in .tools/installed.log. Preflight then creates .env and andromeda.yml with agent.model: llama3.2:3b, pulls the models it needs, and opens the web app and usage dashboard before the TUI. Later runs only do quick checks.
./start.sh setup # open the setup menu again
./start.sh uninstall # pick what to remove (uninstall.cmd on Windows); --dry-run shows the commands
bun run setup:ollama # install or start Ollama and pull configured models
bun run setup:audio # or setup:image, setup:video, setup:yolo: the Python media backend in parts
For a manual install, run bun install, copy .env.example and andromeda.example.yml, and run bun run main.ts. Set ANDROMEDA_SKIP_SETUP=1 to skip every check, ANDROMEDA_COMPONENTS="ollama browser" to pick menu items without the menu, or ANDROMEDA_DEFAULT_MODEL to pull a different first model. See model configuration for alternate engines.
Start it from anywhere
Run main.ts from any directory, or give it a shell alias:
alias andromeda-js='bun ~/path/to/andromeda-js/main.ts'
andromeda-js # full-screen TUI in the current directory
andromeda-js --plain # line-by-line REPL
The directory you launch from becomes the workspace: the folder the file tools can read and write. If it has no andromeda.yml of its own, the project's andromeda.yml is used (unless ANDROMEDA_CONFIG is set). .env is always read from the project. Everything Andromeda creates or downloads (models, speech, images, videos, screenshots, downloads) goes in one data/ folder, next to its personality/, pages/ and notes/; move it with storage.dir.
Optional extras
| Feature | What to install |
|---|---|
| Browser tools | bunx playwright install chromium (Playwright is an optional dependency) |
| Voice (Whisper, Kokoro, Qwen TTS and voice cloning) | Nothing: it runs in TypeScript. Whisper and Kokoro run on ONNX Runtime and download on first use; Qwen TTS uses a native qwen-tts built into vendor/. The microphone needs ffmpeg. |
| Images (Qwen-Image 2.1) and video (Wan 2.1) | Nothing: stable-diffusion.cpp's prebuilt sd-cli installs on first use, or ahead of time with bun run preflight |
| Reading PDFs | pdftotext (brew install poppler) |
Vision (view_image) | ollama pull qwen2.5vl:3b or a vision-capable model on your engine |
| Semantic memory recall | Ollama: ollama pull mxbai-embed-large:335m; DMR: docker model pull ai/nomic-embed-text-v2-moe |
| Code knowledge graph | uv tool install graphifyy |
First run
On a new workspace, Andromeda creates starter personality files and personality/BOOTSTRAP.md. On the first conversation it asks what to call itself, how it should sound, what to call you and how you like to work. When you confirm, it writes personality/USER.md and deletes the bootstrap file.
After that, just type. Some things to try:
› explain how this project is structured
› add a --verbose flag to cli.py and run the tests
› research the latest Ollama release and summarize what changed
› open news.ycombinator.com and tell me the top story
› draw a lighthouse at dusk, watercolor
› remind me every weekday at 9 to check the build
Press Shift+Tab to switch to plan mode before a larger change, so the agent investigates and writes a plan without editing anything. Esc interrupts a turn. Type /help for every command. Using the CLI covers all of this in detail.
Memory is configured in andromeda.yml or with --memory local|qdrant. With memory on, conversations are saved and /resume brings them back.
Model setup
The TypeScript launcher performs a prerequisite check and can install the configured Ollama model. Hardware-based model recommendations and the interactive engine/model picker described below are Python-only; TypeScript reads the same andromeda.yml settings.
On first launch, start.sh checks dependencies and the selected model; it does not run the hardware-based model picker. Set agent.engine and agent.model in andromeda.yml to choose your backend and model.
Your first agent
The SDK is async. Create an agent, give it tools bound to a workspace, and run a prompt:
import { Agent, createBuiltinTools } from "./src/libs/index.ts";
const agent = new Agent({
name: "coder",
model: "gpt-oss:20b",
instructions:
"You are a coding assistant. Inspect relevant files, make precise " +
"edits, and run focused checks before answering.",
tools: createBuiltinTools(process.cwd()),
});
const result = await agent.run("Explain how the package is structured");
console.log(result.output);
agent.run() resolves to a RunResult with the final output, optional model thinking, the complete normalized message history, and RunMetrics. Pass signal (an AbortSignal) to cancel a run.
Continue a conversation
History is explicit. Pass the previous result's messages into the next run:
const first = await agent.run("Read package.json");
const second = await agent.run("What Bun version does it require?", { history: first.messages });
The input array is copied, so your array is never mutated.
Give it memory
Or let a memory provider keep the history for you. resource scopes long-term memory (a user, a team, a project); thread is one conversation:
import { Agent, Memory } from "./src/libs/index.ts";
const agent = new Agent({ name: "assistant", model: "gpt-oss:20b", memory: new Memory({ options: { lastMessages: 20 } }) });
await agent.generate("Remember that releases happen on Fridays.", {
memory: { thread: "planning", resource: "project-acme" },
});
agent.stream(prompt, { memory, onDelta }) is the streaming version of generate. See Memory for working memory, semantic recall and Qdrant.
Custom tools
Declare a tool with a zod shape. It becomes the JSON schema, and the model's arguments are validated before the call (strings like "5" are coerced, as in Pydantic's lax mode):
import { tool } from "./src/libs/index.ts";
import { z } from "zod";
const issueStatus = tool({
name: "issue_status",
description: "Return the current status of an issue.",
parameters: { issueId: z.number().int(), includeComments: z.boolean().default(false) },
execute: async ({ issueId, includeComments }) => {
// ...
return `issue ${issueId} is open`;
},
});
Many agents at once
import { AgentRunner } from "./src/libs/index.ts";
const outcomes = await new AgentRunner({ maxConcurrency: 2 }).runMany([
{ agent: reviewer, prompt: "Review the API", taskId: "review" },
{ agent: tester, prompt: "Suggest test cases", taskId: "tests" },
]);
for (const outcome of outcomes) {
console.log(outcome.taskId, outcome.succeeded ? outcome.result?.output : outcome.error);
}
Outcomes keep input order, and a failed task doesn't discard its siblings unless you pass failFast: true.
Workspace & personality
The CLI shapes its behavior from Markdown files you can read and edit. AGENTS.md sits at the project root; personal context lives under personality/ (data/personality/ in andromeda-js):
| File | Purpose | Loaded |
|---|---|---|
AGENTS.md | Operating and project instructions | Main agent and subagents |
personality/SOUL.md | Personality, boundaries and tone | Main agent |
personality/IDENTITY.md | Name and role | Main agent |
personality/USER.md | Your confirmed preferences | Main agent |
personality/MEMORY.md | Lessons saved with remember_lesson | Main agent, when present |
personality/HEARTBEAT.md | Checklist for self-started check-ins | Heartbeat turns only |
personality/BOOTSTRAP.md | One-time setup conversation | Until setup is confirmed |
The system prompt is rebuilt before every turn, so manual edits take effect immediately. Existing files are never overwritten; a missing core file in a partly configured workspace is marked in the prompt rather than recreated.
Self-learning. When you correct Andromeda, it can call remember_lesson, which appends one dated sentence to personality/MEMORY.md. Lessons are capped at 400 characters, duplicates are skipped, and text that looks like a credential is rejected. Facts about you go to working memory instead; reusable procedures are learned as experience.
Keep personality files private if they hold personal context, and never put credentials in USER.md or MEMORY.md.
Safety
What the runtime enforces:
- Workspace scoping. File and search tools can't reach outside the workspace, including through
..or symlinks, unless you add a folder with--add-dir,/add-diroragent.extra_dirs. - Schema validation. Tool arguments are validated (zod) and unknown arguments are rejected.
- Atomic writes. File replacement is atomic, and writes to the same path are serialized.
- Bounded output. Reads, searches, listings and shell output are capped; shell timeouts kill the whole process group.
- Approvals. The CLI asks before destructive shell commands (
rm,sudo,git push,git reset --hard, …) and before creating or running a tool the model wrote, in every permission mode. - Package checks (Python framework). Before
run_commandinstalls packages (npm, pnpm, yarn, bun, npx, pip, uv, poetry, pipx, cargo), each one is looked up in its registry. A package that doesn't exist is refused; one that is very new or barely downloaded, a common sign of a fake package, needs your approval even in auto mode. - Desktop and code. Every desktop-control action and every
browser_run_codesnippet asks for approval, even in auto mode. - Local state. Everything the app saves stays inside the project's git-ignored
.andromeda/folder. - Least privilege.
createReadOnlyTools()builds reviewer agents; subagent profiles can be narrowed but never broadened.
Trust boundary: run_command starts in the workspace but is not a sandbox. A shell command can reach anything the Bun process can, including the network. For untrusted prompts or models, use ask mode or run the whole process in an OS or container sandbox.
For your own agents, authorizeTool is the hook for approvals and policy. See Tools.