aegis-desktop
v1.0.0
Published
Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.
Readme
AEGIS Desktop

A standalone Electron chat app over the AEGIS API, with an agentic tool loop: the model can read, write, and edit files, list directories, glob, grep, run shell commands in a persistent session, and delegate whole sub-tasks to subagents. It does not require Claude Code.
Install
Prebuilt binaries (AppImage / MSI+NSIS / dmg) ship with each release, and the package is on npm:
npm install -g aegis-desktop # requires Node 18+
aegis # launchEither way, on first launch:
- Open Settings in the sidebar.
- Paste an AEGIS key into the API key row — get a free one at
https://aegiscloud.org.
AEGIS_API_KEYis picked up from the environment if you would rather not paste it. - Pick a model class from the picker at the bottom of the composer.
The published package is aegis-desktop
on npm; this repo is its source. Two of the three classes bill your AEGIS
account — either the pooled margin, or the BYOK handling fee on top of your own
provider key; the free Local class runs on your own hardware and bills
nobody.
Using it
Everything is in the window — there is no slash-command line to learn. Plain text in the composer is a prompt.
| Where | What it does |
|---|---|
| Composer | Type and press Enter. Shift+Enter for a newline. |
| Class picker | Bottom of the composer — switches the route mid-conversation, context intact. |
| Model dropdown | Next to it — pins a model id for the selected class, or leaves it on "server default (auto)". |
| Settings (sidebar) | API key, provider keys, tool-confirmation toggle. |
| Queue card (sidebar) | The unattended work queue — same file the CLI drains. |
| Quick Launcher card | Enable/rebind the global hotkey. |
| remember (on any reply) | Pin that message to cross-machine cloud memory. |
Approval cards appear inline before exec, writeFile or editFile runs:
Allow once, Allow for this session, or Deny. Turn them off entirely
with Settings → "Confirm before running tools" — on by default.
Model classes
Pick from three routes on the model-class picker, switchable mid-conversation with context intact:
| Class | Transport | Key held in | Billed? |
|---|---|---|---|
| Aegis Cloud | aegiscloud.org — the pooled route, on the server's own default model; the pool auto-routes across whichever providers are live | main process | yes — pooled margin |
| Bring your own key | your provider key, relayed by AEGIS — see below | main process | yes — flat AEGIS handling fee |
| Local | a model on your own machine or network — your Ollama daemon at localhost:11434 by default | n/a — no key | no — free, no AEGIS account needed |
The Cloud class offers everything the server advertises: the pooled brain
first, as one row (Nexus — the nexus-brain/aegis-brain and the
-smart/-neo spellings the server sends as aliases of it are folded into that
one row, because they all name the same route), then every per-provider id the
same catalogue lists, so deepseek, anthropic, groq, openai … can be
pinned directly. Both hosts build that list from one shared rule
(client/brain-catalog.js, offerableCatalog), so the GUI and the terminal
cannot show one account two different lists. A pinned per-provider id is still a
pooled turn: your account key pays the pool's margin, and the plan and
balance gates on /api/v1/chat/completions apply to it exactly as they do to
the brain row. The same models are also reachable through Bring your own
key below — your provider key, and a flat handling fee instead of the pool's
margin.
Get a free AEGIS key at https://aegiscloud.org for the Cloud and BYOK
classes. The generic OpenAI-/Anthropic-compatible direct-dial lanes that used
to live here were removed for good — those providers are reachable only
through Bring your own key, which bills the handling fee. Local is
narrower: it points at a model on your own machine or your own network, and the
endpoint check (desktop/lib/local/local.js) refuses anything public —
fail-closed, run when the base URL is saved and again before a request is
dialled. Your Ollama daemon at localhost:11434 is the default, and any
OpenAI-compatible server on that box — llama.cpp, LM Studio, vLLM — works by
pointing the base URL at it. So there's no vendor to bill and no key to hold.
Bring your own key (BYOK)
For the providers AEGIS does not run in its pool — bring your own key and the models that key unlocks appear as their own entries, one per provider.
- Settings → find the row named
BYOK: <Provider>(OpenAI, Anthropic, DeepSeek, Groq, xAI, Mistral, Gemini, OpenRouter, …), paste your provider key and Save. There is no base-URL field on these rows: a BYOK turn always talks to AEGIS's own relay (/api/v1/byok/chat/completions), which is what attaches your AEGIS key. - Or skip the UI and put the key in
~/.aegiscode/.env(OPENAI_API_KEY=…,ANTHROPIC_API_KEY=…); the app reads that file into the environment at startup, and a provider with no key saved in Settings is resolved from it. - Select the Bring your own key class and pick a model.
A model the catalogue does not name yet. Vendors ship models faster than any
client release, and the relay forwards whatever model string it is handed — so
Settings ends with an Add model row (provider + model id + Add model). It
writes the same ~/.aegiscode/models.json the CLI's /model-add writes, so a
model added here appears in the terminal's /models immediately and one added
there appears in this picker on its next refresh; the row prints the file path,
which is also the bulk-edit path. The provider id is offered from the live
catalogue but you may type one, and a provider the relay takes no key for is
refused rather than stored (a declaration nothing can attach a key to would show
up in no picker at all). Each declared model gets a Remove row.
What it costs. AEGIS pays your provider nothing on this lane, so there is no
provider cost to take a margin on — instead your AEGIS account is charged a flat
handling fee per 1k tokens, for the routing, prompt assembly, caching, tool
bridging and uptime that still happen server-side. The rate is the server's own
(published on GET /api/v1/byok/providers) and is shown in Settings directly
under the provider rows; it is never hardcoded here, so it cannot drift from the
ledger that bills you. It is deliberately below the pooled price for the same
traffic — BYOK stays the cheaper lane, it just is not the free one.
Two keys are needed. Your provider key and an AEGIS account key: the handling fee has to be billed somewhere. With no AEGIS key connected the class shows every model but says exactly that, rather than failing opaquely.
And the relay decides whether your bank has to be funded. The route
publishes its own policy on GET /api/v1/byok/providers (fee.gate_mode,
fee.require_account, fee.require_balance) and the app reads it there rather
than assuming one: in the balance mode the live deployment runs, a BYOK turn
needs an account with a balance, and a zero bank answers 402 Insufficient
balance. Top up to continue using BYOK. — your provider key is still valid, the
AEGIS handling fee is what ran out (see the top-up surface). In the softer
owner mode a named account is enough and an unfunded fee is recorded as owed.
An anonymous caller (no AEGIS key at all) is refused in every enforcing mode,
which is the released clients' own gate too — this class does not serve a turn
nobody can be billed for.
The agentic tool loop works here too. The relay's BYOK route accepts the
OpenAI-shaped tools/tool_choice and translates them to each provider's own
wire format (Anthropic-backed callers get real tool calls), so file, shell and
search access below are available on this lane — the engine sends the same tool
schema it sends on the pooled lane. What the class does not get is the cloud
drain in the autonomous queue, which is Aegis Cloud-only (see
desktop/lib/local/autonomous.js) — that is the fan-out Pro buys, not the tool
loop.
Screenshots
Captured from the real app, not mocked up: the stills come from
scripts/capture-marketing-shots.mjs and the demo at the top of this page from
scripts/record-demo-gif.mjs. Both drive the actual renderer against a local
stub and refuse to publish an asset that fails their assertions.
The model-class picker — all three routes, switchable mid-conversation:

A completed answer in the transcript:

The tool-call approval card with a proposed edit — what blocks exec,
writeFile and editFile until you allow, allow for the session, or deny:

Tools available to the model
| Tool | What it does |
|---|---|
| readFile · writeFile · editFile | File access scoped to the working directory |
| listDir · glob · grep | Navigate and search a tree |
| exec | Run commands in a persistent shell session |
| task | Delegate a self-contained sub-task to a subagent |
Before exec, writeFile, or editFile runs, a diff/approval card asks you
to confirm — approve once, approve for the rest of the conversation, or deny.
Flip Settings → "Confirm before running tools" off if you'd rather the
agent run mutating tool calls without asking; it's on by default.
Conversations persist locally and sync to AEGIS cloud memory via a pending queue that flushes on each "Sync now" or heartbeat retry. The remember button on any assistant reply pins that message to cross-machine memory — queued locally if you're offline.
Autonomous queue
The unattended work queue, shared with the CLI: tasks are appended to
~/.aegiscode/queue.jsonl and drained later, one at a time, with tool approval
disabled — there is nobody there to click an approval card. The desktop
sidebar's queue card and aegiscode autonomous are two views of the same file.
aegiscode autonomous add "fix the flaky retry test" --cwd ~/repo --commit
aegiscode autonomous list
aegiscode autonomous run # drain one task, then stop
aegiscode autonomous proceed --max 3 # drain up to three
aegiscode autonomous reconcile --auto # queue the next unfinished phase of your plan file, then drain it
aegiscode autonomous retry <id> # put a finished task back
aegiscode autonomous clear --all # empty the queueA task's text is stored verbatim — add "fix --json in the parser" is a task,
not a flag. Drains commit only the paths that task's own tool layer wrote, so a
drain never sweeps a peer's in-flight edits into your commit.
Aegis Cloud only
A queued task runs on the pooled brain — nexus-brain (alias
aegis-brain) — for the same reason the Claude Code plugin is cloud-only: the
queue hands work to a loop with no human in it, and the pooled class is the one
the server can route, budget, and bill on its own. A task that states another
model id is refused where you can still see it, instead of failing minutes into
a drain as an opaque server error:
| Where the model was stated | What happens |
|---|---|
| autonomous add --model <id> | refused, with the reason, at add time — nothing is queued |
| A hand-edited queue.jsonl | refused pre-flight by the worker (ms: 0), before any turn is billed |
| AEGIS_MODEL=<id> in the environment | ignored for queue runs, and reported as a note — that variable is shared with the interactive surfaces, which do run direct providers |
| nothing stated | nexus-brain |
What a queued task costs
The queue has two shapes, and the cheap one is the default:
| | Single pass (default) | Fan-out (opt in) |
|---|---|---|
| Provider calls | 1 | workers + 1 (investigation passes, then synthesis) |
| Effort rung | medium | high |
| How to ask for it | nothing — it is the default | AEGIS_AUTONOMOUS_FANOUT=1 |
An earlier version sent every queued task as a fan-out at the priciest rung:
one queued line could become several reasoning calls plus a synthesis, all at
high. Making the fan-out opt-in, and letting effort follow the shape of the
task rather than always topping out, removes the worker multiplication and
roughly halves the budget on a one-line task. --effort (or
AEGIS_AUTONOMOUS_EFFORT) still wins outright, and --workers N is only sent
when you are actually fanning out.
Known gap: there is no
--fanoutflag yet — the opt-in is the environment variable orsinglePass: falsein the task record.--single-passstill parses, but it now agrees with the default instead of overriding it.
A turn that runs out of rounds keeps its work
The tool loop runs against a round horizon: 24 rounds for an interactive turn
(AEGIS_CHAT_MAX_ROUNDS), 40 for a queued one
(AEGIS_AUTONOMOUS_MAX_ROUNDS). A model that reached it mid-turn used to lose
everything it had assembled, because the cap was turn state.
The horizon is now session state, held in lib/local/session-rounds.js. When
a turn stops at the cap the interruption is filed against the session, and the
next message in that conversation is prefixed with a continuation preamble —
cut off after N tool rounds; continue, do not restart — along with
max(4, ⌈horizon/4⌉) extra rounds so re-orientation does not eat the new
horizon. The resume is announced in the transcript, so it is visible rather
than silent.
- In-memory and process-local (30-minute TTL, 64 entries) — it never leaves the process and never touches disk.
- Claiming an entry consumes it: one resume per interruption, so a chain of interruptions is a chain of deliberate asks, never an automatic loop.
- A stated horizon always wins; the ledger only pads its own default.
- A caller that mints a fresh session key every turn adopts the most recent held entry (bounded to 10 minutes) instead of losing the work.
Keyboard shortcuts
In the main window
| Shortcut | Action |
|---|---|
| Cmd/Ctrl+N | New chat |
| Cmd/Ctrl+K | Open search (memory inspector) |
| Cmd/Ctrl+S | Save as… (export the open session as Markdown) |
| Cmd/Ctrl+R | Reload |
| Cmd/Ctrl+Shift+I | Toggle DevTools |
| Enter | Send the composer prompt |
| Shift+Enter | Newline in the composer |
| Esc | Close the memory inspector overlay |
Global quick launcher
Cmd/Ctrl+Shift+Space (default, configurable in the sidebar's Quick
Launcher card) toggles a small, frameless, always-on-top prompt window near
your cursor — from anywhere on the desktop, even when AEGIS Desktop isn't the
focused app. It streams a one-shot answer over the same transport the main
window uses, with the agent's tool-calling loop turned off (no file/shell
access, no approval prompts) — just a fast question and an answer.
| Shortcut | Action |
|---|---|
| Cmd/Ctrl+Shift+Space (default, configurable) | Toggle the quick launcher |
| Enter | Ask the typed prompt |
| Shift+Enter | Newline in the prompt |
| Cmd/Ctrl+Enter | Add the current answer to the main window as a new chat turn |
| Esc | Close the quick launcher |
| (click away) | Also closes it — closing never steals focus from whatever window had it before |
Packaged builds (the installed app) always register the global shortcut.
Dev runs (npm start / electron .) do not, unless you turn on "enable
global shortcut" in the sidebar's Quick Launcher card — this keeps a local
dev session from silently grabbing a systemwide hotkey. If the configured
accelerator is already claimed by another application, registration fails
gracefully: a warning is logged to the main process console and the
Quick Launcher card shows the reason instead of the app crashing or hanging.
Deep links
The app registers an aegis:// protocol handler:
| Link | What it does |
|---|---|
| aegis://open?session=<id> | Resumes a saved session |
| aegis://new?prompt=<text> | Starts a fresh chat with that prompt pre-filled |
Run from source
A source checkout is available to contributors with repository access only — the desktop host is not a public repository. From the checkout, run this directory directly:
cd desktop
npm install
npm startBuild a distributable
npm run dist # packaged app (AppImage / MSI+NSIS / dmg)
npm run dist:dir # unpacked dir, for quick testingChecks
npm run check # node --check every main-process + renderer file
node ../test/desktop-shell.mjs # headless IPC smoke test (no Electron binary needed)Structure
main.js Electron main process — window + IPC shell only
preload.js Context-isolated IPC bridge exposed to the renderer
renderer/ UI (vanilla JS, no framework)
lib/local/ Model classes, agentic tool loop, prompt
lib/local/queue.js The shared work queue (~/.aegiscode/queue.jsonl)
lib/local/autonomous.js The unattended worker — directive, digest, commits
lib/local/session-rounds.js Session-scoped tool-round ledger (in-memory)
lib/sync/ Local session/memory persistence + sync queue
vendor/aegis.js The AEGIS transport client (thin — no engine logic)
bin/aegis.js `aegis` CLI entry point for the global npm installThis directory is part of the aegiscode-plugin monorepo,
which also ships a Claude Code plugin and the shared client/aegis.js
transport over the same AEGIS backend — see the repo root for that fuller
architecture picture, and cli/README.md for the terminal
host over the same engine.
One source of truth. This directory is it. The npm package
aegis-desktop is built from a
git subtree split of it — same code, published without the rest of the
monorepo. Edits land here and are synced out; nothing is authored elsewhere.
Built by Niklas Borneklint — aegiscloud.org · @aegisinfo
Part of the ÆGIS ecosystem.
