simba-agent
v1.18.0
Published
Simba Agent - a terminal coding agent that reads, edits and runs your code. Powered by OpenRouter.
Maintainers
Readme
Simba Agent
A terminal coding agent. Reads your code, edits it, runs commands, and keeps every conversation on disk. Runs on OpenRouter.
Requires Node.js 22 or newer (node --version).
npm install -g simba-agentsimbaOne free API key is the whole setup — see below.
The API key
Simba looks for OPENROUTER_API_KEY in this order, first match wins:
- A real environment variable — CI, containers,
export ./.envin the folder you run from — per-project override~/.simba/.env— per-user, survives reinstalls and upgrades.envnext to the installed package — for working on a checkout
Simba ships with no API key. Put your own in place once and it applies to every project on the machine:
mkdir -p ~/.simba && echo "OPENROUTER_API_KEY=your-key" > ~/.simba/.envFree keys: https://openrouter.ai/keys. Nothing secret lives in this repository or in the published package — earlier versions bundled a fallback key, which was a mistake, and it was removed in 1.11.0.
Developing on a checkout
git clone https://github.com/sppideey/simba-agent.git
cd simba-agent && npm install && npm linkLayout
| File | Responsibility |
| --- | --- |
| llm.js | The only file that talks to a provider. Exports chat(messages, tools). |
| tools.js | Tool schemas and implementations. Sandboxing, confirmation, truncation. |
| session.js | Persistence to ~/.simba/sessions/. |
| context.js | Token accounting and summarizing older turns. |
| skills.js | Loading SKILL.md instruction packs. |
| ui.js | Wordmark, transcript, spinner, markdown, prompts, errors. |
| agent.js | The agentic loop and the CLI. |
Everything above llm.js speaks a provider-neutral message format:
{ role: 'user', content: string }
{ role: 'assistant', content?: string, toolCalls?: [{ id, name, args }] }
{ role: 'tool', toolCallId: string, name: string, content: string }
{ role: 'system', content: string }OpenRouter speaks the OpenAI wire format, so pointing SIMBA_BASE_URL at
another compatible host works without code changes:
SIMBA_BASE_URL=https://api.groq.com/openai/v1 OPENROUTER_API_KEY=gsk-... simbaModels
/models lists a curated nine, every one probed with a real tool call before
being listed. All are free; the paid balance is only a fallback.
| | Model | |
| --- | --- | --- |
| ★ | cohere/north-mini-code:free | default — built for code and UI work |
| ★ | nvidia/nemotron-3.5-lightning:free | recommended — 1M context, for long sessions |
| | nvidia/nemotron-3-super-120b-a12b:free | strong all-round, quick to answer |
| | nvidia/nemotron-3-ultra-550b-a55b:free | deepest reasoning, 1M context, ~9s first token |
| | nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free | small, reasoning tuned, overthinks |
| | poolside/laguna-s-2.1:free | code specialist, large context |
| | poolside/laguna-xs-2.1:free | smaller laguna, fastest to reply |
| | google/gemma-4-31b-it:free | open general purpose, often congested |
| | google/gemma-4-26b-a4b-it:free | sparse gemma, often congested |
/models opens an arrow-key picker: ↑↓ to move, Enter to select,
Esc to cancel. The nine above sit at the top, followed by every other free
tool-capable model OpenRouter currently serves, fetched live so the list does
not go stale. /models <name> still accepts any model id directly.
The arrows only belong to the picker while it is open — everywhere else they scroll the conversation, which is also how the mouse wheel reaches the app.
Rate limits
OpenRouter meters requests, not tokens — roughly 1000/day on free models once the account has bought $10 of credit, ~50/day before that. One agent turn is several requests.
This is why compaction rarely fires now: with a 1M-token window the context window stopped being the binding constraint.
Free endpoints are shared, so a busy one returns a brief 429. Simba waits it out and resumes the same turn rather than erroring.
Tools
| Tool | Notes |
| --- | --- |
| read_file | Numbered lines, paged via offset/limit. |
| write_file | Creates parent directories. For new files and full rewrites. |
| edit_file | Exact-match replace. Fails on zero matches or two-plus matches. |
| multi_edit | Several exact-match edits to one file in one call, all-or-nothing. |
| list_dir | Directories first, then files with sizes. |
| glob | **, *, ?, {a,b}. Newest first. Skips node_modules, .git, build dirs. |
| grep | Regex over file contents, returns file:line: text. |
| run_command | Runs without asking. Servers are detected and time out early with a hint; background: true keeps them running and returns the PID. Plan mode withholds it entirely. |
| web_search | Searches the web via Tavily. Needs TAVILY_API_KEY — see below. |
Enabling web search
web_search is the one tool that needs its own key, and none ships with the
package. Without it Simba says so and answers from training data instead.
Get a free key at https://tavily.com (1000 searches/month, no card), then:
mkdir -p ~/.simba && echo "TAVILY_API_KEY=tvly-your-key" >> ~/.simba/.envPlan and build mode
Build (default) can edit files and run commands. Plan is read-only — the writing tools are withheld from the model entirely, so it investigates and tells you what it would change instead of changing it.
Toggle with Ctrl+B, or click the chip at the bottom right. Start in plan mode
with simba --plan.
Clicking the chip needs the terminal to forward mouse clicks. It does not work on Windows — ConPTY does not pass mouse input to applications — so use Ctrl+B there.
Verification is enforced
Simba detects how your project checks itself at startup (npm test,
cargo test, go test ./..., pytest, make test). If it changes code and
then tries to finish the turn without running anything, it gets sent back to
run the check and report the real result. "It should work" is not an outcome.
Rules the tools enforce, not the model:
- Paths resolve inside the directory Simba started in. Anything outside needs a
y/n. - Results are capped with a
[truncated]marker: ~12k characters generally, ~48k for a file read, so the model can see a whole file before editing it. Override withSIMBA_MAX_TOOL_OUTPUT/SIMBA_MAX_FILE_OUTPUT. edit_filenever guesses. Ambiguous match means the file is left alone and the model is told how many matches there were.
Sessions
Saved to ~/.simba/sessions/<id>.json after every step — including
mid-turn, after each tool result — so a crash or Ctrl+C loses nothing. Each
file holds the complete message history plus id, created/updated timestamps,
the project directory, model, token usage, and a title derived from your first
message.
Startup is always a clean, empty session. Nothing from previous runs appears
until you ask: /sessions lists them, /resume picks one up. Sessions from
the folder you are in sort first.
Writes are atomic (temp file plus rename), so an interrupted save cannot leave a half-written session behind.
Context management
At 75% of the per-request token budget, the oldest turns are replaced with a summary rather than dropped, and the cut never falls between a tool call and its result. The session file keeps the full untruncated history regardless.
Set SIMBA_MAX_CONTEXT_TOKENS to force compaction earlier.
Skills
A skill is a folder with a SKILL.md:
---
name: code-review
description: Shown in the system prompt so the model knows when to reach for it.
---
Instructions go here. Only loaded when the skill is actually used.Only names and descriptions go into the system prompt. The body is pulled in
via the load_skill tool the moment a task matches, which keeps the base
prompt small however many skills you add.
Ships with build-app, code-review, debug, frontend-design, and
write-tests. Personal models can be added in ~/.simba/models.json, which
lives outside the package and survives every upgrade. Drop your own into skills/, or into .simba/skills/ inside a
project to override per-repo.
Commands
| Command | |
| --- | --- |
| /help | Command list |
| /skills | List skills, marking which are loaded |
| /new | Save the current conversation and start fresh |
| /session | List past sessions and pick one to continue (/session 3 skips the picker) |
| /clear | Clear the screen (history kept) |
| /models | Show or switch model; also reports this session's token spend and live quota |
| /web-search <query> | Research something and bring the results into the conversation |
| /copy | Copy the last reply to the clipboard |
Ctrl+C interrupts a running turn. Ctrl+D saves and quits. Tab completes commands.
Flags
-m, --model <name> model to use
-C, --cwd <dir> run against another directory
--debug print stack traces on failure
-h, --help usageErrors
No bare stack traces. Every error states what was attempted, what failed, and what to try:
✗ Failed while sending a request to qwen/qwen3.6-27b.
Per-minute limit exceeded for qwen/qwen3.6-27b — free again in 12s.
→ Wait 12s and send again — Groq's per-minute bucket refills continuously.
/cost shows what is left.
(rate_limit)Distinguished: missing/invalid API key, per-minute vs per-day rate limits, network failure, malformed tool arguments, models that can't tool-call, hallucinated tool names, oversized conversations, file not found, permission denied, binary file, and corrupt session files. Transient 5xx and network failures are retried twice with backoff before you see anything.
Two failure modes are fed back to the model rather than shown to you, and it gets two attempts to correct itself: malformed tool arguments (invalid JSON, missing or wrong-typed fields) and hallucinated tool names rejected by Groq's server-side validator.
Tests
npm test60 tests, no network or API key required.
