Files
flynn/docs/plans/2026-02-02-flynn-design.md
William Valentin ee19c11cc3 Finalize design decisions
- Separate sessions per frontend with explicit transfer
- Automatic backend routing based on task type
- Text responses by default (voice is future)
- Immediate notification delivery

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 20:45:14 -08:00

343 lines
12 KiB
Markdown

# 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.