# Data Model & Layout System This document covers agor's data model (SQLite schemas, project group config), layout system, xterm budget management, and keyboard shortcuts. --- ## SQLite Databases The backend manages two SQLite databases, both in WAL mode with 5-second busy timeout for concurrent access: | Database | Location | Purpose | |----------|----------|---------| | `sessions.db` | `~/.local/share/agor/` | Sessions, layout, settings, agent state, metrics, anchors | | `btmsg.db` | `~/.local/share/agor/` | Inter-agent messages, tasks, agents registry, audit log | WAL checkpoints run every 5 minutes via a background tokio task to prevent unbounded WAL growth. All queries use **named column access** (`row.get("column_name")`) — never positional indices. Rust structs use `#[serde(rename_all = "camelCase")]` so TypeScript interfaces receive camelCase field names on the wire. --- ## Project Group Config (`~/.config/agor/groups.json`) Human-editable JSON file defining workspaces. Each group contains up to 5 projects. Loaded at startup by `groups.rs`, not hot-reloaded. ```jsonc { "version": 1, "groups": [ { "id": "work-ai", "name": "AI Projects", "projects": [ { "id": "agor", "name": "Agents Orchestrator", "identifier": "agor", "description": "Terminal emulator with Claude integration", "icon": "\uf120", "cwd": "/home/user/code/Agents Orchestrator", "profile": "default", "enabled": true } ] } ], "activeGroupId": "work-ai" } ``` ### TypeScript Types (`src/lib/types/groups.ts`) ```typescript export interface ProjectConfig { id: string; name: string; identifier: string; description: string; icon: string; cwd: string; profile: string; enabled: boolean; } export interface GroupConfig { id: string; name: string; projects: ProjectConfig[]; // max 5 } export interface GroupsFile { version: number; groups: GroupConfig[]; activeGroupId: string; } ``` --- ## SQLite Schema (v3 Additions) Beyond the core `sessions` and `settings` tables, v3 added project-scoped agent persistence: ```sql ALTER TABLE sessions ADD COLUMN project_id TEXT DEFAULT ''; CREATE TABLE IF NOT EXISTS agent_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, project_id TEXT NOT NULL, sdk_session_id TEXT, message_type TEXT NOT NULL, content TEXT NOT NULL, parent_id TEXT, created_at INTEGER NOT NULL, FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS project_agent_state ( project_id TEXT PRIMARY KEY, last_session_id TEXT NOT NULL, sdk_session_id TEXT, status TEXT NOT NULL, cost_usd REAL DEFAULT 0, input_tokens INTEGER DEFAULT 0, output_tokens INTEGER DEFAULT 0, last_prompt TEXT, updated_at INTEGER NOT NULL ); ``` --- ## Layout System ### Project Grid (Flexbox + scroll-snap) Projects are arranged horizontally in a flex container with CSS scroll-snap for clean project-to-project scrolling: ```css .project-grid { display: flex; gap: 4px; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory; } .project-box { flex: 0 0 calc((100% - (N-1) * 4px) / N); scroll-snap-align: start; min-width: 480px; } ``` N is computed from viewport width: `Math.min(projects.length, Math.max(1, Math.floor(containerWidth / 520)))` ### Project Box Internal Layout Each project box uses a CSS grid with 4 rows: ``` +-- ProjectHeader (auto) --------------------+ +---------------------+---------------------+ | AgentSession | TeamAgentsPanel | | (flex: 1) | (240px/overlay) | +---------------------+---------------------+ | [Tab1] [Tab2] [+] TabBar auto | +--------------------------------------------+ | Terminal content (xterm or scrollback) | +--------------------------------------------+ ``` Team panel: inline at >2560px viewport (240px wide), overlay at <2560px. Collapsed when no subagents running. ### Responsive Breakpoints | Viewport Width | Visible Projects | Team Panel Mode | |---------------|-----------------|-----------------| | 5120px+ | 5 | inline 240px | | 3840px | 4 | inline 200px | | 2560px | 3 | overlay | | 1920px | 3 | overlay | | <1600px | 1 + project tabs | overlay | --- ## xterm.js Budget: 4 Active Instances WebKit2GTK OOMs at ~5 simultaneous xterm.js instances. The budget system manages this: | State | xterm.js Instance? | Memory | |-------|--------------------|--------| | Active-Focused | Yes | ~20MB | | Active-Background | Yes (if budget allows) | ~20MB | | Suspended | No (HTML pre scrollback) | ~200KB | | Uninitialized | No (placeholder) | 0 | On focus: serialize least-recent xterm scrollback, destroy it, create new for focused tab, reconnect PTY. Suspend/resume cycle < 50ms. ### Project Accent Colors Each project slot gets a distinct Catppuccin accent color for visual distinction: | Slot | Color | CSS Variable | |------|-------|-------------| | 1 | Blue | `var(--ctp-blue)` | | 2 | Green | `var(--ctp-green)` | | 3 | Mauve | `var(--ctp-mauve)` | | 4 | Peach | `var(--ctp-peach)` | | 5 | Pink | `var(--ctp-pink)` | Applied to border tint and header accent via `var(--accent)` CSS custom property set per ProjectBox. --- ## Adapters (IPC Bridge Layer) Adapters wrap Tauri `invoke()` calls and `listen()` event subscriptions. They isolate the frontend from IPC details and provide typed TypeScript interfaces. | Adapter | Backend Module | Purpose | |---------|---------------|---------| | `agent-bridge.ts` | sidecar + commands/agent | Agent query/stop/restart | | `pty-bridge.ts` | pty + commands/pty | Terminal spawn/write/resize | | `claude-messages.ts` | (frontend-only) | Parse Claude SDK NDJSON -> AgentMessage | | `codex-messages.ts` | (frontend-only) | Parse Codex ThreadEvents -> AgentMessage | | `ollama-messages.ts` | (frontend-only) | Parse Ollama chunks -> AgentMessage | | `message-adapters.ts` | (frontend-only) | Provider registry for message parsers | | `provider-bridge.ts` | commands/claude | Generic provider bridge (profiles, skills) | | `btmsg-bridge.ts` | btmsg | Inter-agent messaging | | `bttask-bridge.ts` | bttask | Task board operations | | `groups-bridge.ts` | groups | Group config load/save | | `session-bridge.ts` | session | Session/layout persistence | | `settings-bridge.ts` | session/settings | Key-value settings | | `files-bridge.ts` | commands/files | File browser operations | | `search-bridge.ts` | search | FTS5 search | | `secrets-bridge.ts` | secrets | System keyring | | `anchors-bridge.ts` | session/anchors | Session anchor CRUD | | `remote-bridge.ts` | remote | Remote machine management | | `ssh-bridge.ts` | session/ssh | SSH session CRUD | | `ctx-bridge.ts` | ctx | Context database queries | | `memora-bridge.ts` | memora | Memora database queries | | `fs-watcher-bridge.ts` | fs_watcher | Filesystem change events | | `audit-bridge.ts` | btmsg (audit_log) | Audit log queries | | `telemetry-bridge.ts` | telemetry | Frontend -> Rust tracing | | `notifications-bridge.ts` | notifications | Desktop notification trigger | | `plugins-bridge.ts` | plugins | Plugin discovery | --- ## Keyboard Shortcuts Three-layer shortcut system prevents conflicts between terminal input, workspace navigation, and app-level commands: | Shortcut | Action | Layer | |----------|--------|-------| | Ctrl+K | Command palette | App | | Ctrl+G | Switch group (palette filtered) | App | | Ctrl+1..5 | Focus project by index | App | | Alt+1..4 | Switch sidebar tab + open drawer | App | | Ctrl+B | Toggle sidebar open/closed | App | | Ctrl+, | Toggle settings panel | App | | Escape | Close sidebar drawer | App | | Ctrl+Shift+F | FTS5 search overlay | App | | Ctrl+N | New terminal in focused project | Workspace | | Ctrl+Shift+N | New agent query | Workspace | | Ctrl+Tab | Next terminal tab | Project | | Ctrl+W | Close terminal tab | Project | | Ctrl+Shift+C/V | Copy/paste in terminal | Terminal | Terminal layer captures raw keys only when focused. App layer has highest priority.