Astrophage
Shipped04.2026 - ∞|7m
A desktop AI assistant whose companions live on the taskbar and drive real OS operations, with every tool call gated by a signed intent token before it is allowed to run.
The measurement
- Measured on
- counts rendered from the source arrays themselves, so the page cannot drift from the repository
- Against a baseline of
- an ungated assistant executes every command the model proposes
What it does
- An Electron application with three surfaces: taskbar companion overlays, a chat window, and a Control Centre for agents, tasks, connectors and settings.
- A three-stage security gate wraps every tool call. The user's message produces a signed intent plan; each proposed call is validated against that token and a local policy; every decision, allow or block, lands in an audit trail.
- The gate is fail-closed and catches intent drift: if the command actually dispatched does not match the plan the model committed to, the token check severs it before execution.
- A policy engine backs the token with deny-list precedence and deep argument inspection across 27 dangerous command patterns, then an optional allow-list whitelist mode.
- 13 in-process MCP tools - clipboard, email composition, calendar, notifications, file read/write/search and shell - routed through one client manager that also accepts external MCP servers over stdio.
- Six model backends sit behind a single session interface: Gemini, Claude, Codex, Copilot, a generic agent session and a vision session driven by screen capture.
How it is put together
Six figures, every one traced from the source files linked beneath it. Where a caption states a count, that count is the length of the array the figure renders.
what the main process owns, and every route a tool call can takean ungated assistant, where whichever tool the model picks simply runs
The companions are the reason the app exists rather than being a skin on a chat box: they sit on the taskbar, positioned against real taskbar geometry, so the assistant is present without owning a window. Note the two columns under main - the model backends and the tool surface are independent, so swapping which model answers does not touch what it can do.
Show the numbers
| Surface | What it is |
|---|---|
| Taskbar companions | Always-on-top character overlays positioned against taskbar geometry. Multiple clones, each with its own role. |
| Chat window | The conversation surface, with multi-terminal sessions running inside it. |
| Control Centre | Dashboard, chat, agents, tasks, connectors and settings, behind a command palette. |
every route between a user's message and a command running, including the refusalan ungated assistant, which executes whatever the model proposes
The branch is the point. A pipeline has one exit; a gate has two, and the refused path is drawn because "we logged the block, with its reason" is what makes the thing debuggable rather than merely restrictive. The check that matters is against the signed token: a call can name a permitted tool and still be severed, because the command no longer matches the plan the model committed to. That is intent drift.
Show the numbers
| # | Stage | What it does |
|---|---|---|
| 1 | Intent planning | The user's message produces a plan; the SDK signs it and returns a short-lived intent token |
| 2 | Tool validation | Every proposed call is checked against that token and against local policy, before dispatch |
| 3 | Audit logging | Allow or block, the decision and its reason land in a trail with arguments sanitised |
the sequence of checks a call is put through, and what each can doan allow-list alone, where a permitted tool can still be handed a hostile argument
- 01Tool deny listAbsolute precedence. A tool in both lists is denied.can block
- 02Argument patternsDeep inspection of command, path and script fields against 27 patterns.can block
- 03Tool allow listWhen set, anything not named is refused. Whitelist mode.can block
- 04DispatchOnly a call that survives all three, and matches the signed intent token.proceeds
every substring the policy engine refuses inside a command, path or script fieldno argument inspection at all, which is the default for most tool-calling agents
- rm -r
- rm -f
- sudo
- chmod
- chown
- /etc/shadow
- /etc/passwd
- /root
- .bash_history
- > /dev/null
- powershell
- cmd /c
- .env
- mkfs
- dd if=
- shutdown
- reboot
- :(){
- fork bomb
- | sh
- | bash
- curl |
- wget |
- > /dev/sda
- kill -9
- killall
- pkill
Read this list critically, because it is short.These are literal substrings. rm -r with two spaces, an absolute /bin/rm, or a base64-encoded payload all walk straight past it. Publishing the list rather than the number is the point: a blocklist you can audit in ten seconds is more useful than a count you have to trust. It earns its place as defense in depth behind figure 2, and it is not the thing keeping the system safe.
Show the numbers
| # | Pattern |
|---|---|
| 1 | rm -r |
| 2 | rm -f |
| 3 | sudo |
| 4 | chmod |
| 5 | chown |
| 6 | /etc/shadow |
| 7 | /etc/passwd |
| 8 | /root |
| 9 | .bash_history |
| 10 | > /dev/null |
| 11 | powershell |
| 12 | cmd /c |
| 13 | .env |
| 14 | mkfs |
| 15 | dd if= |
| 16 | shutdown |
| 17 | reboot |
| 18 | :(){ |
| 19 | fork bomb |
| 20 | | sh |
| 21 | | bash |
| 22 | curl | |
| 23 | wget | |
| 24 | > /dev/sda |
| 25 | kill -9 |
| 26 | killall |
| 27 | pkill |
what the assistant can actually do on the host, by toola chat-only assistant, which can describe an action but not take it
System (4)
- get_current_time
- get_system_info
- show_notification
- open_url
Clipboard (2)
- get_clipboard
- set_clipboard
Productivity (2)
- compose_email
- create_calendar_event
Files (4)
- list_files
- read_file
- search_files
- write_file
Shell (1)
- run_shell_command
These run in-process with zero configuration. The same client manager also connects external MCP servers over stdio, so the tool surface extends without the security gate changing: every call, built-in or external, goes through figure 2.
the model providers behind a single session contracthard-coding one vendor's SDK into the application
- GeminiThe default. Function calling drives the MCP tool loop.
- ClaudeAlternate reasoning backend behind the same session contract.
- CodexCode-oriented session.
- CopilotAlternate provider session.
- AgentGeneric long-running agent session.
- VisionScreen-capture driven; the assistant can look at what is on screen.
Seeing it run
Demo videos ↗Screenshots from the running application.


public/media/astrophage/blocked-command.pngWhere it falls down
The 27-pattern list is a substring blocklist, and substring blocklists are porous: `rm -r` with two spaces, an absolute `/bin/rm`, or a base64-encoded payload all walk past it. It is worth having as defense in depth, but the signed intent token is the control actually doing the work here, and the honest version of this project treats the pattern list as a seatbelt rather than the brakes.
Stack
- JavaScript
- Electron
- MCP
- Gemini 2.5 Flash
- ArmorIQ SDK
- MongoDB
- Node.js