Open-source local Grok Bot that runs on NVIDIA DGX Spark. Desktop + iOS, local inference.
Overview
An open-source, local Grok Bot-like desktop assistant. Named teammates, shared computer, skills and routines — running entirely on an NVIDIA DGX Spark (GB10 Grace Blackwell superchip, 128 GB coherent unified memory). Inference stays on the desk. There is no cloud LLM. Snorlax-Bot is an independent recreation from the public product shape of Grok Bot (named bots, a persistent computer, skills/routines, MCP connectors, question widgets, desktop + iOS). It does not include, and is not derived from, any Grok Bot source. Grok Bot is a team of always-on agents with a computer of their own in the cloud. Snorlax-Bot is that product idea turned inward: the DGX Spark is the computer. Bots live on your LAN, talk through a thin local runtime, and (in later versions) browse, click, schedule, and call tools without sending tokens off-box. v0.31 is still small: . Header click opens agent identity (PATCH) or the channel member list.
README
Snorlax-Bot
An open-source, local Grok Bot-like desktop assistant. Named teammates, shared computer, skills and routines — running entirely on an NVIDIA DGX Spark (GB10 Grace Blackwell superchip, 128 GB coherent unified memory). Inference stays on the desk. There is no cloud LLM.
Founder: Chinghau Chu. License: Apache-2.0.
Snorlax-Bot is an independent recreation from the public product shape of Grok Bot (named bots, a persistent computer, skills/routines, MCP connectors, question widgets, desktop + iOS). It does not include, and is not derived from, any Grok Bot source.
Why this exists
Grok Bot is a team of always-on agents with a computer of their own in the cloud. Snorlax-Bot is that product idea turned inward: the DGX Spark is the computer. Bots live on your LAN, talk through a thin local runtime, and (in later versions) browse, click, schedule, and call tools without sending tokens off-box.
v0.31 is still small: **named teammates + identity pane + group channel threads
- chat with @mentions + report-back + extra channels + built-in tools + a
thin desktop Computer pane + a runtime MCP client + question widgets +
skills and cron/webhook/Slack/GitHub routines (Add / Remove + enable/pause + Copy webhook URL; Slack/GitHub listeners when that plugin is connected) + MCP connect chrome
(Settings plugins list +
kind=connectcard) + assistant markdown + MCP Add custom (Settings POST / DELETE; no separate disconnect) + curated plugin catalog (Settings Catalog Slack/GitHub Add; not a store) + chat attachments (composer paperclip / drop; user-right image + file chips;attachmentIdsin that turn) + agent-sent attachments (LEFTkind=messagereuses that chrome; runtime binds write_file / screenshot) + video attachments (composer drop/pick; 220×160 player;kind=video; 50MB; not fed to the model) + watch-video tool (watch_video{ attachmentId };Watched {name}on the existing kind=tool line; no auto-inject; desktop/iOS idle) + create agent/channel tools (create_agent/create_channelwrapPOST /v1/agents;Created {name}tool line; 项目 / 员工 seed skill) + IME-safe composer Enter (does not send while composing) + composer clipboard paste (Cmd-V / Ctrl-V / paste event of image/file/video → same pending chips; text-only paste stays in the field) + Copy / Regenerate / Speak on assistant LEFTkind=message(Copy 1:1 and channel; Regenerate 1:1 latest only;{ regenerate: true }; Speak is local piper viaPOST /v1/speak, no autoplay) + mermaid diagrams on completed LEFTkind=message(fencedmermaid; invalid falls back to fence chrome) + TeX math on completed LEFTkind=message(inline\( \)/ block$$; KaTeX; invalid falls back to monospace source) + stick-to-bottom while streaming (follow within ~64px; freeze on scroll-up; Send / Regenerates re-arm; 12px muted Jump to latest) + short multi-bubbles on completed LEFTkind=message(split on blank lines after the stream completes; Copy / Speak / Regenerates on the last bubble) + same-turn consecutive LEFT bubbles 6px/6pt (different turns / after tool-widget-approve-connect stay 12px/12pt) + Stop generating mid-stream (12px/12pt muted Stop at the chat-column bottom; client abort; keep partial LEFT as completed; composer stays focused) + waiting ··· until first token (LEFT 12px/12pt muted pulsing ··· after Send until the first assistant token; Reduce Motion static) + streaming caret on the growing LEFT bubble (12px/12pt muted blinking caret after the first token until complete / Stop) + compact tool traces (2+ consecutive kind=tool → 12px/12pt mutedN tools+ chevron; default collapsed) + identity-pane Box computer preview (Bearer PNG) + desktop Box takeover (Open / Done; pointer/key in 1280×800) + teach-a-task (Record / Stop / Save as skill inside takeover) + skill markdown editor (identity-pane Skills list + Edit sheet) + iOS Open/Done takeover (same v0.15 session protocol) + iOS Record (same v0.16 record protocol on the takeover bar) + composer/skill autocomplete (1:1 only; channel/is plain text) + blank New skill (identity-pane Add →POST { name, body })**. Header click opens agent identity (PATCH) or the channel member list. Agent identity lists routines with a live enable/pause switch, Add, Remove, and Copy for webhook URLs, then skills with Add / Edit / Remove. 1:1s are user ↔ that agent only; a user@chip(or agent DM) opens a handoff thread in a channel (seedsnorlax-bot-groupby default). B answers in the thread; A reports back in A’s 1:1 as A. Seedsnorlax-botcan be deleted (no auto-reseed). Users can create more channels. Agents can list/read/write files, run a workspace shell (no extra network), search or fetch the web, and call MCP tools the runtime loaded from~/.snorlax-bot/mcp.json— the runtime owns that loop and auto-runs tools; clients never call the model, the tools, or MCP. Channel work lives in a sandbox under~/.snorlax-botwhen the channel’s shared-project toggle is on (default off); otherwise each agent uses its own workspace. Desktop shows that sandbox as a 320px right pane (file tree + text preview; collapsible — 12px seam chevron, default collapsed). The agent identity pane shows a live 16:10 computer preview (Bearer PNG of the runtime-owned 1280×800 display). Desktop Open takes over that display (overlay on chat + info pane; Esc / Done). While driving, desktop Record captures pointer/key and Save as skill writes SKILL.md (v0.9 list). iOS matches the preview in the agent sheet and Open is a full-screen takeover (tap the 16:10 shot or 12pt Open; Keyboard + Done). While driving, iOS Record captures pointer/key and Save as skill writes SKILL.md (same v0.16 HTTP). No file browser. No VNC / separate Box window. That slice is meant to actually run on a laptop or a Spark, with a mocked model backend when a 70B-class checkpoint is not present.
Hardware target
| DGX Spark (intended) | |
|---|---|
| Superchip | GB10 Grace Blackwell |
| CPU | 20-core Arm (aarch64) |
| GPU | Blackwell, compute capability 12.1 (sm_121) |
| Memory | 128 GB LPDDR5x, coherent unified CPU+GPU pool |
| Intended models | ~70B FP8 by default; ~200B-class locally; two Sparks linked for ~405B |
The 128 GB pool is shared by the OS, vLLM weights, KV cache, the FastAPI runtime, and (later) the sandbox computer. Serving flags must leave headroom — see docs/architecture.md.
Repository layout
desktop/ Tauri + TypeScript chat client
ios/ Swift/SwiftUI companion (list, chat, settings)
runtime/ FastAPI agent runtime (in front of vLLM)
protocol/ OpenAPI contract for /v1
docs/ Architecture, Mac-local oMLX recipe, next tickets
Architecture (one paragraph)
Clients never talk to the model server. A thin FastAPI runtime owns agents,
transcripts, and LAN bearer-token auth. It sits in front of oMLX
(Mac-local OpenAI-compat) or vLLM (Spark); TensorRT-LLM is a later swap
behind the same interface. SQLite on disk holds agents and messages. The
seeded agent id is snorlax-bot (PATCH-able; DELETE 204, no auto-reseed). Seeded channel snorlax-bot-group DELETE is 204, no auto-reseed. Bind is 127.0.0.1 until a token
exists, then 0.0.0.0 so a phone on the same LAN can reach the host. Details:
docs/architecture.md. Mac-local recipe:
docs/mac-local.md. Locked HTTP contract:
protocol/openapi.yaml (copy: runtime/openapi.yaml).
How to run locally (v0 vertical slice)
You do not need a DGX Spark or a cloud LLM. The default inference backend
is mock (CI and first boot). On a Mac, point the runtime at local oMLX.
On a Spark, use vLLM. Clients always talk to :8787, never :8000.
1. Runtime
Python 3.11+.
cd runtime
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
snorlax-runtime
On first start the process:
- Creates a data directory (default
~/.snorlax-bot, override withSNORLAX_DATA_DIR) - Writes
~/.snorlax-bot/tokenand~/.snorlax-bot/snorlax.db - Seeds agent
snorlax-bot(name Snorlax, title Assistant) and channelsnorlax-bot-group(name Snorlax-Bot) on a first empty DB. Deleting the seed agent or the seed channel does not reseed (never recreatesnorlax-bot-group). After seed channel delete, clients select an agent first if any remain, else a remaining channel. Tool workspaces are created lazily under~/.snorlax-bot/workspaces/. - Listens on
127.0.0.1:8787until that token file exists, then0.0.0.0:8787on later launches.SNORLAX_TOKENoverrides the file. Clients useSNORLAX_URL+SNORLAX_TOKEN; they never read the Spark disk.
export SNORLAX_TOKEN=''
curl -sS -H "Authorization: Bearer $SNORLAX_TOKEN" http://127.0.0.1:8787/v1/agents
Chat (SSE):
curl -N -H "Authorization: Bearer $SNORLAX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"Hello from the LAN"}' \
http://127.0.0.1:8787/v1/agents/snorlax-bot/messages
Tests:
cd runtime && pytest
2. Desktop (web, then Tauri)
Node 20+.
cd desktop
npm install
npm run dev
Open http://127.0.0.1:1420, paste the runtime URL (http://127.0.0.1:8787
or http://localhost:8787) and the bearer token, then chat with Snorlax.
Loopback URLs persist across reload.
The same UI is wrapped by Tauri for a native window:
cd desktop
npm run tauri dev
3. On a Mac (oMLX)
oMLX is the first-class local OpenAI-compat backend. It is not vLLM. Recipe: docs/mac-local.md.
curl -sS http://127.0.0.1:8000/v1/models # copy data[0].id
export SNORLAX_INFERENCE_BACKEND=omlx
export SNORLAX_OMLX_BASE_URL=http://127.0.0.1:8000/v1
export SNORLAX_MODEL=''
# No API key: runtime does not send Authorization to localhost inference.
snorlax-runtime
Turn API-key auth off in oMLX admin. Desktop Settings: Runtime URL
http://127.0.0.1:8787. Clients never call :8000.
4. On a real DGX Spark
Point the runtime at local vLLM instead of mock or oMLX:
export SNORLAX_INFERENCE_BACKEND=vllm
export SNORLAX_VLLM_BASE_URL=http://127.0.0.1:8000/v1
export SNORLAX_MODEL=meta-llama/Llama-3.3-70B-Instruct-FP8
snorlax-runtime
Default Spark model is 70B-class FP8, config-swappable. v0.25
attachmentIds images are included in that turn; v0.26 agent-sent
write_file / screenshot files bind onto the assistant kind=message.
Legacy MessageCreate.images persist off-model. Video wait.
v0 vs later
See ROADMAP.md. Short version: v0.26 is named agents plus
runtime-owned file/shell/web tools in a ~/.snorlax-bot sandbox, a
thin desktop Computer pane over that sandbox, a runtime MCP client
(stdio + LAN from mcp.json), connect chrome (GET /v1/plugins +
kind=connect), Settings Add custom (POST /v1/plugins + uninstall),
curated plugin catalog (GET /v1/plugins/catalog; Slack/GitHub Add;
not a store),
question widgets in the transcript, cron XOR webhook/Slack/GitHub routines
(Add / Remove on the identity pane; fire a SKILL.md into that agent’s 1:1), assistant markdown
(clients render; content stays a plain string), desktop Box takeover,
teach-a-task (Record inside takeover → SKILL.md), a skill markdown
editor (identity-pane Edit sheet), blank New skill (POST { name, body }),
iOS Open/Record, and
1:1 composer / skill autocomplete (Send loads SKILL.md; channel /
stays plain text), and
v0.25 chat attachments (composer paperclip / drop; user-right image +
file chips; POST /v1/agents/{id}/attachments + attachmentIds), and
v0.26 agent-sent attachments (LEFT kind=message reuses that chrome;
runtime binds write_file / screenshot), and
v0.30 composer clipboard paste (same pending chips; text-only paste
stays in the field; OpenAPI stays 0.18.0), and
v0.31 Copy / Regenerate on assistant LEFT kind=message ({ regenerate: true };
OpenAPI stays 0.18.0), and
v0.44 local TTS Speak on completed LEFT kind=message (POST /v1/speak;
piper; no autoplay; OpenAPI stays 0.18.0), and
v0.45 mermaid diagrams on completed LEFT kind=message (official mermaid
/ iOS WKWebView; invalid falls back to fence chrome; OpenAPI stays 0.18.0), and
v0.46 TeX math on completed LEFT kind=message (inline \( \) / block $$;
KaTeX; invalid falls back to monospace source; OpenAPI stays 0.18.0), and
v0.48 short multi-bubbles on completed LEFT kind=message (split on blank
lines after the stream completes; Copy / Speak / Regenerates on the last
bubble; OpenAPI stays 0.18.0), and
v0.49 optimistic user-RIGHT bubble on Send (text + chips immediately;
upload first; block a second Send; reconcile on success; 4xx/5xx restore
text + chips + muted Couldn’t send.; Regenerates unchanged; OpenAPI stays
0.18.0), and
v0.50 Stop generating mid-stream (12px/12pt muted Stop at the
chat-column bottom, above Jump to latest; client abort of the
in-flight stream; stop appending tokens; partial LEFT stays as
completed; hide Stop when idle; composer stays focused; no
POST /cancel; OpenAPI stays 0.18.0), and
v0.51 waiting ··· until first token (LEFT 12px/12pt muted
pulsing ··· after Send until the first assistant token;
first token swaps to the growing LEFT bubble; Stop / error /
empty reply dismisses the dots; OpenAPI stays 0.18.0), and
v0.52 streaming caret on the growing LEFT bubble (12px/12pt
muted blinking caret at the end of mid-stream LEFT text after
the first token until complete / Stop; never with waiting ···;
Reduce Motion static; OpenAPI stays 0.18.0), and
v0.53 Esc = Stop while generating (window Esc / iOS
UIKeyCommand Escape aborts the in-flight stream the same as
tapping Stop; do not steal Esc during IME or a pending
widget / approve / connect card; on-screen Stop unchanged;
no POST /cancel; OpenAPI stays 0.18.0), and
v0.54 same-turn multi-bubble gap (consecutive LEFT bubbles
from the same completed turn 6px/6pt; different turns /
after tool-widget-approve-connect stay 12px/12pt; mid-stream
and user-right unchanged; OpenAPI stays 0.18.0), and
v0.55 Send muted while generating (Send control muted and
disabled from Send until complete / Stop / error / empty;
Enter does not send; composer stays editable; re-enables
immediately; Stop + Esc unchanged; OpenAPI stays 0.18.0), and
v0.56 compact tool traces (2+ consecutive kind=tool lines
collapse to one 12px/12pt muted N tools + chevron;
default collapsed; single tool unchanged; widget/approve/
connect never fold; OpenAPI stays 0.18.0), and
v0.57 mid-stream plaintext (live LEFT kind=message is
plain text — no live markdown, mermaid, or math; on
complete / Stop markdown renders once then the blank-line
split; Copy / Speak / Regenerates still last-bubble-only;
OpenAPI stays 0.18.0), and
v0.58 Reduce Motion static ··· (OS Reduce Motion on →
static muted ···, no pulse; off → existing pulse; caret
Reduce Motion unchanged; OpenAPI stays 0.18.0), and
v0.59 focus composer after Stop (Stop or Esc abort returns
focus to the composer immediately; desktop window; iOS
only when a hardware keyboard is attached — do not force
the software keyboard; complete / error / empty leave
focus alone; OpenAPI stays 0.18.0), and
v0.60 Esc = Jump when frozen (when the Jump to latest chip
is visible and nothing is generating, Esc activates Jump
— same as clicking the chip; while generating, Esc still
Stop; same IME / pending-card skips as v0.53; desktop
window Esc; iOS hardware Escape only; OpenAPI stays 0.18.0), and
v0.61 Copied feedback (on Copy of a completed LEFT kind=message,
muted 12px/12pt Copied beside the Copy control for 1.2s; Copy
stays in place and clickable; no toast; Speak / Regenerate
unchanged; OpenAPI stays 0.18.0), and
v0.62 focus composer after Jump (chip click or Esc=Jump
returns focus after scroll/re-arm/dismiss; desktop always;
iOS hardware keyboard only — do not force the software
keyboard; complete / error / empty leave focus alone;
OpenAPI stays 0.18.0), and
v0.63 Jump chip fade (appear/dismiss 120ms opacity fade;
Reduce Motion on → instant show/hide; click / Esc=Jump
behavior unchanged; OpenAPI stays 0.18.0), and
v0.68 recall last message (empty composer Up-arrow, or iOS
long-press Edit as new message on your latest bubble, puts
that text in the composer; Escape or clearing restores
empty; no recall while typing or during IME; Esc still Stop
while generating; OpenAPI stays 0.18.0), and
v0.69 per-chat drafts persist (unsent text stays with its
chat across switches and relaunch; desktop localStorage;
iOS UserDefaults; Send clears that chat; Up-arrow recall
only when the composer is empty; OpenAPI stays 0.18.0), and
v0.70 unread chat dot (a 6px accent dot on a sidebar row
when an assistant reply finishes in a chat that is not open,
or while the app/window is not focused; opening that chat
clears it; the dots stay after a restart; OpenAPI stays
0.18.0), and
v0.71 in-chat find (Cmd/Ctrl-F in an open chat shows a find
bar over the loaded transcript; matches highlight;
Enter/Shift-Enter and up/down jump between them; n of m;
Esc closes and clears highlights; iOS search filters and
highlights those loaded messages; no new HTTP; OpenAPI
stays 0.18.0), and
v0.72 quick chat switcher (Cmd/Ctrl-K opens a small overlay
that fuzzy-filters existing agents and chats by name; arrow
keys and Enter switch to that chat; Esc closes; iOS search
field on the chat list narrows chats by name; no new HTTP;
OpenAPI stays 0.18.0), and
v0.73 keyboard shortcuts (Cmd/Ctrl-/ or ? outside a text field
opens a small overlay of the chat shortcuts that already exist;
Esc or a click outside closes it; iOS Gestures & shortcuts
sheet on the chat screen lists long-press actions and
hardware-keyboard shortcuts; no new HTTP; OpenAPI stays 0.18.0), and
v0.74 per-chat reading place (switching away remembers if you
were at the latest messages or parked on an earlier one;
coming back restores that place; within ~64px of the bottom
still opens at the latest; a new reply while you were away
shows Jump to latest; Send, Regenerate, and Jump still snap
to the bottom; session memory only; no new HTTP; OpenAPI
stays 0.18.0), and
v0.75 drag files onto the chat (dragging files over the open
chat shows Drop files to attach; drop adds pending chips like
the paperclip; Esc or dragging away clears it; text drags do
not; iOS drop on the composer adds the same chips; nothing
auto-sends; no new HTTP; OpenAPI stays 0.18.0), and
v0.76 New messages divider (a thin muted New messages line
above the first assistant reply that arrived since you last
viewed that chat; the same not-open or unfocused condition as
the unread dot; it stays put while the chat is open; Send or
coming back with nothing new clears it; your own bubbles and
collapsed tool lines do not anchor it; a day separator stays
above it; desktop local storage; iOS UserDefaults; no
animation; no new HTTP; OpenAPI stays 0.18.0), and
v0.77 unread count outside the chat list (when one or more
chats carry the unread dot, the desktop window title reads
(2) Snorlax-Bot and the dock or taskbar badge shows that
count where Tauri supports it; both clear when nothing is
unread; the iOS chat list title shows the same count; the
app icon badge shows it only when badge permission is
already granted; no new HTTP; OpenAPI stays 0.18.0), and
v0.78 retry a failed send (when a Send fails because the
network or runtime is down, or an inference_unavailable error
arrives before any reply, the user bubble stays in the
transcript, muted, with Couldn’t send · Retry; Retry sends
the same text and attachments again; Remove dismisses it;
no auto-retry; iOS also offers Retry on long-press; no new
HTTP; OpenAPI stays 0.18.0).
Later: full sandbox computer GUI, MCP marketplace catalog,
Slack/GitHub inbound listeners.
Contributing
CONTRIBUTING.md. Next concrete tickets: docs/tickets.md.
License
Apache License 2.0. Copyright 2026 Chinghau Chu.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add chinghauchu/snorlax-bot