A locally-hosted personal assistant server for a household Mac Mini. Each user gets a Personal Agent (PA) that holds conversation, maintains memory across sessions, and delegates specialized tasks to Tool Agents (TAs).
# Install dependencies
pip install -r requirements.txt
# Create server.env with your API keys
cp server.env.example server.env # or create manually — see Configuration
# Start the server
./server.sh
# Open the web UI
open http://localhost:8080Click New Assistant in the sidebar to create your first PA. The agent will introduce itself and ask for your name.
User ──WebSocket──► Personal Agent (PA)
│
┌──────────┼──────────┐
▼ ▼ ▼
help TA clock TA weather TA ...
Personal Agents maintain conversation history, short- and long-term memory, and orchestrate Tool Agents via a tag protocol embedded in the model's output stream.
Tool Agents are stateless handlers for specialized tasks (time, weather, web search, etc.). They are invoked by the PA using <<Q: name\n...\n>> tags and reply with <<A: name\n...\n>> tags.
See DESIGN.md for full design documentation.
config.json (top-level defaults):
{
"model": "omlx:Qwen3.6-35B-A3B-UD-MLX-4bit",
"thinking": true,
"mlx_url": "http://localhost:8000/v1/chat/completions",
"memory_compaction_trigger_size": 4096
}server.env (not committed — create manually):
export MLX_API_KEY=your_mlx_key
export OPENAI_API_KEY=your_openai_keyConfig is deep-merged in this order: config.json → src/agents/personal/config.json → user/<PA>/config.json. Later values override earlier ones.
| Prefix | Description |
|---|---|
omlx: |
Local MLX server (OpenAI-compatible, default) |
openai: |
OpenAI API |
ollama: |
Ollama (stub, not yet implemented) |
Example: "model": "openai:gpt-4o" to use OpenAI instead of a local model.
./server.sh # default port 8080
PORT=9000 ./server.sh # custom port via env
python main.py --port 8080 --reload # direct invocation
python main.py --base /path/to/data # custom PA data directory--reload enables hot-reload for development (default in server.sh).
--base sets the directory where PA data is stored (default: user/).
The interface has a collapsible sidebar (PAs → sessions → TA sessions) and a chat area.
- New Assistant — creates a PA and immediately starts the first session
- + on a PA — adds a new session
- × on a PA or session (hover to reveal) — deletes with confirmation
- ◀/▶ toggle in the chat header — collapses/expands the sidebar
| Command | Description |
|---|---|
/help |
List all commands |
/think [on|off] |
Enable or disable model thinking |
/show [type] |
Show bubbles of a given type |
/hide [type] |
Hide bubbles of a given type |
/status |
Show PA name, session ID, model |
/time |
Show last response timing (TTFT, TPS, tokens) |
/memory [short|long] |
Show memory contents |
/prompt |
Show the full session preamble |
/new |
Start a fresh session (flushes notes to long-term memory) |
/save |
Open session transcript in a new tab |
/close |
Close this session and return to the session list |
/reset |
Restart this session (keeps memory, clears conversation) |
Short-term memory (user/<PA>/<session>/memory.txt) — the PA appends notes during a session using <<NOTE: tags. Restored as a FYI at session startup after a server restart.
Long-term memory (user/<PA>/memory.txt) — shared across all sessions. Written by the PA using <<REMEMBER: tags for important facts, and auto-compacted when it exceeds memory_compaction_trigger_size bytes.
config.json default config
system.txt top-level system prompt
server.sh server start script
server.env API keys (not committed)
main.py CLI entry point
requirements.txt
src/
server.py FastAPI app + REST + WebSocket
session.py PA/TA session lifecycle and registry
models.py model backend abstraction
stream_parser.py tag scanner (<<Q>>, <<A>>, <<NOTE>>, etc.)
config.py JSON deep-merge loader
prompts.py system prompt builder
agents/
personal/ PA implementation
help/ lists available TAs
clock/ answers date/time questions
weather/ weather (placeholder)
user/
<PA-name>/ one directory per personal agent
memory.txt long-term memory
config.json per-PA config overrides (optional)
system.txt per-PA system prompt additions (optional)
<session-id>/ one directory per session
memory.txt short-term memory
log.txt session log
www/
index.html
index.css
index.js
- Create
src/agents/<name>/agent.pywith ahandle_question(question, ta_session) -> strfunction. - Add a
system.txtdescribing the agent's purpose (first line is used as the description by thehelpTA). - Register the import in
_dispatch_ta()insrc/agents/personal/agent.py.
The PA will discover the new agent via the help TA automatically.