Sparkbot is a self-hosted AI chat assistant you deploy on your own server, laptop, or desktop. It is designed to be a full office worker agent — handling chat, file analysis, meeting capture, web search, calendar access, and memory across sessions.
Sparkbot runs anywhere Docker is installed: Windows, macOS, Linux, or a cloud server.
One command:
# Linux / macOS
bash scripts/quickstart.sh
# Windows (PowerShell)
.\scripts\quickstart.ps1Then open http://localhost:3000.
Or manually:
cp .env.example .env.local # add at least one LLM API key
docker compose -f compose.local.yml up --buildDefault passphrase: sparkbot-local
Change it by setting SPARKBOT_PASSPHRASE in .env.local.
python sparkbot-cli.py # interactive
python sparkbot-cli.py --setup # provider keys + model roles
python sparkbot-cli.py "/model gpt-5-mini" # prompt for key if needed, then switch
python sparkbot-cli.py "What's on my calendar?" # one-shot
echo "Summarise my inbox" | python sparkbot-cli.pyRequires Python 3.10+. No extra packages — pure stdlib.
On first run it prompts for the URL and passphrase and saves them to ~/.sparkbot/cli.json.
The CLI does not run models by itself. It connects to a running Sparkbot server.
Use --setup or /setup to send provider API keys and choose the Sparkbot model stack on your own instance.
For terminal-only installs, /model <id> will prompt for the matching provider API key if that model is not configured yet.
Keys are stored on that Sparkbot instance, not in the CLI config file.
On a fresh self-hosted install, Sparkbot opens Sparkbot Controls automatically when no provider/model setup exists yet. That first-run panel is where users:
- paste provider API keys
- choose the model stack
- configure comms channels
- keep the execution gate off by default
See deployment.md for the full Traefik + Docker Compose + Let's Encrypt setup.
To reproduce the website download bundle from committed source:
bash scripts/package-public-download.shDefault artifacts are written to dist/public-download/latest/.
To publish directly to the website download directory, pass --publish-dir /var/www/sparkpitlabs.com/downloads/sparkbot/latest.
For versioned or tag-bound packaging instructions, see docs/public-downloads.md.
- Code interpreter shipped. Sparkbot can now execute Python 3, Node.js, and Bash directly from chat via
run_code(language, code). Subprocess-sandboxed with configurable timeout (default 30s, max 120s). Disable withSPARKBOT_CODE_DISABLE=true. - Named browser sessions added. After logging in to a site,
browser_save_session(session_id, name)persists cookies and localStorage to disk.browser_restore_session(name)reloads them in any future conversation — no re-login required.browser_list_sessions()shows active and saved sessions. - Knowledge base (RAG) shipped.
ingest_document(text, name, source?)stores text or auto-fetches a URL and indexes it with SQLite FTS5 (BM25).search_knowledge(query)retrieves relevant chunks with full-text ranking.list_knowledge()anddelete_knowledge(name)round out the API. Zero new dependencies. - Multi-tool skill hook added. The skill plugin system now supports
_register_extra(registry)so a single.pyfile can register multiple tools (used by the knowledge base plugin). - System prompt expanded to ~400 words. The new
SYSTEM_PROMPTcovers Identity, Collaboration (no echo/repeat), Proactivity, Quality, Boundaries, Tool Philosophy, and Tone in named sections. Loaded frombackend/prompts/system.mdat startup — editable without touching Python code. Override path viaSPARKBOT_SYSTEM_PROMPT_FILE. - Room context injection. Every LLM call now receives a
## Room Contextblock with the room name, optional description, and execution gate state. Sparkbot knows what room it's in without being told.
- Operator UI is now a real control surface. The
/spinepage gained three new operator tabs: Security (break-glass activation/deactivation, guardian status overview, PIN setup guidance), Vault (encrypted secret listing, add/delete with break-glass gate), and Task Guardian (write-mode runtime toggle). The Projects tab now has a "New Project" button and per-project Archive action. - All task mutations now emit Spine events. Previously only
reopenwent through the Task Master adapter; create, complete, assign, and delete now all route through the adapter, making the canonical Spine event log complete for task lifecycle. - Vault, memory, and token guardian now produce Spine events.
vault.secret_added/used/deleted,memory.fact_stored, andtoken_guardian.quota_eventare now wired — the Spine event log covers all major guardian subsystems. - Project management has a REST API.
ProjectExecutiveAdapteris now exposed via HTTP: create, update, archive projects; attach/detach tasks. The canonical Spine project lifecycle is now reachable from any API client or UI. - Security defaults are self-hosted friendly. Break-glass and vault no longer restrict to a hardcoded
sparkbot-userusername. IfSPARKBOT_OPERATOR_USERNAMESis not set, any authenticated human user is an operator (open mode). Restrict by setting the env var. The.env.examplenow documents all guardian security variables with inline setup commands. - Sparkbot can read any URL. The new
fetch_urltool lets Sparkbot retrieve and read page content from any public URL — useful for researching docs, checking pages, or participating in external resources.
- Guardian Spine became a background operating subsystem. It now acts as Sparkbot’s canonical cross-guardian catalog and history layer: structured subsystem events, canonical projects/tasks/events/handoffs/approvals, project lineage and dependencies, Task Master-oriented queue views, operator-global inspection routes, and explicit Memory/Executive/Approval/Task Guardian hooks.
- Roundtable became instance-based and autonomous. Launching Roundtable now creates a fresh meeting room, meetings can continue chair-led without manual user turns between speakers, and the UI exposes ongoing meetings with end/delete controls.
- Sparkbot can inspect its own safe runtime state from chat. Asking what stack/provider/model it is running now returns live operational state such as provider/model routing, Token Guardian mode, fallback status, agent overrides, Ollama reachability, and breakglass status without exposing secrets.
- Breakglass works naturally from chat again. When a task crosses a privileged boundary, Sparkbot now asks for
/breakglass, prompts for the PIN in chat, resumes the waiting action after approval, and supports/breakglass close.
- Public release discipline matured. Sparkbot now ships from a tracked packaging flow with committed-source bundles, checksums, release notes, and traceable public tags through
v1.3.0. - Security hardening shipped publicly. The public release line picked up fixes for unauthenticated user enumeration, IDOR on user update/delete paths, upload path traversal, unsafe inline file serving, and audit-log safety.
- Guardian Auth + Vault went live. Sparkbot now supports PIN-gated break-glass privileged mode plus encrypted vault-backed secret storage for operator workflows.
- Workstation became a real product surface. The workstation UI shipped, gained in-shell navigation, and became the post-login landing path for chat-session users.
- Live terminal landed in workstation. Terminal panels now run real xterm.js + WebSocket PTY sessions in the live internal instance instead of placeholder panels.
- Memory got better, not just larger. Guardian memory now promotes safer learned profile/workflow context with redaction before higher-value memory promotion. This shipped publicly in
v1.3.0. - Autonomy became more evidence-bound. Verifier Guardian now evaluates scheduled runs and interactive high-risk actions, and Task Guardian has bounded retries plus escalation instead of optimistic looping.
Sparkbot is no longer just a self-hosted chat UI with integrations. It now has a clearer public release process, a stronger workstation/control surface, safer adaptive memory, privileged operator controls, and early verifier-backed autonomy.
For privacy and retention details, see PRIVACY.md.
Most AI assistants give the LLM unrestricted access to your email, calendar, Slack, and GitHub the moment you connect them. Sparkbot does not.
Every tool the LLM can call is classified, policy-gated, and logged before it touches anything external. External writes — send email, post to Slack, create a GitHub issue — require explicit confirmation in the UI. Server commands require the room owner to enable an execution gate. The LLM cannot bypass either control. This is architecture, not a config option.
| What competitors do | What Sparkbot does |
|---|---|
| LLM calls tools freely | Policy layer classifies every tool: read / write / execute / admin |
| External writes happen silently | Confirmation modal required before any external mutation |
| No audit trail | Every tool call logged + redacted before write |
| Secrets may leak into logs | Audit redaction strips key-name and token-pattern values at write time |
| Session token in localStorage | HttpOnly Secure SameSite=Strict cookie — never reachable from JavaScript |
| No dependency scanning | pip-audit + npm audit on every push via GitHub Actions |
Full architecture: SECURITY.md
Browser
│
└── nginx (your-domain.com)
├── / → static files (/var/www/sparkbot)
├── /api/ → FastAPI backend (configurable port)
└── /ws/ → WebSocket (same port, upgrade headers)
| Component | Path | Port |
|---|---|---|
| FastAPI backend | /home/youruser/sparkbot-v2/backend |
configurable |
| React frontend | /home/youruser/sparkbot-v2/frontend |
— (built/deployed) |
| PostgreSQL | system service | 5432 |
| nginx | /etc/nginx/sites-available/sparkbot |
80/443 |
Backend
- FastAPI — async Python API framework
- SQLModel — ORM (PostgreSQL)
- litellm — unified LLM routing (100+ providers)
- caldav — CalDAV calendar access
- JWT authentication
Frontend
- React + TypeScript + Vite
- TanStack Router
- shadcn/ui + Tailwind CSS
react-markdown+react-syntax-highlighter(Prism oneDark)
- Streaming responses — token-by-token SSE (
/messages/stream), typing cursor, no waiting - Conversation context — last 20 messages passed as history on every LLM call
- Safe self-inspection — Sparkbot can answer chat questions about its current provider/model stack, Token Guardian mode, routing, Ollama reachability, and breakglass status using live backend state
- Guardian Spine task capture — actionable chat and meeting language is normalized into canonical tracked work with event history, approval state, handoff notes, and markdown mirrors under the guardian data directory
- Markdown rendering — headings, lists, bold, tables, code blocks in bot replies
- Syntax highlighting — fenced code blocks with language detection (oneDark theme)
- Copy-code button — one click to clipboard on every code block
- Message search — full-text search across room history (
/search) - File uploads — images (vision analysis), documents (text extraction + summarisation), other files (10 MB max)
- Voice input — click the mic button to record; Whisper transcribes the audio and the transcript enters the normal LLM pipeline (all policy/tool/confirmation logic unchanged)
- Text-to-speech replies — enable voice mode (speaker button) to hear bot replies spoken aloud via OpenAI TTS
Record a voice message directly in the browser — no extra packages needed.
How it works:
- Click the mic button (between the upload and text input) — browser prompts for mic permission
- While recording the button pulses red and shows a second counter
- Click again to stop — audio is sent to Whisper (
whisper-1) for transcription - The transcript appears as your human message; the bot replies via the normal streaming pipeline
- Enable voice mode (speaker icon after the send button) to have bot replies read aloud automatically
UI controls in the input bar:
| Button | State | Action |
|---|---|---|
| 🎙 Mic | idle → click | Start recording |
🔴 Ns |
recording → click | Stop + send |
| 🔇 VolumeX | voice mode off | Toggle on — replies spoken aloud |
| 🔊 Volume2 | voice mode on | Toggle off |
Voice mode preference is persisted in localStorage.
Guardian Spine is Sparkbot’s background operating subsystem for cross-guardian coordination. It is not just a room-task feature.
Current backend role:
- catalog actionable work from chat messages, agent outputs, meeting artifacts, and room task lifecycle changes
- accept structured subsystem events from other guardians and internal systems
- maintain canonical projects, tasks, task events, project events, handoffs, approvals, lineage, dependencies, and related-work links
- preserve source traceability across chat, meeting, task, memory, executive, approval, and system-originated events
- expose queryable state for Task Master, other guardians, and backend inspection routes
- act as the shared backend operating substrate for work-state across rooms, projects, guardians, and Task Master
Structured subsystem event contract:
SpineSourceReferenceSpineProjectInputSpineTaskInputSpineSubsystemEvent
Built-in integration hooks:
ingest_subsystem_event(...)ingest_memory_signal(...)ingest_executive_decision(...)emit_approval_event(...)emit_breakglass_event(...)ingest_task_guardian_result(...)emit_task_master_action(...)emit_room_lifecycle_event(...)emit_project_lifecycle_event(...)emit_handoff_event(...)emit_meeting_output_event(...)emit_worker_status_event(...)
Task Master contract:
TaskMasterSpineAdapteris the primary backend adapter for execution/assignment logic over Spine- Task Master should read queues and workload state from Spine first, then round-trip assignment/status actions back into Spine
- explicit task lifecycle mutations now route through the adapter for create, assign, complete, reopen, and delete/archive flows in the room task surface
- canonical task mutation paths now fail closed instead of silently swallowing adapter errors in the room task CRUD path
- Task Master is not a competing source of truth; Guardian Spine remains the canonical catalog/history layer
Current built-in producers that emit into Spine:
- chat message and meeting-artifact intake
- room task lifecycle sync and Task Master-style task actions
- Executive guardian decisions
- approval store/consume/discard events
- breakglass request/open/close/failure events
- Task Guardian verifier/run outcomes
- room lifecycle creation events
- structured meeting summary/decision/action-item events
- worker agent status/progress events from agent-style room messages
- explicit handoff events
- explicit project lifecycle events
Task Master-facing derived views now available in the backend:
- open queue
- blocked queue
- orphan queue
- approval-waiting queue
- stale queue
- recently resurfaced queue
- assignment-ready queue
- project workload summary
- missing-source-traceability queue
- missing-project-linkage queue
- executive-directive queue
- recent cross-room events
- high-priority blocked tasks
- high-priority approval-waiting tasks
- stale unowned tasks
- unassigned executive directives
- resurfaced tasks without follow-up
- missing durable linkage
- fragmentation indicators
Read-only room inspection routes:
GET /api/v1/chat/rooms/{room_id}/spine/overviewGET /api/v1/chat/rooms/{room_id}/spine/tasksGET /api/v1/chat/rooms/{room_id}/spine/tasks/orphanedGET /api/v1/chat/rooms/{room_id}/spine/tasks/{task_id}/lineageGET /api/v1/chat/rooms/{room_id}/spine/tasks/{task_id}/approvalsGET /api/v1/chat/rooms/{room_id}/spine/eventsGET /api/v1/chat/rooms/{room_id}/spine/handoffsGET /api/v1/chat/rooms/{room_id}/spine/projectsGET /api/v1/chat/rooms/{room_id}/spine/projects/{project_id}/tasksGET /api/v1/chat/rooms/{room_id}/spine/projects/{project_id}/handoffsGET /api/v1/chat/rooms/{room_id}/spine/projects/{project_id}/eventsGET /api/v1/chat/rooms/{room_id}/spine/task-master/overview
Read-only operator/global inspection routes:
GET /api/v1/chat/spine/operator/producersGET /api/v1/chat/spine/operator/events/recentGET /api/v1/chat/spine/operator/queues/openGET /api/v1/chat/spine/operator/queues/blockedGET /api/v1/chat/spine/operator/queues/approval-waitingGET /api/v1/chat/spine/operator/queues/staleGET /api/v1/chat/spine/operator/queues/orphanedGET /api/v1/chat/spine/operator/queues/missing-sourceGET /api/v1/chat/spine/operator/queues/missing-projectGET /api/v1/chat/spine/operator/queues/resurfacedGET /api/v1/chat/spine/operator/queues/executive-directivesGET /api/v1/chat/spine/operator/projectsGET /api/v1/chat/spine/operator/projects/workloadGET /api/v1/chat/spine/operator/task-master/overviewGET /api/v1/chat/spine/operator/signals/high-priority-blockedGET /api/v1/chat/spine/operator/signals/high-priority-approvalGET /api/v1/chat/spine/operator/signals/stale-unownedGET /api/v1/chat/spine/operator/signals/unassigned-executiveGET /api/v1/chat/spine/operator/signals/resurfaced-no-followupGET /api/v1/chat/spine/operator/signals/missing-durable-linkageGET /api/v1/chat/spine/operator/signals/fragmentation
Canonical rule:
- Guardian Spine’s structured store is the source of truth.
- Markdown mirrors under the guardian data directory are secondary audit/handoff mirrors, not the canonical writer.
- Other guardians may observe, emit events, propose changes, and query state, but they should not silently maintain conflicting canonical work-state truth when Spine already models that domain.
- Remaining intentional exceptions are internal mirror-sync helpers inside Spine itself and low-level canonical-writer functions inside the Spine service; those are implementation details, not alternate sources of truth.
SSE protocol for voice (superset of /messages/stream):
data: {"type": "transcription", "text": "what's the weather in Tokyo?"}
data: {"type": "human_message", "message_id": "..."}
data: {"type": "token", "token": "..."}
data: {"type": "done", "message_id": "..."}
Endpoints:
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/chat/rooms/{id}/voice |
Audio → Whisper → SSE stream (multipart, audio field ≤ 5 MB) |
POST |
/api/v1/chat/voice/tts |
Text → audio/mpeg stream |
Both require authentication. Room membership is enforced on the per-room endpoint.
Upload a PDF, DOCX, TXT, Markdown, or CSV and the bot reads and summarises it. Use the caption field as your prompt — e.g. "What are the action items?" or "Summarise the key findings".
| Format | How text is extracted |
|---|---|
.pdf |
pypdf (text-layer PDFs; scanned/image PDFs return no text) |
.docx |
python-docx |
.txt / .md / .csv |
UTF-8 decode |
Up to 12,000 characters (~3k tokens) are sent to the LLM. Large documents are truncated with a note.
| Command | Description |
|---|---|
/help |
List all commands |
/clear |
Clear local view (server history preserved) |
/new |
Fresh start |
/export |
Download conversation as .md |
/search <query> |
Search message history with highlighting |
/meeting start|stop|notes |
Meeting mode — capture notes, decisions, actions |
/breakglass |
Open privileged mode from chat; Sparkbot prompts for your operator PIN |
/breakglass close |
Close the current privileged session |
/model |
List available AI models |
/model <id> |
Switch to a different AI model |
/memory |
List stored facts the bot remembers about you |
/memory clear |
Wipe all stored memories |
/tasks |
List open tasks in this room |
/tasks done |
List completed tasks |
/tasks all |
List all tasks regardless of status |
/remind |
List pending reminders for this room |
/agents |
List available named agents |
Activated with /meeting start. While active, prefix messages with:
note:→ captured as a meeting notedecided:→ recorded as a decisionaction:→ added as an action item
/meeting stop exports the full notes as a dated .md file.
- Roundtable launches a fresh meeting instance each time instead of reusing one long-running chat.
- Meeting rooms can run in autonomous chair-led mode: framing, specialist perspectives, synthesis, optional refinement, then a final recommendation or action plan.
- The owner can still interrupt at any time, but the room no longer waits for a user reply between every bot turn.
- The meeting stops when it is solved, blocked, looping, ready for approval, or needs owner input.
- The Roundtable UI includes a meetings manager so ongoing meetings can be opened, ended, or deleted.
- Privileged actions do not silently fail or dump users into an API-only workflow.
- If a task requires elevated approval, Sparkbot replies in chat with a breakglass prompt.
- Send
/breakglass, enter the operator PIN, and Sparkbot continues the waiting privileged action after approval. - PIN submissions are handled as chat approval input and are not stored in plaintext chat history.
- Use
/breakglass closeto end the privileged session explicitly.
- Open mode (default): if
SPARKBOT_OPERATOR_USERNAMESis not set, any authenticated human user is a guardian operator. Suitable for single-user installs. - Restricted mode: set
SPARKBOT_OPERATOR_USERNAMES=your-usernamein.envto limit operator access to specific chat usernames. - PIN protection: set
SPARKBOT_OPERATOR_PIN_HASH(generate with the inline command in.env.example) to require a PIN before break-glass privileges are granted. - Vault: set
SPARKBOT_VAULT_KEY(generate with the inline command in.env.example) to enable the encrypted secrets vault. Break-glass activation is required before writing secrets. - All guardian security settings are documented with setup commands in
.env.exampleunder the--- Guardian Security ---section. - The
/spineSecurity tab provides a first-run-friendly GUI for break-glass, vault, and guardian status.
Drop a .py file into backend/skills/ and it auto-loads on the next restart — no other files need editing.
Each skill file must export:
DEFINITION— OpenAI function-calling schema dictexecute—async def execute(args, *, user_id=None, room_id=None, session=None) -> str
Optionally declare POLICY to set guardian scope/action (defaults to read/allow).
backend/skills/
├── example_weather.py # get_weather via wttr.in (no API key)
├── calendar_list_events.py # list Google Calendar events (uses GOOGLE_* OAuth vars)
├── calendar_create_event.py # create Google Calendar event (requires confirmation)
├── news_headlines.py # news_headlines: HN top stories or BBC RSS (no API key)
├── currency_convert.py # currency_convert: live rates via open.er-api.com (no API key)
└── crypto_price.py # crypto_price: BTC/ETH/SOL/… via CoinGecko (no API key)
Set SPARKBOT_SKILLS_DIR env var to change the directory (relative to backend/ or absolute).
Skills are guarded by the same policy/executive/memory stack as built-in tools. Built-in tools always take priority — skills are only reached as fallback.
The bot calls tools automatically mid-conversation — a chip appears briefly in the UI while the tool runs, then disappears as the response streams in.
| Tool | Trigger emoji | Description |
|---|---|---|
web_search |
🔍 | Search the web (Brave → SerpAPI → DuckDuckGo fallback chain) |
fetch_url |
🌐 | Fetch and read the full content of any public URL — lets Sparkbot visit and read websites, not just search |
get_datetime |
🕐 | Current UTC date and time |
calculate |
🧮 | Safe AST-based math evaluator (no eval()) |
create_task |
📋 | Create a task in the current room (with optional assignee + due date) |
list_tasks |
📋 | List open/done/all tasks in the current room |
complete_task |
✅ | Mark a task as done by ID |
calendar_list_events |
📅 | List upcoming calendar events via CalDAV |
calendar_create_event |
📅 | Create a calendar event via CalDAV |
set_reminder |
⏰ | Schedule a reminder (once/daily/weekly) to be sent to this room |
list_reminders |
⏰ | List pending reminders for this room |
cancel_reminder |
⏰ | Cancel a reminder by ID |
gmail_fetch_inbox |
📬 | Fetch recent Gmail messages via Google Workspace API |
gmail_search |
📬 | Search Gmail using Gmail query syntax |
gmail_get_message |
📬 | Read a Gmail message in detail by message ID |
gmail_send |
📤 | Send an email through Gmail API |
drive_search |
📁 | Search Google Drive files and folders |
drive_get_file |
📁 | Read Drive file metadata and text content when available |
drive_create_folder |
📁 | Create a folder in Google Drive |
server_read_command |
🖥️ | Run approved read-only diagnostics on the local server |
server_manage_service |
🛠️ | Start, stop, or restart an approved local systemd service |
ssh_read_command |
🔐 | Run approved read-only diagnostics on an approved SSH host alias |
email_fetch_inbox |
📧 | Fetch N recent (or unread) emails from IMAP inbox |
email_search |
📧 | Search inbox by subject or sender keyword |
email_send |
📤 | Send an email via SMTP |
github_list_prs |
🐙 | List pull requests (open/closed/all) for a repo |
github_get_pr |
🐙 | Full PR details — title, body, diff stats, files, CI checks |
github_create_issue |
🐙 | Create a GitHub issue with title, body, optional labels |
github_get_ci_status |
🔬 | Latest workflow run results for a branch |
notion_search |
📝 | Search Notion pages by keyword |
notion_get_page |
📝 | Read a Notion page (blocks → readable text) |
notion_create_page |
📝 | Create a Notion page with markdown-aware content |
confluence_search |
🏔️ | CQL search across Confluence spaces |
confluence_get_page |
🏔️ | Read a Confluence page (strips storage HTML) |
confluence_create_page |
🏔️ | Create a Confluence page in any space |
slack_send_message |
💬 | Post a message to a Slack channel |
slack_list_channels |
💬 | List public Slack channels |
slack_get_channel_history |
💬 | Fetch recent messages from a channel |
remember_fact |
— | Store a fact about the user for future sessions |
forget_fact |
— | Remove a stored fact by ID |
Tool calling uses litellm's function-calling API (OpenAI format, compatible with all supported models). Up to 5 tool-calling rounds per message.
The bot proactively calls remember_fact when you reveal your name, role, timezone, preferences, or ongoing projects. Curated facts are stored in the user_memories DB table for the /memory UI, and Sparkbot now also uses a vendored Memory Guardian layer to retain redacted message/tool context and inject relevant packed memory into prompts.
/memory— list stored facts (with short IDs)/memory clear— wipe all stored facts- API:
GET /api/v1/chat/memory/,DELETE /api/v1/chat/memory/{id},DELETE /api/v1/chat/memory/
Memory Guardian phase-1 notes:
- durable user memory session:
user:{user_id} - room-context memory session:
room:{room_id}:user:{user_id} - current user-facing
/memoryendpoints still list curated fact memories, while prompt retrieval uses the richer memory ledger
When configured, the bot can read and create calendar events in natural language:
- "What's on my calendar this week?" → calls
calendar_list_events - "Schedule a standup tomorrow at 9am for 30 minutes" → calls
calendar_create_event
Works with any CalDAV-compatible service: Google Calendar, iCloud, Nextcloud, Baikal, Radicale, Fastmail.
Prefix any message with @agentname to route to a specialist:
| Agent | Emoji | Specialty |
|---|---|---|
@researcher |
🔍 | Finds accurate info; uses web search proactively; cites sources |
@coder |
💻 | Clean, working code with explanations |
@writer |
✍️ | Writing, editing, emails, summaries, docs |
@analyst |
📊 | Structured reasoning, data analysis, calculations |
- Type
@in the input to get an agent autocomplete picker - The bot's response shows an agent badge (e.g.
🔍 RESEARCHER) above the text /agentscommand lists all available agents- Custom agents configurable via
SPARKBOT_AGENTS_JSONenv var
Model preferences are per-user (in-memory, resets on service restart). Switch at any time with /model <id>.
| Model ID | Description |
|---|---|
gpt-4o-mini |
GPT-4o Mini — fast, cost-effective (default) |
gpt-4o |
GPT-4o — most capable OpenAI model |
gpt-4.5 |
GPT-4.5 — OpenAI advanced reasoning model |
gpt-5-mini |
GPT-5 Mini — fast, cost-effective next-gen model |
claude-3-5-haiku-20241022 |
Claude Haiku — fast Anthropic model |
claude-sonnet-4-5 |
Claude Sonnet — balanced Anthropic model |
gemini/gemini-2.0-flash |
Gemini Flash — fast Google model |
groq/llama-3.3-70b-versatile |
Llama 3.3 70B via Groq — very fast |
minimax/MiniMax-M2.5 |
MiniMax M2.5 — reasoning + tool calling |
Token Guardian phase-2 note:
- Sparkbot can run a shadow routing pass per prompt and log a
tokenguardian_shadowaudit event with classification, confidence, recommended model, and estimated cost, without changing the live model dispatch yet.
Agent Shield / Executive / Task Guardian phase notes:
- Sparkbot now applies a policy decision to every tool call and logs
policy_decisionaudit entries withallow,confirm, ordeny. - Room-level
execution_allowedis now enforced for server and SSH operations. - High-risk tool calls are wrapped by an Executive Guardian decision journal under
data/guardian/executive/decisions/. - Task Guardian can schedule approved read-only jobs and post their results back into the room.
- Sparkbot DM now includes a controls panel for execution gate, recent policy decisions, and Task Guardian jobs.
- Consumer rollout notes live in
consumer_readiness_checklist.md. - Telegram bridge can run in long-polling mode with only
TELEGRAM_BOT_TOKEN; private Telegram chats map into real Sparkbot rooms, and/approveor/denycan resolve pending confirmations. - Discord gateway bot responds to DMs and @mentions; each channel maps to a dedicated Sparkbot room;
/approveand/denywork identically to Telegram. - WhatsApp Cloud API bridge (via pywa) mounts a webhook at
/whatsapp; user-initiated replies are free-form within the 24-hour session window;approve/denytext commands handle pending confirmations. - GitHub bridge accepts signed webhooks for issue comments and PR review comments;
/sparkbotor@sparkbotin a comment maps the thread into a Sparkbot room, andapprove/denyresolves pending confirmations in-thread. - All three inbound bridges emit an explicit startup status log so ops can confirm enabled vs disabled state immediately after boot.
All configuration is via environment variables. The systemd service file (e.g. /etc/systemd/system/sparkbot-v2.service) is the canonical place to set them for production. A .env file in backend/ works for local dev.
OPENAI_API_KEY=sk-...
SECRET_KEY=<random 32+ char string>
POSTGRES_SERVER=localhost
POSTGRES_DB=sparkbot
POSTGRES_USER=sparkbot
POSTGRES_PASSWORD=...ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=...
GROQ_API_KEY=gsk_...
MINIMAX_API_KEY=...
SPARKBOT_MODEL=gpt-4o-mini # default model for all usersGOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REFRESH_TOKEN=...
GOOGLE_GMAIL_USER=me # optional, default "me"
GOOGLE_DRIVE_SHARED_DRIVE_ID=... # optional, for a shared drive default corpusRecommended Google OAuth scopes on the refresh token:
https://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/gmail.sendhttps://www.googleapis.com/auth/drive.readonlyhttps://www.googleapis.com/auth/drive.file
SPARKBOT_MEMORY_GUARDIAN_ENABLED=true
SPARKBOT_MEMORY_GUARDIAN_DATA_DIR=./data/memory_guardian
SPARKBOT_MEMORY_GUARDIAN_MAX_TOKENS=1200
SPARKBOT_MEMORY_GUARDIAN_RETRIEVE_LIMIT=6SPARKBOT_TOKEN_GUARDIAN_SHADOW_ENABLED=trueSPARKBOT_EXECUTIVE_GUARDIAN_ENABLED=true
SPARKBOT_TASK_GUARDIAN_ENABLED=true
SPARKBOT_TASK_GUARDIAN_POLL_SECONDS=60
SPARKBOT_TASK_GUARDIAN_MAX_OUTPUT=2000
SPARKBOT_GUARDIAN_DATA_DIR=./data/guardianTELEGRAM_BOT_TOKEN=
TELEGRAM_POLL_ENABLED=true
TELEGRAM_ALLOWED_CHAT_IDS=
TELEGRAM_REQUIRE_PRIVATE_CHAT=true
TELEGRAM_POLL_TIMEOUT_SECONDS=45
TELEGRAM_POLL_RETRY_SECONDS=5Notes:
- First version uses Telegram long polling so setup can stay as simple as adding the bot token.
- Telegram is private-chat-first; each Telegram chat is mapped into a dedicated Sparkbot room.
- Sparkbot stores Telegram conversations in the same room history and uses
/approveor/denyfor pending confirmations. - Reminder and Task Guardian room notifications can fan back out to linked Telegram chats.
DISCORD_BOT_TOKEN=
DISCORD_ENABLED=false
DISCORD_DM_ONLY=false # true = DMs only; false = DMs + @mentions
DISCORD_GUILD_IDS= # comma-separated snowflakes to restrict guilds (optional)Setup:
- Discord Developer Portal → New App → Bot → copy token
- Bot Settings → Message Content Intent must be enabled (privileged intent)
- OAuth2 → URL Generator → scopes:
bot+ permissions: Send Messages, Read Message History - Set
DISCORD_ENABLED=trueand paste the token
Notes:
- Each DM channel and each guild channel where the bot is @mentioned maps to a dedicated Sparkbot room.
- Runs as an asyncio task alongside FastAPI — same pattern as Telegram, no extra process needed.
message.contentin DMs is always available; guild messages require the Message Content Intent toggle in the portal./approveand/denyresolve pending tool confirmations (same as Telegram).
WHATSAPP_ENABLED=false
WHATSAPP_PHONE_ID= # numeric Phone Number ID from Meta Developer Portal
WHATSAPP_TOKEN= # permanent system user token (whatsapp_business_messaging scope)
WHATSAPP_VERIFY_TOKEN=sparkbot-wa-verify # must match what you set in Meta portal
WHATSAPP_APP_ID= # optional: for auto webhook registration
WHATSAPP_APP_SECRET= # optional: for auto webhook registration
WHATSAPP_ALLOWED_PHONES= # comma-separated E.164 numbers to allowlist (optional)
PUBLIC_URL=https://your-domain.comSetup:
- Meta Developer Portal → New App → WhatsApp → Add Phone Number
- System User → generate permanent token with
whatsapp_business_messagingscope - Webhook URL:
https://yourdomain.com/whatsapp(pywa mounts this automatically) - Verify Token: match
WHATSAPP_VERIFY_TOKEN; subscribe to:messages
Notes:
- The registered phone cannot be simultaneously used in personal WhatsApp or WhatsApp Business App. Use a dedicated number or Meta's free sandbox test number for development.
- User-initiated replies within the 24-hour session window are free-form text — no templates needed for a conversational bot.
- Business-initiated messages (templates, marketing) are billed per message — not relevant for a reactive chatbot.
approve/denytext resolves pending tool confirmations (same as Telegram/Discord).- Uses pywa 3.8.0 — the only well-maintained Python library for the official Cloud API.
GITHUB_BRIDGE_ENABLED=false
GITHUB_TOKEN= # token used for GitHub tools + posting bridge replies
GITHUB_WEBHOOK_SECRET= # required for X-Hub-Signature-256 verification
GITHUB_BOT_LOGIN=sparkbot
GITHUB_DEFAULT_REPO=owner/repo
GITHUB_ALLOWED_REPOS=owner/repo,owner/another-repoSetup:
- GitHub repo → Settings → Webhooks → Add webhook
- Payload URL:
https://yourdomain.com/api/v1/chat/github/events - Content type:
application/json - Secret: match
GITHUB_WEBHOOK_SECRET - Events:
Issue commentsandPull request review comments
Notes:
- The bridge verifies
X-Hub-Signature-256and ignores repos outsideGITHUB_ALLOWED_REPOSwhen the allowlist is set. - Use
/sparkbot your requestor@sparkbot your requestin a comment to invoke Sparkbot. approve/denyin the same thread resolves pending confirmations.- Each GitHub issue / PR thread maps to a dedicated Sparkbot room and shares the same audit, policy, and approval flow as chat.
SPARKBOT_SKILLS_DIR=skills # path relative to backend/ or absoluteRequires OPENAI_API_KEY (already needed for the default model).
SPARKBOT_TTS_VOICE=alloy # alloy | echo | fable | onyx | nova | shimmer
SPARKBOT_TTS_MODEL=tts-1 # tts-1 (fast) or tts-1-hd (higher quality)SPARKBOT_ALLOWED_SERVICES=sparkbot-v2
SPARKBOT_SERVICE_USE_SUDO=false
SPARKBOT_SERVER_COMMAND_TIMEOUT_SECONDS=20
SPARKBOT_SSH_ALLOWED_HOSTS=
SPARKBOT_SSH_ALLOWED_SERVICES=
SPARKBOT_SSH_CONNECT_TIMEOUT_SECONDS=10
SPARKBOT_SSH_COMMAND_TIMEOUT_SECONDS=30Notes:
server_read_commandis limited to built-in read-only profiles like system overview, memory, disk, listeners, process snapshot, service status, and recent service logs.server_manage_serviceis limited to services listed inSPARKBOT_ALLOWED_SERVICES, requires roomexecution_allowed=true, and uses the chat confirmation modal before execution.ssh_read_commandonly works for SSH aliases listed inSPARKBOT_SSH_ALLOWED_HOSTS.- Task Guardian only schedules approved read-only tools. It does not run arbitrary shell commands.
- If service actions require elevation, set
SPARKBOT_SERVICE_USE_SUDO=trueand give the Sparkbot service user a narrow passwordless sudo rule for the allowed units.
Sparkbot uses a fallback chain: Brave → SerpAPI → DuckDuckGo. DuckDuckGo works out of the box with no API key — web search is available immediately after install. For higher reliability and rate limits, configure one of the paid providers:
BRAVE_SEARCH_API_KEY=... # Brave Search API — free tier 2k req/day, most reliable
SERPAPI_KEY=... # SerpAPI (Google) — fallback
SEARCH_CACHE_TTL_SECONDS=300 # Cache identical queries for N seconds (default 300)
OPENCLAW_CONFIG_PATH=... # Optional: path to openclaw.json to reuse its search keyCALDAV_URL=https://www.google.com/calendar/dav/you@gmail.com/events
CALDAV_USERNAME=you@gmail.com
CALDAV_PASSWORD=your-app-passwordProvider setup:
| Provider | URL | Password |
|---|---|---|
| Google Calendar | https://www.google.com/calendar/dav/{email}/events |
App Password |
| iCloud | https://caldav.icloud.com |
App-Specific Password |
| Nextcloud | https://your-server/remote.php/dav/ |
Account password |
| Fastmail | https://caldav.fastmail.com/dav/ |
App password |
| Baikal / Radicale | Your server URL | Account password |
After changing env vars: sudo systemctl restart sparkbot-v2
Docker (recommended — works on all platforms):
docker compose -f compose.local.yml up --buildWithout Docker (for backend development):
# Backend
cd backend
python3 -m venv venv && source venv/bin/activate
pip install -e .
uvicorn app.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
bun install
bun run dev# Build frontend
cd frontend
bun run build
sudo cp -r dist/* /var/www/sparkbot/
# Restart backend
sudo systemctl restart sparkbot-v2
# Health check
curl https://your-domain.com/api/v1/utils/health-check/| Method | Path | Description |
|---|---|---|
POST |
/api/v1/chat/users/bootstrap |
Auto-create user + DM room, return room_id |
POST |
/api/v1/chat/users/login |
Login with passphrase → sets HttpOnly chat_token cookie |
DELETE |
/api/v1/chat/users/session |
Logout → clears session cookie |
GET |
/api/v1/chat/rooms/{id}/messages |
Room message history |
POST |
/api/v1/chat/rooms/{id}/messages/stream |
Send message, receive SSE stream |
POST |
/api/v1/chat/rooms/{id}/upload |
Upload file, receive SSE stream |
POST |
/api/v1/chat/rooms/{id}/voice |
Voice recording → Whisper → SSE stream |
POST |
/api/v1/chat/voice/tts |
Text → audio/mpeg TTS stream |
GET |
/api/v1/chat/messages/{id}/search?q= |
Full-text message search |
GET |
/api/v1/chat/models |
List available LLM models |
POST |
/api/v1/chat/model |
Set model preference {"model": "gpt-4o"} |
GET |
/api/v1/chat/memory/ |
List stored user memories |
DELETE |
/api/v1/chat/memory/{id} |
Delete a specific memory |
DELETE |
/api/v1/chat/memory/ |
Clear all memories |
GET |
/api/v1/chat/audit |
Recent tool audit log (room-scoped) |
GET |
/api/v1/utils/health-check/ |
Health check → true |
Interactive API docs: http://localhost:8000/docs (or whichever port you configured)
sparkbot-v2/
├── backend/
│ ├── app/
│ │ ├── api/
│ │ │ ├── main.py # Router assembly
│ │ │ └── routes/chat/
│ │ │ ├── llm.py # litellm routing, model registry
│ │ │ ├── tools.py # LLM tool definitions + executors
│ │ │ ├── rooms.py # Room CRUD + streaming message endpoint
│ │ │ ├── messages.py # Message CRUD + search
│ │ │ ├── memory.py # User memory CRUD endpoints
│ │ │ ├── uploads.py # File upload + vision SSE
│ │ │ ├── voice.py # Voice: Whisper transcription + TTS endpoints
│ │ │ ├── model.py # Model switching endpoints
│ │ │ ├── github.py # GitHub webhook bridge endpoint
│ │ │ ├── users.py # Chat user management + bootstrap
│ │ │ └── websocket.py # WebSocket handler
│ │ ├── services/
│ │ │ ├── skills.py # Skill plugin loader (auto-discovers backend/skills/)
│ │ │ ├── telegram_bridge.py # Telegram long-poll bridge
│ │ │ ├── discord_bridge.py # Discord gateway bot bridge
│ │ │ ├── whatsapp_bridge.py # WhatsApp Cloud API webhook bridge (pywa)
│ │ │ ├── github_bridge.py # GitHub issue / PR comment webhook bridge
│ │ │ └── guardian/ # Policy, executive, memory, task guardian
│ │ ├── models.py # SQLModel DB models
│ │ ├── crud.py # DB helper functions
│ │ └── alembic/ # DB migrations
│ ├── skills/ # Drop skill .py files here — auto-loaded on restart
│ │ ├── example_weather.py # get_weather via wttr.in (no API key)
│ │ ├── calendar_list_events.py # list Google Calendar events
│ │ ├── calendar_create_event.py # create Google Calendar event (confirmation required)
│ │ ├── morning_briefing.py # morning_briefing: Gmail + Calendar + reminders digest
│ │ ├── news_headlines.py # news_headlines: HN + BBC RSS (no API key)
│ │ ├── currency_convert.py # currency_convert: live FX rates (no API key)
│ │ └── crypto_price.py # crypto_price: CoinGecko (no API key)
│ ├── pyproject.toml # Python dependencies
│ └── venv/ # Python virtualenv
├── frontend/
│ ├── src/
│ │ ├── pages/SparkbotDmPage.tsx # Main chat UI (streaming, commands, tools, meeting)
│ │ ├── components/chat/
│ │ │ ├── MessageBubble.tsx # Markdown + syntax highlight + copy button
│ │ │ └── ChatInput.tsx # Input bar
│ │ └── lib/chat/types.ts # Shared TypeScript types
│ └── dist/ # Built frontend (deployed to /var/www/sparkbot-remote)
└── uploads/ # Uploaded files storage
Sparkbot v2 has passed a full internal security audit (Phases A–E). Security is the primary differentiator — see SECURITY.md for the full architecture.
User message → Token Guardian → Memory Guardian → LLM
│ tool_calls
▼
Agent Shield (policy)
│ allowed / confirmed
▼
Executive Guardian (journal)
│
▼
Tool executes → audit log
| Control | Implementation |
|---|---|
| Policy layer | Every tool classified read / write / execute / admin; unknown tools denied |
| Write-tool gate | LLM cannot email/Slack/GitHub/Notion/Confluence/Calendar/Drive autonomously — confirmation modal required |
| Execution gate | Server + SSH require room owner to explicitly enable; defaults off |
| Executive journal | High-risk actions written to a decision log before + after execution |
| Audit trail | Every tool call logged (allow/confirm/deny) with redacted args |
| Audit redaction | Secret-pattern keys and token-format values stripped at write time |
| Session tokens | HttpOnly Secure SameSite=Strict cookie — never exposed to JavaScript |
| Response headers | HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, Referrer-Policy |
| Rate limiting | Passphrase login: 10 attempts / 15 min per IP |
| Room authz | All message/upload/audit endpoints gated by membership; non-members get 403 |
| Dep scanning | pip-audit + npm audit on every push and weekly via GitHub Actions |
| Secret scanning | gitleaks pre-commit hook + CI gate |
| Git history | .env purged from all commits via git filter-repo |
- ✅ Streaming SSE responses, markdown rendering, conversation context
- ✅ Slash commands + autocomplete, meeting mode, message search, file uploads
- ✅ Multi-model support via litellm (7 providers)
- ✅ Tool calling framework (25+ tools across web, calendar, email, GitHub, Notion, Confluence, Slack)
- ✅ Persistent per-user memory (DB-backed, injected into system prompt)
- ✅ Calendar integration (CalDAV), task management, proactive reminders
- ✅ Document summarisation (PDF/DOCX/TXT/MD/CSV)
- ✅ Email integration (IMAP + SMTP), GitHub, Notion, Confluence, Slack
- ✅ Multi-agent rooms (@researcher / @coder / @writer / @analyst)
- ✅ Audit log (tool calls recorded, room-scoped,
/auditcommand) - ✅ Voice input + TTS replies (Whisper transcription → LLM pipeline → optional TTS playback)
- ✅ Discord bridge — gateway bot, DMs + @mentions,
/approve//denyconfirmation flow - ✅ WhatsApp bridge — Meta Cloud API webhook (pywa), free-form replies in 24h service window
- ✅ GitHub bridge — signed webhook for issue / PR comments, room mapping,
approve/deny
- ✅ Phase A — access control + secret hygiene
- ✅ Phase B — authentication/session hardening
- ✅ Phase C — runtime correctness
- ✅ Phase D — write-tool gate, audit redaction, HttpOnly cookies, security headers
- ✅ Phase E — dependency scanning CI workflow
- ✅ Proactive notification fan-out — reminders and Task Guardian results now push to Telegram, Discord, AND WhatsApp (was Telegram-only)
- ✅ Google Calendar skill —
calendar_list_eventsandcalendar_create_eventvia existing Google OAuth (no new packages) - ✅ News skill —
news_headlines: Hacker News top stories (tech) or BBC RSS (world/business/science/sports/health), no API key - ✅ Currency skill —
currency_convert: live FX rates via open.er-api.com, no API key - ✅ Crypto skill —
crypto_price: BTC/ETH/SOL/… prices + 24h change + market cap via CoinGecko, no API key
- Key rotation — run after active testing window closes (see
ROTATION_RUNBOOK.md) - Skill marketplace / built-in skill library (filesystem drop-in is the foundation)
- ✅ Task Guardian write-actions —
gmail_send,slack_send_message,calendar_create_eventcan now run on a schedule. Pre-authorized via the existingguardian_schedule_taskconfirmation modal. Opt-in viaSPARKBOT_TASK_GUARDIAN_WRITE_ENABLED=true. - ✅ Morning briefing skill —
morning_briefing: one-shot compound digest combining Gmail unread summary, Google Calendar events, and pending room reminders. Fans out to Telegram/Discord/WhatsApp via Phase 1 fan-out. Perfect daily Task Guardian job.
- ✅ Reply threading UI — hover any message to reply; banner above input shows quoted snippet;
reply_to_idsent in stream POST body; quote preview renders inside bubble - ✅ Message edit UI — hover own messages to edit inline; auto-resizing textarea; saves via PATCH
/api/v1/chat/messages/{room_id}/message/{message_id};· editedtimestamp badge
- ✅ Guardian health card — four color-coded subsystem tiles (LLM, Task Guardian, Token Guardian, Comms/Approvals) replace the plain dashboard summary; shows last run status, pending approvals, and routing mode inline
- ✅ Onboarding copy — three-panel layout: Start here steps, updated first prompts (morning briefing, crypto), "How Sparkbot protects you" explainer (write confirmations, execution gate, Token Guardian shadow mode)
- ✅ Task tool dropdown — added morning_briefing, calendar_create_event, news_headlines, crypto_price, currency_convert to the Task Guardian create-job form
- ✅ Per-room persona — 500-char freetext instruction prepended to every LLM system prompt in the room; saved via PATCH room; textarea + char counter in settings dialog; Alembic migration included
- ✅ Skill marketplace UI —
GET /api/v1/chat/skillslists all loaded plugins with name, description, action_type (read/write), high_risk, and execution_gate flags; settings dialog shows colored chip cards; auto-refreshes on open - ✅ Voice quick-capture —
POST /rooms/{id}/voice/transcribereturns{"text":"..."}(no LLM); voiceMode OFF = mic transcribes and pastes to input; voiceMode ON = original full voice-message flow with TTS readback
- ✅ Spawn Agent in Control Center — "Spawn Agent" section in the settings dialog; select from 11 specialty templates (Data Scientist, DevOps, Legal Advisor, HR Manager, Marketing, Finance, Customer Support, PM, Security Analyst, Technical Writer, or Custom); auto-fills emoji, name, description, and system prompt; name sanitized to lowercase alphanumeric/underscore
- ✅ CustomAgent DB persistence —
custom_agentstable (Alembic migrationc4e8b2f9a017); spawned agents survive restart;created_byFK to user - ✅ Hot-load runtime registry — spawned agents available via
@namemention immediately after creation; no restart required;_RUNTIME_AGENTSdict updated in-process byregister_agent()/unregister_agent() - ✅ Built-in agent protection — DELETE endpoint returns 403 for built-in agents (researcher, coder, writer, analyst)
- ✅ Active agents list — settings dialog shows all custom agents with Remove buttons; built-in agents shown as read-only badges
- Skill scheduler helper — detect "every morning / daily" intent in chat and auto-suggest a Task Guardian job
- Mobile-optimized input — swipe-to-reply, better touch targets
- Mobile UX — swipe-to-reply, larger touch targets