Add shared artifact-tag normalization/validation and apply it to capture, drift, and prune scripts for --tag/--report-tag/--baseline-tag paths. Architecture diagrams reviewed; no flow changes required.
89 KiB
Flynn
Self-hosted personal AI assistant with Telegram and Terminal interfaces.
Features
- Multi-Frontend: Telegram bot + Terminal UI (minimal & fullscreen modes) + Web UI dashboard
- Multi-Model: Anthropic Claude, OpenAI, GitHub Copilot, Gemini, Bedrock, Zhipu AI (GLM), xAI (Grok), Ollama, llama.cpp with intelligent routing
- Multi-Channel: Telegram, Discord, Slack, WhatsApp, Matrix, Signal, Mattermost, Microsoft Teams, Google Chat, LINE, Feishu/Lark, Zalo, and iMessage (BlueBubbles) with unified adapter interface
- Web Dashboard: SPA control panel with health monitoring, chat, session browser, usage stats, and settings editor
- Model Switching: Switch between cloud/local models on demand
- Session Persistence: SQLite-backed conversation history
- Fallback Chains: Automatic failover when primary model fails
- Hook Engine: Confirmation system for sensitive operations
- Tool Framework: Shell, file, file patch, web-fetch, web-search, browser control, image analysis, media send, audio transcribe, system info
- Docker Sandboxing: Per-session container isolation for tool execution
- Multi-Agent Routing: Config-driven agent selection per sender/channel with tool profiles
- Media Pipeline: Image analysis, outbound attachments, audio transcription and native audio passthrough across all channels
- Talk Mode (Wake Phrase): Optional wake-phrase gating (
audio.talk_mode) with timed conversation windows - Capture Tools:
screen.captureandcamera.capturetools for host capture workflows - Session Transfer: Move conversations between frontends
- CLI: Full command-line interface (
flynn start,send,doctor,completion, etc.) - Optional Pi Embedded Backend: Canary-only in-process Pi runtime path (
pi_embedded) with native fallback - Shell Completion: Auto-generated completions for bash, zsh, and fish with
--installflag - Cron Scheduling: Automated messages on cron schedules with output routing
- Daily Briefing Automation: Optional built-in morning briefing preset (calendar + inbox + tasks summary prompt)
- Inbound Webhooks: HTTP endpoints that trigger agent processing with HMAC auth and template rendering
- Heartbeat Monitor: Periodic health checks (gateway, model, channels, memory, disk) with failure notifications
- Scheduled Backups: Interval- or cron-based snapshot backups with optional startup run
- MinIO File Sharing Tool: Upload a local file and return a temporary MinIO share link via
minio.share - MinIO Knowledge Ingestion Tool: Pull text-like objects from MinIO into memory namespaces via
minio.ingest - Kubernetes Homelab Tools: Inspect pods/deployments and fetch logs via
k8s.pods,k8s.deployments,k8s.logs - Gmail Pub/Sub Watcher: Monitor Gmail inbox via Google Cloud Pub/Sub push notifications with polling fallback
- Vector Memory Search: Hybrid keyword + semantic search with embeddings (OpenAI, Gemini, Ollama, llama.cpp, Voyage AI)
- Docker Deployment: Multi-stage Dockerfile and docker-compose.yml for production containers
- Health Diagnostics:
flynn doctorvalidates config, connectivity, and system state - MCP Integration: External tool servers via Model Context Protocol
- Skills System: Extensible capability packages (bundled, managed, workspace tiers)
- Gateway Lock: Single-client mode — reject additional WebSocket connections when one is active
- Tailscale Serve: Auto-expose gateway via Tailscale Serve on daemon start with lifecycle management
- DM Pairing Codes: Allow unknown senders to pair with the bot via time-limited codes across all channels, with SQLite-backed persistence across restarts
- Lane Queue: Per-session FIFO queue serializes concurrent gateway requests
- Node Capability Negotiation: Optional companion-node registration and capability discovery over gateway RPC
Quick Start
# Install flynn CLI command (build + global link)
make install
# Copy and configure
cp config/default.yaml ~/.config/flynn/config.yaml
# Edit config with your API keys and Telegram bot token
# Optional: regenerate/check derived config profiles
pnpm config:profiles:generate
pnpm config:profiles:check
# Run
flynn start
# Or run without building
pnpm start
CLI Commands
Flynn provides a full CLI via the flynn binary (or npx tsx src/cli/index.ts during development):
| Command | Description |
|---|---|
flynn start |
Start the Flynn daemon (Telegram, WebChat, cron, Gmail watcher) |
flynn tui |
Launch the interactive terminal UI |
flynn send <message> |
Send a one-shot message and print the response |
flynn sessions |
List active sessions |
flynn doctor |
Validate config and check system health |
flynn config |
Show resolved configuration (secrets redacted) |
flynn backup |
Create a snapshot backup and optionally upload to MinIO |
flynn completion <shell> |
Generate shell completions (bash, zsh, fish) |
flynn setup |
Interactive setup wizard |
flynn onboard |
Guided onboarding alias for setup wizard |
flynn gmail-auth |
Authenticate with Gmail via OAuth2 |
flynn gcal-auth |
Authenticate with Google Calendar via OAuth2 |
flynn gdocs-auth |
Authenticate with Google Docs via OAuth2 |
flynn gdrive-auth |
Authenticate with Google Drive via OAuth2 |
flynn gtasks-auth |
Authenticate with Google Tasks via OAuth2 |
flynn google-auth --service <name> |
Unified Google OAuth entrypoint (gmail, gcal, gdocs, gdrive, gtasks) |
flynn gemini-auth |
Store a Gemini API key in ~/.config/flynn/auth.json |
flynn skills |
List/install/manage skills |
flynn companion |
Run a minimal companion node client or export a companion bootstrap manifest |
flynn setup / flynn onboard now include:
- a Personal Assistant Mode first-run preset (announce delivery, proactive memory, talk mode defaults, TTS fallback policy),
- post-save live readiness checks (model, channel, memory, automation),
- and a guided first-success task sequence after config save.
Examples
# Start daemon with custom config
flynn start --config ~/my-config.yaml
# One-shot query
flynn send "What's the weather in London?"
# Check system health
flynn doctor --config ~/.config/flynn/config.yaml
# Treat warnings as failures (useful in CI)
flynn doctor --strict
# Show current config (secrets masked)
flynn config
# List sessions
flynn sessions
# Create backup now (uses config backup + MinIO settings)
flynn backup
# Generate shell completions
flynn completion bash # Print bash completions to stdout
flynn completion zsh --install # Install zsh completions to ~/.zshrc
flynn completion fish --install # Install fish completions to config
Skills Registry
Flynn supports registry-backed skill discovery and install-by-id.
Registry source configuration:
skills:
registry_source: ~/.config/flynn/skills-registry.json
You can also set FLYNN_SKILLS_REGISTRY_SOURCE instead of skills.registry_source.
Common commands:
# Discover registry entries
flynn skills registry list
flynn skills registry list --search todo --publisher acme --json
flynn skills registry show todoist
# Install by registry id
flynn skills install --registry-id todoist
# For non-local registry sources (git/archive), explicit confirmation is required
flynn skills install --registry-id todoist --confirm
Registry metadata (publisher, homepage, sha256) is treated as declared and unverified. Skill scanner checks still run before installation succeeds.
Configuration
Config location: ~/.config/flynn/config.yaml (or set FLYNN_CONFIG)
telegram:
bot_token: "your-telegram-bot-token"
allowed_chat_ids: [123456789] # Your Telegram user ID
require_mention: false # Default false: respond in allowed groups without @mention
# Optional: Matrix
matrix:
homeserver_url: "https://matrix.example.org"
access_token: "${MATRIX_ACCESS_TOKEN}"
allowed_room_ids: ["!room1:example.org"]
require_mention: true
# Optional: Signal (signal-cli)
signal:
account: "+15551234567"
signal_cli_path: "signal-cli"
allowed_numbers: ["+15550001111"]
allowed_group_ids: []
require_mention: true
mention_name: "flynn"
poll_interval_ms: 5000
send_timeout_ms: 15000
# Optional: Microsoft Teams (Bot Framework)
teams:
app_id: "${TEAMS_APP_ID}"
app_password: "${TEAMS_APP_PASSWORD}"
allowed_conversation_ids: []
require_mention: true
# Bot Framework messaging endpoint should point to:
# POST https://<your-flynn-host>/teams/events
# Optional: Mattermost
mattermost:
server_url: "${MATTERMOST_SERVER_URL}"
bot_token: "${MATTERMOST_BOT_TOKEN}"
allowed_channel_ids: [] # Recommended: explicit channel IDs
require_mention: true
mention_name: "flynn"
poll_interval_ms: 3000
# Optional: Google Chat
google_chat:
service_account_key_file: "~/.config/flynn/google-chat-service-account.json"
# or:
# service_account_json: "${GOOGLE_CHAT_SERVICE_ACCOUNT_JSON}"
webhook_token: "${GOOGLE_CHAT_WEBHOOK_TOKEN}"
allowed_space_names: []
require_mention: true
# Google Chat messaging endpoint should point to:
# POST https://<your-flynn-host>/google-chat/events
# Optional: iMessage via BlueBubbles
bluebubbles:
endpoint: "http://localhost:1234"
api_key: "${BLUEBUBBLES_API_KEY}"
webhook_token: "${BLUEBUBBLES_WEBHOOK_TOKEN}"
allowed_chat_guids: []
require_mention: true
mention_name: "flynn"
# BlueBubbles webhook endpoint should point to:
# POST https://<your-flynn-host>/bluebubbles/events
# Optional: LINE
line:
channel_access_token: "${LINE_CHANNEL_ACCESS_TOKEN}"
channel_secret: "${LINE_CHANNEL_SECRET}"
allowed_source_ids: [] # Empty = allow all users/groups/rooms
require_mention: true
mention_name: "flynn"
# Binary attachments: when backup.minio.enabled is configured, Flynn uploads
# LINE binary attachments to MinIO and sends a share URL automatically.
# LINE webhook endpoint should point to:
# POST https://<your-flynn-host>/line/events
# Optional: Feishu / Lark
feishu:
app_id: "${FEISHU_APP_ID}"
app_secret: "${FEISHU_APP_SECRET}"
webhook_token: "${FEISHU_WEBHOOK_TOKEN}"
allowed_chat_ids: []
require_mention: true
mention_name: "flynn"
endpoint: "https://open.feishu.cn"
# Feishu webhook endpoint should point to:
# POST https://<your-flynn-host>/feishu/events
# Optional: Zalo
zalo:
oa_access_token: "${ZALO_OA_ACCESS_TOKEN}"
webhook_token: "${ZALO_WEBHOOK_TOKEN}"
allowed_user_ids: []
require_mention: true
mention_name: "flynn"
endpoint: "https://openapi.zalo.me"
# Binary attachments: when backup.minio.enabled is configured, Flynn uploads
# Zalo binary attachments to MinIO and sends a share URL automatically.
# Zalo webhook endpoint should point to:
# POST https://<your-flynn-host>/zalo/events
models:
default:
provider: anthropic
model: claude-opus-4-5-20251101
api_key: sk-ant-api03-...
# api_keys: [sk-ant-primary-..., sk-ant-secondary-...] # Optional rotation pool
# auth_profile_cooldown_ms: 30000 # Optional cooldown before retrying a failed key profile
local:
provider: ollama
model: qwen2.5:14b
fallback_chain: [local]
hooks:
confirm: [shell.*, file.write, file.patch]
log: [web.*, file.read]
silent: [notify]
Safety Model
Flynn is designed to be safe-by-default when expanded beyond "chat":
- Tool policy restricts which tools are even available to a given context (profiles + allow/deny + per-agent/per-provider overrides).
- Skills can declare explicit capabilities (
manifest.json.permissions) which are enforced at runtime. - Sandboxing can isolate high-risk execution (shell/process) per-session via Docker.
- Prompt-injection hardening treats fetched content/tool output as untrusted data and blocks obviously unsafe tool calls when untrusted content is present.
- Audit logs record tool usage and approvals with redaction.
Details: docs/security/SAFE_PERSONAL_AGENT.md
Agent-Oriented Architecture Diagram
If you want a fast mental model of where to start as an AI agent / contributor:
docs/architecture/AGENT_DIAGRAM.mddocs/architecture/CONTRIBUTOR_MAP.mddocs/architecture/TYPESCRIPT_MAP.mddocs/architecture/SYMBOL_INDEX.md
Model Providers
| Provider | Config |
|---|---|
| Anthropic | provider: anthropic, api_key/api_keys or auth_token |
| OpenAI | provider: openai, api_key/api_keys, optional endpoint |
| Vercel AI Gateway | provider: vercel, api_key or AI_GATEWAY_API_KEY, optional endpoint |
| GitHub Copilot | provider: github, auto-login via OAuth device flow |
| Gemini | provider: gemini, api_key |
| Bedrock | provider: bedrock, AWS credentials |
| Ollama | provider: ollama, model, optional endpoint |
| Zhipu AI (GLM) | provider: zhipuai, api_key or ZHIPUAI_API_KEY, optional endpoint |
| xAI (Grok) | provider: xai, api_key/api_keys or XAI_API_KEY |
| MiniMax | provider: minimax, api_key/api_keys or MINIMAX_API_KEY, optional endpoint |
| Moonshot (Kimi) | provider: moonshot, api_key/api_keys or MOONSHOT_API_KEY, optional endpoint |
| llama.cpp | provider: llamacpp, endpoint |
Model Tiers
Configure multiple models for different purposes:
models:
fast: { provider: anthropic, model: claude-sonnet-4-... }
default: { provider: anthropic, model: claude-opus-4-5-... }
complex: { provider: anthropic, model: claude-opus-4-5-... }
local: { provider: ollama, model: qwen2.5:14b }
Each tier can optionally specify auth_mode (auto | api_key | oauth) to control whether Flynn uses API keys vs OAuth/token auth for that provider. use_oauth: true remains supported as a compatibility alias for auth_mode: oauth.
When multiple keys are configured via api_keys, Flynn rotates across key profiles on provider failures and sticks to the last successful profile until it fails. Set auth_profile_cooldown_ms to temporarily cool down failing profiles before retrying them.
Note: with provider: openai + auth_mode: oauth (Codex backend), Flynn currently does not send tool definitions to the provider. Tool execution is therefore unavailable in that mode, and any textual tool_use output should be treated as non-executable model text.
Note: with provider: ollama, tool execution depends on model capabilities. If Ollama reports that the selected model does not support tools, Flynn omits tool definitions for that request.
Note: with provider: llamacpp, tool execution depends on the served model/template correctly emitting OpenAI-style tool_calls. Models/templates that do not preserve tool-call structure may fall back to plain text behavior.
Agent Backends
Flynn can run with the built-in native backend or delegate message processing to external CLI backends.
backends:
default: codex
native: { enabled: true }
codex: { enabled: false, path: /usr/local/bin/codex, args: [], timeout_ms: 120000 }
claude_code: { enabled: false, path: /usr/local/bin/claude, args: [], timeout_ms: 120000 }
opencode: { enabled: false, path: /usr/local/bin/opencode, args: [], timeout_ms: 120000 }
gemini: { enabled: false, path: /usr/local/bin/gemini, args: [], timeout_ms: 120000 }
pi_embedded:
enabled: false
timeout_ms: 120000
no_tools_mode: true
model: openclaw-default
system_prompt_mode: hybrid # flynn | pi_default | hybrid
module: "@mariozechner/pi-agent-core" # optional module override
pi_embedded is intended for canary migration cohorts. In spike mode (no_tools_mode: true), Flynn keeps tool-oriented turns on native and only routes plain-text turns to Pi.
When pi_embedded is selected, Flynn forwards the assembled runtime system prompt (for example SOUL.md/IDENTITY.md/USER.md/TOOLS.md, plus runtime/security sections) to Pi when system_prompt_mode is flynn or hybrid.
Runtime backend mode can be controlled live (persisted in ~/.local/share/flynn/preferences.json):
/runtime statusshows runtime mode and effective backend selection/runtime activate piforcespi_embeddedglobally/runtime deactivate piforces native for Pi-routed turns/runtime use configresets tobackends.default
/backend ... remains a supported alias for compatibility.
This manual runtime mode control is the intended Pi activation/deactivation switch.
In TUI sessions, /runtime ... is forwarded through the gateway/daemon command path (same control-plane behavior as Telegram/WebChat). flynn tui now auto-attaches to gateway and auto-starts local daemon+gateway if needed.
To evaluate canary performance from audit logs, run:
pnpm audit:backend-canary \
--audit ~/.local/share/flynn/audit.log \
--backend pi_embedded \
--baseline native \
--session telegram:8367012007 \
--gate-min-target-routes 8 \
--gate-min-baseline-routes 2 \
--gate-min-target-attempts 8 \
--format markdown
For controlled guardrail probes, also require:
--gate-min-guard-pi-no-tools-count 1 --gate-min-guard-capability-query-count 1 --gate-min-guard-attachments-present-count 1
To generate a local synthetic guard probe log through the router path:
pnpm audit:backend-canary:probes
Phase-2 evaluation checklist and decision template: docs/plans/pi_embedded_evaluation.md.
When args is non-empty:
- use
{prompt}in an argument to inject the full generated prompt directly into argv. - if
{prompt}is not present, Flynn appends backend-specific prompt args.
If multiple external backends are enabled, set backends.default to choose explicitly. If omitted, Flynn selects by priority: codex -> claude_code -> opencode -> gemini -> pi_embedded.
You can also route specific named agents to a backend:
agent_configs:
coder:
model_tier: complex
backend: codex # native | codex | claude_code | opencode | gemini | pi_embedded
pi_canary:
model_tier: default
backend: pi_embedded
Native Audio Support
Voice messages from channels can be handled in two ways:
- Native passthrough -- Audio sent directly to models that support audio input (Gemini, OpenAI, GitHub). No transcription step needed.
- Whisper transcription -- Audio transcribed to text via a Whisper-compatible API, then sent as text to models that don't support audio input (Anthropic, Bedrock, Ollama, llama.cpp).
Flynn automatically routes based on the model's capabilities. You can override this per-tier:
models:
default:
provider: gemini
model: gemini-2.0-flash
supports_audio: true # Force native audio (auto-detected for known providers)
fast:
provider: anthropic
model: claude-sonnet-4
supports_audio: false # Force transcription (default for Anthropic)
Audio Transcription
Configure a Whisper-compatible endpoint for models that don't support native audio:
audio:
enabled: true
provider:
type: custom # openai, groq, ollama, llamacpp, custom
endpoint: "http://127.0.0.1:18801/v1/audio/transcriptions"
api_key: "${WHISPER_API_KEY}" # Optional Bearer token
model: "whisper-1" # Model name (default: whisper-1)
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable audio transcription (default: false) |
provider.type |
yes | Provider type: openai, groq, ollama, llamacpp, or custom |
provider.endpoint |
yes | Whisper-compatible API endpoint |
provider.api_key |
no | Bearer token for authentication |
provider.model |
no | Model name sent in request (default: whisper-1) |
talk_mode.enabled |
no | Enable wake-phrase talk mode gating (default: false) |
talk_mode.wake_phrase |
no | Phrase that activates talk mode (default: hey flynn) |
talk_mode.timeout_ms |
no | Active listen window after wake (default: 120000) |
talk_mode.allow_manual_toggle |
no | Enable `/talk on |
Without an audio config, voice messages from non-audio-capable models will display an error message to the user. For local transcription, you can run a whisper.cpp server:
# Option 1: Manual docker run
docker run -d \
--name whisper-server \
-p 18801:8080 \
ghcr.io/ggml-org/whisper.cpp:main \
--model /app/models/ggml-base.en.bin \
--host 0.0.0.0 \
--port 8080 \
--convert \
--language en \
--inference-path /v1/audio/transcriptions
# Option 2: Using docker-compose profile (service is pre-defined in docker-compose.yml)
docker compose --profile voice up -d whisper-server
Audio persistence and diagnostics:
- Latest inbound voice bytes are stored per-session in
~/.local/share/flynn/sessions.dbundersession_config.key = "lastAudioAttachment"(used to safely hydrateaudio.transcribecalls). /resetclears session history and session config for that chat, includinglastAudioAttachment.- To clear one session manually:
DELETE FROM session_config WHERE session_id='<channel:sender>' AND key='lastAudioAttachment'. - When Flynn rewrites bad model-provided audio tool args, it emits audit event
tool.args_rewritten. - Runbook:
docs/runbooks/VOICE_TRANSCRIPTION_DEBUG.md. - SQLite quick reference:
docs/runbooks/SQLITE_QUICK_REFERENCE.md.
Web Search
Flynn provides web search tools through the web_search config:
web.search- general web results (Brave or SearXNG provider)web.search.news- news results (Brave provider only)
Brave Search configuration:
web_search:
provider: brave
api_key: "${BRAVE_API_KEY}"
max_results: 5
SearXNG configuration:
web_search:
provider: searxng
endpoint: "http://searxng:8080"
fallback_endpoint: "https://searxng.homelab.local" # Optional backup endpoint
max_results: 5
| Field | Required | Description |
|---|---|---|
provider |
no | brave or searxng (default: brave) |
api_key |
yes (Brave) | Brave Search API key |
endpoint |
yes (SearXNG primary) | Primary base URL for self-hosted SearXNG |
fallback_endpoint |
no (SearXNG) | Backup SearXNG base URL used if primary endpoint fails |
max_results |
no | Default result count for web.search* tools (default: 5, max: 20) |
Brave-specific query fields (supported by web.search, and by web.search.news except safeSearch):
country(for exampleus,gb)searchLang(for exampleen,de)freshness(pd,pw,pm,py)safeSearch(off,moderate,strict) -web.searchonly
Optional local Brave Search container (for MCP-based workflows):
docker compose --profile search up -d brave-search
Optional local SearXNG container (for self-hosted web search):
docker compose --profile search up -d searxng
Local-first + homelab fallback pattern:
- If Flynn runs in Docker Compose, set
endpoint: "http://searxng:8080". - If Flynn runs on the host (
pnpm dev/pnpm start), setendpoint: "http://127.0.0.1:18803". - Set
fallback_endpointto your existing homelab SearXNG URL.
Example for host-run Flynn with local compose SearXNG and homelab backup:
web_search:
provider: searxng
endpoint: "http://127.0.0.1:18803"
fallback_endpoint: "https://searxng.homelab.local"
max_results: 5
Note: Flynn's built-in web.search* tools call Brave's HTTP API directly and use web_search.api_key; they do not route through the brave-search MCP container.
Text-to-Speech (TTS) Reply Audio
Flynn can attach synthesized voice replies (OpenAI-compatible /v1/audio/speech) alongside text responses.
tts:
enabled: true
enabled_channels: [telegram, whatsapp, discord] # Empty = all channels
providers:
- name: primary
type: custom # openai | custom
endpoint: "https://tts-primary.example.com/v1/audio/speech"
api_key: "${PRIMARY_TTS_API_KEY}"
model: "gpt-4o-mini-tts"
voice: "alloy"
format: "mp3" # mp3 | wav | opus
- name: backup
type: openai
api_key: "${OPENAI_API_KEY}"
model: "gpt-4o-mini-tts"
voice: "nova"
format: "opus"
fallback:
max_attempts: 2
failure_cooldown_ms: 60000
provider: # Legacy single-provider shape (still supported)
type: openai # openai | custom
endpoint: "https://api.openai.com/v1/audio/speech"
api_key: "${OPENAI_API_KEY}" # Optional Bearer token
model: "gpt-4o-mini-tts"
voice: "alloy"
format: "mp3" # mp3 | wav | opus
| Field | Required | Description |
|---|---|---|
tts.enabled |
no | Enable voice reply synthesis (default: false) |
tts.enabled_channels |
no | Channels allowed to receive voice replies ([] means all channels) |
tts.providers[] |
no | Ordered provider chain for synthesis fallback |
tts.providers[].name |
no | Provider label used for health tracking/debug logs |
tts.provider.type |
no | openai or custom (default: openai) |
tts.provider.endpoint |
no | OpenAI-compatible /v1/audio/speech endpoint (openai defaults to OpenAI API URL) |
tts.provider.api_key |
no | Bearer token for authentication |
tts.provider.model |
no | TTS model (default: gpt-4o-mini-tts) |
tts.provider.voice |
no | Voice identifier (default: alloy) |
tts.provider.format |
no | Output format: mp3, wav, opus (default: mp3) |
tts.fallback.max_attempts |
no | Max providers attempted per reply before text fallback (default: 3) |
tts.fallback.failure_cooldown_ms |
no | Cooldown for providers after synthesis failures (default: 60000) |
If all configured providers fail, Flynn deterministically returns text-only (no dropped reply) and retries unhealthy providers after their cooldown window.
Capture Tools
Flynn includes host capture tools:
screen.capture-> captures current screen and returns base64 image payloadcamera.capture-> captures one camera frame and returns base64 image payload
Notes:
- These are host-command wrappers and require platform binaries:
- macOS:
screencapture(screen),imagesnap(camera) - Linux:
grimor ImageMagickimport(screen),ffmpeg(camera)
- macOS:
Telegram Commands
| Command | Description |
|---|---|
/start |
Initialize bot |
/reset |
Clear conversation history |
/status |
Show current model and status |
/local |
Switch to local model |
/cloud |
Switch to cloud model |
/model |
Show model info and options |
/approvals |
List pending approval gates for current session |
/approve [id] |
Approve latest (or specific) pending gate |
/deny [id] [reason] |
Deny latest (or specific) pending gate |
| `/skill <list | search |
/runtime <status|activate pi|deactivate pi|use config> |
Show or control global runtime backend mode (/backend ... alias also supported) |
/subagents [list|summary <id> [limit]|cancel <id>|delete <id>] |
Inspect and control spawned subagent sessions |
Web UI Dashboard
Flynn includes a built-in web control dashboard served by the WebSocket gateway. Access it at http://localhost:18800 (or your configured gateway port).
Pages
| Page | Description |
|---|---|
| Dashboard | System health cards, channel status, usage stats, assistant-health quick actions, playbook presets with rollback, activation checklist, and daily briefing preview/test-send. Auto-refreshes every 10s |
| Chat | Session selector, streaming tool events, markdown rendering with syntax highlighting |
| Sessions | Browse all sessions, view message history, delete sessions |
| Usage | Token usage summary cards, per-session breakdown table, auto-refresh |
| Settings | Edit hook patterns (confirm/log/silent), Personal Assistant Mode toggles, view tools/services, and redacted config |
The dashboard is a vanilla JS SPA with no build step — hash-based routing, ES modules, and the existing WebSocket JSON-RPC protocol.
Terminal UI
# Minimal mode (readline)
pnpm tui
# Fullscreen mode (React/Ink)
pnpm tui:fs
TUI Commands
| Command | Description |
|---|---|
/help |
Show help |
/model |
Show all model tiers and which is active |
/model <tier> |
Switch active tier (local, default, fast, complex, or aliases ollama, sonnet, haiku, opus) |
/model <tier> <provider/model> |
Hot-swap a tier's provider and model at runtime |
/backend [provider] |
TUI-local command: show or switch local model backend (ollama, llamacpp) |
/runtime <status|activate pi|deactivate pi|use config> |
Forward runtime backend mode control to daemon/gateway command service |
/login [provider] |
Authenticate with GitHub (OAuth device flow) |
/reset |
Clear history |
/status |
Show session info |
/tools |
Show authoritative runtime tool list for this session |
/research <task> |
Delegate a task to agent_configs.research |
/council <task> |
Run dual D/P councils pipeline with bridge+meta merge (brief in TUI, full artifacts saved to disk) |
/subagents [list|summary <id> [limit]|cancel <id>|delete <id>] |
Inspect and control spawned subagent sessions |
/compact |
Compact conversation context |
/usage |
Show token usage and cost |
/context |
Show estimated context-window usage |
/verbose |
Toggle verbose output mode |
/pair |
Generate/list/revoke DM pairing codes |
/fullscreen |
Switch to fullscreen mode |
/transfer <dest> |
Transfer session to another frontend (telegram or tui) |
/approvals |
List pending approval gates for current session |
/approve [id] |
Approve latest (or specific) pending gate |
/deny [id] [reason] |
Deny latest (or specific) pending gate |
| `/skill <list | search |
/quit |
Exit |
TUI keyboard controls: Esc cancels active prompt/running response. Ctrl+C clears the current input; press Ctrl+C twice quickly to exit.
flynn tui now attaches to the gateway command path at startup (and auto-starts local daemon+gateway when unavailable), so /runtime ... in TUI matches Telegram/WebChat behavior. /backend [provider] remains the TUI-local backend switch for local tier providers.
Runtime Model Switching
Switch providers and models on the fly without editing config or restarting:
# Show current tiers
/model
# Switch active tier
/model fast
/model complex
# Hot-swap a tier's provider/model
/model default anthropic/claude-sonnet-4
/model default zhipuai/glm-4.7
/model fast github/gpt-4o-mini
/model local ollama/glm-4.7-flash
The provider name must match a supported provider (anthropic, openai, gemini, ollama, llamacpp, openrouter, vercel, bedrock, github, zhipuai, xai, minimax, moonshot, synthetic). Tab completion is available for both tiers and provider names.
For cloud Zhipu models, ensure ZHIPUAI_API_KEY is set or api_key is configured in the relevant tier.
Note: The /model command works in the TUI only. WebChat sessions inherit the active tier from the daemon.
Research Agent Quickstart
Add a dedicated research sub-agent and delegate to it with /research:
agent_configs:
research:
model_tier: complex
tool_profile: messaging
system_prompt: |
You are a research agent. Find, verify, and synthesize information.
Prefer primary sources. Include links and concrete dates.
Keep output structured and concise.
Then use:
/research compare k0s vs k3s for a 3-node homelab in 2026
You can also trigger this without the slash command:
research compare k0s vs k3s for a 3-node homelab in 2026
look up best practices for k8s backup retention
If the research agent is not configured, Flynn returns a setup hint with available agent names.
flynn setup can configure this under the Security section.
Councils Pipeline Quickstart
Enable deterministic dual-council orchestration and define required role agents:
councils:
enabled: true
agent_configs:
council_d_arbiter:
model_tier: default
tool_profile: messaging
system_prompt: You are the D arbiter. Score and shortlist ideas conservatively.
council_d_freethinker:
model_tier: default
tool_profile: messaging
system_prompt: You are the D freethinker. Generate and ground ideas in JSON only.
council_p_arbiter:
model_tier: default
tool_profile: messaging
system_prompt: You are the P arbiter. Prefer novelty and leverage while scoring.
council_p_freethinker:
model_tier: default
tool_profile: messaging
system_prompt: You are the P freethinker. Generate contrarian ideas in JSON only.
council_meta_arbiter:
model_tier: default
tool_profile: messaging
system_prompt: You are the meta arbiter. Select from provided IDs only; no novel mechanisms.
Run from chat:
/council design a 30-day plan to cut CI flakiness by 50%
By default, /council prints a concise execution brief in the TUI and persists full artifacts under:
~/.local/share/flynn/councils/
When FLYNN_DATA_DIR is set, artifacts are written to:
${FLYNN_DATA_DIR}/councils/
Each run saves:
*.mdsummary report (brief + full conversation trace + raw JSON block)*.jsonfull structuredCouncilRunResult
Or via tools:
{"name":"council.run","args":{"task":"design a 30-day plan to cut CI flakiness by 50%"}}
Subagent Sessions (subagent.* tools)
Flynn now supports multi-turn child agent sessions scoped to the current parent session.
Available tools:
subagent.spawn— create a child session from anagent_configs.<name>profilesubagent.send— send follow-up work to an existing child sessionsubagent.list— list active child sessionssubagent.cancel— request cancellation for a running child turnsubagent.delete— remove a child session and clear its historysubagent.summary— inspect transcript summary for a child session
Session controls:
- Queue modes:
followup(FIFO) orinterrupt(latest-wins cancellation + supersede). - Budget guardrails: max turns, max total tokens, and per-turn timeout.
- Tool profile override per subagent spawn (
minimal|messaging|coding|full). - Runtime inspection command:
/subagents [list|summary <id> [limit]|cancel <id>|delete <id>].
Example flow:
{"name":"subagent.spawn","args":{"agent":"research","subagent_id":"plan-research","task":"Survey backup strategies for a 3-node homelab."}}
{"name":"subagent.send","args":{"subagent_id":"plan-research","message":"Now compare operational risk and recovery-time tradeoffs."}}
{"name":"subagent.summary","args":{"subagent_id":"plan-research","limit":20}}
{"name":"subagent.list","args":{}}
Config controls:
agents:
subagents:
enabled: true
max_active_sessions: 6
idle_ttl_ms: 3600000
queue_mode: followup
default_tool_profile: minimal
max_turns: 40
max_total_tokens: 200000
turn_timeout_ms: 120000
Running as Service
Ollama (Recommended)
If running Ollama locally, use a user-level service to ensure it stays alive and reachable:
cat > ~/.config/systemd/user/ollama.service << 'EOF'
[Unit]
Description=Ollama Service (User)
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/ollama serve
Restart=always
RestartSec=3
Environment="HOME=%h"
Environment="OLLAMA_MODELS=%h/.ollama"
Environment="LD_LIBRARY_PATH=/usr/lib:/opt/cuda/lib64"
Environment="CUDA_VISIBLE_DEVICES=0"
Environment="OLLAMA_KEEP_ALIVE=-1"
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now ollama
Flynn daemon
# Create systemd user service
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/flynn.service << 'EOF'
[Unit]
Description=Flynn Personal AI Assistant
After=network.target ollama.service
[Service]
Type=simple
WorkingDirectory=/path/to/flynn
ExecStart=/usr/bin/pnpm start
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=default.target
EOF
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now flynn
# View logs
journalctl --user -u flynn -f
# From the repo root, rebuild + restart service after code changes
make restart
Hook Engine
Control sensitive operations with pattern matching:
hooks:
confirm: # Requires user approval via Telegram
- shell.*
- process.start
- process.kill
- browser.*
- message.send
- cron.create
- cron.delete
- file.write
- file.patch
log: # Logs but doesn't block
- web.*
- file.read
silent: # Executes without notification
- notify
For unrestricted deployments, pair hooks with agent-level sensitive gating:
agents:
# deny_without_elevation | confirm_without_elevation
sensitive_mode: confirm_without_elevation
immutable_denylist:
- tool: shell.exec
args_pattern: "git push origin main"
reason: "direct main pushes are blocked"
- tool: shell.exec
args_pattern: "git reset --hard"
reason: "destructive hard reset is blocked"
Browser Automation Tools
Flynn ships these browser tools:
browser.navigatebrowser.screenshotbrowser.clickbrowser.typebrowser.contentbrowser.wait_forbrowser.assertbrowser.extractbrowser.checkpoint.savebrowser.checkpoint.resumebrowser.evalbrowser.evaluate(alias ofbrowser.eval)
These tools are backed by a Puppeteer/CDP browser manager and are only registered when browser.enabled: true.
They can still be filtered out by tool policy (tools.profile, tools.allow, tools.deny).
At startup, Flynn logs the browser tools that remain available after policy filtering.
Browser runtime guardrails support domain allowlists, explicit high-risk-domain confirmation, retry controls, and a bounded workflow step budget.
browser:
enabled: true
headless: true
max_pages: 5
default_timeout: 30000
allowed_domains: ["*.example.com"]
high_risk_domains: ["bank.example.com"]
require_confirmation_for_high_risk: true
max_workflow_steps: 120
default_retry_attempts: 1
max_retry_attempts: 5
retry_delay_ms: 250
# executable_path: /usr/bin/google-chrome
# ws_endpoint: ws://127.0.0.1:9222/devtools/browser/<id>
tools:
profile: coding # or full
Cron Scheduling
Schedule automated messages on cron schedules. Each job fires an inbound message through the agent pipeline and routes the response to a configured output channel.
Set automation.delivery_mode to control automation session behavior:
shared_session(default): reuse one session per cron job/webhook name.isolated_job: create a fresh session per cron trigger/webhook request.announce: create a fresh ephemeral announce-style run per trigger (no shared automation history).
automation:
delivery_mode: shared_session
reactions:
- name: boss-email
on: [gmail]
filter:
contains: "boss@company.com"
run: |
Summarize this email and propose next actions.
{{text}}
cron:
- name: daily-summary
schedule: "0 9 * * *" # 9 AM daily
message: "Give me a summary of today's tasks"
output:
channel: telegram # Route response to Telegram
peer: "123456789" # Chat ID to send to
timezone: Europe/London # Optional timezone
enabled: true
once_per_local_day: false # Optional dedupe: max one trigger/day in job timezone
- name: hourly-check
schedule: "0 * * * *" # Every hour
message: "Check system status"
output:
channel: telegram
peer: "123456789"
enabled: false # Disabled, won't fire
model_tier: fast # Use fast tier for quick checks
# Optional built-in daily briefing preset.
# This automatically registers a cron job; you only set schedule/output/prompt.
daily_briefing:
enabled: true
name: daily-briefing
schedule: "0 8 * * *"
timezone: America/New_York
dedupe_per_local_day: true
output:
channel: telegram
peer: "123456789"
model_tier: fast
prompt: |
Create my daily briefing.
Summarize today's calendar, unread/important email, and top pending tasks.
# Optional scheduled MinIO -> memory synchronization.
# Runs direct ingestion jobs without requiring interactive tool calls.
minio_sync:
enabled: true
interval: "6h"
run_on_start: false
notify_on_success: false
tasks:
- prefix: "knowledge/"
namespace_base: "global/knowledge/minio"
mode: append
max_objects: 20
max_chars_per_object: 8000
force: false
Cron Config Fields
| Field | Required | Description |
|---|---|---|
automation.delivery_mode |
no | Automation session strategy: shared_session, isolated_job, or announce (default: shared_session) |
automation.reactions.* |
no | Event-triggered prompt rewrite rules for inbound automation/chat events (supports channel matching + text/metadata filters) |
name |
yes | Unique job identifier |
schedule |
yes | Cron expression (standard 5-field) |
message |
yes | Text sent to the agent when the job fires |
output.channel |
yes | Channel name to route the response (e.g. telegram) |
output.peer |
yes | Peer/chat ID on the output channel |
timezone |
no | IANA timezone (defaults to system timezone) |
enabled |
no | Whether the job is active (default: true) |
model_tier |
no | Model tier for this job: fast, default, complex, or local |
once_per_local_day |
no | If true, suppress duplicate triggers within the same local day (job timezone) |
automation.daily_briefing.* |
no | Built-in daily briefing preset; generates an extra cron job when enabled: true and output is set |
automation.minio_sync.* |
no | Scheduled MinIO prefix ingestion into memory namespaces (direct daemon automation) |
Reactions (Event -> Action Prompting)
Reactions let you convert inbound events into deterministic agent prompts without adding new webhook endpoints or cron jobs.
- Matchers support:
- channel list (
on) - text filters (
contains,regex) - metadata path filters (
metadatawith dot paths)
- channel list (
- Templates support:
{{text}},{{channel}},{{sender_id}}{{metadata.some.path}}
- Priority + cooldown semantics:
priority(higher wins, default100; ties break by config order).cooldown_mssuppresses repeated matches per sender/session.stop_on_matchdefaults totrue; setfalseto make a rule non-blocking (only chosen if no blocking rule matches).
Backup Scheduling
Daemon backups can run on a fixed interval (backup.interval) or a cron schedule (backup.schedule). If both are set, backup.schedule takes precedence.
backup:
enabled: true
schedule: "0 2 * * *" # Optional cron schedule (nightly 2 AM)
interval: "24h" # Fallback when schedule is not set
run_on_start: true # Also run once on daemon start
notify:
channel: telegram
peer: "123456789"
failure_threshold: 1 # Notify after this many consecutive failures
notify_recovery: true # Send a recovery message after failure clears
local_dir: ~/.local/share/flynn/backups
include_vectors: true
minio:
enabled: true
endpoint: 127.0.0.1:9000
access_key: "${MINIO_ACCESS_KEY}"
secret_key: "${MINIO_SECRET_KEY}"
bucket: flynn-backups
prefix: flynn
secure: true
# Optional absolute path to MinIO client binary if not on PATH
mc_path: /usr/local/bin/mc
MinIO Share Tool
When backup.minio.enabled is configured, Flynn also exposes MinIO tools:
minio.share: upload a local file to the configured MinIO bucket and return a temporary download URL (mc share download)minio.ingest: read a text-like object (plus PDF/DOCX via extractor tools when available) from MinIO and append/replace a memory namespaceminio.sync: recursively ingest a MinIO prefix into nested memory namespaces with object and size limits (including PDF/DOCX extraction when available)
PDF/DOCX ingestion runtime requirements:
- PDF extraction requires
pdftotext. - DOCX extraction requires
pandocordocx2txt. flynn setupnow checks these dependencies after config save whenbackup.minio.enabled: true, and prints OS-aware install hints when missing.flynn doctorreportsMinIO ingest extractorsstatus (with install hints) so missing binaries are visible in health checks.
Kubernetes Tools
Optional Kubernetes tools are available when k8s.enabled: true:
k8s.pods: list pod status (phase, ready count, restarts)k8s.deployments: list deployment replica readinessk8s.logs: fetch recent pod logs
k8s:
enabled: true
kubectl_path: kubectl
default_namespace: observability
allowed_namespaces: [observability, platform]
Inbound Webhooks
HTTP endpoints that trigger agent processing. Each webhook accepts POST requests, optionally verifies an HMAC signature, renders a message template, and routes the agent's response to an output channel.
automation:
delivery_mode: shared_session
webhooks:
- name: github-push
secret: "whsec_..." # HMAC secret for signature verification
message: "GitHub push to {{json.repository.full_name}}: {{json.head_commit.message}}"
output:
channel: telegram
peer: "123456789"
- name: alertmanager
message: "Alert: {{json.alerts.0.annotations.summary}}"
output:
channel: discord
peer: "channel-id"
Webhooks are available at POST /webhooks/:name on the gateway HTTP server. They bypass gateway token auth and use their own per-webhook HMAC verification instead.
Webhook Config Fields
| Field | Required | Description |
|---|---|---|
automation.delivery_mode |
no | Automation session strategy: shared_session, isolated_job, or announce (default: shared_session) |
name |
yes | Unique webhook identifier (used in URL path) |
secret |
no | HMAC secret for X-Webhook-Signature header verification (SHA-256) |
message |
no | Template for the message sent to the agent (default: {{body}}) |
output.channel |
yes | Channel name to route the response (e.g. telegram) |
output.peer |
yes | Peer/chat ID on the output channel |
enabled |
no | Whether the webhook is active (default: true) |
Template Variables
| Variable | Description |
|---|---|
{{body}} |
Raw request body as string |
{{json.field}} |
Extract a field from JSON body (dot notation for nested fields) |
Heartbeat Monitor
Periodic health checks that validate system components and notify a configured channel on failure.
automation:
heartbeat:
enabled: true
interval: "5m" # Check every 5 minutes
notify_cooldown: "30m" # Suppress repeated alerts inside cooldown window
checks: [gateway, model, channels, memory, disk, process_memory, backup, provider_errors]
notify:
channel: telegram
peer: "123456789"
failure_threshold: 2 # Notify after 2 consecutive failures
disk_threshold_mb: 100 # Warn when <100MB free
process_memory_threshold_mb: 1500 # Warn when RSS memory exceeds threshold
backup_failure_threshold: 1 # Warn when backup failures meet threshold
provider_error_rate_threshold: 0.5 # Warn when provider error rate >= threshold
provider_error_min_calls: 5 # Minimum model calls per provider before evaluation
Heartbeat Checks
| Check | What it validates |
|---|---|
gateway |
HTTP server is responsive |
model |
Model router has an active tier configured |
channels |
At least one channel adapter is connected |
memory |
Memory directory is readable and writable |
disk |
Free disk space exceeds threshold |
process_memory |
Flynn process RSS memory usage stays under threshold |
backup |
Backup scheduler consecutive failures stay under threshold |
provider_errors |
Model provider error rates stay below threshold |
The monitor sends a notification when failures reach the configured threshold and a recovery notification when all checks pass again.
Repeated failure/recovery notifications are throttled by notify_cooldown.
Heartbeat Config Fields
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable the heartbeat monitor (default: false) |
interval |
no | Check interval: 60s, 5m, 1h (default: 5m) |
notify_cooldown |
no | Minimum time between repeated heartbeat notifications of the same type (default: 30m) |
checks |
no | Which checks to run (default: gateway, model, channels, memory, disk, process_memory, backup, provider_errors) |
notify.channel |
no | Channel to send failure/recovery notifications |
notify.peer |
no | Peer/chat ID for notifications |
failure_threshold |
no | Consecutive failures before notifying (default: 2) |
disk_threshold_mb |
no | Disk space warning threshold in MB (default: 100) |
process_memory_threshold_mb |
no | RSS memory threshold in MB for process_memory check (default: 1500) |
backup_failure_threshold |
no | Consecutive backup failures threshold for backup check (default: 1) |
provider_error_rate_threshold |
no | Error-rate threshold (0..1) for provider_errors check (default: 0.5) |
provider_error_min_calls |
no | Minimum provider calls before applying error-rate threshold (default: 5) |
Common Schedules and Routing
- Nightly backups to Telegram alerts:
backup.schedule: "0 2 * * *"backup.notify.channel: telegram
- Weekday daily briefing to Discord:
automation.daily_briefing.schedule: "0 8 * * 1-5"automation.daily_briefing.output.channel: discord
- High-frequency heartbeat to Slack:
automation.heartbeat.interval: "2m"automation.heartbeat.notify.channel: slack
- MinIO sync every 6h to WebChat:
automation.minio_sync.interval: "6h"automation.minio_sync.notify.channel: webchat
flynn setup now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task, with prompts for output channel/peer routing.
After save, setup prints focused live readiness checks and a first-success task path so new installs can validate end-to-end operation immediately.
See docs/operations/OPERATOR_PACK.md for an operations runbook and verification checklist.
Example Operator Pack output routing:
backup:
enabled: true
schedule: "0 2 * * *"
notify:
channel: telegram
peer: "123456789"
automation:
heartbeat:
enabled: true
notify:
channel: telegram
peer: "123456789"
notify_cooldown: "30m"
daily_briefing:
enabled: true
output:
channel: telegram
peer: "123456789"
minio_sync:
enabled: true
notify:
channel: telegram
peer: "123456789"
Gmail Pub/Sub Watcher
Monitor a Gmail inbox and forward new messages into the agent pipeline.
Supported delivery modes:
- Push (Gmail watch → Pub/Sub topic → HTTP push subscription →
POST /gmail/push) - Pull (Pub/Sub pull subscription → Flynn periodically pulls messages; no inbound webhook)
- Polling (Gmail History API polling fallback)
Prerequisites
- Create a Google Cloud project with the Gmail API enabled
- Create OAuth2 credentials (Desktop application type) and download the JSON file
- Run
flynn gmail-authto complete the OAuth2 flow and store the refresh token- Requests Gmail scopes for settings + read access (
gmail.settings.basic+gmail.readonly) - Flynn stores service tokens in
~/.config/flynn/auth.jsonand keeps per-service token files for compatibility
- Requests Gmail scopes for settings + read access (
For Pub/Sub delivery (push/pull), also enable the Pub/Sub API and create:
- A topic (e.g.
projects/your-project/topics/gmail-push) - A subscription (push and/or pull)
Configuration
automation:
gmail:
enabled: true
credentials_file: ~/.config/flynn/gmail-credentials.json
token_file: ~/.config/flynn/gmail-token.json # Default location
# Push mode (optional)
pubsub_topic: projects/your-project/topics/gmail-push
disable_push: false
# Pull mode (optional; no inbound webhook required)
pubsub_subscription_id: projects/your-project/subscriptions/gmail-pull
pubsub_pull_interval: "60s"
pubsub_max_messages: 10
watch_labels: [INBOX] # Labels to watch
poll_interval: "60s" # Polling fallback interval
message: "New email from {{from}}: {{subject}}\n\n{{snippet}}"
output:
channel: telegram
peer: "123456789"
Push notifications arrive at POST /gmail/push on the gateway HTTP server (bypasses gateway auth).
Pull mode uses Application Default Credentials (e.g. GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json) to access Pub/Sub.
Gmail Config Fields
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable the Gmail watcher (default: false) |
credentials_file |
yes | Path to Google OAuth2 credentials JSON |
token_file |
no | Path to stored OAuth2 refresh token (default: ~/.config/flynn/gmail-token.json) |
pubsub_topic |
no | Pub/Sub topic for Gmail watch push notifications (projects/<project>/topics/<topic>) |
disable_push |
no | Disable watch registration even if pubsub_topic is set (default: false) |
pubsub_subscription_id |
no | Pub/Sub pull subscription (projects/<project>/subscriptions/<sub>) |
pubsub_pull_interval |
no | How often to pull subscription messages (default: 60s) |
pubsub_max_messages |
no | Max messages per pull cycle (default: 10) |
watch_labels |
no | Gmail labels to watch (default: [INBOX]) |
poll_interval |
no | Polling fallback interval: 60s, 5m (default: 300s) |
history_start |
no | ISO date string — only process emails received after this date |
message |
no | Template for the agent message (default: New email from {{from}}: {{subject}}\n\n{{snippet}}) |
output.channel |
yes | Channel name to route the response (e.g. telegram) |
output.peer |
yes | Peer/chat ID on the output channel |
Gmail Tools
When automation.gmail.enabled: true, Flynn registers Gmail tools:
gmail.list— list recent messages by labelgmail.search— query messages with Gmail search syntaxgmail.read— fetch full message body by IDgmail.filter.create— create Gmail filters (criteria + actions like archive/label/forward)
gmail.filter.create requires a token issued with current scopes. If your token predates this change, re-run flynn gmail-auth.
Template Variables
| Variable | Description |
|---|---|
{{from}} |
Sender address |
{{to}} |
Recipient address |
{{subject}} |
Email subject line |
{{snippet}} |
Gmail-provided message snippet |
{{date}} |
Email date |
{{id}} |
Gmail message ID |
{{labels}} |
Comma-separated label names |
Google Calendar Tools
Query Google Calendar events from within conversations. Provides three tools: calendar.today (today's agenda), calendar.list (date range), and calendar.search (full-text search).
Prerequisites
- A Google Cloud project with the Calendar API enabled
- OAuth2 credentials (Desktop application type) — the same credentials file used for Gmail works
- Run
flynn gcal-authto complete the OAuth2 flow and store the refresh token- Also persisted in
~/.config/flynn/auth.jsonfor shared runtime refresh handling
- Also persisted in
Configuration
automation:
gcal:
enabled: true
credentials_file: ~/.config/flynn/gmail-credentials.json
token_file: ~/.config/flynn/gcal-token.json # Default location
calendar_ids: [primary] # Calendar IDs to query
Google Calendar Config Fields
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable the calendar tools (default: false) |
credentials_file |
yes | Path to Google OAuth2 credentials JSON |
token_file |
no | Path to stored OAuth2 refresh token (default: ~/.config/flynn/gcal-token.json) |
calendar_ids |
no | Calendar IDs available for queries (default: [primary]) |
For full local operation guidance (token acquisition, storage, migration, refresh/renewal, and service scopes), see Google OAuth Runbook.
Google Docs, Drive, and Tasks Tools
Flynn also supports Google Docs, Drive, and Tasks tools:
- Docs:
docs.list,docs.search,docs.read - Drive:
drive.list,drive.search,drive.read - Tasks:
tasks.lists,tasks.list
Enable in config:
automation:
gdocs:
enabled: true
credentials_file: ~/.config/flynn/gmail-credentials.json
token_file: ~/.config/flynn/gdocs-token.json
gdrive:
enabled: true
credentials_file: ~/.config/flynn/gmail-credentials.json
token_file: ~/.config/flynn/gdrive-token.json
gtasks:
enabled: true
credentials_file: ~/.config/flynn/gmail-credentials.json
token_file: ~/.config/flynn/gtasks-token.json
Authenticate with:
flynn gdocs-auth
flynn gdrive-auth
flynn gtasks-auth
# or: flynn google-auth --service gdocs|gdrive|gtasks
Vector Memory Search
The memory system supports hybrid search combining keyword matching with semantic vector similarity. When embeddings are enabled, memory.search uses both approaches and merges results with configurable weighting.
Memory persistence is hybrid:
- Manual writes via
memory.write - Automatic fact extraction during context compaction when
memory.auto_extract: true
memory:
enabled: true
auto_extract: true
proactive_extract:
enabled: false # Per-turn extraction beyond compaction-only writes
min_tool_calls: 1 # Trigger only when this many tool calls occur in a turn
namespace: global # Target namespace for extracted durable facts
daily_log:
enabled: false # Append each turn to dated namespaces (daily/YYYY-MM-DD)
namespace_prefix: daily
include_session_metadata: true # Include session/channel/sender headers in each log block
max_user_chars: 2000 # Per-turn cap before truncating user text
max_assistant_chars: 4000 # Per-turn cap before truncating assistant text
max_context_tokens: 2000
embedding:
enabled: true
provider: openai # openai, gemini, ollama, llamacpp
model: text-embedding-3-small
api_key: "${OPENAI_API_KEY}"
chunk_size: 512 # Tokens per chunk
chunk_overlap: 50 # Overlap between chunks
top_k: 5 # Top results from vector search
hybrid_weight: 0.7 # 0.0 = keyword only, 1.0 = vector only
qmd:
enabled: false # Experimental markdown-native search backend
top_k: 8 # Max QMD results
min_score: 0.15 # Minimum match score (0-1)
Embedding Providers
| Provider | Config |
|---|---|
| OpenAI | provider: openai, api_key, model (default: text-embedding-3-small) |
| Gemini | provider: gemini, api_key, model |
| Ollama | provider: ollama, endpoint (default: 127.0.0.1:11434), model |
| llama.cpp | provider: llamacpp, endpoint, optional model |
| Voyage AI | provider: voyageai, api_key or VOYAGE_API_KEY, model (default: voyage-3-large) |
Embeddings are indexed in the background — when memory is written, the namespace is marked dirty and re-indexed within 30 seconds. The vector index is stored in vectors.db alongside the session database.
Search backend selection:
memory.embedding.enabled: true-> hybrid keyword+vector backendmemory.embedding.enabled: falseandmemory.qmd.enabled: true-> QMD markdown backend- otherwise -> keyword-only fallback
When the selected backend is unavailable (for example embedding provider errors), search falls back gracefully to keyword matching.
memory.auto_extract controls whether compaction appends extracted durable facts to global memory.
memory.proactive_extract controls optional per-turn extraction after responses (useful for tool-heavy workflows).
memory.daily_log controls optional append-only daily turn logs in dated namespaces. Use include_session_metadata when you want better cross-session traceability, and lower the per-turn char caps to reduce noise/volume in long-running chat logs.
Proactive Context Management
Flynn can proactively signal context pressure, checkpoint conversation state to memory, and auto-compact before hard context overflows.
compaction:
enabled: true
threshold_pct: 80
keep_turns: 4
summary_max_tokens: 1024
importance_threshold: 1
proactive:
enabled: true
warn_pct: 75
checkpoint_pct: 85
auto_compact_pct: 95
checkpoint_cooldown_ms: 300000
memory_namespace: session/checkpoints
Gateway surfaces this via:
context_warningstreamed event duringagent.send(beforedone)system.contextUsageRPC for per-session context budget snapshots
Embedding Config Fields
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable vector search (default: false) |
provider |
no | Embedding provider (default: openai) |
model |
no | Embedding model name (default: text-embedding-3-small) |
endpoint |
no | Provider endpoint (required for ollama/llamacpp) |
api_key |
no | API key (required for openai/gemini/voyageai) |
dimensions |
no | Vector dimensions (auto-detected from model if not set) |
chunk_size |
no | Max tokens per chunk (default: 512) |
chunk_overlap |
no | Token overlap between chunks (default: 50) |
top_k |
no | Number of vector results to return (default: 5) |
hybrid_weight |
no | Vector vs keyword weight, 0.0-1.0 (default: 0.7) |
QMD Config Fields
| Field | Required | Description |
|---|---|---|
enabled |
no | Enable experimental markdown-native QMD backend (default: false) |
top_k |
no | Max QMD results returned by memory.search (default: 8) |
min_score |
no | Minimum relevance score (0.0-1.0) for QMD matches (default: 0.15) |
Session End Summary
Optionally summarize conversations when a WebSocket session ends and append the summary to memory.
sessions:
end_summary:
enabled: true
tier: fast
memory_namespace: session/summaries
Audit Trail
Flynn writes structured audit events to audit.path, including tool execution, session lifecycle, and user actions (user.action) from both channel and gateway requests.
Session lifecycle now includes proactive context maintenance events:
session.compactfor normal compaction passessession.checkpointwhen proactive checkpoint summaries are written to memorysession.auto_compactwhen proactive critical-threshold auto-compaction runs
Phase 0 baseline observability adds:
run.state(start/complete/cancel_requested/cancelled/error)run.cancel(cancel intent/ack + latency)reaction.match/reaction.skip(reaction decision outcomes + skip reasons)
Baseline summaries can be generated from audit logs:
pnpm audit:phase0-baseline --audit ~/.local/share/flynn/audit.log --since 2026-02-25T00:00:00Z --format markdown
Live baseline artifacts (sample JSONL + JSON/Markdown summaries) can be captured with anonymized identifiers:
pnpm audit:phase0-baseline:live
Backend-scoped channel windows:
pnpm audit:phase0-baseline:live:pi
pnpm audit:phase0-baseline:live:native
One-shot refresh for all live baseline windows (channel, gateway, backend-scoped pi_embedded, backend-scoped native):
pnpm audit:phase0-baseline:live:refresh
Backend drift/freshness gate for backend-scoped artifacts (pi_embedded vs native):
pnpm audit:phase0-baseline:live:drift
Optional drift thresholds now include reaction decision deltas (--max-reaction-match-rate-drop-pp, --max-reaction-skip-rate-increase-pp) in addition to run/cancel/error/cancel-latency thresholds.
This command writes drift reports to:
docs/plans/artifacts/phase0_baseline_live_backend_drift_<UTC-date>.mddocs/plans/artifacts/phase0_baseline_live_backend_drift_<UTC-date>.json
Cadence scheduling (example: every 6 hours via host cron) with rolling timestamp tags, drift check, and automatic retention apply:
0 */6 * * * cd /path/to/flynn && pnpm audit:phase0-baseline:live:refresh:drift:rolling:prune >> ~/.local/share/flynn/phase0_baseline_refresh.log 2>&1
audit:phase0-baseline:live* scripts now default to the current UTC date tag when --tag is omitted.
Use audit:phase0-baseline:live:refresh:drift:rolling when you want each cadence run to keep a distinct tag (YYYY-MM-DD-HHMMSS) so drift checks compare against a recent prior snapshot immediately.
Use audit:phase0-baseline:live:prune for dry-run retention planning, and audit:phase0-baseline:live:prune:apply to prune older rolling-tag artifacts while keeping the newest snapshots per family. Retention depth defaults to 8 tags per family and can be overridden via non-negative integer KEEP_PER_FAMILY=<n>. Prune runs also write reports to docs/plans/artifacts/phase0_baseline_live_prune_<tag>.{md,json}, and retention now includes these rolling prune reports as a managed family.
Both rolling commands accept TAG=<YYYY-MM-DD-HHMMSS> override (artifact tags must be simple filename-safe tokens using letters/numbers/._-); audit:phase0-baseline:live:refresh:drift:rolling:prune now reuses the same rolling pipeline/tag and then applies prune retention for that exact tag.
Gateway-origin windows can be captured separately (for example when validating cancel paths):
pnpm audit:phase0-baseline:live:gateway
The gateway command auto-selects the most recent session window containing both run.cancel and run.state=cancelled (with configurable padding). You can also capture explicit backend-scoped channel windows by restricting source/backend/time bounds:
node --import tsx/esm scripts/capture-phase0-live-baseline.ts \
--audit ~/.local/share/flynn/audit.log \
--source channel \
--backend pi_embedded \
--since <epoch_ms_or_iso> \
--until <epoch_ms_or_iso> \
--sample-out docs/plans/artifacts/phase0_baseline_live_backend_pi_embedded_<tag>.jsonl \
--summary-json-out docs/plans/artifacts/phase0_baseline_live_backend_pi_embedded_<tag>.json \
--summary-md-out docs/plans/artifacts/phase0_baseline_live_backend_pi_embedded_<tag>.md
Use --backend native for native-only windows, or omit --backend for all backends.
Gateway Lock
Single-client mode for the WebSocket gateway. When enabled, only one WebSocket connection is allowed at a time. Additional connections are rejected with close code 4003.
server:
lock: true
The web UI detects the locked state and disables auto-reconnect when rejected.
Gateway WebSocket Rate Limit
Per-connection ingress throttling for WebSocket requests. Excess bursts are rejected; repeated violations close the connection (4008).
server:
ws_rate_limit:
enabled: true
capacity: 30
refill_per_sec: 15
max_violations: 8
violation_window_ms: 10000
Gateway Lane Queue Policy
Per-session FIFO queue policy for concurrent gateway requests (agent.send).
server:
queue:
mode: collect # collect | followup | steer | steer_backlog | interrupt
cap: 50 # max pending requests per session lane
overflow: drop_old # drop_old | drop_new
debounce_ms: 0 # delay before running next queued item
summarize_overflow: true
overrides:
channels:
ws:
mode: followup
cap: 10
sessions:
ws:vip-user:
mode: interrupt
overflow: drop_new
debounce_ms: 100
Notes:
collectkeeps all queued requests (subject tocap).followupkeeps at most one pending item while a request is active; newer followups replace older pending items.steerandsteer_backlogreplace pending backlog with the newest request while one is active.interruptuses steer-backlog queueing behavior and now also requests active-run cancellation when a newer request arrives.- When interrupt preemption occurs, gateway emits a transient content notice to the requester and writes a
queue.preemptaudit event. - Active cancellation remains best-effort and stops at agent safe points; use
agent.cancelfor explicit user-triggered cancellation control. debounce_msdelays the next queued execution, helping collapse bursty same-session traffic.summarize_overflowenables richer overflow error messages and payload metadata.- On overflow,
drop_oldevicts the oldest pending request,drop_newrejects the new request. - Override precedence: exact
sessionsmatch first, thenchannels.
Runtime session controls from chat commands:
/queueshows effective session queue policy./queue set <mode|cap|overflow|debounce_ms|summarize_overflow> <value>sets a per-session override./queue resetclears per-session queue overrides.
Gateway Node Capability Negotiation
Optional gateway surface for companion clients and node-role negotiation:
server:
nodes:
enabled: true
allowed_roles: [companion]
feature_gates:
ui.canvas: true
location:
enabled: true
push:
enabled: true
Methods:
node.registerregisters role + declared capabilities for the current connection.node.capabilities.getreturns negotiated protocol version and enabled capabilities.node.location.setupdates the node's last-known location (whenserver.nodes.location.enabledis true).node.location.getreturns the node's stored location payload.node.status.setpublishes companion status/heartbeat fields (platform,appVersion,batteryPct, etc.).node.push_token.setregisters node push tokens (APNs for iOS/macOS, FCM for Android) whenserver.nodes.push.enabledis true.system.locationprovides an operator view of registered node locations.system.nodesreturns registered node snapshots (role, capabilities, identity, location/status).system.capabilitiesreturns gateway protocol and node policy snapshot.
Companion runtime helper:
src/companion/runtimeClient.tsprovides a typed Node/WebSocket client for companion runtimes (macOS/iOS/Android workers) with wrappers fornode.register,node.capabilities.get,node.location.set/get,node.status.set,node.push_token.set,system.capabilities,system.nodes, canvas artifact RPCs (canvas.put/get/list/delete/clear), andsendAgentMessagehandoff support (agent.sendwithdone/errorresolution), plus convenience helpers (bootstrapNode, optionalautoConnect,dispose(),waitForIdle()for pending-work drain synchronization, optional reconnect-time node state replay viaenableNodeStateRecovery) and event helpers (subscribeEvents(),subscribeEvent(),subscribeAgentStream(),subscribeAgentTyping(),subscribeContextWarning(),waitForEvent()with timeout/predicate/abort support plus event-name/timeout validation and deterministic teardown cancellation including socket-close rejection,waitForAnyEvent()with event-list/timeout validation,waitForAgentStream(),waitForAgentTyping(),waitForContextWarning(),clearEventSubscriptions()returning cleared-subscription/cancelled-wait counts,cancelPendingEventWaits()returning cancelled waiter count,listKnownEventNames(),eventSubscriptionCount) plus in-flight observability viapendingRequestCount,pendingEventWaitCount,hasPendingWork,idle,lastDisconnectCode,lastDisconnectReason,getPendingWorkSnapshot(),getEventSurfaceSnapshot(), andgetConnectionSnapshot()(including disconnect metadata, cleared on successful reconnect).src/companion/platformClients.tsprovides platform-focused wrappers:MacOSCompanionClient(platform: "macos", APNs push registration)IOSCompanionClient(platform: "ios", APNs push registration)AndroidCompanionClient(platform: "android", FCM push registration)- shared
bootstrap()helper (register+getCapabilities, optionalsystem.capabilities) for startup handshakes - shared
publishHeartbeat()helper for periodicnode.status.setupdates with safe defaults createHeartbeatLoop()convenience helper that returns a boundCompanionHeartbeatLoop- optional
defaultSessionIdfor canvas helper calls sosessionIdcan be omitted per call - optional reconnect recovery toggle (
recoverNodeStateOnReconnect, default true) to replay node registration/status/location/push after transport reconnect sendMessageHandoff()passthrough helper for companion-originatedagent.sendhandoff- lifecycle passthroughs for connection state/teardown (
connected,disconnect(code?, reason?),dispose(code?, reason?)) - stream passthrough helpers (
subscribeEvents,subscribeEvent,clearEventSubscriptions,cancelPendingEventWaits,listKnownEventNames,eventSubscriptionCount,subscribeAgentStream/Typing/ContextWarning,waitForEvent,waitForAnyEvent,waitForAgentStream/Typing/ContextWarning) - runtime observability/control passthroughs (
pendingRequestCount,pendingEventWaitCount,hasPendingWork,idle,lastDisconnectCode,lastDisconnectReason,getPendingWorkSnapshot(),getEventSurfaceSnapshot(),getConnectionSnapshot(),connected,waitForIdle())
src/companion/heartbeatLoop.tsprovidesCompanionHeartbeatLoopfor periodic heartbeat scheduling (publishHeartbeat) with start/stop safety, optional interval jitter (jitterRatio) to spread load (with safe normalization for invalid random samples),tickNow()for manual sends, success/error hooks, loop observability (successCount,lastSuccessAt,failureCount,lastFailure,getState()), and optional auto-stop after repeated failures.src/companion/bootstrapManifest.tsprovidescreateCompanionBootstrapManifest()for generating a typed gateway/node/runtime bootstrap contract used by packaging flows, including optional initial status/location/push payloads.src/companion/releaseBundle.tsprovideswriteCompanionReleaseBundle()for writing a distributable companion bundle directory (bootstrap JSON + launcher script + README + SHA-256 checksums + release manifest).src/companion/shellTemplate.tsprovideswriteCompanionShellTemplate()for emitting platform starter shells (macOS launcher wrapper snippet, iOS/Android bootstrap + runtime skeleton files, and bootstrap JSON).src/companion/releaseVerify.tsprovidesverifyCompanionReleaseBundle()for validating bundle checksums and optional signature metadata.src/companion/releasePipeline.tsprovidesbuildAndVerifyCompanionReleaseBundle()for build-and-verify automation (including signed bundle flows).src/companion/macosMenuBarApp.tsprovidesgenerateMacOSMenuBarApp()for emitting a runnable Swift Package macOS menu-bar companion scaffold.src/companion/referenceApps.tsprovidesgenerateReferenceCompanionApps()for deterministic macOS/iOS/Android starter shells plus a runnablemacos-appSwift Package scaffold.
Minimal companion CLI:
flynn companion --onceconnects to the gateway, registers a node, publishes one heartbeat, then exits.flynn companion --platform macos --heartbeat 30runs a long-lived node with periodic heartbeats and logsagent.stream/agent.typingevents.flynn companion --once --handoff "summarize my status"performs one post-registrationagent.sendhandoff and prints thedonecontent.flynn companion --export-bootstrap ./companion.bootstrap.jsonwrites a resolved bootstrap manifest for desktop/mobile companion app packaging (use-for stdout).flynn companion --export-release-bundle ./dist/companion-macoswrites a release bundle directory (companion.bootstrap.json,run-companion.sh,README.md,CHECKSUMS.sha256,RELEASE_MANIFEST.json) and exits.flynn companion --export-release-bundle ./dist/companion-macos --signing-key ./keys/release-private.pem --signing-key-id team-k1also writesCHECKSUMS.sha256.sigfor signed verification workflows.flynn companion --platform ios --export-shell-template ./dist/companion-ios-templatewrites a platform-native starter template directory (companion.bootstrap.json, bootstrap/runtime starter files,README.md) and exits.flynn companion --verify-release-bundle ./dist/companion-macos --verify-signing-key ./keys/release-public.pem --verify-signing-key-id team-k1 --require-signatureverifies checksums and signature metadata before install.pnpm companion:bundle -- --output ./dist/companion-macos --platform macos --signing-key ./keys/release-private.pem --signing-key-id team-k1builds and verifies a release bundle in one step.pnpm companion:reference-apps -- --output ./apps/companionregenerates macOS/iOS/Android reference app starter shells plusapps/companion/macos-apprunnable scaffold using a reproducible defaultgeneratedAttimestamp (override with--generated-at <iso>).pnpm companion:reference-apps:checkregeneratesapps/companionand fails if tracked reference-app artifacts drift from generator output.- GitHub Actions workflow
.github/workflows/companion-release-bundle.ymlruns build-and-verify bundle automation and uploads release artifacts on manual dispatch. - GitHub Actions workflow
.github/workflows/companion-reference-apps-check.ymlenforces reference-app generator sync on pull requests.
run-companion.sh verifies bundle checksums (CHECKSUMS.sha256) before launching flynn companion.
flynn companion --once --platform ios --app-version 1.2.3 --device-name "iPhone" --status-text ready --battery-pct 84 --power-source batterysends richer initial node status metadata.flynn companion --once --latitude 37.3349 --longitude -122.009 --location-source gpsbootstraps node location metadata.flynn companion --once --platform android --push-token <fcm-token>(or--platform ios --push-token <apns-token>) registers push routing metadata during bootstrap.
Companion release runbook: docs/operations/COMPANION_RELEASE_BUNDLE.md
WebChat PWA Push Subscriptions
Enable installable WebChat PWA metadata and browser push-subscription storage on the gateway:
server:
webchat_push:
enabled: true
vapid_public_key: ${WEBCHAT_VAPID_PUBLIC_KEY}
max_subscriptions: 5000
Notes:
- WebChat now serves
manifest.webmanifestand a service worker (/sw.js). - Settings page includes Push controls (Enable/Disable) that subscribe the current browser and register/unregister its endpoint with the gateway.
- Gateway endpoints:
GET /webchat/push/public-keyGET /webchat/push/subscriptionsPOST /webchat/push/subscriptionsDELETE /webchat/push/subscriptions
- These endpoints are protected by normal gateway HTTP auth (
server.token+server.auth_http) and support?token=query auth for browser clients.
Canvas / A2UI Foundation
Gateway provides a session-scoped canvas artifact API for companion/UI surfaces:
canvas.putupserts an artifact (artifactId,type,content, optionaltitle/metadata).canvas.getretrieves a single artifact.canvas.listlists artifacts for a session (most recently updated first).canvas.deleteremoves one artifact.canvas.clearremoves all artifacts for a session.
This foundation is currently in-memory (runtime ephemeral) and intended as the first step for richer visual workspace flows.
Gateway Request Body Limit
Cap inbound HTTP POST body size (webhooks and Gmail push) to reduce memory-DoS risk.
server:
max_request_body_bytes: 1048576 # 1 MiB
Tailscale Serve
Automatically expose the gateway via Tailscale Serve when the daemon starts. Requires Tailscale to be installed and authenticated on the host.
server:
tailscale:
serve: true
When enabled, Flynn runs tailscale serve on startup to expose the gateway port over your tailnet, and cleans up on shutdown. The flynn doctor command includes a Tailscale availability check.
Bonjour / mDNS Discovery
Optionally advertise the gateway on your local network so LAN clients can discover Flynn without manual host entry.
server:
localhost: false
discovery:
enabled: true
service_name: flynn-gateway
service_type: _flynn._tcp
txt:
env: home-lab
Notes:
- Discovery is disabled by default.
server.localhostmust befalsefor LAN clients to connect.- Flynn advertises non-secret metadata only (instance/version + optional
txtkeys you provide). - Runtime uses host tools (
avahi-publish-serviceon Linux,dns-sdon macOS) when available.
DM Pairing Codes
Allow unknown senders to authenticate with the bot via time-limited pairing codes. Works across all channel adapters (Telegram, Discord, Slack, WhatsApp).
pairing:
enabled: true
code_ttl: "10m" # Code expiry time (default: 10 minutes)
code_length: 6 # Code length (default: 6 digits)
How it works
- Generate a code via the TUI (
/pair generate), gateway API (pairing.generate), or web dashboard - Share the code with the user
- The user sends the code as their first DM to the bot
- If valid, the user's sender ID is permanently approved for that channel (persisted in SQLite, survives daemon restarts)
- Approved users can be listed (
/pair list) and revoked (/pair revoke <channel> <id>)
TUI Commands
| Command | Description |
|---|---|
/pair |
Generate a new pairing code |
/pair generate [label] |
Generate a code with optional label |
/pair list |
List pending codes and approved senders |
/pair revoke <channel> <id> |
Revoke an approved sender |
WhatsApp Chromium Sandbox
WhatsApp adapter now launches Chromium in sandboxed mode by default.
If you must disable Chromium sandboxing in a high-trust/containerized environment:
whatsapp:
no_sandbox: true
Gateway API
| Method | Description |
|---|---|
pairing.generate |
Generate a new pairing code (optional label param) |
pairing.list |
List pending codes and approved senders |
pairing.revoke |
Revoke an approved sender (channel + senderId params) |
system.presence |
List observed sender presence with online/offline status inferred from recent inbound activity |
Shell Completion
Generate shell completions for bash, zsh, or fish:
# Print completions to stdout
flynn completion bash
flynn completion zsh
flynn completion fish
# Install directly to shell config
flynn completion bash --install # Appends to ~/.bashrc
flynn completion zsh --install # Appends to ~/.zshrc
flynn completion fish --install # Writes to ~/.config/fish/completions/flynn.fish
Docker Deployment
Flynn includes a production-ready Dockerfile with multi-stage build.
# Build the image
docker build -t flynn .
# Run with config and data volumes
docker run -d \
--name flynn \
-p 18800:18800 \
-v ./config.yaml:/config/config.yaml:ro \
-v flynn-data:/data \
-e ANTHROPIC_API_KEY=sk-ant-... \
flynn
Or use the included docker-compose.yml:
# Copy your config
cp ~/.config/flynn/config.yaml ./config.yaml
# Start with compose
docker compose up -d
# View logs
docker compose logs -f
Docker Environment Variables
| Variable | Description |
|---|---|
FLYNN_CONFIG |
Config file path (default: /config/config.yaml) |
FLYNN_DATA_DIR |
Data directory path (default: /data) |
ANTHROPIC_API_KEY |
Anthropic API key |
OPENAI_API_KEY |
OpenAI API key |
TELEGRAM_BOT_TOKEN |
Telegram bot token |
Volumes
| Mount Point | Purpose |
|---|---|
/config/config.yaml |
Configuration file (read-only) |
/data |
Persistent data (sessions DB, memory files, vector index) |
PaaS Deployment (Fly.io / Railway / Render)
See docs/deployment/PAAS.md and the templates under deploy/.
Nix Deployment
See docs/deployment/NIX.md for the flake (package + dev shell + optional NixOS module).
Doctor Diagnostics
flynn doctor runs health checks to validate your setup:
$ flynn doctor
Flynn Doctor
============
[PASS] Config file exists (/home/user/.config/flynn/config.yaml)
[PASS] Config parses (valid YAML)
[PASS] Config validates (schema valid)
[PASS] Env vars resolved
[PASS] Data directory writable (/home/user/.local/share/flynn)
[PASS] Session DB accessible (sessions.db)
[PASS] Model connectivity (anthropic: claude-sonnet)
[PASS] Telegram bot configured (1 allowed chat(s))
[SKIP] MCP servers configured (none configured)
[PASS] Skills loaded (3 skill(s))
Results: 8 passed, 0 failed, 0 warnings, 1 skipped
Check Details
| Check | What it validates |
|---|---|
| Config file exists | Config YAML file is present at the expected path |
| Config parses | File is valid YAML syntax |
| Config validates | YAML content passes Zod schema validation |
| Env vars resolved | Any ${VAR} references in config have values set |
| Data directory writable | Can write to ~/.local/share/flynn/ |
| Session DB accessible | SQLite database opens and queries succeed |
| Model connectivity | Default model provider and model name are configured |
| Telegram bot configured | Bot token is present and reasonable length |
| MCP servers configured | Lists configured MCP tool servers |
| Skills loaded | Discovers and loads skill packages |
| Skills registry | Verifies registry source is configured and catalog is reachable/parsible |
Exit code is 1 if any check fails, 0 otherwise. Checks that depend on a valid config are skipped when config is invalid.
Session Management
- Sessions persist in
~/.local/share/flynn/sessions.db - Session ID format:
{frontend}:{userId}(e.g.,telegram:123456789) - History survives restarts
- Transfer sessions between frontends with
/transfer <telegram|tui>
Architecture
src/
├── agents/ # Multi-agent routing
├── auth/ # OAuth flows (GitHub Copilot)
├── backends/native/ # Agent implementation + orchestrator
├── channels/ # Channel adapters (Telegram, Discord, Slack, WhatsApp, Matrix, WebChat)
│ └── pairing.ts # DM pairing code manager
├── cli/ # CLI commands (commander)
│ └── completion.ts # Shell completion generator (bash/zsh/fish)
├── config/ # YAML config + Zod validation
├── context/ # Token estimation + compaction
├── daemon/ # Lifecycle management + routing
├── frontends/
│ ├── telegram/ # Telegram bot
│ └── tui/ # Terminal UI (minimal + fullscreen)
├── gateway/ # WebSocket gateway + web UI dashboard
│ ├── handlers/ # JSON-RPC method handlers (agent, sessions, system, pairing, tools, config)
│ ├── tailscale.ts # Tailscale Serve lifecycle management
│ ├── lane-queue.ts # Per-session FIFO request queue
│ └── ui/ # SPA dashboard (vanilla JS)
│ ├── pages/ # Dashboard, Chat, Sessions, Usage, Settings
│ └── lib/ # WebSocket RPC client
├── hooks/ # Confirmation engine
├── mcp/ # MCP tool server integration
├── memory/ # Persistent memory store + vector search
├── models/ # Model providers + router + media pipeline + audio capabilities
├── prompt/ # System prompt templating (auto-injects current date/time)
├── sandbox/ # Docker sandboxing
├── session/ # SQLite persistence
├── skills/ # Skill packages
├── tools/ # Builtin tools (shell, file, web, browser, process, media, audio, system.info)
└── automation/ # Cron scheduler, webhooks, heartbeat monitor, Gmail watcher
System Prompt
Flynn assembles its system prompt from layered template files (SOUL.md, AGENTS.md, IDENTITY.md, USER.md, TOOLS.md, BOOTSTRAP.md, HEARTBEAT.md) searched in configurable directories. The first match per file wins.
A Runtime Context section is automatically appended to every system prompt with the current date and time, so the model always knows when "now" is without needing a tool call.
The system.info tool provides on-demand access to more detailed runtime information: current date/time, hostname, platform, architecture, OS release, uptime, Node.js version, memory usage, and working directory.
Development
# Dev mode with watch
pnpm dev # Daemon
pnpm tui:dev # TUI
# Type check
pnpm typecheck
# Lint
pnpm lint
# Test
pnpm test
Environment Variables
| Variable | Description |
|---|---|
FLYNN_CONFIG |
Override config file path |
FLYNN_DATA_DIR |
Override data directory (default: ~/.local/share/flynn) |
ANTHROPIC_API_KEY |
Anthropic API key (fallback) |
OPENAI_API_KEY |
OpenAI API key (fallback) |
GEMINI_API_KEY |
Gemini API key (fallback; GOOGLE_API_KEY also supported) |
License
MIT