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

12 KiB

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

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

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