# Flynn Design Document **Date:** 2026-02-02 **Status:** Draft ## Overview Flynn is a self-hosted personal AI agent accessible via Telegram and a local TUI. It runs on your workstation, behind Tailscale, with no internet exposure. You text it like a friend - it can search the web, run commands, query your K8s cluster, manage files, and proactively notify you of events. ## Core Principles - **Tailscale-only** - Daemon binds to Tailscale interface + localhost, never `0.0.0.0` - **Single-user** - Your Telegram chat ID is the only authorized user - **Hook-gated** - Sensitive operations require confirmation before execution - **Smart routing** - Model selection based on task complexity, with multi-provider fallback - **Multi-frontend** - One daemon, multiple interfaces (Telegram, TUI, web later) - **Backend-agnostic** - Can delegate to Claude Code CLI, OpenCode CLI, or native agent ## What Flynn Is Not - Not a multi-user platform - Not internet-facing - Not a replacement for Claude Code CLI - it complements it with mobile/async access ## Architecture ``` ┌─────────────┐ │ Telegram │──┐ │ (grammY) │ │ └─────────────┘ │ │ ┌─────────────────────────────────────┐ ┌─────────────┐ │ │ Flynn Daemon │ │ TUI │──┼───▶│ │ │ (Ink) │ │ │ ┌───────────┐ ┌──────────────┐ │ └─────────────┘ │ │ │ Session │ │ Hook Engine │ │ │ │ │ Manager │◀──▶│ (confirm via │ │ ┌─────────────┐ │ │ └───────────┘ │ active front)│ │ │ Web UI │──┘ │ │ └──────────────┘ │ │ (future) │ │ ▼ │ └─────────────┘ │ ┌───────────┐ ┌──────────────┐ │ │ │ Model │───▶│ Backends │ │ │ │ Router │ │ • Claude Code│ │ │ └───────────┘ │ • OpenCode │ │ │ │ • Native │ │ │ ┌───────────┐ └──────────────┘ │ │ │ Notifier │ │ │ │ (cron/wh) │ │ │ └───────────┘ │ └─────────────────────────────────────┘ binds to Tailscale IP + localhost ``` ### Components **Flynn Daemon** - Long-running TypeScript/Node.js process exposing an internal API via WebSocket or Unix socket. **Session Manager** - Separate sessions per frontend (Telegram, TUI). Sessions can be explicitly transferred via `/transfer` command. Persists to SQLite. Shared persistent memory (user preferences, summaries) across all sessions. **Model Router** - Routes requests to the appropriate model based on task complexity: - Local LLMs (Ollama/llama.cpp) for private tasks, triage, offline - Haiku for quick replies - Sonnet for general work - Opus for complex reasoning - Fallback chain: Anthropic → OpenAI → Gemini → Local **Hook Engine** - Intercepts tool calls before execution. Sensitive operations send confirmation to the active frontend (Telegram or TUI). Non-sensitive operations execute immediately. **Backends** - Three execution modes: - **Claude Code CLI** - Spawns your existing Claude Code setup with all agents/skills - **OpenCode CLI** - Alternative agent runner - **Native agent** - Lightweight built-in for simple tasks, triage, notifications **Notifier** - Cron scheduler and webhook listener for proactive messages. ## Frontends ### Telegram - grammY-based bot - Allowlist by chat ID (single user) - Voice message transcription via Whisper (local or API) - Optional TTS responses - Inline keyboard buttons for hook confirmations - Commands: `/status`, `/reset`, `/cron`, `/hooks`, `/model` ### TUI - Hybrid mode: minimal readline by default, Tab to expand full-screen - Minimal mode: simple prompt with streaming output - Full-screen mode: conversation pane, status bar, tool output - Separate session from Telegram, with `/transfer` to move context between frontends - Built with Ink (React for CLI) ## Tool System ### Built-in Tools | Tool | Hook | Description | |------|------|-------------| | `shell` | confirm | Execute bash commands | | `file.read` | log | Read files | | `file.write` | confirm | Write files | | `web.search` | log | Search the web | | `web.fetch` | log | Fetch URL content | | `notify` | silent | Send proactive message | | `cron.manage` | confirm | Create/list/delete scheduled tasks | ### MCP Integration Flynn connects to MCP servers as a client. Server configs defined in `config.yaml`. MCP tool calls go through the Hook Engine - any tool can be gated as sensitive. ### Hook Classification ```yaml hooks: confirm: # Requires confirmation - shell.* - file.write - k8s.mutate - cron.* - backend.claude_code log: # Executes but logs - web.* - file.read silent: # No notification - notify ``` ## Network & Security **Inbound access:** - Daemon binds to Tailscale IP + localhost only - No internet-facing ports - Telegram Bot API is outbound-only (long polling) **Outbound access:** - Full LAN access (other machines, services, NAS, local APIs) - Internet access for web search, LLM APIs **Authentication:** - Telegram: chat ID allowlist (hardcoded in config) - TUI: localhost only (implicit trust) - Future web UI: Tailscale IP only **Security boundaries:** 1. Telegram allowlist - only your chat ID 2. Tailscale - no internet exposure 3. Hook engine - sensitive ops require confirmation 4. LAN access controlled by what tools expose ## Configuration Location: `~/.config/flynn/config.yaml` ```yaml # Identity telegram: bot_token: ${FLYNN_TELEGRAM_TOKEN} allowed_chat_ids: [123456789] # Network server: tailscale_only: true localhost: true port: 18800 # Model routing models: local: provider: ollama # or llamacpp endpoint: http://localhost:11434 model: llama3.2 for: [triage, private] fast: provider: anthropic model: claude-haiku default: provider: anthropic model: claude-sonnet complex: provider: anthropic model: claude-opus fallback_chain: [anthropic, openai, gemini, local] # Backends backends: claude_code: enabled: true path: /usr/bin/claude opencode: enabled: true path: /usr/bin/opencode native: enabled: true # Hooks hooks: confirm: - shell.* - file.write - k8s.mutate - cron.* - backend.claude_code log: - web.* - file.read silent: - notify # MCP servers mcp: servers: - name: filesystem command: mcp-filesystem args: ["/home/will"] - name: brave-search command: mcp-brave-search ``` ## Project Structure ``` flynn/ ├── src/ │ ├── daemon/ │ │ ├── index.ts # Main daemon entry │ │ ├── server.ts # WebSocket/Unix socket API │ │ └── session.ts # Session manager │ │ │ ├── frontends/ │ │ ├── telegram/ │ │ │ ├── bot.ts # grammY bot setup │ │ │ ├── handlers.ts # Message/command handlers │ │ │ └── voice.ts # Transcription/TTS │ │ │ │ │ └── tui/ │ │ ├── app.ts # TUI entry (Ink) │ │ ├── minimal.ts # Readline mode │ │ └── fullscreen.ts # Panel mode │ │ │ ├── backends/ │ │ ├── router.ts # Backend selection logic │ │ ├── claude-code.ts # Claude Code CLI spawner │ │ ├── opencode.ts # OpenCode CLI spawner │ │ └── native/ │ │ ├── agent.ts # Built-in lightweight agent │ │ └── tools/ # Native tool implementations │ │ │ ├── models/ │ │ ├── router.ts # Model selection logic │ │ ├── anthropic.ts # Anthropic API client │ │ ├── openai.ts # OpenAI fallback │ │ ├── gemini.ts # Gemini fallback │ │ └── local/ │ │ ├── ollama.ts # Ollama client │ │ └── llamacpp.ts # llama.cpp server client │ │ │ ├── hooks/ │ │ ├── engine.ts # Hook interception logic │ │ └── confirm.ts # Confirmation flow │ │ │ ├── notify/ │ │ ├── cron.ts # Scheduled tasks │ │ └── webhook.ts # Webhook listener │ │ │ └── mcp/ │ └── client.ts # MCP server connections │ ├── config/ │ └── default.yaml # Default config template │ ├── package.json ├── tsconfig.json └── README.md ``` ## Key Dependencies - `grammy` - Telegram bot framework - `ink` - React for CLI (TUI) - `@anthropic-ai/sdk` - Anthropic API - `openai` - OpenAI fallback - `@google/generative-ai` - Gemini fallback - `ollama` - Ollama client - `croner` - Cron scheduling - `better-sqlite3` - Session persistence - `@modelcontextprotocol/sdk` - MCP client ## Comparison with OpenClaw | Aspect | OpenClaw | Flynn | |--------|----------|-------| | Channels | 12+ (WhatsApp, Telegram, Discord, etc.) | Telegram + TUI | | Network | Internet-facing optional | Tailscale-only | | Security model | Docker sandboxes, complex policies | Hook-based confirmation | | Multi-user | Yes, with isolation | Single-user | | Backends | Pi agent (custom) | Claude Code, OpenCode, Native | | Complexity | High (Gateway, Nodes, Canvas, etc.) | Focused | Flynn takes OpenClaw's core value proposition (text your AI like a friend) and strips it down to a single-user, security-focused design that integrates with existing CLI agents rather than replacing them. ## Implementation Phases ### Phase 1: Foundation - Daemon skeleton with WebSocket API - Basic Telegram bot (text only) - Native agent with Anthropic API - Config loading ### Phase 2: Core Features - Model router with fallback chain - Hook engine with Telegram confirmations - Session persistence - Local LLM integration (Ollama) ### Phase 3: TUI - Minimal readline mode - Full-screen panel mode - Shared sessions with Telegram ### Phase 4: Backend Integration - Claude Code CLI spawner - OpenCode CLI spawner - Backend routing logic ### Phase 5: Advanced - Voice transcription/TTS - Cron scheduler - Webhook listener - MCP client integration - llama.cpp support ## Design Decisions 1. **Session sharing** - Separate sessions per frontend. Explicit transfer via `/transfer telegram` or `/transfer tui`. Shared persistent memory (preferences, summaries) across sessions. 2. **Backend selection** - Automatic routing based on task type. Native for simple queries/triage, Claude Code for code tasks, OpenCode as alternative. 3. **Voice responses** - Text by default. Voice response is future consideration. 4. **Notification delivery** - Immediate by default. Per-cron batching configuration possible in future if needed.