Draft

PRD: Modular Parachute Computer

Restructuring Parachute from a monolithic application into a modular personal computer platform.

Version: 0.2.0 Date: February 2, 2026 Author: Aaron Gabriel

Executive Summary

Parachute is evolving from a monolithic application into a modular personal computer platform. This PRD defines the architecture for:

  • Parachute Computer - The foundational platform (Python server + management UI)
  • Brain Module - Knowledge layer with published interface (optional, swappable)
  • Module System - Discoverable modules that extend functionality (all modules are vault modules)
  • Core Modules - Daily and Chat as the first installable modules
  • Flutter App Architecture - Shared parachute_core package with separate apps
The Goal: Create a truly personal computer where the core platform rarely changes, but users can extend functionality through modules living in their vault.

Vision & Principles

The Core Insight

Parachute Computer should be a platform, not an application.

The current monolith tightly couples Chat, Daily, and Vault. This restructuring inverts the relationship: Parachute Computer becomes the foundation, and modules plug into it.

Guiding Principles

Vault-Centric

The user's vault (~/Parachute) is the source of truth. Modules, configurations, and data live there. The platform serves the vault.

Local-First

Everything runs on the user's machine. No cloud dependency. Data never leaves without explicit action.

Module Independence

Modules can be developed, tested, and deployed independently. A bug in one module shouldn't crash others.

Progressive Disclosure

The default experience is simple (Daily + Chat). Power users can add modules, configure permissions, build their own.

Interoperability

Modules communicate through standard interfaces (HTTP, MCP). No tight coupling between modules.

User Ownership

Users own their modules. They can inspect, modify, fork, and share them freely.

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Flutter Apps (Separate Packages)                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────┐  │
│  │ Parachute Daily │  │ Parachute Chat  │  │ Parachute Computer          │  │
│  │ (journaling)    │  │ (conversation)  │  │ (module management)         │  │
│  └────────┬────────┘  └────────┬────────┘  └─────────────┬───────────────┘  │
│           │                    │                         │                   │
│  ┌────────┴────────────────────┴─────────────────────────┴───────────────┐  │
│  │                    parachute_core (Flutter package)                    │  │
│  │  • Server connection   • Auth/API keys     • Theming/Design tokens    │  │
│  │  • Common widgets      • Sync service      • Transcription (optional) │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                                HTTP/SSE
                                      │
┌─────────────────────────────────────┴───────────────────────────────────────┐
│                    Parachute Computer (Python Server)                        │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                         Core Router (FastAPI)                          │ │
│  │  /api/brain/*    → Brain (integrated)                                  │ │
│  │  /api/modules/*  → Module Management                                   │ │
│  │  /api/daily/*    → Daily Module                                        │ │
│  │  /api/chat/*     → Chat Module                                         │ │
│  │  /api/{custom}/* → User-defined Modules                                │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                    parachute.core (Python package)                     │ │
│  │  • Claude SDK wrapper      • Module discovery & lifecycle              │ │
│  │  • Brain (knowledge layer) • Database layer (SQLite)                   │ │
│  │  • File access (gated)     • MCP infrastructure                        │ │
│  │  • Hooks system            • Event bus                                 │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │                     Modules (all vault modules)                       │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐  │   │
│  │  │ Daily       │  │ Chat        │  │ Telegram    │  │ Custom     │  │   │
│  │  │ (Parachute) │  │ (Parachute) │  │ Bot         │  │ Modules    │  │   │
│  │  │ verified    │  │ verified    │  │ user        │  │            │  │   │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘  │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│         │ Trusted modules in main container │                                │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │              Agent Sandboxes (Docker per untrusted agent)             │   │
│  │  Claude SDK runs with scoped permissions per agent definition         │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
                                      │
                            ~/Parachute (vault)

Key Architectural Changes

Current Future
Single monolithic Flutter app Multiple apps sharing parachute_core package
Hardcoded features (Chat, Daily, Vault) Discoverable modules that register routes
Lima VM for isolation Bare metal + Docker containers for sandboxing
Settings screen for configuration Parachute Computer UI for module management

Parachute CLI

The parachute CLI is the primary interface for managing Parachute on any system (Mac, Linux, headless servers, Raspberry Pi).

Commands

CLI Reference
# Server management
parachute start                    # Start server (default port 3333)
parachute start --port 3334        # Start on custom port
parachute stop                     # Stop server
parachute status                   # Show server status

# Module management
parachute modules list             # List installed modules
parachute modules install <url>    # Install from git repo
parachute modules install <path> --dev   # Install from local path (symlink)
parachute modules install <url> --path python/  # Install subdirectory of repo
parachute modules remove <name>    # Remove a module
parachute modules status           # Show module status (loaded, pending approval, etc.)
parachute modules diff <name>      # Show what changed in pending update
parachute modules approve <name>   # Approve module update

# Configuration
parachute config                   # Show current config
parachute config set <key> <value> # Set config value
parachute vault                    # Show vault path
parachute vault set <path>         # Set vault path

# Authentication
parachute auth status              # Show auth status
parachute keys list                # List API keys
parachute keys create <name>       # Create new API key
parachute keys revoke <name>       # Revoke API key

Module Installation Flow

Example: Installing Brain Module
$ parachute modules install https://github.com/OpenParachutePBC/parachute-brain

Cloning parachute-brain...
Validating manifest.yaml...
  Name: brain
  Version: 1.0.0
  Provides: BrainInterface

Installing dependencies...
Computing hash: sha256:abc123...

✓ Module 'brain' installed and approved
  Path: ~/.modules/brain/

Restart server to load: parachute restart

Implementation

The CLI lives in parachute-computer and is installed via pip:

pip install parachute-computer
# or
pipx install parachute-computer

This provides the parachute command globally, enabling management of Parachute on headless servers, Raspberry Pis, and any Linux/Mac system.

Parachute Computer Core

The core platform provides foundational services that all modules depend on.

Core Services

Service Purpose Access
Claude SDK Wrapper Unified interface to Claude Agent SDK with HOME override for vault-portable sessions All modules
Permission Handler Enforce read/write/bash permissions per module All modules
Database Layer SQLite operations (sessions, metadata, chunks) Via core API
File Access Gated filesystem operations respecting permissions Per-module
MCP Infrastructure Spawn and manage MCP servers for agent tools Module-declared
Event Bus Publish/subscribe for cross-module events All modules
Module Discovery Find, load, validate modules from config Core only

Configuration

Module registry at ~/.parachute/modules.yaml:

modules.yaml
# All modules are vault modules - no "built-in" distinction
# Fresh vault starts empty, user installs what they want
modules:
  daily:
    enabled: true
    source: parachute://daily    # Official Parachute module
    version: "1.0.0"
    trust: verified              # Signed by Parachute
    hash: "sha256:abc123..."     # Verified on load

  chat:
    enabled: true
    source: parachute://chat
    version: "1.0.0"
    trust: verified
    hash: "sha256:def456..."

  telegram-bot:
    enabled: true
    source: local                # User-created in vault
    path: "Modules/telegram-bot"
    trust: trusted               # User explicitly trusts
    hash: "sha256:789abc..."     # Re-approve if changes
    agents:                      # Agent-level sandboxing
      responder:
        paths: ["Brain/projects/client-x.md", "Work/client-x/"]
        capabilities: [read]
      admin:
        paths: ["Brain/**"]
        capabilities: [read, write]

  experimental-module:
    enabled: true
    source: github://someuser/cool-module
    version: "0.1.0"
    trust: untrusted             # Runs in separate container
    hash: "sha256:xyz789..."

Module Manifest

Every module must have a manifest.yaml at its root defining its metadata, interfaces, agents, and capabilities.

manifest.yaml - Complete Example
# Required fields
name: brain
version: 1.0.0
description: Knowledge layer for Parachute

# Entry point - must export a class inheriting from Module
module: module.py

# What interfaces this module provides
provides:
  - BrainInterface

# What interfaces this module can use (optional dependencies)
optional_requires:
  - JournalInterface
  - ConversationInterface

# Python dependencies (installed in module's virtualenv)
dependencies:
  - pyyaml>=6.0
  - aiofiles>=23.0

# Agents defined by this module
agents:
  indexer:
    description: "Indexes new content into the knowledge graph"
    sandbox:
      workspace_access: rw      # none | ro | rw
      allowed_paths:
        - Brain/
      network: none             # none | bridge
      memory: 512m
      cpus: 1.0
      pids_limit: 256
    system_prompt: prompts/indexer.md
    capabilities:
      - Read
      - Write
      - Edit

# Hooks this module registers
hooks:
  - event: daily.entry.created
    handler: hooks:on_entry_created
    async: true

# MCP tools exposed (callable by other modules' agents)
mcp_tools:
  - name: brain_search
    description: Search entities by keyword
    handler: tools:brain_search
  - name: brain_resolve
    description: Resolve text to para_id
    handler: tools:brain_resolve

# API routes (auto-prefixed with /api/{module_name}/)
routes:
  prefix: /api/brain

Module Directory Structure

Example: parachute-brain/
parachute-brain/
├── manifest.yaml           # Required: module metadata
├── module.py               # Required: exports BrainModule class
├── interface.py            # Interface definition (BrainInterface)
├── store.py                # Entity storage
├── search.py               # Search implementation
├── tools.py                # MCP tool handlers
├── hooks.py                # Hook handlers
├── prompts/
│   └── indexer.md          # Agent system prompts
├── tests/
│   └── test_brain.py
└── CLAUDE.md               # Instructions for AI working on this module

Key Manifest Fields

Field Required Description
name Yes Unique module identifier
version Yes Semantic version string
module Yes Python file exporting Module subclass
provides No Interfaces this module publishes
optional_requires No Interfaces this module can use (graceful if missing)
agents No Agent definitions with sandbox configs
mcp_tools No MCP tools exposed to other modules' agents

Brain Module & Interface System

Brain is a module with a published interface, not baked into core. This enables users to upgrade, swap, or even skip Brain entirely. Modules depend on interfaces, not implementations.

Key Insight: Daily and Chat work without Brain—they just get smarter when Brain is installed. Interface-based dependencies mean loose coupling and graceful degradation.

Interface-Based Architecture

                    ┌─────────────────────────────────────┐
                    │           Daily Module              │
                    │  • Creates journal entries          │
                    │  • Detects entity mentions          │
                    └──────────────┬──────────────────────┘
                                   │
                                   ▼
┌──────────────────────────────────────────────────────────────────────┐
│                    BRAIN MODULE (BrainInterface)                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │   Entity    │  │   Para-ID   │  │  Relation   │  │  Context    │ │
│  │   Store     │  │   System    │  │   Graph     │  │  Provider   │ │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘ │
│                                                                       │
│  Publishes: BrainInterface (search, resolve, link, suggest)          │
│  Other modules optionally depend on this interface                   │
│                                                                       │
│  ~/Parachute/Brain/                                                  │
│    types.yaml          # Dynamic entity type definitions             │
│    entities/           # Markdown files per entity                   │
└──────────────────────────────────────────────────────────────────────┘
                                   │
                                   ▼
                    ┌─────────────────────────────────────┐
                    │           Chat Module               │
                    │  • Loads context from Brain         │
                    │  • Updates Brain after sessions     │
                    └─────────────────────────────────────┘

The Daily → Brain → Chat Flow

Here's how knowledge flows through the system:

1. Daily Creates Entry

You journal: "Met with Kevin about the LVB curriculum. He suggested we add a module on prompt engineering."

2. Brain Detects Mentions

Agent (via hook) scans entry and finds:

  • Kevin → resolves to person-abc123 (Kevin from Regen Hub)
  • LVB → resolves to project-def456 (Learn Vibe Build)
  • prompt engineering → no match, suggests creating topic

Brain auto-links the entry and optionally updates entity files with new context.

3. Chat Loads Context

Later, you open Chat and mention "working on LVB". A hook detects this and:

  • Loads Brain/projects/learn-vibe-build.md into context
  • Loads recent Daily entries mentioning LVB
  • If the entity points to ~/Projects/lvb/, loads that project's AGENTS.md

4. Brain Updates from Chat

After the chat, a hook asks: "Update Brain with new information?" New facts about Kevin or LVB get written back to their entity files.

Entities Can Point to External Work

Project entities can reference folders outside the vault, enabling context-aware assistance:

Brain/entities/projects/parachute.md
---
para_id: project-parachute
type: project
status: active
work_path: ~/Projects/parachute    # External folder
agents_path: ~/Projects/parachute/CLAUDE.md
aliases:
  - Parachute
  - parachute project
  - the vault app
tags:
  - open-source
  - flutter
  - python
---

# Parachute

Open & interoperable extended mind technology.

## Quick Context
- Flutter app + Python base server
- Working on modular architecture restructure
- See [[learn-vibe-build]] for related teaching

## Relationships
- [[aaron-gabriel]] - Founder
- [[kevin-regen-hub]] - Advisor on distribution

Dynamic Entity Types

Entity types are not hardcoded. Users start with sensible defaults but can create their own. Daily can suggest new types based on journal content.

Brain/types.yaml
types:
  person:
    icon: "👤"
    folder: "people"
    fields: [aliases, tags, relationships]

  project:
    icon: "📁"
    folder: "projects"
    fields: [status, work_path, aliases, tags, relationships]

  topic:
    icon: "💡"
    folder: "topics"
    fields: [aliases, tags]

  resource:
    icon: "📚"
    folder: "resources"
    fields: [url, aliases, tags]

  # User-defined types
  organization:
    icon: "🏢"
    folder: "organizations"
    fields: [type, aliases, tags, relationships]

  event:
    icon: "📅"
    folder: "events"
    fields: [date, location, attendees]

Brain MCP Tools

Available to all modules and agents:

Tool Purpose
brain-search Semantic search across all entities
brain-resolve Resolve text mention to para_id (alias resolution)
brain-link Create wiki-style link between entries and entities
brain-suggest Suggest entities/types based on text content
brain-types List/create/modify entity types
brain-context Load entity files + related context for a topic

Para-ID System

Para-IDs are stable UUIDs that enable cross-module references. Each ID is prefixed with its source module, making it clear where data lives while allowing portable references across the system.

Format

Para-ID Examples
para:{module}:{12-char-alphanumeric}

Examples:
- para:brain:a1b2c3d4e5f6    → Brain entity (person, project, topic)
- para:daily:x7y8z9w0v1u2    → Daily journal entry
- para:chat:m3n4o5p6q7r8     → Chat session

Why Para-IDs Matter

  • Cross-module linking: Daily entry can reference a Brain entity without knowing how Brain stores it
  • Portable references: IDs survive if Brain switches from markdown files to a graph database
  • Audit trail: Easy to trace what accessed what
  • Stable pointers: Content can move/rename without breaking links

Module Responsibility

Each module maintains its own ID → location mapping:

Entity Store with Para-ID
class EntityStore:
    def resolve(self, para_id: str) -> Optional[Path]:
        """Map para_id to actual file location."""
        # para:brain:a1b2c3d4e5f6 → Brain/entities/people/kevin.md
        record = self.db.get(para_id)
        return Path(record.file_path) if record else None

    def create(self, entity_type: str, name: str) -> str:
        """Create new entity, return para_id."""
        para_id = f"para:brain:{generate_id()}"
        file_path = f"Brain/entities/{entity_type}/{slugify(name)}.md"
        self.db.insert(para_id=para_id, file_path=file_path)
        return para_id

Cross-Module References

When Daily mentions a Brain entity, the [[Kevin]] syntax is resolved at runtime via Brain's resolve() function, and the para_id is stored in frontmatter for stable linking:

Daily Entry with Para-ID References
# Daily/entries/2024-01-15-morning.md
---
mentions:
  - para:brain:a1b2c3d4e5f6  # Kevin
  - para:brain:b2c3d4e5f6g7  # LVB project
---

Had a great call with [[Kevin]] about [[Learn Vibe Build]]...

Module System

All modules are vault modules. A fresh vault starts empty—Parachute Computer offers to install official modules (Daily, Chat) during onboarding, but they install into the vault just like any other module.

module.py - Module Definition
from parachute.core import Module, route, mcp_tool, event
from parachute.core import Module, route, mcp_tool, event

class DailyModule(Module):
    """Daily journaling and reflection module."""

    name = "daily"
    version = "1.0.0"
    description = "Voice-first journaling with agent reflections"

    # Declare required permissions
    permissions = {
        "read": ["Daily/**", "parachute/memory.md"],
        "write": ["Daily/**"],
        "bash": False,
        "mcp": ["daily-journal", "daily-search"],
    }

    # HTTP Routes (exposed at /api/daily/*)
    @route("GET", "/entries")
    async def list_entries(self, date: str = None, limit: int = 50):
        """List journal entries."""
        ...

    @route("POST", "/query")
    async def query_streaming(self, message: str, session_id: str = None):
        """Stream a conversation about journal entries."""
        async for event in self.core.query(
            message=message,
            session_id=session_id,
            mcp_servers=self.get_mcp_servers(),
        ):
            yield event

    # MCP Tools (exposed to Claude agent)
    @mcp_tool("daily-journal")
    async def search_journals(self, query: str, date_from: str = None):
        """Search journal entries by content."""
        ...

    # Event handlers
    @event("daily.entry.created")
    async def on_entry_created(self, entry_id: str, content: str):
        """Handle new journal entry - trigger reflection pipeline."""
        # Check if Brain module is available (optional dependency)
        brain = self.get_interface("BrainInterface")
        if brain:
            mentions = await brain.suggest(content)
            for mention in mentions:
                if mention.confidence > 0.8:
                    await brain.link(entry_id, mention.para_id)

        await self.trigger_reflection_agent(entry_id)

Trusted Module Communication

Trusted modules can directly import and call each other's Python code (they run in the same process):

Direct Python Imports (Trusted Modules)
# In a trusted module, you can import other trusted modules directly
from parachute.core.brain import brain
from parachute.modules.daily import daily_module

# Direct function calls - no HTTP overhead
entities = await brain.search("Kevin")
entries = await daily_module.get_recent_entries(limit=5)

# This only works for trusted modules in the main container
# Untrusted modules must use the HTTP API

Module Discovery Flow

  1. Server starts and loads ~/.parachute/modules.yaml
  2. For each enabled module:
    • If source: builtin → Load from parachute/modules/{name}/
    • If source: vault → Load from {vault}/{path}/module.py
  3. Validate module class against interface
  4. Check permissions against policy
  5. Register routes at /api/{name}/*
  6. Start MCP servers if declared
  7. Call on_enable() lifecycle hook

Core Modules (Daily, Chat)

Official Parachute modules available for easy installation. These are vault modules like any other—just signed and verified by Parachute.

Fresh Vault Experience: When you create a new vault, it's empty. Parachute Computer asks "Would you like to install recommended modules?" and offers Daily and Chat. They install into your vault just like any module would.

📔 Daily Module Verified (Parachute)

Voice-first journaling with AI-powered reflections. The foundation of Parachute's "extended mind" experience.

Data Location: ~/Parachute/Daily/

Source: parachute://daily

Voice transcription Entry management Reflection agents Journal search Brain integration

MCP Tools: daily-search, daily-today, daily-create

Events: daily.entry.created, daily.reflection.completed

💬 Chat Module Verified (Parachute)

Conversational AI interface with agentic capabilities. Session management, context loading, and Brain-aware conversations.

Data Location: ~/Parachute/Chat/

Source: parachute://chat

Streaming chat Session persistence Context loading Agent selection Brain context Tool permissions

MCP Tools: chat-sessions, chat-context

Events: chat.session.created, chat.session.message

Example: User-Created Module

Here's what a Telegram bot module might look like—with agent-level sandboxing:

🤖 Telegram Bot User-Created

Custom bot that responds to Telegram messages with context from a specific project.

Location: ~/Parachute/Modules/telegram-bot/

Telegram integration Scoped agents Limited Brain access
Modules/telegram-bot/module.py
from parachute.core import Module, route, agent
from parachute.core.brain import brain

class TelegramBotModule(Module):
    name = "telegram-bot"
    version = "0.1.0"

    # Define agents with different sandbox scopes
    agents = {
        "responder": {
            # This agent can only see one project
            "paths": [
                "Brain/projects/client-x.md",
                "~/Work/client-x/"
            ],
            "capabilities": ["read"],
        },
        "admin": {
            # Admin agent has broader access
            "paths": ["Brain/**"],
            "capabilities": ["read", "write"],
        }
    }

    @route("POST", "/webhook")
    async def handle_telegram(self, message: dict):
        # Use the scoped responder agent
        response = await self.run_agent(
            "responder",
            f"Answer this question: {message['text']}"
        )
        return {"reply": response}

Skills vs Modules

Skills and modules serve different purposes. Understanding the distinction prevents confusion about where functionality should live.

Aspect Modules Skills
What they are Python packages extending Parachute Claude Code agent skills (markdown instructions)
Language Python Markdown
Runs where Server process Inside agent sandbox
Provides Endpoints, MCPs, interfaces Agent instructions
Installed via parachute modules install Copy to skills folder
Trust level Fully trusted (main process) Runs in agent's sandbox
Where they live ~/.modules/ ~/Parachute/.claude/skills/ or {project}/.claude/skills/

How They Interact

Modules control which skills agents can access:

Agent Config with Skills Access
agents = {
    "workspace": AgentConfig(
        sandbox=SandboxConfig(
            workspace_access="rw",
            allowed_paths=["${CWD}"],
        ),
        # Agent can use workspace-level skills
        allow_workspace_skills=True,
        # Agent can use user-level skills
        allow_user_skills=True,
    ),
    "restricted": AgentConfig(
        sandbox=SandboxConfig(workspace_access="none"),
        # No access to any skills
        allow_workspace_skills=False,
        allow_user_skills=False,
    ),
}
Example: The Remotion video rendering skill lives in ~/Parachute/.claude/skills/remotion.md, not as a module. It's just agent instructions for using the Remotion CLI. If Remotion needed server-side infrastructure (like a render queue), that would be a module.

Flutter App Architecture

The current monolith becomes multiple packages sharing a core.

Current (Monolith)

app/
  lib/
    features/
      chat/
      daily/
      vault/
      settings/
    core/
      services/
      providers/

Future (Modular)

app/
  packages/
    parachute_core/      ← Shared
    parachute_daily/     ← Daily-specific
    parachute_chat/      ← Chat-specific
    parachute_computer/  ← Management UI
  apps/
    daily/               ← Standalone app
    chat/                ← Standalone app
    computer/            ← Full app

Package Contents

Package Contents Optional?
parachute_core Server connection, auth, theming, common widgets, sync Required by all
parachute_daily Journal UI, voice input, entry management -
parachute_chat Chat UI, session management, message bubbles -
parachute_computer Module management, permissions, settings -

Unified Shell Architecture

The shell application dynamically loads module UIs based on what's installed on the server.

shell/app.dart
class ParachuteApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ShellScaffold(
        // Modules register their UI widgets
        modules: [
          if (moduleRegistry.hasModule('daily'))
            ModuleTab(
              name: 'Daily',
              icon: Icons.today,
              builder: () => DailyApp(),  // From parachute-daily/flutter
            ),
          if (moduleRegistry.hasModule('chat'))
            ModuleTab(
              name: 'Chat',
              icon: Icons.chat,
              builder: () => ChatApp(),   // From parachute-chat/flutter
            ),
        ],
      ),
    );
  }
}

Each module's Flutter package exports a top-level widget that the shell loads:

parachute-daily/flutter/lib/daily_module.dart
class DailyApp extends StatelessWidget {
  // This is the entry point the shell loads
  // It receives server connection from app-core
}

pubspec.yaml Dependencies

parachute-app-core/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  parachute_daily:
    path: ../parachute-daily/flutter  # or git reference
  parachute_chat:
    path: ../parachute-chat/flutter
MVP Approach: Module Flutter packages are included at build time via pubspec.yaml path references. Dynamic runtime loading (like VS Code extensions) is future work.

Agent Sandboxing

Agents run in Docker containers with configurable isolation. This is based on OpenClaw's battle-tested sandboxing model.

Key Insight: Modules are trusted Python code that runs in the main process. The sandboxing happens at the agent level—when Claude SDK runs, that's where containerization and permission scoping matters.

Sandbox Configuration

Defined in module's manifest.yaml or at runtime:

SandboxConfig
@dataclass
class SandboxConfig:
    # Filesystem access
    workspace_access: str = "none"  # none | ro | rw
    allowed_paths: list[str] = []   # Paths within vault (e.g., ["Daily/", "Brain/"])

    # Resource limits
    memory: str = "512m"
    cpus: float = 1.0
    pids_limit: int = 256

    # Network
    network: str = "none"  # none | bridge

    # Security
    read_only_root: bool = True
    cap_drop: list[str] = ["ALL"]

Container Implementation

When an agent runs, parachute-computer:

  1. Creates a Docker container from parachute-sandbox:latest (Debian slim)
  2. Mounts vault paths as specified in allowed_paths
  3. Sets resource limits
  4. Runs Claude SDK inside the container
  5. Captures output and cleans up
Dockerfile.sandbox
FROM debian:bookworm-slim

# Install Claude CLI and dependencies
RUN apt-get update && apt-get install -y \
    python3 python3-pip nodejs npm curl \
    && npm install -g @anthropic-ai/claude-code \
    && rm -rf /var/lib/apt/lists/*

# Non-root user
RUN useradd -m -u 1000 parachute
USER parachute

WORKDIR /workspace

Module Hash Verification

Protects against agents modifying their own module code:

modules.yaml - Hash Verification
modules:
  telegram-bot:
    source: local
    path: "Modules/telegram-bot"
    trust: trusted
    hash: "sha256:a1b2c3d4e5f6..."  # Computed on first load

# On server restart:
# 1. Compute hash of module.py
# 2. If hash differs from stored → require user re-approval
# 3. This prevents agents from modifying their own code

If a module's code changes (even a single character), the user must re-approve it before it loads. This prevents prompt injection attacks where an agent tries to modify its own permissions.

Trust Levels

Level Description Behavior
Verified Signed by Parachute (Daily, Chat) Runs in main container, signature checked
Trusted User explicitly trusts this module Runs in main container, hash verified
Untrusted Third-party or experimental Separate container (future), hash verified

Agent-Level Sandboxing

The real sandboxing happens when Claude SDK runs. Modules define agent types with scoped permissions:

Agent Sandbox Definition
class TelegramBotModule(Module):
    # Module runs in main process (trusted)
    # But its AGENTS run in sandboxed containers

    agents = {
        "responder": {
            # Tightly scoped - only sees one project
            "paths": [
                "Brain/projects/client-x.md",
                "~/Work/client-x/"
            ],
            "capabilities": ["read"],
            "bash": False,
            "network": False,
        },
        "admin": {
            # Broader access but still scoped
            "paths": ["Brain/**", "Daily/**"],
            "capabilities": ["read", "write"],
            "bash": ["git", "npm"],
            "network": ["api.telegram.org"],
        }
    }

    async def handle_message(self, text: str):
        # When this runs Claude SDK, it uses the "responder" sandbox
        return await self.run_agent("responder", text)

Container Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Host Machine                              │
│                                                                  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │              Main Container (Parachute Computer)           │  │
│  │  • FastAPI server (port 3333)                             │  │
│  │  • Brain module (if installed)                            │  │
│  │  • All trusted modules (loaded as Python)                 │  │
│  │  • Module hash verification on load                       │  │
│  │                                                           │  │
│  │  Volumes:                                                 │  │
│  │    /vault     ← ~/Parachute (read-write)                  │  │
│  │    /app       ← Server code (read-only)                   │  │
│  │                                                           │  │
│  │  When Claude SDK runs → spawns agent sandbox              │  │
│  └───────────────────────────────────────────────────────────┘  │
│                              │                                   │
│                              ▼                                   │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │              Agent Sandbox (per Claude SDK run)            │  │
│  │  • Scoped filesystem access (per agent definition)        │  │
│  │  • Scoped bash commands (if any)                          │  │
│  │  • Scoped network access (if any)                         │  │
│  │  • HOME=/vault for SDK session storage                    │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Permission Flow

  1. Module loads → Hash checked against stored value
  2. If hash changed → User must re-approve
  3. Module calls run_agent() → Core looks up agent sandbox definition
  4. Claude SDK spawns → Runs with scoped permissions from agent definition
  5. Agent tries to access file → Permission handler checks against allowed paths

Chat Sandbox Modes

The Chat module defines three agent configurations with different trust levels.

Mode Trust Sandbox Use Case
Default Low Locked General conversation (like Claude.ai)
Workspace Medium Scoped to cwd Working in a project directory
Trusted High None Power user, full access
Chat Module Agent Configurations
class ChatModule(Module):
    name = "chat"
    provides = ["ConversationInterface"]
    optional_requires = ["BrainInterface", "JournalInterface"]

    agents = {
        "default": AgentConfig(
            description="Safe general assistant (like Claude.ai)",
            sandbox=SandboxConfig(
                workspace_access="none",
                network="bridge",  # Can call MCPs
            ),
            allowed_interfaces=["BrainInterface", "JournalInterface"],
            capabilities=["Read"],  # Can read vault via interfaces, not direct FS
            system_prompt="prompts/default.md",
        ),

        "workspace": AgentConfig(
            description="Project-focused assistant with file access",
            sandbox=SandboxConfig(
                workspace_access="rw",
                allowed_paths=["${CWD}"],  # Scoped to selected directory
                network="bridge",
            ),
            capabilities=["Read", "Write", "Edit", "Bash"],
            system_prompt="prompts/workspace.md",
            load_cwd_claude_md=True,  # Load CLAUDE.md from selected directory
        ),

        "trusted": AgentConfig(
            description="Full access (power user mode)",
            sandbox=None,  # No sandbox
            requires_explicit_approval=True,
            capabilities=["Read", "Write", "Edit", "Bash", "Computer"],
            system_prompt="prompts/trusted.md",
        ),
    }

System Prompt Composition

For each mode, system prompts compose differently:

Mode Prompt Components
Default Chat module base prompt → Brain context → Journal context
Workspace Workspace prompt → CLAUDE.md from cwd → Brain context → Project-specific context
Trusted Trusted prompt → Full Brain access → All CLAUDE.md files in hierarchy

Chat Bot Integration

The Chat module supports direct integration with external messaging platforms (Telegram, Discord), enabling conversations that can be viewed and managed from both the bot interface and the Parachute UI.

Concept

A Parachute session can be linked to an external bot conversation:

  • One Parachute session = one Telegram DM, one Discord channel, or one group chat
  • Messages flow through Parachute, which orchestrates Claude responses
  • The bot is a lightweight interface; Parachute is the source of truth
  • Users can "zoom into" any bot conversation in the Chat UI to see full thinking context
Architecture Flow
┌─────────────────────────────────────────────────────────────────┐
│                         Parachute                               │
│  ┌─────────────┐     ┌──────────────┐     ┌──────────────────┐ │
│  │ Chat Session │ ←→ │ Claude Agent │ ←→ │ Brain/Journal    │ │
│  │   (SQLite)   │     │  (sandboxed) │     │   Interfaces     │ │
│  └──────┬──────┘     └──────────────┘     └──────────────────┘ │
│         │                                                       │
│         │ linked_bot: telegram:123456                           │
│         ↓                                                       │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    Bot Connectors                         │  │
│  │  ┌────────────┐  ┌─────────────┐  ┌────────────────────┐ │  │
│  │  │  Telegram  │  │   Discord   │  │  Future: Slack...  │ │  │
│  │  └────────────┘  └─────────────┘  └────────────────────┘ │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Session Linking

Each chat session can optionally be linked to an external bot chat:

Session Metadata (SQLite)
session:
  id: "abc123"
  title: "Project Discussion"
  linked_bot:
    platform: "telegram"        # telegram | discord
    chat_id: "123456789"        # Platform-specific chat ID
    chat_type: "dm"             # dm | group
    linked_at: "2024-01-15T10:30:00Z"

Bot Connector Architecture

Each platform has a connector that receives messages, finds/creates the linked Parachute session, passes the message to the Chat module, and sends Claude's response back through the platform.

Telegram Connector Example
class TelegramConnector:
    """Bridges Telegram messages to Parachute sessions."""

    async def on_message(self, update: TelegramUpdate):
        chat_id = update.message.chat.id

        # Find or create linked session
        session = await self.session_service.get_by_bot_link(
            platform="telegram",
            chat_id=str(chat_id)
        )
        if not session:
            session = await self.session_service.create(
                title=f"Telegram: {update.message.chat.title or 'DM'}",
                linked_bot={
                    "platform": "telegram",
                    "chat_id": str(chat_id),
                    "chat_type": "group" if update.message.chat.type != "private" else "dm"
                }
            )

        # Process through Chat module (respects session permissions)
        response = await self.chat_module.send_message(
            session_id=session.id,
            content=update.message.text,
            source="telegram"
        )

        # Send response back to Telegram
        await self.bot.send_message(
            chat_id=chat_id,
            text=response.content
        )

Per-Session Permissions

Each session has its own permission configuration, critical for bot-linked sessions where different chats need different access levels:

Session Permissions Dataclass
@dataclass
class SessionPermissions:
    """Per-session access control."""

    # Folder access (paths within vault the agent can see)
    allowed_folders: list[str] = field(default_factory=list)
    # e.g., ["Projects/parachute/", "Brain/"]

    # MCP tools this session can use
    allowed_mcps: list[str] | None = None  # None = all, [] = none
    # e.g., ["brain_search", "brain_resolve"]

    # Agent capabilities
    capabilities: list[str] = field(default_factory=lambda: ["Read"])
    # e.g., ["Read", "Write", "Edit", "Bash"]

    # Sandbox config override (if None, uses mode default)
    sandbox_override: SandboxConfig | None = None

System Prompt Awareness

The agent's system prompt is dynamically constructed to include its permissions context, so it "knows" what folders, tools, and capabilities it has access to. Permissions are enforced at both prompt level AND sandbox level—the agent literally cannot exceed its configured boundaries.

System Prompt Builder
def build_system_prompt(session: Session, mode: str) -> str:
    """Compose system prompt with permission awareness."""

    base_prompt = load_prompt(f"prompts/{mode}.md")

    # Add folder access context
    if session.permissions.allowed_folders:
        folder_context = f"""
## Folder Access

You have access to the following folders in the vault:
{chr(10).join(f'- {f}' for f in session.permissions.allowed_folders)}

You cannot access files outside these folders.
"""
    else:
        folder_context = """
## Folder Access

You do not have direct filesystem access in this session.
Use available interfaces (Brain, Journal) to access information.
"""

    return f"{base_prompt}\n\n{folder_context}"

Configuration UI

In the Chat app, users can configure per-session permissions:

Session Permissions UI
┌─────────────────────────────────────────────────────────────────┐
│ Session: Project Discussion                                     │
│ Linked: Telegram DM (@username)                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ Mode: [Workspace ▼]                                             │
│                                                                 │
│ ─── Folder Access ───────────────────────────────────────────── │
│ [✓] Projects/parachute/                                         │
│ [✓] Brain/                                                      │
│ [ ] Daily/                                                      │
│ [ ] Full vault access                                           │
│                                                                 │
│ ─── MCP Tools ───────────────────────────────────────────────── │
│ [✓] brain_search                                                │
│ [✓] brain_resolve                                               │
│ [ ] web_fetch                                                   │
│ [ ] All tools                                                   │
│                                                                 │
│ ─── Capabilities ────────────────────────────────────────────── │
│ [✓] Read files                                                  │
│ [✓] Write files                                                 │
│ [✓] Edit files                                                  │
│ [ ] Run commands (Bash)                                         │
│                                                                 │
│                              [Save Permissions]                 │
└─────────────────────────────────────────────────────────────────┘

Bot Setup (Settings)

Global bot configuration in the Settings screen:

Bot Settings UI
┌─────────────────────────────────────────────────────────────────┐
│ Bot Integrations                                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ ─── Telegram ────────────────────────────────────────────────── │
│ Status: ● Connected                                             │
│ Bot: @MyParachuteBot                                            │
│ Token: ••••••••••••••••••••                    [Edit] [Remove]  │
│                                                                 │
│ ─── Discord ─────────────────────────────────────────────────── │
│ Status: ○ Not configured                                        │
│                                               [Add Discord Bot] │
│                                                                 │
│ ─── Default Permissions for New Bot Sessions ────────────────── │
│ Mode: [Default ▼]                                               │
│ Folder Access: [None - use interfaces only]                     │
│ MCP Tools: [Brain tools only]                                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Use Cases

Use Case Description Permissions
Personal Assistant via Telegram DM your bot for quick queries, see full context in Parachute later Default mode, Brain interfaces only
Project Channel Link a Discord channel to a workspace-mode session Workspace mode, project folder access
Shared Family Bot Group chat with restricted permissions Default mode, read-only Brain access
ClawdBot-Style Setup Full-featured bot with thinking visible in Parachute UI Workspace/Trusted mode, full access

MVP Scope

  • Telegram connector (single bot token in settings)
  • Session linking (one session = one chat)
  • Per-session folder access configuration
  • Per-session MCP tool filtering
  • Settings UI for bot configuration
  • Session permissions UI in chat detail view
Future: Discord connector, Slack connector, multiple bot tokens per platform, session merging (combine bot + direct chats).

Suggestions System

Sandboxed agents can propose changes rather than executing them directly. This creates a trust-building loop where users (or orchestrator agents) approve suggestions before they take effect.

Why Suggestions?

  • Safety: Sandboxed agents can't accidentally damage data
  • Trust building: Log of approved/rejected suggestions helps calibrate agent behavior
  • User control: Clear approval points for sensitive operations
  • Orchestration: Parent agents can filter/combine child agent suggestions

Suggestion Hierarchy

Suggestion Flow
Sandboxed Agent
     │
     │ suggests: "Update Kevin entity with new project"
     ↓
Orchestrator Agent (optional)
     │
     │ filters/batches suggestions
     │ suggests: "3 entity updates from today's journal"
     ↓
User
     │
     │ reviews and approves/rejects
     ↓
Action Executed

Suggestion Data Structure

Suggestion Dataclass
@dataclass
class Suggestion:
    id: str
    source_agent: str           # Which agent proposed this
    source_session: str         # Which session it came from
    timestamp: datetime

    # What's being suggested
    action: str                 # "create" | "update" | "delete" | "link"
    target_module: str          # "brain" | "daily" | "chat"
    target_id: Optional[str]    # para_id if updating existing

    # The actual change
    payload: dict               # Module-specific data
    reasoning: str              # Why the agent thinks this is good

    # Resolution
    status: str                 # "pending" | "approved" | "rejected" | "modified"
    resolved_by: Optional[str]  # "user" | "orchestrator:{id}"
    resolved_at: Optional[datetime]
    modifications: Optional[dict]  # If user tweaked the suggestion

User Approval UI

Pending Suggestions View
┌─────────────────────────────────────────────────────────────────┐
│ Pending Suggestions (3)                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 📝 Update: Kevin                                   [Brain]  │ │
│ │                                                             │ │
│ │ Add to "Recent Activity":                                   │ │
│ │ "- 2024-01-15: Discussed LVB curriculum with Aaron"         │ │
│ │                                                             │ │
│ │ Source: Daily reflection • 2 min ago                        │ │
│ │                                                             │ │
│ │              [Reject]  [Edit]  [Approve ✓]                  │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ✨ Create: AI Safety (topic)                       [Brain]  │ │
│ │                                                             │ │
│ │ "Emerging area of interest based on journal mentions"       │ │
│ │                                                             │ │
│ │ Source: Daily reflection • 2 min ago                        │ │
│ │                                                             │ │
│ │              [Reject]  [Edit]  [Approve ✓]                  │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│                              [Approve All]  [Review Later]      │
└─────────────────────────────────────────────────────────────────┘
Training Loop: Every suggestion and its resolution is logged. This enables future improvements where agents learn which suggestions get approved vs rejected, improving suggestion quality over time.

Module Permission UI

When installing or managing modules, users see clear permission screens similar to OAuth consent flows.

Installation Flow

Module Installation Screen
┌─────────────────────────────────────────────────────────────────┐
│ Install Module: parachute-contacts                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ 📇 Contacts Module                                    v1.0.0    │
│ by OpenParachutePBC                                             │
│                                                                 │
│ Sync and manage your contacts with AI-powered enrichment.       │
│                                                                 │
│ ─── This module requests access to: ─────────────────────────── │
│                                                                 │
│ ✓ Create folder: Contacts/                                      │
│ ✓ Read/Write: Contacts/**                                       │
│ ✓ Read: Brain/** (to link contacts to entities)                 │
│                                                                 │
│ ─── Agents this module can run: ─────────────────────────────── │
│                                                                 │
│ • contact_enricher                                              │
│   - Network access: Yes (to fetch public info)                  │
│   - File access: Contacts/ only                                 │
│   - Can suggest: Brain entity updates                           │
│                                                                 │
│ • contact_sync                                                  │
│   - Network access: Yes (CardDAV servers)                       │
│   - File access: Contacts/ only                                 │
│                                                                 │
│ ─── MCP tools provided: ─────────────────────────────────────── │
│                                                                 │
│ • contacts_search - Search contacts by name/email               │
│ • contacts_get - Get contact details                            │
│ • contacts_suggest_link - Suggest Brain entity link             │
│                                                                 │
│            [Cancel]              [Install & Approve]            │
└─────────────────────────────────────────────────────────────────┘

Module Management View

Installed Modules Screen
┌─────────────────────────────────────────────────────────────────┐
│ Installed Modules                                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 🧠 Brain                                    v1.2.0  ✓ Active │ │
│ │ Knowledge layer with entity management                      │ │
│ │                                                             │ │
│ │ Permissions: Read/Write Brain/**, MCP: brain_*              │ │
│ │ Agents: indexer (sandboxed), linker (sandboxed)             │ │
│ │                                                             │ │
│ │                        [Permissions]  [Disable]  [Remove]   │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 📔 Daily                                    v1.0.0  ✓ Active │ │
│ │ Voice journaling and reflection                             │ │
│ │                                                             │ │
│ │ Permissions: Read/Write Daily/**, Read Brain/**             │ │
│ │ Agents: reflection (sandboxed)                              │ │
│ │                                                             │ │
│ │                        [Permissions]  [Disable]  [Remove]   │ │
│ └─────────────────────────────────────────────────────────────┘ │
│                                                                 │
│                                          [+ Install Module]     │
└─────────────────────────────────────────────────────────────────┘

Maintenance Agent

A special agent configuration for system administration tasks. It has broad local access but restricted network access, preventing accidental data exfiltration while allowing powerful local operations.

Purpose

  • Fix configuration issues
  • Manage modules
  • Debug problems
  • Update settings
  • Clean up data

Configuration

Maintenance Agent Config
agents = {
    "maintenance": AgentConfig(
        description="System maintenance with local-only access",
        sandbox=SandboxConfig(
            workspace_access="rw",
            allowed_paths=[
                ".parachute/",      # Config files
                ".modules/",        # Module code (read for debugging)
                "Chat/",            # Can fix chat data
                "Daily/",           # Can fix daily data
                "Brain/",           # Can fix brain data
            ],
            network="none",         # NO network access - can't exfiltrate
            memory="1g",
            cpus=2.0,
        ),
        capabilities=["Read", "Write", "Edit", "Bash"],
        system_prompt="prompts/maintenance.md",
        # Extra safety: require explicit user invocation
        requires_explicit_invocation=True,
    ),
}

Invocation

The maintenance agent is invoked explicitly, not as a default mode:

Using Maintenance Agent
# In chat
User: /maintenance
System: Starting maintenance agent (local access only, no network)...

User: Why aren't my daily entries syncing?
Agent: Let me check the Daily module configuration and recent logs...

# Via CLI
parachute maintenance "Check why brain search is slow"
Key Safety Feature: Network is completely disabled for the maintenance agent. It can see everything locally but cannot send data anywhere external, preventing accidental data exfiltration.

API Key Scoping

API keys are scoped by module to provide fine-grained access control for multi-device access.

~/.parachute/keys.yaml
api_keys:
  - key_hash: "sha256:abc123..."
    name: "iPhone"
    created: "2024-01-15T10:30:00Z"
    scopes:
      - module: "daily"
        capabilities: ["read", "write"]
      - module: "chat"
        capabilities: ["read", "write"]
      - module: "brain"
        capabilities: ["read"]  # Can query but not modify

  - key_hash: "sha256:def456..."
    name: "Shared Family Device"
    scopes:
      - module: "daily"
        capabilities: ["read"]  # Read-only access to journal

  - key_hash: "sha256:ghi789..."
    name: "Admin Device"
    scopes:
      - module: "*"
        capabilities: ["read", "write", "admin"]  # Full access + module management

Enforcement Flow

  1. Request arrives with Authorization: Bearer <key>
  2. Server hashes key (SHA-256), looks up in registry
  3. Extracts module from route (e.g., /api/daily/entriesdaily)
  4. Checks if key has scope for that module + capability
  5. Returns 403 Forbidden if not authorized

Special Scopes

Scope Description
module: "*" Access all modules
capabilities: ["admin"] Module management (approve upgrades, install/remove modules)
Localhost bypass Configurable via auth_mode: remote|always|disabled
Cross-Module Access: When modules call each other via interfaces (not HTTP), they use the module's own permissions, not the API key's. API key scoping only applies to external HTTP access.

Inter-Module Communication

Multiple patterns for modules to communicate, depending on trust level.

Trusted Modules: Direct Python

Trusted modules run in the same process and can import each other directly:

Direct Python Calls (Zero Overhead)
# In Daily module
from parachute.core.brain import brain
from parachute.modules.chat import chat_module

# Direct function calls - no serialization, no HTTP
mentions = await brain.suggest(entry_content)
recent_chats = await chat_module.get_sessions(topic="LVB")

All Modules: Standard Patterns

Pattern Use Case Example
Direct Import Trusted module → trusted module from parachute.core.brain import brain
Internal HTTP Untrusted module → any module POST /api/brain/resolve
MCP Tools Agent using module capabilities Chat agent calls brain-search tool
Event Bus Decoupled reactions Hook listens for daily.entry.created
Same Machine: All communication stays local. Direct Python calls have zero overhead. HTTP calls are localhost-only with negligible latency.

Hooks System

User-configurable scripts triggered by system events. More flexible than hardcoded curator logic.

~/.parachute/hooks/
hooks/
├── on-session-start.py    # Load context files, inject memory
├── on-session-end.py      # Summarize to memory, cleanup
├── on-daily-entry.py      # Trigger reflection, link mentions
├── on-context-limit.py    # Memory flush before compaction
└── on-project-mention.py  # Load project AGENTS.md

Hook Configuration

Hooks follow Claude Code's compatible format—stdin JSON, exit codes, stdout response:

~/.parachute/hooks.yaml
hooks:
  daily.entry.created:
    - type: "command"
      command: "python -m parachute_brain.hooks.detect_mentions"
      timeout: 30
    - type: "command"
      command: "/path/to/custom/backup-script.sh"
      async: true  # Non-blocking

  session.end:
    - type: "command"
      command: "python -m parachute.hooks.activity_summary"

Hook I/O

Hooks receive JSON on stdin and return results via exit code:

Input JSON (stdin)
{
  "event": "daily.entry.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "vault_path": "/Users/aaron/Parachute",
  "module": "daily",
  "data": {
    "entry_id": "2024-01-15-morning",
    "entry_path": "/Users/aaron/Parachute/Daily/entries/2024-01-15-morning.md"
  }
}
  • Exit 0 = success (stdout parsed as JSON if present)
  • Exit 2 = blocking error (stderr fed to caller, action blocked if blockable)
  • Other = non-blocking error (logged, execution continues)

Hook Events

Event Trigger Context Provided
session.start New chat/daily session begins session_id, module, working_directory
session.end Session closes or times out session_id, message_count, duration
daily.entry.created New journal entry saved entry_id, content, audio_path
context.limit.approaching Context window 80% full session_id, token_count, threshold
brain.mention.detected Entity mentioned in text source_id, para_id, confidence
vs. Curator: Hooks are user-configurable and event-driven. The current curator approach becomes a default hook that users can modify or replace.

Observability & Audit

Track what's calling what across the system. This builds trust and enables debugging.

Event Log

Every significant action is logged:

Audit Event Structure
@dataclass
class AuditEvent:
    timestamp: datetime
    event_type: str          # "api_call" | "mcp_call" | "module_call" | "agent_start" | "suggestion"

    # Source
    source_type: str         # "flutter_app" | "api_key" | "agent" | "module" | "hook"
    source_id: str           # Session ID, API key name, agent ID, etc.

    # Target
    target_module: str
    target_endpoint: str     # "/api/brain/search" or "brain.resolve()"

    # Details
    para_ids_involved: list[str]  # Any para_ids referenced
    duration_ms: int
    success: bool
    error: Optional[str]

Storage

Audit Log Location
~/Parachute/.parachute/
└── audit/
    ├── 2024-01-15.jsonl    # One file per day
    ├── 2024-01-16.jsonl
    └── ...

Benefits

Benefit Description
Debugging "Why did this agent access that file?"
Trust Users can see exactly what's happening
Performance Identify slow endpoints or excessive calls
Security Detect unusual access patterns

Visualization (Future)

A knowledge graph view showing module interactions:

System Activity Graph
┌─────────────────────────────────────────────────────────────────┐
│ System Activity (Last 24h)                                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│         ┌─────────┐                                             │
│         │  Daily  │                                             │
│         └────┬────┘                                             │
│              │ 47 calls                                         │
│              ↓                                                  │
│         ┌─────────┐         ┌─────────┐                        │
│         │  Brain  │ ←────── │  Chat   │                        │
│         └─────────┘ 156     └─────────┘                        │
│              ↑              calls                               │
│              │ 12 calls                                         │
│         ┌────┴────┐                                             │
│         │ Flutter │                                             │
│         │   App   │                                             │
│         └─────────┘                                             │
│                                                                 │
│ Recent: brain.search("Kevin") → 3 results (23ms)                │
│         daily.create_entry() → para:daily:x7y8z9 (156ms)        │
│         chat.send_message() → streaming (2.3s)                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Claude Authentication

Parachute uses Claude's native authentication rather than managing API keys directly.

How It Works

Users authenticate with Claude directly:

# On the host machine (one-time setup)
claude login

This stores OAuth credentials in ~/.claude/.credentials.json. With the HOME override, credentials land in ~/Parachute/.claude/.credentials.json, making them portable with the vault.

Credential Discovery

The Claude SDK checks in order:

  1. ANTHROPIC_API_KEY environment variable
  2. CLAUDE_CODE_OAUTH_TOKEN environment variable
  3. Stored credentials in ~/.claude/.credentials.json

Parachute relies on option 3 with the HOME override.

Headless / Remote Servers

For servers without a browser (Linux VPS, Raspberry Pi):

# On a machine with a browser
claude login
# Copy credentials to remote server
scp ~/.claude/.credentials.json user@server:~/Parachute/.claude/

Why Not API Keys?

  • Simpler: Users don't manage a separate API key
  • Consistent: Same auth model as Claude Code CLI
  • Secure: OAuth tokens are scoped and refreshable
  • Portable: Credentials move with vault

Testing Strategy

Comprehensive testing across all layers: unit, integration, and end-to-end.

Backend Testing (Python)

Framework: pytest + pytest-asyncio

Test Directory Structure
parachute-computer/tests/
├── unit/
│   ├── test_module_loader.py
│   ├── test_interface_registry.py
│   └── test_sandbox.py
├── integration/
│   ├── test_module_lifecycle.py
│   └── test_hook_execution.py
└── api/
    ├── test_modules_api.py
    └── test_health.py

Frontend Testing (Flutter)

Frameworks: Widget tests (built-in), Integration tests (built-in), Patrol for E2E

Why Patrol over Playwright: Flutter renders to canvas, not DOM. Playwright can't inspect Flutter widgets. Patrol integrates with Flutter's semantics tree for reliable element selection.

End-to-End Testing

Flow: Start test server → Run Flutter app → Execute Patrol tests

CI Script
#!/bin/bash
set -e

# Start test server
VAULT_PATH=~/ParachuteTest PORT=3334 parachute start &
sleep 5

# Run backend tests
cd parachute-computer && pytest
cd parachute-brain && pytest
cd parachute-daily/python && pytest
cd parachute-chat/python && pytest

# Run Flutter tests
cd parachute-app-core
flutter test
patrol test --target integration_test/e2e_test.dart

# Cleanup
parachute stop

Agentic Testing

For dynamic testing beyond scripted scenarios, leverage Claude to explore the app:

claude --prompt "
You are testing the Parachute app running on localhost:3334.
Explore the Daily module:
1. Create a voice entry (simulate with text)
2. Verify it appears in the entry list
3. Check that Brain linking works if Brain is installed
4. Report any issues you find
"

CI/CD Pipeline

GitHub Actions workflow runs on every push:

  • Backend job: Python tests on ubuntu-latest
  • Frontend job: Flutter tests on macos-latest
  • E2E job: Full integration after backend + frontend pass

Migration Plan

1-2 week sprint creating new repositories with selective code migration from existing system. Test vault at ~/ParachuteTest/ on port 3334.

Approach: Build fresh in new repos, copy code selectively from existing ~/Projects/parachute/. Existing system stays untouched as reference.

Repository Structure

New Repositories
github.com/OpenParachutePBC/
├── parachute-computer   # Python server + module system (NO Brain!)
├── parachute-brain      # Brain module - publishes BrainInterface
├── parachute-daily      # Daily module (Python + Flutter)
├── parachute-chat       # Chat module (Python + Flutter)
├── parachute-app-core   # Shared Flutter package
└── parachute            # (existing - reference only)

1 Foundation Days 1-3

Core infrastructure with dummy module validation.

  • Create parachute-computer repo with FastAPI server skeleton
  • Implement Brain (entity store, para-id, types, search)
  • Module base class with @route, @mcp_tool, @event decorators
  • Module loader (reads modules.yaml, validates hash, registers routes)
  • Create parachute-app-core with transcription services
  • Set up test vault at ~/ParachuteTest/

2 Daily Module Days 4-6

Daily working end-to-end with Brain integration.

  • Create parachute-daily repo (Python module + Flutter package)
  • Port voice transcription and entry management
  • Implement minimal reflection agent (validates SDK integration)
  • Daily → Brain flow: detect mentions, link entities
  • Verify offline-first entry creation works

3 Chat Module Days 7-9

Chat working with Brain context loading.

  • Create parachute-chat repo (Python module + Flutter package)
  • Port streaming chat and session management
  • Brain → Chat flow: load context when entities mentioned
  • Working directory / context folder selection
  • Post-session Brain update hooks

4 Integration & Hardening Days 10-14

Full system with security features.

  • Hooks system (discovery, event firing, default hooks)
  • Agent-level sandboxing (scoped permissions for Claude SDK runs)
  • Module hash verification (re-approve if code changes)
  • System prompt composition (module + Brain + hooks)
  • End-to-end validation: Daily → Brain → Chat flow

Test Environment

Setting Test Production
Vault ~/ParachuteTest/ ~/Parachute/
Server Port 3334 3333
Codebase New repos Existing parachute/

Decisions Made

Key architectural decisions resolved during planning.

Architecture

Question Decision
Brain: module or core? Module. Publishes BrainInterface. Other modules optionally depend on interface, not implementation. Can be upgraded/swapped.
Module sandboxing level? Agent-level. Modules are trusted Python. Sandboxing applies when Claude SDK runs.
Built-in vs third-party modules? No distinction. All modules are vault modules. Parachute modules are just signed/verified.
Module communication? Direct Python imports for trusted modules. HTTP/MCP for untrusted.
System prompts? Layered. Module base + Brain entity context + hook injection.
Transcription location? App-core. Used by both Daily and Chat, stays in shared package.
SDK session storage? In vault. HOME=~/Parachute so .claude/ travels with data.

Remaining Questions (Decide During Implementation)

  1. Module hot-reload: Can modules update without server restart?
  2. Relationship types: Should Brain relationships be typed or freeform?
  3. Reflection agent scope: What should the minimal reflection agent do exactly?