Keywords: autonomous coding agent, multi-agent software engineer, Textual TUI dashboard, LiteLLM multi-model orchestrator, Claude GPT Gemini coding loop, AI SRE webhook hotfix, open source Devin alternative, terminal AI coding assistant, production incident LLM agent, Python asyncio agent loop
ArrowCode is a Textual terminal dashboard that runs five LLM roles (Researcher, Architect, Reviewer, Implementer, SRE) in an asyncio loop against the current repo, with a FastAPI webhook lane for production alerts. API keys are entered in the TUI and stored in local
config.json.
ArrowCode is a single-process Python app: a Textual dashboard plus a background engine. The engine calls models through LiteLLM (litellm.acompletion), so OpenAI, Anthropic, Gemini, and Azure share one code path.
| Piece | What it is |
|---|---|
| Dashboard | Textual TUI: war room log, current task, backlog table, hotkeys |
| Engine | asyncio loop, default interval 30 seconds (engine.loop_interval_seconds) |
| Five roles | Researcher, Architect, Reviewer, Implementer, SRE |
| Keys | Setup screen writes config.json (gitignored). Env vars are a fallback. |
| Webhook | FastAPI on 127.0.0.1:8765 (configurable), POST /webhook |
| Git | GitHandler commits dirty files, or logs a simulated SHA if there is nothing to commit |
| Demo mode | If an LLM call fails, that role logs a simulation line and the Researcher can seed 5 demo tasks |
It is not a full sandbox coding product. The Implementer asks the model for a unified diff and then commits the current working tree. It does not parse or apply that diff to disk.
What does ArrowCode actually do when I launch it? It opens a TUI, starts an asyncio loop, and (on first run with no keys) shows a setup form. After you save at least one provider key, the Researcher requests 3 JSON tasks, then each pending task goes Architect to Reviewer to Implementer. A FastAPI thread listens for production alerts and interrupts the loop so the SRE role can respond.
Do I need OpenAI, Anthropic, and Gemini all at once? No. Enter at least one key. Default models are Gemini 1.5 Pro (Researcher), Claude 3.5 Sonnet (Architect and Reviewer), GPT-4o (Implementer), and GPT-4o-mini (SRE). If the configured model has no matching key, LiteLLM raises and that step falls back to a simulation log line.
Is this a free Devin or OpenHands replacement? No. Those tools apply patches, run tests, and often isolate work in a sandbox. ArrowCode is a visible multi-role loop in your terminal: design, a one-round review debate, a requested diff string, and a git commit of whatever is already dirty (or a simulated SHA). Use it as an orchestrator UI, not as an unattended code writer.
| Aider / CLI pair tools | OpenHands-style SWE agents | ArrowCode | |
|---|---|---|---|
| How you work | You chat and approve each change | Long autonomous runs, often sandboxed | 30s engine loop plus hotkeys (a s r w q) |
| Applies model patches to files | Yes (typical) | Yes (typical) | No. Diff text is logged, not applied |
| Live multi-role TUI | No | Usually a web UI | Textual dashboard in the terminal |
| Models | One primary model, you switch | Varies | Per-role defaults via LiteLLM, editable in config.json |
| Production webhook | No | Rare in the CLI | POST /webhook wakes the SRE role |
| Works with zero keys | Limited | Usually not | Seeds a 5-item demo backlog so the UI is not empty |
| Install surface | pip CLI | Docker / extra runtime | ./install.sh then ./run.sh (Python 3.10+, Mac or Linux) |
Tradeoff: you get a war-room view and a webhook interrupt for cheap. You do not get a patch applier, test runner, or isolated workspace. Empty working trees produce simulated commits so the loop does not create blank git history.
- Config.
core/config.pyreads and writes<repo>/config.json. Keys, per-role model ids,loop_interval_seconds(30),auto_commit(true), andwebhook_port(8765) live there.memory.jsonstores backlog, a capped 500-line log, andtasks_done. - Launch.
main.pystarts the Textual app. Ifsetup_completeis false or no provider is available,SetupScreenis shown first.DashboardScreenthen starts the engine, the webhook thread, and a 1-second clock tick. - Loop (
Engine._run_forever). Drain the SRE queue first. If the backlog is empty, call the Researcher. Otherwise pick the firstpendingtask and run_pipeline. - Pipeline. Architect writes a short design. Reviewer critiques it. Architect may rebut in one sentence. Implementer is asked for a unified diff. If
auto_commitis true,GitHandler.commitstages dirty and untracked files. No file changes means a simulated SHA, not an empty commit. - Researcher. Prompt asks for 3 JSON objects
{title, rationale}. The prompt includes a bounded file listing (skips.git,venv, caches; cap 80 paths). It does not send file contents. Parse failures yield no tasks. LLM errors seed the demo titles in_demo_backlog(). - SRE.
POST /webhookwithservice,error,severity, optionaltrace. The dashboard thread callsEngine.raise_production_alert, which marks production Critical and queues the alert. After the hotfix step, that alert is removed. Status returns to Healthy when the list is empty, otherwise Degraded. - LLM routing.
Engine._llmsets env vars from config (OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY, Azure trio) and callslitellm.acompletionat temperature 0.4. Default model map is inDEFAULT_AGENT_MODELS. - UI bus. Agents publish
AgentEventvalues (chat,status,commit,error,task,alert). Widgets subscribe. Hotkeys:aadd task,ssettings,rforce Researcher scan,wprint webhook URL,qquit.
Input: keys in TUI -> config.json
|
v
Textual Dashboard
|
+----------+----------+
| |
Engine asyncio loop FastAPI :8765
| |
Researcher -> Architect POST /webhook
| Reviewer |
| Implementer v
| git commit SRE hotfix
v
memory.json (backlog + log)
ArrowCode/
├── install.sh # Python 3.10+ check, venv, pip install
├── run.sh # activates venv, exec python main.py
├── requirements.txt
├── main.py # Textual App
├── core/
│ ├── engine.py # loop, roles, event bus
│ ├── config.py # config.json + memory.json
│ └── git_handler.py # GitPython commit or simulated SHA
├── ui/
│ ├── dashboard.py
│ ├── setup_screen.py
│ └── components.py
├── server/
│ └── webhook.py # FastAPI + uvicorn daemon thread
└── tests/
Requirements: Python 3.10+, Git, Mac or Linux (Windows: WSL2). At least one provider API key if you want live model calls.
git clone https://github.com/pandeyvishwas51-oss/ArrowCode.git
cd ArrowCode
./install.sh
./run.shinstall.sh checks Python 3.10+, creates ./venv, installs requirements.txt, and chmod +x run.sh. Dependencies: textual, litellm, fastapi, uvicorn, GitPython, httpx, pydantic, rich.
If ./install.sh is not executable:
chmod +x install.sh run.sh
./install.sh
./run.sh- Run
./run.sh. - On first launch, paste at least one key (OpenAI, Anthropic, Gemini, or Azure key + base URL + version). Click Save & Launch.
- Watch the war room. With working keys, the Researcher should add 3 tasks. With no keys, you still get the 5 demo backlog items.
- Press
a, type a task title, Enter. That row ispendingand the next loop cycle will pick it. - Press
wto print the webhook URL, then in another terminal:
curl -X POST http://127.0.0.1:8765/webhook \
-H "Content-Type: application/json" \
-d '{
"service": "billing-api",
"error": "NullReferenceException at handler.py:42",
"severity": "critical"
}'Health check: curl http://127.0.0.1:8765/health returns {"status":"healthy"}.
Edit config.json (after one successful setup) to change models or timing:
{
"agent_models": {
"researcher": "gemini/gemini-1.5-pro",
"architect": "claude-3-5-sonnet-20241022",
"reviewer": "claude-3-5-sonnet-20241022",
"implementer": "gpt-4o",
"sre": "gpt-4o-mini"
},
"engine": {
"loop_interval_seconds": 30,
"auto_commit": true,
"webhook_port": 8765
}
}Set "auto_commit": false if you want LLM log lines without git commits.
In config.json at the repo root. That file is gitignored. Config.get_litellm_env() also reads OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, and the Azure variables if the JSON fields are empty.
It walks the tree for a short path list (up to 80 files) and sends those paths in the prompt. It does not attach file bodies. Treat that listing as the whole "scan".
It will not apply the generated diff. If auto_commit is true and GitPython sees dirty or untracked files, it creates a real commit with message arrowcode(implementer): <title>. A clean tree gets a simulated SHA in the log only. ensure_repo() on dashboard mount will git init if .git is missing, so run this from a repo you are willing to commit in.
Yes. s reopens the key form. Model ids are in config.json under agent_models. Any LiteLLM model string your key supports is valid.
3.10 and newer. install.sh refuses anything older.
Not in this repo. Unit tests live under tests/ and do not need API keys:
python -m pip install -r requirements.txt pytest
python -m pytest -qChange engine.webhook_port in config.json and restart. Bind address is 127.0.0.1 only.
The key for that role's model is missing or invalid. Press s and save a matching provider key. The loop keeps running in simulation for failed calls.
Use a real TTY (ssh -t) and a terminal that handles Textual. Local is simpler.
MIT. See LICENSE.
MIT.