New reference docs: - agents/ref-btmsg.md: inter-agent messaging schema and CLI - agents/ref-bttask.md: kanban task board operations - providers/ref-providers.md: Claude/Codex/Ollama/Aider comparison - config/ref-settings.md: (already committed) New guides: - contributing/dual-repo-workflow.md: community vs commercial repos - plugins/guide-developing.md: Web Worker sandbox API and publishing New pro docs: - pro/features/knowledge-base.md: persistent memory + symbol graph - pro/features/git-integration.md: context injection + branch policy - pro/marketplace/README.md: 13 plugins catalog Split files: - architecture/data-model.md: from architecture.md (schemas, layout) - production/hardening.md: from production.md (supervisor, sandbox, WAL) - production/features.md: from production.md (FTS5, plugins, secrets, audit)
7.7 KiB
Production Features
User-facing production features: search, plugins, secrets, notifications, audit logging, error classification, and session metrics.
FTS5 Full-Text Search
The search system uses SQLite's FTS5 extension for full-text search across three data types. Accessed via a Spotlight-style overlay (Ctrl+Shift+F).
Architecture
SearchOverlay.svelte (Ctrl+Shift+F)
|
+-- search-bridge.ts -> Tauri commands
|
+-- search.rs -> SearchDb (separate FTS5 tables)
|
+-- search_messages -- agent session messages
+-- search_tasks -- bttask task content
+-- search_btmsg -- btmsg inter-agent messages
Virtual Tables
| Table | Source | Indexed Columns |
|---|---|---|
search_messages |
Agent session messages | content, session_id, project_id |
search_tasks |
bttask tasks | title, description, assignee, status |
search_btmsg |
btmsg messages | content, sender, recipient, channel |
Operations
| Tauri Command | Purpose |
|---|---|
search_init |
Creates FTS5 virtual tables if not exist |
search_all |
Queries all 3 tables, returns ranked results |
search_rebuild |
Drops and rebuilds all indices (maintenance) |
search_index_message |
Indexes a single new message (real-time) |
Frontend (SearchOverlay.svelte)
- Triggered by Ctrl+Shift+F
- Spotlight-style floating overlay centered on screen
- 300ms debounce on input to avoid excessive queries
- Results grouped by type (Messages, Tasks, Communications)
- Click result to navigate to source (focus project, switch tab)
Plugin System
The plugin system allows extending agor with custom commands and event handlers. Plugins are sandboxed JavaScript executing in a restricted environment.
Plugin Discovery
Plugins live in ~/.config/agor/plugins/. Each plugin is a directory containing a plugin.json manifest:
{
"name": "my-plugin",
"version": "1.0.0",
"description": "A custom plugin",
"main": "index.js",
"permissions": ["notifications", "settings"]
}
The Rust plugins.rs module scans for plugin.json files with path-traversal protection (rejects .. in paths).
Sandboxed Runtime (plugin-host.ts)
Plugins execute via new Function() in a restricted scope:
Shadowed globals (13):
fetch, XMLHttpRequest, WebSocket, Worker, eval, Function, importScripts, require, process, globalThis, window, document, localStorage
Provided API (permission-gated):
| API | Permission | Purpose |
|---|---|---|
bt.notify(msg) |
notifications |
Show toast notification |
bt.getSetting(key) |
settings |
Read app setting |
bt.setSetting(key, val) |
settings |
Write app setting |
bt.registerCommand(name, fn) |
(always allowed) | Add command to palette |
bt.on(event, fn) |
(always allowed) | Subscribe to app events |
The API object is frozen (Object.freeze) to prevent tampering. Strict mode is enforced.
Security Notes
The new Function() sandbox is best-effort — it is not a security boundary. A determined attacker could escape it. Landlock provides the actual filesystem restriction. The plugin sandbox primarily prevents accidental damage from buggy plugins.
35 tests cover the plugin system including permission validation, sandbox escape attempts, and lifecycle management.
Secrets Management
Secrets (API keys, tokens) are stored in the system keyring rather than in plaintext files or SQLite.
Backend (secrets.rs)
Uses the keyring crate with the linux-native feature (libsecret/DBUS):
pub struct SecretsManager;
impl SecretsManager {
pub fn store(key: &str, value: &str) -> Result<()>;
pub fn get(key: &str) -> Result<Option<String>>;
pub fn delete(key: &str) -> Result<()>;
pub fn list() -> Result<Vec<SecretMetadata>>;
pub fn has_keyring() -> bool;
}
Metadata (key names, last modified timestamps) is stored in SQLite settings. The actual secret values never touch disk — they live only in the system keyring (gnome-keyring, KWallet, or equivalent).
No Fallback
If no keyring daemon is available (no DBUS session, no gnome-keyring), secret operations fail with a clear error message. There is no plaintext fallback — this is intentional to prevent accidental credential leakage.
Notifications
Agor has two notification systems: in-app toasts and OS-level desktop notifications.
In-App Toasts (notifications.svelte.ts)
- 6 notification types:
success,error,warning,info,agent_complete,agent_error - Maximum 5 visible toasts, 4-second auto-dismiss
- Toast history (up to 100 entries) with unread badge in NotificationCenter
- Agent dispatcher emits toasts on: agent completion, agent error, sidecar crash
Desktop Notifications (notifications.rs)
Uses notify-rust crate for native Linux notifications. Graceful fallback if notification daemon is unavailable (e.g., no D-Bus session).
Notification Center (NotificationCenter.svelte)
Bell icon in the top-right with unread badge. Dropdown panel shows notification history with timestamps, type icons, and clear/mark-read actions.
Audit Logging
All significant events are logged to the audit_log table:
| Event Type | Logged When |
|---|---|
message_sent |
Agent sends a btmsg message |
message_read |
Agent reads messages |
channel_created |
New btmsg channel created |
agent_registered |
Agent registers with btmsg |
heartbeat |
Agent sends heartbeat |
task_created |
New bttask task |
task_status_changed |
Task status update |
wake_event |
Wake scheduler triggers |
prompt_injection_detected |
Suspicious content in agent messages |
The AuditLogTab component displays audit entries with filtering by event type and agent, with 5-second auto-refresh and max 200 entries.
Error Classification
The error classifier (utils/error-classifier.ts) categorizes API errors into 6 types with appropriate retry behavior:
| Type | Examples | Retry? | User Message |
|---|---|---|---|
rate_limit |
HTTP 429, "rate limit exceeded" | Yes (with backoff) | "Rate limited -- retrying in Xs" |
auth |
HTTP 401/403, "invalid API key" | No | "Authentication failed -- check API key" |
quota |
"quota exceeded", "billing" | No | "Usage quota exceeded" |
overloaded |
HTTP 529, "overloaded" | Yes (longer backoff) | "Service overloaded -- retrying" |
network |
ECONNREFUSED, timeout, DNS failure | Yes | "Network error -- check connection" |
unknown |
Anything else | No | "Unexpected error" |
20 unit tests cover classification accuracy across various error message formats.
Session Metrics
Per-project historical session data is stored in the session_metrics table:
| Column | Type | Purpose |
|---|---|---|
project_id |
TEXT | Which project |
session_id |
TEXT | Agent session ID |
start_time |
INTEGER | Session start timestamp |
end_time |
INTEGER | Session end timestamp |
peak_tokens |
INTEGER | Maximum context tokens used |
turn_count |
INTEGER | Total conversation turns |
tool_call_count |
INTEGER | Total tool calls made |
cost_usd |
REAL | Total cost in USD |
model |
TEXT | Model used |
status |
TEXT | Final status (success/error/stopped) |
error_message |
TEXT | Error details if failed |
100-row retention per project (oldest pruned on insert). Metrics are persisted on agent completion via the agent dispatcher.
The MetricsPanel component displays this data as:
- Live view — fleet aggregates, project health grid, task board summary, attention queue
- History view — SVG sparklines for cost/tokens/turns/tools/duration, stats row, session table