Technical Case Study · 2026

Amplifier Meets the Desktop

A Python Sidecar Story

From PyInstaller binaries to portable Python distributions

Architectural decisions & lessons learned from embedding a Python AI agent framework in a cross-platform desktop app

The Problem

Two Worlds, One App

🖥️

The Desktop App

Built with Electron — JavaScript all the way down. Great for cross-platform UI. But JavaScript isn't Python.

🐍

Amplifier Runtime

Python through and through. A modular AI agent framework that relies deeply on Python's packaging ecosystem.

  • Can't run Python in-process inside an Electron renderer or main process
  • Users shouldn't need Python installed — the app must be self-contained out of the box
  • Package modules must be discoverable — Amplifier's entire architecture depends on this
  • Must work cross-platform — macOS, Windows, Linux — no OS-specific hacks
The solution: run Amplifier as a child process — a "sidecar" — and bridge the two worlds over HTTP.
System Design

The Sidecar Architecture

Desktop App (Electron)
↓  spawns child process
Amplifier Runtime (Python)
↓  serves HTTP / SSE on localhost
Backend (Node.js API)
↓  streams responses via SSE
Frontend (React UI)
1
Electron spawns Python

Starts the Amplifier runtime as a child process, passes a port number via CLI args

2
Python starts HTTP server

Amplifier runtime binds to localhost and signals readiness via stdout

3
Backend connects

The Node.js backend discovers the port and proxies requests to Amplifier

4
UI streams responses

React frontend receives SSE events and renders agent output in real time

First Attempt

The PyInstaller Approach

"One binary, no Python required" — sounded perfect for a desktop app.
  • Collect all Python modules — PyInstaller traces imports and bundles all dependencies
  • Compile to a single executable — users run one file, no install step needed
  • ~43 MB final binary — reasonably compact for distribution
  • Great track record — widely used for shipping Python CLI tools and scripts
build# PyInstaller bundles everything into # a single self-extracting binary pyinstaller amplifier_cli.py \ --onefile \ --hidden-import amplifier_core \ --hidden-import amplifier_foundation \ --collect-all amplifier_module_loop_streaming

On paper: bundle Amplifier's Python packages into a frozen binary, ship it as an Electron resource, spawn at runtime. The plan was clean. The execution… was not.

The Failure

Module Discovery Fails at Startup

stderrRuntimeError: Cannot initialize without orchestrator: Module 'loop-streaming' has no valid Python package at /tmp/_MEI81mChB/amplifier_module_loop_streaming-1.0.0.dist-info Traceback (most recent call last): File "amplifier_app_runtime/cli.py", line 42, in <main> File "amplifier_core/bootstrap.py", line 118, in initialize RuntimeError: Cannot initialize without orchestrator
🔍

What happened?

The Amplifier agent never loaded. The runtime binary started, then immediately crashed before initializing.

📂

The clue

Notice /tmp/_MEI81mChB/ — that's PyInstaller's temporary extraction directory at runtime.

🧩

The gap

The path points to a .dist-info directory, not a Python package. Something fundamental is broken.

Root Cause Analysis

PyInstaller vs importlib.metadata

How amplifier-core discovers modules at startup:

1
Reads dist-info/RECORD

Inspects the installed package manifest via importlib.metadata

2
Filters for source files

Looks for .py package entries, skips .dist-info entries

3
Empty result → fallback

Falls back to using the .dist-info directory itself as the module root

4
Searches for __init__.py

Tries to find a Python package inside a dist-info folder. Fails.

"No valid Python package"

Runtime crashes before any agent loads

PyInstaller strips RECORD file paths when bundling bytecode into the binary. The metadata structure exists but the file path entries point nowhere meaningful.

⚠️ The Core Tension

PyInstaller freezes Python — converts source to bytecode, strips the filesystem structure. But importlib.metadata relies on that filesystem structure being real.

The Insight

Amplifier's Architecture is Built on Python Packaging

🔌

The Philosophy

Amplifier discovers modules via Python entry points and importlib.metadata. You can pip install amplifier-module-provider-anthropic and it just appears — no configuration needed.

📦

The Dependency

The modular, swappable architecture depends on Python packaging being alive and intact — real dist-info directories, real RECORD files, real site-packages paths on disk.

PyInstaller "freezes" this ecosystem. It's the wrong tool for a framework that treats Python packaging as a first-class runtime feature. We needed an approach that works with Python's package system, not around it.

The question shifted: from "how do we freeze Python?""how do we ship a real Python installation that users don't have to install themselves?"

The Solution

python-build-standalone: Ship a Real Python

🐍

python-build-standalone

A portable Python distribution that finds its standard library relative to itself — no system-wide installation, no PATH dependencies, no conflicts with a user's own Python.

📁

Real site-packages

Install Amplifier's packages normally into site-packages. Full metadata, full RECORD files, full entry points. importlib.metadata works perfectly.

📦

Bundle as app resource

Copy the entire directory tree as an Electron extraResource. Spawn by absolute path at runtime.

electron main// At runtime in Electron main process const pythonBin = path.join( resourcesPath, 'python-runtime', 'bin', 'python3' ); const proc = spawn(pythonBin, [ '-m', 'amplifier_app_runtime.cli', '--port', port ]); // No system Python needed. // No PATH. No conflicts. Just works.
~156 MB Bundle size
100% Metadata intact
Build Process

From Source to Bundled Runtime

bash# Step 1: Get python-build-standalone uv python install cpython-3.13 # Downloads a relocatable Python binary # Step 2: Create venv & install packages uv venv ./python-runtime uv pip install . # Full metadata written to site-packages # Step 3: Verify before bundling python -c " import importlib.metadata as m for pkg in required_packages: files = m.files(pkg) assert files, f'RECORD missing for {pkg}' print(f'✅ {pkg}: {len(files)} recorded files') " # Step 4: Copy to Electron resources cp -r ./python-runtime ./electron/resources/

Why uv?

  • uv python install downloads exactly python-build-standalone distributions
  • Reproducible — same Python version on every CI run
  • Fast — written in Rust, installs in seconds
  • Venvs created by uv use the standalone Python as base
The build script verifies importlib.metadata works correctly before bundling. If verification fails, the build fails. Ship only what you've proven works.
Build Verification

Confidence Before Shipping

build output▶ Verifying importlib.metadata integrity... ✅ amplifier-core == 1.0.9 (65 recorded files) ✅ amplifier-foundation == 1.0.0 (57 recorded files) ✅ amplifier-module-loop-streaming == 1.0.0 (10 recorded files) ✅ amplifier-module-provider-anthropic == 1.0.0 (8 recorded files) ✅ amplifier-module-hooks == 1.0.0 (12 recorded files) All packages verified — importlib.metadata works correctly. ▶ Copying runtime to electron/resources/python-runtime... ✅ Build complete. Bundle size: 156 MB
🔐

Verified at build time

Not "works on my machine" — the exact metadata structure used at runtime is validated in CI before packaging.

📋

File count as signal

Recording how many files are in each package's RECORD catches partial installs and truncated metadata.

🚫

Fail the build, not the user

If any package lacks valid metadata, the build exits non-zero. Users never see the RuntimeError.

Trade-offs

PyInstaller vs Portable Venv

Dimension PyInstaller Binary Portable Venv
Bundle size ~43 MB  compact ~150–160 MB  larger but functional
Module discovery ✗ Broken  RECORD paths invalid ✓ Works  full importlib.metadata
Upgrade one module ✗ Full recompile  rebuild entire binary ✓ Replace one package  in site-packages
Amplifier bundles ✗ Fragile  entry points break ✓ Full support  all bundles load
Debugging crashes ✗ Opaque  frozen bytecode, no source ✓ Real Python files  readable source
Startup speed Extracts to /tmp on each launch Direct execution, no extraction step
CI reproducibility Compiled binary, hard to inspect ✓ Verifiable  file hashes + metadata check

The size tradeoff is real — but a broken 43 MB binary is less useful than a working 156 MB one.

Design Pattern

Mapping Amplifier's Protocol Boundary to HTTP

When integrating Amplifier across language boundaries, its core abstractions map cleanly to HTTP primitives:

DisplaySystem SSE stream
ApprovalSystem REST endpoint
StreamingHook WebSocket / SSE events
SpawnCapability REST call
Session lifecycle Resource URLs + DELETE

The spirit is preserved

Amplifier's architecture is about mechanisms, not policies — pluggable systems, clear interfaces, composable modules. HTTP is just another boundary to cross. The philosophy travels.

The host language doesn't need to understand Amplifier internals. It just makes HTTP calls. The Python side handles everything else.
Lessons Learned

Key Takeaways

📦

Preserve the Python packaging ecosystem

Amplifier's module discovery depends on real dist-info directories and intact RECORD files. Any distribution approach that strips or virtualizes this will break module loading.

🚢

Don't freeze — ship

python-build-standalone gives you a portable, self-contained Python without PyInstaller's tradeoffs. The extra ~110 MB buys you a fully functional runtime that behaves exactly like development.

🌉

The sidecar pattern works

Running Amplifier as a child process with an HTTP API cleanly bridges the language gap. Electron speaks HTTP. Amplifier speaks HTTP. No FFI, no shared memory, no complexity.

🔮

Amplifier's philosophy applies cross-language

Mechanisms, not policies — even when your host app is JavaScript. The protocol boundary maps naturally to HTTP. The modularity is preserved. The sidecar is still Amplifier.

The Bottom Line

"Amplifier's modular design works because it trusts Python's package ecosystem. Work with it, not around it."

📚

python-build-standalone

github.com/indygreg/python-build-standalone

uv package manager

docs.astral.sh/uv — fast Python tooling in Rust

Technical Case Study · Cross-Platform Desktop Integration · Amplifier AI Agent Framework

1 / 14
More Amplifier Stories