I've spent the last year building Ghost, a native macOS AI workspace — ⌥Space summons it from the menu bar, and it does chat, RAG, Mac automation, coding-agent work, calendar, messaging and timers through one harness. It's production software (v2.1.0, notarized, App Store-style distribution), so this isn't a weekend toy: every decision below has been battle-tested by real users. Here's what I'd do again, and what I'd argue about.
- Deterministic routing beats LLM intent classification for the critical fork.
When a user types something in the terminal section, Ghost decides command-vs-agent without asking a model — prefix rules (! = run as command, > = agent) and deterministic checks, with the model only consulted downstream for agent task planning. LLM classifiers get you 95%, but the 5% failure lands in the place you can least afford it: "quietly guesses wrong" is worse than "doesn't guess." The prompt-level intent classifier (14 intents: Answer, Research, Files, Summarize, Create, Organize, Automation, Messages, Code, Debug, Review, Shell…) does exist for routing — but it's advisory, not security-critical.
- The model never touches the filesystem. Ever.
The model emits a tool request. Ghost normalizes the path, checks permissions, runs app-owned Swift code, returns a machine-readable receipt. This single rule eliminates most path-traversal and prompt-injection attack surface — the LLM can hallucinate /etc/passwd all day, but it never gets open().
- Verification, not trust, for side effects.
Agents report "done" constantly while doing nothing — no exception means green. So: every write is confirmed against the actual filesystem (exists, non-empty, correct path). A claimed-but-unconfirmed write is surfaced as an error, not accepted. Same principle extends to the undo journal: before/after state snapshots make every file edit, calendar event, reminder, and app quit one-tap reversible — two deliberate exceptions (uninstall, junk-clean) move things to Trash instead, and the card says so rather than showing a dead Undo button.
- Risk tiers + approval modes, not a binary permission switch.
~78 tools classified: Low (read-only) / Medium (creates) / High (patches/deletes/shell) / Blocked (fail closed). Three approval modes — Ask, Safe, Auto-run — mapped per tier. Computer-use fallback for anything uncovered: Ghost writes the AppleScript and runs it only after the user approves the exact script. The model proposes; the human disposes.
- Weak local models deserve a harness too.
Every local model (Ollama, LM Studio) gets probed on first launch: chat quality, JSON mode, native tool-calling, argument accuracy. Models that can't do native function calls get a managed tool loop instead of being excluded. Local providers are locked to that managed loop — no escape hatch to agent mode.
- Egress is guarded as hard as ingress.
Localhost, private IPv4/IPv6, link-local and multicast are blocked; DNS resolution checks every address; redirects to private destinations are rejected. Your agent shouldn't be a better SSRF vector than your web app.
The uncomfortable tradeoff I'd love pushback on: I chose per-tier approval modes over per-folder/per-app permission matrices, because granularity that users don't configure is security theater. But a user asked for exactly that this week. Where's the line for you — is per-folder write ACLs worth the settings UI, or do approval prompts scale better in practice?
Tech context: native SwiftUI/AppKit (no Electron), SQLite+FTS5 for RAG, 8 providers + BYO Claude Code/Codex, agent modes Plan/Build/Explore/Review with a live Agent Console.
Source: r/AI_Agents · by /u/Tunashavetoes