Data Flow

How data moves through the Parachute system: from user input to AI response, from voice to transcript, from app to vault.

Chat Message Flow

When a user sends a chat message, here's how it flows through the system:

┌──────────────────────────────────────────────────────────────────────────┐
│  1. USER INPUT                                                            │
│                                                                           │
│     User types message in ChatScreen                                      │
│     ↓                                                                     │
│     ChatInput widget captures text + attachments                          │
│     ↓                                                                     │
│     chatMessagesNotifier.sendMessage()                                    │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  2. APP STATE UPDATE                                                      │
│                                                                           │
│     Add optimistic user message to state                                  │
│     Add empty assistant message (for streaming)                           │
│     ↓                                                                     │
│     ChatService.sendMessage() → HTTP POST /api/chat                       │
│     ↓                                                                     │
│     Open SSE connection for streaming response                            │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  3. SERVER RECEIVES REQUEST                                               │
│                                                                           │
│     POST /api/chat                                                        │
│     ├── Validate auth (API key or localhost)                              │
│     ├── Parse ChatRequest                                                 │
│     └── Call orchestrator.run_streaming()                                 │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  4. ORCHESTRATOR PREPARES                                                 │
│                                                                           │
│     Load agent definition (or default vault-agent)                        │
│     Get or create session:                                                │
│     ├── New: Create pending placeholder, SDK will assign ID               │
│     └── Resume: Load from DB, verify SDK session exists                   │
│     Build system prompt:                                                  │
│     ├── Agent system prompt                                               │
│     ├── Context files/folders                                             │
│     └── Working directory context                                         │
│     Process attachments:                                                  │
│     ├── Save to vault/Chat/assets/{date}/                                 │
│     └── Build attachment message parts                                    │
│     Check permissions:                                                    │
│     ├── Session grants (Read, Write, Bash, MCP)                           │
│     └── Global deny patterns                                              │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  5. CLAUDE SDK CALL                                                       │
│                                                                           │
│     claude_sdk.query_streaming():                                         │
│     ├── Override HOME to vault path (for .claude/ isolation)              │
│     ├── Build SDK query options (model, max_tokens, permissions)          │
│     └── Call SDK with async generator                                     │
│                                                                           │
│     SDK connects to Claude AI:                                            │
│     ├── Sends conversation history                                        │
│     ├── Processes tool calls internally                                   │
│     └── Streams response chunks                                           │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  6. STREAMING RESPONSE                                                    │
│                                                                           │
│     SDK yields events → Orchestrator yields SSE events                    │
│                                                                           │
│     Event types:                                                          │
│     ├── session: {id, title, created_at} — First event                    │
│     ├── text: {content} — Text chunk                                      │
│     ├── thinking: {content} — Thinking content                            │
│     ├── tool_use_start: {id, name, input} — Tool beginning                │
│     ├── tool_result: {tool_use_id, result} — Tool completed               │
│     ├── message_stop: {} — Message complete                               │
│     └── error: {message} — Error occurred                                 │
│                                                                           │
│     Each event sent as:                                                   │
│     data: {"type": "text", "content": "Hello"}                            │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  7. APP RECEIVES & UPDATES                                                │
│                                                                           │
│     ChatService parses SSE events                                         │
│     ├── session: Save session ID to state                                 │
│     ├── text: Append to assistant message content                         │
│     ├── thinking: Add to thinking section                                 │
│     ├── tool_use_start: Add ToolCall to message                           │
│     ├── tool_result: Update ToolCall with result                          │
│     └── message_stop: Mark message complete                               │
│                                                                           │
│     UI updates reactively via Riverpod:                                   │
│     MessageBubble renders streaming content                               │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  8. SESSION PERSISTENCE                                                   │
│                                                                           │
│     After message complete:                                               │
│                                                                           │
│     Server:                                                               │
│     ├── session_manager.update() — Update DB metadata                     │
│     ├── SDK writes to ~/.claude/projects/.../*.jsonl                      │
│     └── Activity hook triggers (title + summary generation)               │
│                                                                           │
│     App:                                                                  │
│     ├── Update local session state                                        │
│     └── Session available for resume                                      │
└──────────────────────────────────────────────────────────────────────────┘
                    

Vault Structure

The vault is your local-first data store. All Parachute data lives in plain files at ~/Parachute:

~/Parachute/
├── Daily/                          # Journal entries (local-first)
│   ├── 2026-01-28.md              # Today's journal
│   ├── 2026-01-27.md              # Yesterday's journal
│   ├── assets/                     # Media files
│   │   ├── 2026-01-28/
│   │   │   ├── audio-10-30-am.m4a
│   │   │   └── photo-10-45-am.jpg
│   │   └── ...
│   ├── reflection/                 # Reflection agent outputs
│   │   └── 2026-01-28.md
│   ├── content-scout/              # Content-scout outputs
│   │   └── 2026-01-28.md
│   ├── .agents/                    # Custom daily agents
│   │   └── my-agent.md
│   └── .agents/                    # Daily agent configs
│       └── content-scout.md
│
├── Chat/                           # Chat sessions (server-managed)
│   ├── sessions.db                 # SQLite metadata
│   ├── assets/                     # Chat attachments
│   │   └── 2026-01-28/
│   │       └── uploaded-file.pdf
│   └── contexts/                   # Saved context configurations
│       └── my-project.json
│
├── .claude/                        # Claude SDK data
│   ├── projects/                   # Session transcripts (JSONL)
│   │   └── {project-hash}/
│   │       └── {session-id}.jsonl
│   └── settings.json               # SDK settings
│
├── .parachute/                     # Parachute config
│   ├── server-config.json          # Auth settings, API keys
│   ├── agents/                     # Global agent definitions
│   │   └── vault-agent.md
│   └── skills/                     # Skill plugins
│       └── my-skill.md
│
└── ... (user files)                # Rest of your vault
                    
Pointer Architecture: The SQLite database (sessions.db) stores only metadata. Actual message transcripts live in SDK JSONL files. This makes the database a pointer layer, not the source of truth for message content.

Para-ID System

Portable identifiers for cross-device sync. Every journal entry gets a unique ID that follows it everywhere.

Journal Entry Format
# para:abc123def456 10:30 AM

Walked to the coffee shop this morning. Had a great idea about
the project architecture...

## Images
![Morning sky](../assets/2026-01-28/photo-10-30-am.jpg)

## Audio
- Duration: 2:30
- Path: ../assets/2026-01-28/audio-10-30-am.m4a

Format Specification

  • Pattern: para:{12-char-alphanumeric}
  • Example: para:abc123def456
  • Location: H1 header of entry section
  • Generated: On entry creation, never changes
  • Scope: Unique across all modules (Daily, Chat, etc.)

Sync Flow

Device A                           Server                          Device B
   │                                 │                                 │
   │  Create entry                   │                                 │
   │  para:abc123def456              │                                 │
   │                                 │                                 │
   │  ────── Sync (push) ──────────▶│                                 │
   │  Hash: sha256:123...            │                                 │
   │                                 │                                 │
   │                                 │◀────── Sync (pull) ────────────│
   │                                 │  Request manifest               │
   │                                 │                                 │
   │                                 │─────── Manifest ───────────────▶│
   │                                 │  para:abc123def456              │
   │                                 │  Hash: sha256:123...            │
   │                                 │                                 │
   │                                 │◀────── Fetch entry ────────────│
   │                                 │                                 │
   │                                 │─────── Entry content ──────────▶│
   │                                 │  # para:abc123def456 10:30 AM  │
   │                                 │  ...                            │
   │                                 │                                 │
   │                                 │                         Entry saved
   │                                 │                         Same para:ID
                    

Voice Transcription Flow

Local-first transcription using Sherpa-ONNX with streaming display:

┌──────────────────────────────────────────────────────────────────────────┐
│  1. AUDIO CAPTURE                                                         │
│                                                                           │
│     Microphone → record package → PCM audio stream                        │
│     ↓                                                                     │
│     Audio chunks (16kHz, 16-bit, mono)                                    │
│     ↓                                                                     │
│     StreamingVoiceService receives chunks                                 │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  2. VOICE ACTIVITY DETECTION                                              │
│                                                                           │
│     SimpleVAD analyzes audio levels                                       │
│     ├── Below threshold: Silence                                          │
│     └── Above threshold: Speech detected                                  │
│     ↓                                                                     │
│     SmartChunker groups speech into segments                              │
│     ├── Min segment: 1 second                                             │
│     ├── Max segment: 30 seconds                                           │
│     └── Split on silence > 500ms                                          │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  3. TRANSCRIPTION (Sherpa-ONNX)                                           │
│                                                                           │
│     Audio segment → SherpaOnnxService                                     │
│     ├── Runs in isolate (background thread)                               │
│     ├── Uses Parakeet model (465MB)                                       │
│     └── Returns: TranscriptionResult                                      │
│                                                                           │
│     LocalAgreement-2 algorithm:                                           │
│     ├── Re-transcribe every 2 seconds                                     │
│     ├── Compare consecutive transcriptions                                │
│     ├── Confirm text stable across 2+ iterations                          │
│     └── Display: [confirmed] + [tentative] + [interim]                    │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  4. STATE UPDATE                                                          │
│                                                                           │
│     StreamingTranscriptionState:                                          │
│     ├── confirmed: "I went to the store"     ← Stable, won't change       │
│     ├── tentative: "and bought some"         ← Likely stable              │
│     └── interim: "coff"                      ← Still processing           │
│                                                                           │
│     UI shows: "I went to the store and bought some coff▌"                 │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │
┌────────────────────────────────────▼─────────────────────────────────────┐
│  5. SAVE TO JOURNAL                                                       │
│                                                                           │
│     User stops recording → Final transcription                            │
│     ↓                                                                     │
│     JournalService.saveEntry():                                           │
│     ├── Generate para:ID                                                  │
│     ├── Save audio to Daily/assets/{date}/                                │
│     ├── Write markdown entry with transcript                              │
│     └── Update journal day file                                           │
└──────────────────────────────────────────────────────────────────────────┘
                    

Session Lifecycle

New Session

  1. User sends first message (no session_id)
  2. Orchestrator creates "pending" placeholder
  3. SDK executes and generates session ID
  4. SessionManager creates DB record with SDK session ID
  5. SDK writes transcript to ~/.claude/projects/.../session.jsonl
  6. Activity hook triggers title generation

Resume Session

  1. User opens existing session
  2. App loads session from provider (cached or fetched)
  3. User sends message with session_id
  4. SessionManager looks up DB record
  5. SDK resumes with existing session ID
  6. Conversation continues from where it left off

Imported Session (Claude/ChatGPT)

  1. User uploads JSON export file
  2. ImportService parses format (auto-detects source)
  3. Converts messages to SDK JSONL format
  4. Creates session in DB with source flag
  5. Optionally enables "continuation" mode
  6. User can continue conversation with full context

Background Processing

Automated agents and hooks process content in the background:

┌────────────────────────────────────────────────────────────────────────────┐
│                    ACTIVITY HOOK (Post-Session)                              │
│                                                                            │
│  Triggered: After each chat exchange completes                             │
│  Purpose: Generate/update title, summarize to daily chat log               │
│                                                                            │
│  ┌──────────────────┐      ┌──────────────────┐      ┌──────────────────┐ │
│  │  Session         │ ───▶ │  Summarizer      │ ───▶ │  update_title()  │ │
│  │  completes       │      │  Agent           │      │  write_chat_log  │ │
│  │                  │      │  (one-shot)      │      │  track_metrics   │ │
│  └──────────────────┘      └──────────────────┘      └──────────────────┘ │
│                                                                            │
│  Output: Daily/chat-log/{date}.md                                          │
└────────────────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────────────────┐
│                   DAILY AGENTS (Scheduled via APScheduler)                   │
│                                                                            │
│  Location: Daily/.agents/*.md                                              │
│  Schedule: Time from YAML frontmatter (default 3:00 UTC)                   │
│  Runner:   core/daily_agent.py + core/scheduler.py                         │
│                                                                            │
│  Example agent (content-scout.md):                                         │
│  ---                                                                       │
│  schedule: "8:00"                                                          │
│  schedule_enabled: true                                                    │
│  tools: [read_journal, web_search]                                         │
│  ---                                                                       │
│  Find relevant news and articles based on journal interests...             │
│                                                                            │
│  Scheduler discovers agents → Runs at scheduled times → Writes outputs     │
│                                                                            │
│  State: Daily/.{agent-name}/state.json (tracks last run date)              │
└────────────────────────────────────────────────────────────────────────────┘
                    

Streaming Protocol

SSE (Server-Sent Events) for real-time responses:

SSE Event Stream Example
# Connection opened to POST /api/chat

data: {"type": "session", "id": "sess_abc123", "title": "New Chat"}

data: {"type": "prompt_metadata", "model": "claude-sonnet-4", "context_files": 2}

data: {"type": "text", "content": "I'll help you "}

data: {"type": "text", "content": "with that. Let me "}

data: {"type": "tool_use_start", "id": "tool_1", "name": "Read", "input": {"file_path": "/src/main.py"}}

data: {"type": "tool_result", "tool_use_id": "tool_1", "result": "# Main entry point..."}

data: {"type": "text", "content": "I can see the main file. "}

data: {"type": "thinking", "content": "The user wants to understand the architecture..."}

data: {"type": "text", "content": "Here's what I found:"}

data: {"type": "message_stop"}

# Connection closed

Event Types

Type When Content
session First event Session ID, title, created_at
prompt_metadata After session Model, context files count
text During response Text content chunk
thinking During response Thinking/reasoning content
tool_use_start Tool beginning Tool ID, name, input
tool_result Tool completed Tool ID, result/error
message_stop Response complete Empty (signals end)
error On error Error message

Next Steps