Quorra — Service Integrations
Last updated: 2026-07-09
Status: Actual Budget, Matrix bot, Open WebUI Pipe implemented; M2 RAG shipped; modes implemented; CalDAV planning re-platform in progress; PMS + guest concierge designed 2026-07-09 (phased — plans/workspaces-personas-concierge.md)
This document is the canonical reference for every service Quorra integrates with (or deliberately doesn’t). It covers: priority, integration interface, capabilities by permission tier, current status, and architectural notes. This document drives M3 (QuorraAPI tool registration) and M4 (service integrations milestone).
Design principle: conversation-first
Quorra is the primary interface for every integrated service. The native UIs are backups and visual aids, not the primary interaction surface.
This means:
- Every action a user might normally take in an app (log a transaction, add a calendar event, queue a download, search photos) should be achievable through a conversation with Quorra
- Native service UIs remain accessible and useful for browsing, complex management, or visually reviewing what Quorra has done — but they should rarely be necessary for routine interactions
- When designing a new integration, the test is: can a user accomplish everything they need to through conversation alone?
This principle applies across all integrations and is the bar every capability should eventually reach.
Design principle: capability interfaces, not service bindings
Each integration section below describes a capability surface — the set of operations Quorra needs. The specific service behind each capability (Actual Budget, Nextcloud, Immich, etc.) is the current adapter. The interface covers only what Quorra uses, not the backend’s full API surface.
Swapping a backend means writing a new adapter behind the same capability interface — not changing QuorraAPI tools, prompts, or the tool registry. Today’s service choices are provisional; they prove the concept now but may be replaced with native implementations or different third-party services later. The tool naming convention already reflects this: finance_log_transaction is the capability, the actual-bridge sidecar is the adapter.
Keep capability interfaces lean: shaped by what Quorra needs to do, not by what a backend happens to expose. See architecture.md §6.4 for the architectural principle.
Integration overview
| Service | Priority | Status | Scope when implemented | Interface |
|---|---|---|---|---|
| Actual Budget | High | Implemented | finance | actual-bridge Node.js sidecar wrapping @actual-app/api |
| Nextcloud — calendar + contacts | High | Not started | planning (M4 target) | CalDAV / CardDAV |
| Nextcloud — files / knowledge base | High | Not started | cross-cutting (M2 RAG + M4 file ops) | WebDAV + inotify watcher |
| Matrix (bot adapter) | Core — required | Implemented | n/a (adapter) | matrix-nio, calls QuorraAPI |
| Jellyfin | Medium | Not started | media (future) | REST API |
| Protonmail (email) | Medium | Not started | mail (future) | Proton Mail Bridge (IMAP/SMTP local proxy) |
| System / service health | Medium | Not started | ops (future) | psutil + Docker API |
| Open WebUI | Medium | Implemented | n/a (adapter; sessions commit to general today) | Pipe function → QuorraAPI /chat (trusted-service auth) |
| Immich | Low | Not started | photos (future) | REST API |
| Home Assistant | Deferred | No devices yet | home (future) | REST API + WebSocket (future) |
| PMS (Hostaway/Lodgify) | Planned (STR) | Designed | str workspace · guest-concierge persona | Vendor-abstracted adapter (integrations/pms/) — webhook in + programmatic send |
| Authentik | Infrastructure | Already wired | n/a | OIDC/JWT identity only |
| Gitea | Removed | — | — | Not integrating |
Scope column: the chat scope a tool’s integration belongs to once its real implementation lands. The minimal rollout ships general (cross-cutting memory + KB + base conversation) and finance (Actual Budget). All other integrations are currently unregistered — their dummy-data stubs were removed when room scoping landed (2026-05-21) and will reappear under their dedicated scopes when the real implementations ship. The cross-cutting memory tool (memory_save/memory_recall/memory_delete/get_context/search_knowledge) is available in every scope; finance tools are restricted to ["finance"].
Per-service details
Actual Budget
Purpose: Conversational transaction logging and spending queries. The highest-friction daily integration — logging a transaction should be as fast as saying it out loud.
Interface: A thin persistent Python sidecar service (actual-bridge) wraps @actual-app/api (which is JavaScript-only). Quorra calls the sidecar via HTTP. The sidecar holds a persistent open connection to the Actual Budget database to avoid file-lock contention on every call. Runs as a Docker service in the Quorra compose stack.
Capabilities:
- Tier 0: Query transactions, balances, budget categories, monthly spending summaries
- Tier 1: Log a new transaction — notifies after write
- Tier 2: Delete or modify an existing transaction — requires explicit confirmation before executing
Status: Implemented with multi-budget access (M4 + multi-budget extension)
Multi-budget model: A many-to-many relationship between users and Actual Budget files, encoded as budget_access rows in QuorraAPI’s SQLite DB ((user_uuid, sync_id, alias, is_default)). Each user has one or more budgets, each with a per-user alias (personal, business, household, etc.) and at most one default. Shared budgets — e.g. a household budget for two spouses — are represented by the same sync-id appearing in multiple users’ rows.
Per-session access: Every /chat request carries participants: list[str] (Authentik UUIDs or known emails, supplied by the adapter — Matrix bot sends room members, Open WebUI sends [acting_user]). QuorraAPI computes the intersection of all participants’ budget_access rows: a budget is addressable in a session only if every participant has a row for it. This enforces the privacy boundary at the architecture level — in a shared Matrix room, the LLM mechanically cannot reach a budget that isn’t shared by all members. The intersection is recomputed per-request, so membership changes (a spouse joining or leaving) take effect on the very next message with no session restart.
Budget addressing: Each session has an active budget — finance actions default to it without needing the LLM to extract a budget from every utterance. The user changes the active budget by saying “switch to my business budget” (a switch_budget tool call that writes sessions.active_budget_alias). For a one-off action against a different accessible budget without changing the active state, the user names it in conversational form (“log this to my personal budget: 56.00 at schoolyard beer garden to business / Chase Credit”*) so “forgot to switch” mistakes are visible immediately rather than at reconciliation.
Onboarding (future): Bootstrap is a manual INSERT into budget_access for now. A future conversational onboarding workflow will let new household members create their first Actual budget — accounts, categories, alias — entirely from chat with Quorra, writing into the same table.
Notes:
@actual-app/apiis JavaScript-only; the sidecar abstracts this so QuorraAPI (Python) never interacts with JS directly- Actual Budget files live on the local filesystem; sidecar must have read/write access to the correct paths
- The bridge runs one worker process per
(user, budget)pair (one@actual-app/apiinstance per process), spawned lazily and kept alive
Nextcloud — Calendar and Contacts
Purpose: Quorra reads and manages the household calendar and contact book. Natural language scheduling, event lookup, and contact queries.
Interface: CalDAV (calendar) and CardDAV (contacts) via Nextcloud’s built-in endpoints.
Capabilities:
- Tier 0: Query calendar events (day/week/date range), search contacts by name or attribute
- Tier 1: Create calendar event, create or update a contact — notifies after write
- Tier 2: Delete a calendar event or contact — requires explicit confirmation
Status: Superseded 2026-06-09 — calendar/tasks/reminders live in a standalone Radicale CalDAV store (decoupled from Nextcloud), folded into the planning scope. Confirmed and extended 2026-07-09: Radicale becomes the single multi-user household store (shared household principal), the iCloud archive imports into it, and the Nextcloud Calendar app is retired — see plans/calendar-consolidation.md and the Calendar & scheduling decisions. This section will be rewritten as “Radicale — Calendar, Tasks, Reminders (CalDAV)” (incl. the new-member onboarding runbook) in that plan’s Phase 6; contacts/CardDAV are deferred (Radicale speaks it).
Notes:
- Python library in use:
caldav(+icalendar) - Calendar migration from cloud services targets Radicale, not Nextcloud (plan:
plans/calendar-consolidation.md); contacts migration deferred with CardDAV - Authentik UUID bridges to a per-user Radicale htpasswd credential via the
caldav_credentialstable (seeauthorization.md)
Nextcloud — Files / Knowledge Base
Purpose: Quorra is the primary author and maintainer of the knowledge base at ~/data/knowledge/. Users access files through Nextcloud/OnlyOffice; direct edit access is preserved as an escape hatch for small changes. Quorra organizes, files, and maintains the vault — Nextcloud is the storage layer, not the organizing principle.
This is also the primary data source for the RAG pipeline. Every document filed here is a candidate for embedding and retrieval. Quorra’s filing decisions directly shape the quality and structure of the retrieval layer.
This integration has two distinct concerns at different milestones:
- M2 (RAG pipeline): inotify watcher on
~/data/knowledge/— detects direct user edits and Nextcloud sync events, triggers near-real-time re-embed (batched ~5 min), logs the change for the overnight direct-edit audit - M4 (file ops): Quorra’s active file operations — organize, ingest, move, and delete files via WebDAV; document ingestion flow; proactive organization suggestions
Interface: WebDAV for file reads and writes; inotify watcher on ~/data/knowledge/ for detecting changes.
Capabilities:
- Tier 0: Search and read files, locate documents by content or path
- Tier 1: Write files, create/move/rename files, organize directory structure — notifies after
- Tier 2: Delete files — requires explicit confirmation
- Document ingestion flow: A dedicated upload endpoint (separate from the Matrix chat interface) lets users drop a file and provide a conversational description of its content and purpose. Quorra uses that context to determine the correct location in the knowledge base, files the document, and schedules embedding (immediately or overnight based on urgency and load)
- Proactive organization: The inotify watcher detects files added by the user (via Nextcloud, Obsidian sync, etc.) and surfaces them for Quorra to act on — file to the right location, flag for learning, or ask the user for context
Status: Not started
RAG relationship: ~/data/knowledge/ is the canonical RAG corpus. The inotify watcher serves double duty — triggering both filing decisions and embedding pipeline updates (see architecture.md §4.7). Quorra’s organizational choices here directly affect what can be retrieved.
Notes:
- The knowledge base is not an Obsidian mirror. It is Quorra’s own structured vault, stored on the filesystem and accessible via Nextcloud’s WebDAV layer. Direct edit access via Nextcloud/OnlyOffice is the escape hatch for small changes; the write path goes through Quorra for schema compliance (see
docs/technical/knowledge-base-schema.md). - Document ingestion UX is a distinct interface surface, not just a chat feature
Matrix (bot adapter)
Purpose: Primary user-facing chat interface for Quorra. The Matrix bot is a thin adapter only — all intelligence lives in QuorraAPI, not in the bot.
Interface: matrix-nio (Python async Matrix client). Bot receives messages, calls POST /chat on QuorraAPI, posts the response. No local logic.
Capabilities:
- DM rooms: One private 1:1 room per household member — personal queries, personal memory, personal context
- Household room: A shared room where Quorra responds when addressed (e.g.,
@quorra ...) — household-scoped queries and announcements - Notifications: Quorra pushes Tier 1 action confirmations, async events (download complete, etc.), and reminders to the appropriate room
- Streaming UX: Matrix
m.replaceedits update a message progressively as tokens arrive — simulates streaming without SSE
Status: Implemented in repos/quorra-matrix/ as a clean matrix-nio adapter.
Notes:
- Identity resolution: Matrix user ID → Authentik email via static
MATRIX_USER_MAP(env JSON); QuorraAPI resolves email → UUID via its own user map. Upgrade to live Authentik API lookup later. - Room → scope resolution:
MATRIX_ROOM_SCOPE_MAPenv (JSON{room_id: ["finance", …]}), wrapped inresolve_scope(room_id)inquorra_matrix.rooms. DMs and unmapped rooms resolve to["general"]. The bot sends scope on the first/chat/streamrequest of a session; QuorraAPI commits and locks it. The wrapping function exists so the future swap to a DB-backed user-defined room scope is a one-function change. - Bot must handle both DM events and room mention events distinctly
Jellyfin
Purpose: Library awareness, watchlist and playlist management, and “what should we watch” recommendation queries.
Interface: Jellyfin REST API.
Capabilities:
- Tier 0: Search the library (“what Bond films do I have?”), query watch history and watched status, “pick a random unwatched horror movie”, suggest from watchlist
- Tier 1: Create or update custom playlists and watchlists (“add Pulp Fiction to my watch list”)
Status: Not started
Notes:
- No playback control for now — device control (Roku, Apple TV) requires smart home integration and is deferred with Home Assistant
- Jellyfin user identity mapped from Authentik UUID
- Watch status tracking requires querying Jellyfin’s playback history API per user
Protonmail (email)
Purpose: Full email assistant loop — Quorra reads email, extracts actionable items into the knowledge base, drafts replies, and can send on your behalf.
Interface: Proton Mail Bridge running headlessly on JUNC1 as a Docker service. Bridge exposes a local IMAP endpoint (:1143) and SMTP endpoint (:1025). Quorra connects to these via standard Python email libraries (imap_tools for reading, smtplib for sending). Bridge handles end-to-end encryption transparently — Quorra sees plaintext.
Capabilities:
- Tier 0: Read inbox, search emails, summarize threads
- Tier 0: Extract actionable items from emails (bill due, appointment, package arriving) → structured entries added to knowledge base
- Tier 1: Add extracted tasks and reminders to knowledge base; surface in daily to-dos
- Tier 2: Send an email — draft shown to user, explicit confirmation required before sending
- Proactive (future): IMAP IDLE or periodic poll → Matrix notification when a high-priority email arrives
Status: Not started. Depends on Proton Mail Bridge Docker setup, which is not yet running on JUNC1.
Setup prerequisite: Deploy shenxn/protonmail-bridge (or equivalent headless Docker image) as a service in the Quorra compose stack. Authenticate with Proton credentials once; Bridge handles session persistence from that point.
Notes:
- Bridge must be running and authenticated before any email capability works
- Sending email is strictly Tier 2 — always requires explicit user confirmation; never sends silently
- Proactive inbox polling starts as overnight-only (consolidation job), then near-real-time once the pattern is validated
Property Management System (PMS)
Purpose: Enable Quorra to help run a short-term / mid-term rental — reading reservations and interacting with guests on the owner’s behalf (mid-stay questions, issues) through a unified inbox that spans Airbnb, VRBO, Booking.com, and direct bookings. This is the external binding for the str Workspace and its guest-concierge persona (architecture.md §4.1c/§4.1d).
Interface: A vendor-abstracted adapter (integrations/pms/) behind a lean PMSClient capability interface (list/get reservation, get message thread, send message, normalize webhook) — the §6.4 principle applied. Inbound guest messages and reservation changes arrive by webhook at POST /integrations/pms/webhook (signature-verified); replies are sent programmatically via the vendor’s send-message API. Candidate vendors: Hostaway or Lodgify (both self-serve for a single household); OwnerRez and Guesty were evaluated and dropped (send API and/or message webhooks are partnership/sales-gated). Per-workspace credentials stored encrypted (the caldav_credentials pattern).
Capabilities:
- Tier 0: Read reservation details (guest, dates, unit, channel, status) for the acting reservation; read the guest message thread; search guest-visible knowledge (
kb_str_guest) - Tier 1: Draft a guest reply into the owner review queue; escalate to the owner
- Tier 2: Send a message to a guest — draft-and-approve; the owner confirms every outbound message (no autonomous send until a category is explicitly whitelisted)
Status: Designed 2026-07-09 (phased — plans/workspaces-personas-concierge.md); not started. Built in Phase 3, after the workspace/persona core (Phase 1) and the egress-hard offline concierge (Phase 2).
Egress boundary: the guest-concierge that consumes this integration is bounded by three structural controls (persona tool allow-list, retrieval hardwired to kb_str_guest, default-deny guest_visible content gate) plus a ReservationPrincipal carrying no owner identity — so owner data is unreachable by construction, even under prompt injection (architecture.md §8). The adapter also enforces inherited OTA content rules (e.g. Booking.com/VRBO disallow clickable links in messages).
Notes:
- Research across Hospitable/Hostaway/Lodgify/Guesty/OwnerRez found all five expose a programmatic send API + inbound webhook — no read-only dealbreaker. The differentiator is self-serve API access, which is why Hostaway/Lodgify are the shortlist for a single household.
- The unified inbox keeps the guest in their own app (Airbnb/VRBO/direct) while Quorra replies through the PMS — this dissolves the “how do we reach the guest” channel problem into a single service adapter.
- Final Hostaway-vs-Lodgify pick is deferred to a Phase 3 sandbox check of send parity + inbound-webhook latency; the adapter interface abstracts the vendor until then.
System and service health
Purpose: Quorra can answer questions about JUNC1’s operational state — CPU, RAM, GPU, disk, temperatures, uptime, and Docker service statuses.
Interface: Direct system calls (psutil, /proc/) and Docker API (docker Python SDK). At M6, this will wrap the Diagnostic Agent rather than querying the system directly.
Capabilities (reactive — proactive alerting is deferred):
- Tier 0: CPU/RAM/GPU usage, disk space, temperatures, system uptime
- Tier 0: Docker container statuses for all services
- Tier 0: “Is everything okay?” → structured health summary
Status: Not started. A basic version can be implemented before M6 without the Diagnostic Agent. Scope: ops (future).
Notes:
- Reactive-first: Quorra only surfaces health information when asked. Proactive alerting (disk nearly full, service down, temperature spike) is a future addition
- The tool interface must be stable before M6 so the Diagnostic Agent can slot in behind it without changing the QuorraAPI tool registration —
get_system_health()should be the stable interface regardless of what implements it underneath
Open WebUI
Purpose: A ChatGPT-like interface for longer, more complex sessions — a legitimate alternative interface alongside Matrix. Will be superseded by the Quorra PWA (M7) but is a good stopgap and power-user surface. Also serves as the primary testing surface for system prompt iteration.
Interface: An Open WebUI Pipe function (repos/quorra-openwebui-pipe/) that appears as “Quorra” in the model selector. The Pipe calls QuorraAPI’s native POST /chat endpoint using trusted-service auth (shared secret + X-Quorra-User-Email header). No /v1/chat/completions endpoint — the Pipe handles format translation.
Capabilities: Full Quorra capabilities — tool system, RAG, knowledge base, household context — accessible through Open WebUI’s interface. The Pipe maps Open WebUI chat IDs to QuorraAPI session IDs so conversations persist. Tier 2 confirmations are handled conversationally (Quorra asks, user replies “confirm”).
Identity: Open WebUI authenticates users via Authentik SSO (already wired). The Pipe receives the user’s email from Open WebUI’s __user__ context and passes it to QuorraAPI via the X-Quorra-User-Email header. QuorraAPI resolves email → Authentik UUID via a config-based user map (QUORRA_USER_MAP_JSON).
Status: Implemented. Pipe function and trusted-service auth path built.
Notes:
- All clients (Open WebUI, Matrix bot, PWA) call QuorraAPI’s
/chatendpoint directly — no OpenAI-compat wrapper needed. Thin per-client adapters handle auth and format differences. - Raw LLM models (Qwen 3 14B, Qwen 3 8B) remain available as separate options in Open WebUI alongside Quorra
- The
/v1/chat/completionsendpoint was considered and deliberately skipped./chatis the stable external interface. Revisit only if a third-party client that exclusively speaks OpenAI format emerges. - Networking: the Pipe reaches QuorraAPI by Docker hostname (
http://quorra-api:8000), which only resolves ifopen-webuiandquorra-apishare a Docker network. Both must be attached toquorra-net(the integration bus). Adding the network to a compose stack is not enough — theopen-webuicontainer must be recreated for it to take effect.
Immich
Purpose: Natural language photo search — find photos by person, event, date, or location.
Interface: Immich REST API.
Capabilities:
- Tier 0: Search photos by person, date range, location, event description; return photo links or thumbnails
Status: Not started; lower priority — Immich runs well on its own.
Notes:
- Immich runs its own CUDA ML (face recognition, object detection) — Quorra queries these results and does not reimplement them
- Semantic photo search beyond Immich’s built-in capabilities (e.g., “photos from our camping trip”) requires embedding photo metadata into Qdrant, which is an M2+ concern
Home Assistant (deferred)
Purpose: Smart home queries and control; proactive camera-based notifications (e.g., “someone is at the front door”).
Interface: Home Assistant REST API for queries and control; WebSocket event subscriptions for proactive push from HA to Quorra.
Capabilities (future):
- Tier 0: Device state queries (“is the garage door open?”, “what’s the living room temperature?“)
- Tier 1: Device control (“turn off the kitchen lights”, “set the thermostat to 70”)
- Proactive: Camera motion/doorbell events → Quorra
/events→ Matrix notification with optional live feed link
Status: Deferred. Minimal HA devices currently. The first integration will be PoE security cameras; Quorra integration follows from there.
Notes:
- Camera integration is a push-first pattern (HA pushes events to Quorra) unlike most other integrations, which are pull-first
- HA WebSocket subscriptions will be needed for real-time camera event delivery
Gitea
Status: Removed from integration plan. Not integrating.
Authentik
Role: Infrastructure only — not a conversational integration. Authentik provides OIDC/JWT identity; the Authentik UUID is the canonical per-user identifier across all services. QuorraAPI validates incoming JWTs and resolves them to Authentik UUIDs for user scoping. Quorra never “talks to” Authentik conversationally.
Cross-cutting notes
Webhook and push integrations: Home Assistant (future) requires inbound event delivery, routed through POST /events on QuorraAPI. Each event source is identified by a source field in the payload; QuorraAPI dispatches to the appropriate handler.
Multi-user scoping: Every tool must carry user identity. The requesting user’s Authentik UUID is resolved at the session layer and passed through to every tool call. Tier 1 and 2 write operations are tagged with the user’s UUID in all downstream writes.
Tier annotation at registration: Every tool registered with QuorraAPI declares its permission tier at registration time. Tier cannot be promoted at runtime. The tool registry is the authoritative list of what Quorra can do and at what trust level.
Prior codebase reference: The old stack at ~/projects/junc1/_archive/quorra-2026-05-18/ had Actual Budget and Matrix integrations. Reference for API patterns only — do not reuse code.