Desir — LLM-Powered Virtual Assistant
Abstract
Apple has lagged behind in the AI race compared to its competitors, while other major technology companies such as Google and Microsoft are spending a large amount of money to improve their own large language models (LLMs). Unlike current LLMs, Siri has permission-based action execution deeply integrated within Apple's ecosystem. In this project, LLMs serve as the core reasoning engine with Siri-like permission controls, built on top of Pydantic AI with an AI delegation model — an orchestrator agent that routes tasks to specialized sub-agents, ensuring controlled and ethical execution. To start with, the project aims to deploy a base set of permission executions as a simple prototype. While people are currently using LLMs as chatbots, this project strives to create a virtual assistant that can act as an actual assistant.
Motivation
The initial challenge in this project was the difficulty of visualizing and drafting a system around such an abstract concept. At the time, there was only one existing model — Clawdbot (now known as Open Claw) — an open-source model with executable permissions. However, it granted the AI the ability to execute sudo commands without asking the user, which raised serious concerns about security and ethical usage, as granting unrestricted system-level access to an AI without user consent poses significant risks.
Despite this, it has opened an entirely new paradigm for agentic AI — rather than merely making recommendations, AI is now capable of directly executing actions in the digital world, functioning as a genuine assistant for users.
Potential Users
Our potential users are those who seek to move beyond traditional chatbots and desire a more ethical, personalized, and voice-driven experience with a friendly user interface. Much like the fictional AI assistant J.A.R.V.I.S. from the Iron Man franchise, this project aims to bring a truly intelligent, action-capable virtual assistant into the real world.
Key Features
- LLM as Core Reasoning Engine — Uses a large language model as the intelligence layer for decision-making and natural language understanding.
- AI Delegation Model — An orchestrator agent delegates tasks to specialized sub-agents, enabling modular and scalable action execution.
- Permission-Based Action Execution — Actions are pre-defined and controlled, preventing unauthorized or unethical operations.
- Voice-Driven Interface — Supports human-like voice interaction for a natural user experience.
- Observability with Logfire — Real-time tracing and monitoring of agent behavior via Pydantic Logfire.
- Security & Ethics by Design — All executable actions are sandboxed to ensure user safety and responsible AI usage.
Architecture Overview
User (Voice)
│
▼
Browser — React + Vite (frontend/)
AudioRecorder → PCM16 base64 → WebSocket (ws://localhost:8765)
│
▼
Python WebSocket Bridge (server.py)
├── Proxies audio ↔ OpenAI Realtime API (wss://api.openai.com)
├── Streams audio back to browser
├── Handles STOP voice command (cancels response mid-stream)
├── Tracks conversation history {User, desir} per session
└── Single dispatch(tool_name, args) → Orchestrator for all server-side tool calls
│
▼
OpenAI Realtime API (gpt-4o-mini-realtime-preview)
├── Server VAD — detects speech start/end automatically
├── Whisper — transcribes user speech
├── Generates audio response + transcripts
└── Calls tools: send_email | schedule_event | search_web |
search_contact | send_imessage | make_call |
changeThemeColor | update_daily_tasks
│
│ Tool call → server.py dispatch(tool_name, args)
│ Prompt format: "<tool_name>: <args as JSON>"
▼
Orchestrator Agent (ai/agents/orchestrator.py, Pydantic AI)
├── System prompt: ai/prompts/orchestrator.md
├── Receives tool_name + full args JSON as a natural-language prompt
├── Routes to the correct sub-agent via delegation tools
├── Injects OrchestratorDeps (history, email address, API keys, event IDs)
└── Returns response string → server.py → OpenAI Realtime → audio to user
│
├──▶ delegate_email(to, subject, body)
│ └── agent1.py — Email Agent
│ ├── send_email(EmailRequest)
│ │ email_type="notification" → styled HTML template
│ │ email_type="user_request" → plain-text message
│ │ └── tools/sending_email.py → Resend API
│ └── register_domain(domain_name) → Resend Domains API
│
├──▶ delegate_calendar(request)
│ └── agent2.py — Calendar Agent
│ ├── list_calendars()
│ │ └── tools/calendar.py → accli calendars --json
│ ├── create_calendar_event(CalendarRequest)
│ │ └── tools/calendar.py → accli create ...
│ ├── update_calendar_event(CalendarRequest)
│ │ └── tools/calendar.py → accli update <id>
│ ├── delete_calendar_event(CalendarRequest)
│ │ └── tools/calendar.py → accli delete <id>
│ └── check_freebusy(CalendarRequest)
│ └── tools/calendar.py → accli freebusy ...
│ * Event IDs saved in OrchestratorDeps.calendar_event_ids
│ * Falls back to [email protected] calendar
│
├──▶ delegate_search(query)
│ └── agent3.py — Search Agent
│ └── search_web(query)
│ └── Serper API (Google Search) → summary
│
└──▶ delegate_communication(recipient, action, message)
└── agent4.py — Communication Agent
├── send_imessage → macOS Messages (AppleScript)
└── make_call → macOS FaceTime / Phone
Frontend-only tools (forwarded directly to browser, no server or agent logic):
changeThemeColor — updates holographic UI color in real time
update_daily_tasks — updates task list displayed on the interface
Observability:
Logfire — traces every session via logfire.span("session")
Session state injected per call (OrchestratorDeps):
history_context — rolling conversation turns {User, desir}
email_address — [email protected]
search_api_key — Serper API key
tom_history_context — static biographical context (ai/prompts/tombio.md)
calendar_event_ids — dict mapping event title → accli event ID, persists
across tool calls within a session so the agent can
update or delete events it created earlier
How a tool call flows end-to-end
- User speaks → OpenAI Realtime transcribes and detects intent.
- Realtime calls a tool (e.g.
schedule_event) with structured arguments. server.pyreceivesresponse.function_call_arguments.doneand callsdispatch(tool_name, args).dispatchbuilds the prompt"schedule_event: {"title": "...", "when": "..."}"and runs the Orchestrator.- The Orchestrator reads its system prompt, identifies the intent as "calendar", and calls
delegate_calendar(request=...). delegate_calendarappends any known event IDs fromdeps.calendar_event_ids, then runs the Calendar Agent.- The Calendar Agent picks the right tool (
create_calendar_event, etc.) and callstools/calendar.pyviaasyncio.to_thread. tools/calendar.pyshells out toaccli, which writes to macOS Calendar.- The result bubbles back up: Calendar Agent → Orchestrator →
dispatch→server.py→ OpenAI Realtime → spoken response to user.
Getting Started
⚠️ This project is currently in the prototype stage and runs on macOS only (calendar and communication tools use macOS-native APIs).
Prerequisites
- Python 3.13+
- Node.js 18+
- uv — Python package manager
- accli — macOS Calendar CLI (
sudo npm i -g @joargp/accli, thenaccli setup) - OpenAI API key (with Realtime API access)
- Resend API key (email)
- Serper API key (web search)
- Logfire token (observability)
Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
Installation
git clone https://github.com/DuongAnh1201/desir.git
cd desir
uv sync
cd frontend && npm install
Calendar setup (macOS)
sudo npm i -g @joargp/accli
accli setup # grant Calendar access when prompted in System Settings
Environment Variables
Create a .env file in the project root:
OPENAI_API_KEY=sk-...
AI_MODEL=openai:gpt-4o-mini
REALTIME_MODEL=gpt-4o-mini-realtime-preview
REALTIME_VOICE=coral
RESEND_API_KEY=re_...
RESEND_FROM=Desir <[email protected]>
SERPER_API_KEY=...
LOGFIRE_TOKEN=...
LOGFIRE_ENVIRONMENT=local
Running
Terminal 1 — Python backend:
uv run python server.py
Terminal 2 — Frontend:
cd frontend && npm run dev
Open http://localhost:5173, click the power button, and speak.
Project Structure
desir/
├── server.py # WebSocket bridge — browser ↔ OpenAI Realtime
├── config.py # Settings loaded from .env
│
├── ai/
│ ├── agents/
│ │ ├── deps.py # OrchestratorDeps dataclass (session state)
│ │ ├── orchestrator.py # Orchestrator agent + delegation tools
│ │ ├── agent1.py # Email agent
│ │ ├── agent2.py # Calendar agent
│ │ ├── agent3.py # Search agent
│ │ └── agent4.py # Communication agent
│ └── prompts/
│ ├── orchestrator.md # Orchestrator routing rules
│ ├── realtime_session.md # OpenAI Realtime system prompt
│ ├── email_agent.md # Email agent instructions
│ ├── calendar_agent.md # Calendar agent instructions
│ ├── search_agent.md # Search agent instructions
│ └── tombio.md # User biographical context
│
├── tools/
│ ├── sending_email.py # Resend API wrappers
│ └── calendar.py # accli CLI wrappers for macOS Calendar
│
├── schemas/
│ ├── orchestrator.py # OrchestratorResult
│ ├── agent1.py # EmailRequest, EmailResult
│ ├── agent2.py # CalendarRequest, CalendarResult
│ ├── agent3.py # SearchResult
│ └── agent4.py # CommunicationResult
│
└── frontend/ # React + Vite holographic UI
Roadmap
- [x] Orchestrator + sub-agent delegation model (Pydantic AI)
- [x] OpenAI Realtime API voice interface (native audio streaming)
- [x] Python WebSocket bridge (browser ↔ OpenAI ↔ agents)
- [x] Single
dispatch()— all tool calls routed through the orchestrator - [x] STOP voice command — interrupt response mid-stream
- [x] Conversation history tracking per session
- [x] Email agent — Resend API (notification HTML + plain user request)
- [x] Email domain registration — Resend Domains API
- [x] Search agent — Serper API (Google Search)
- [x] Communication agent — iMessage + phone call (macOS)
- [x] Observability — Logfire session tracing
- [x] React + Vite frontend with holographic UI
- [x] Calendar agent — create, update, delete, free/busy via accli + macOS Calendar
- [x] Calendar event ID persistence within session (update/delete by name)
- [ ] Custom domain email sending (Resend domain verification flow)
- [ ] Expand sub-agent action library
- [ ] User testing and feedback
Contributing
Contributions are welcome! Please open an issue or submit a pull request for any improvements or suggestions.
License
This project is licensed under the MIT License.
Log in or sign up for Devpost to join the conversation.