Feature: WASM Extension System for Professional Integrations
Date: 2026-02-16 Status: Approved Version: V1.2 — Extension Ecosystem Effort: XL (12 weeks, 6 phases)
1. Context & Motivation
Problem Statement
Morphee needs professional integrations (JIRA, GitHub, Notion, Linear, Slack, etc.) but hardcoding them has significant drawbacks:
Current Approach (Hardcoded Integrations):
backend/interfaces/integrations/jira.py— Python implementation- Tightly coupled to backend deployment
- No community contributions (third parties can't build integrations)
- Duplication problem: To run integrations on Tauri Rust frontend, must rewrite in Rust
- Not portable with Spaces (can't share integrations with
.morph/directories) - Scalability bottleneck (every integration requires backend deployment)
User's Key Insight: "I don't want to do the work twice, and make JIRA integration only on backend. It could be a WASM executable that user can have on their device too."
Vision: Portable, Sandboxed, Community-Driven Extensions
Transform integrations from hardcoded Python modules to portable WASM extensions:
┌─────────────────────────────────────────────────────────┐
│ Extension Developer │
│ Writes integration in Rust/Go/C/AssemblyScript │
│ Uses morphee-sdk-rust crate │
└─────────────────────────────────────────────────────────┘
↓ compiles to
┌─────────────────────────────────────────────────────────┐
│ jira.wasm (WebAssembly binary) │
│ - Single binary, runs on Python + Rust │
│ - Sandboxed (cannot access filesystem/network/vault) │
│ - Capability-based permissions │
└─────────────────────────────────────────────────────────┘
↓ distributed via
┌─────────────────────────────────────────────────────────┐
│ rg.morphee.ai (OCI registry) │
│ - Public extensions (GitHub Container Registry, free) │
│ - Private extensions (self-hosted Harbor, groups only) │
│ - Versioned, signed, verified │
└─────────────────────────────────────────────────────────┘
↓ runs on
┌──────────────────────────┬────────────────────────────────────────────────┐
│ Python Backend │ Tauri Rust Frontend │
│ wasmtime-py v41+ │ WasmRuntime (wasmer 5.0) │
│ ExtensionManager │ ├── Desktop/Android: Cranelift JIT │
│ Host functions (http, │ └── iOS: Wasmi interpreter (JIT banned) │
│ vault, events, data) │ JSRuntime (webview JS — canvas/UI extensions) │
└──────────────────────────┴────────────────────────────────────────────────┘
↓ stored in
┌─────────────────────────────────────────────────────────┐
│ .morph/extensions/jira.wasm (OpenMorph integration) │
│ - Extensions live in Space's Git repo │
│ - Portable with Space (copy .morph/ = copy extensions) │
│ - File extension association system (Phase 3i) │
└──────────────────────────────────────────────────────── ─┘
Key Benefits:
- Portability: Same binary runs on Python backend AND Rust frontend (no duplication!)
- Security: True sandboxing (memory-safe, cannot escape without explicit host imports)
- Community: Third parties can build and share extensions
- OpenMorph Synergy: Extensions live in
.morph/extensions/*.wasmalongside Space data - Marketplace: OCI registry distribution, versioning, signing
- Performance: 88% of native speed, <1ms warm start after caching
2. Options Investigated
Option A: WebAssembly Extension System (CHOSEN)
Technology Stack (updated 2026-02-20):
- Python Backend:
wasmtime-pyv41+ (best security, 88% native performance, full WASI 0.3 support) - Tauri Rust (all platforms):
wasmer5.0+ — unified API with platform-adaptive backends- Desktop/Android: Cranelift JIT (80-88% native)
- iOS: Wasmi interpreter (50-60% native) — iOS §2.5.2 prohibits JIT; wasmtime JIT rejected by Apple
- Future:
wasm3as ultra-minimal option (64KB, 65% native, sync-only)
- BaseMorphRuntime: WasmRuntime (backend), JSRuntime (frontend canvas/UI), PythonRuntime (future)
- Interface Standard: WebAssembly Component Model + WASI 0.3 (async support, rich types)
- Distribution: OCI registry at
rg.morphee.ai(GHCR for public, Harbor for private) - Security: Install-time granular permissions (like Google Play Store) + resource limits + code signing
Pros:
- ✅ Portability: Same
.wasmbinary runs on Python backend AND Rust frontend - ✅ Security: True sandboxing, capability-based permissions
- ✅ Performance: 88% native (wasmtime JIT), 65% native (wasm3 interpreter on mobile)
- ✅ Industry-Proven: VS Code, Figma, Shopify, Cloudflare all use WASM for extensions
- ✅ Language-Agnostic: Developers can use Rust, Go, C, AssemblyScript, TinyGo
- ✅ OpenMorph Synergy: Extensions stored in
.morph/extensions/*.wasm - ✅ Marketplace-Ready: OCI registry, versioning, signing, verified publishers
Cons:
- ⚠️ Initial implementation effort (12 weeks)
- ⚠️ Learning curve for extension developers (mitigated with SDK + templates)
- ⚠️ Binary size: wasmtime-py ~10MB (desktop/server), wasm3 ~64KB (mobile)
Effort: XL (12 weeks, 6 phases)
Option B: JavaScript/Python Plugin System
Technology Stack:
- Python backend:
importlib+ dynamic import - Tauri frontend: JavaScript via
eval()or embedded Deno runtime
Pros:
- ✅ Simpler for developers (more people know JS/Python)
- ✅ No compilation step
Cons:
- ❌ No true sandboxing — JS/Python can escape, access arbitrary filesystem/network
- ❌ Not portable — need separate Python and JS implementations
- ❌ Security nightmare —
eval()is dangerous, dynamic imports hard to audit - ❌ Not future-proof — can't run on mobile without embedding V8/Deno (~20MB+ binary)
Verdict: ❌ Rejected — security risks unacceptable
Option C: Hardcode Integrations (Status Quo)
Build each integration as Python module (backend/interfaces/integrations/jira.py).
Pros:
- ✅ Fastest to ship first integration (1 week)
Cons:
- ❌ Not scalable — every integration requires backend deployment
- ❌ No community — third parties can't contribute
- ❌ Doubles work — must rewrite in Rust for frontend execution
- ❌ Not portable — doesn't align with OpenMorph vision
Verdict: ❌ Rejected — user explicitly said "I don't want to do the work twice"
3. Decision
Chosen approach: Option A — WebAssembly Extension System
Reasoning:
- Solves portability problem: One binary runs on Python backend AND Rust frontend (no duplication)
- Aligns with OpenMorph: Extensions live in
.morph/extensions/*.wasmas versioned files - Security-first: True sandboxing with install-time granular permissions (like Google Play Store)
- Future-proof: Industry standard (VS Code, Figma, Shopify, Cloudflare all use WASM)
- Marketplace-ready: OCI registry at
rg.morphee.ai, versioning, signing, verified publishers - Mobile-compatible: wasm3 interpreter (64KB) + optional wasmtime for power users
Trade-offs Accepted:
- 12 weeks upfront investment vs 1 week per hardcoded integration
- Extension developers need to learn Rust/Go/AssemblyScript (mitigated with excellent SDK + templates)
- Binary size: ~10MB wasmtime on desktop (acceptable), ~64KB wasm3 on mobile (acceptable)
Technology Decisions (updated 2026-02-20):
- Python Runtime:
wasmtime-pyv41+ (88% native performance, full WASI 0.3 async support) - Tauri Rust Runtime (all platforms):
wasmer5.0+ — unified API with pluggable backends- Desktop (macOS/Windows/Linux): Cranelift JIT (80-88% native speed)
- Android: Cranelift JIT (80-88% native speed)
- iOS: Wasmi interpreter (50-60% native speed) — iOS prohibits JIT compilation (App Store Review Guidelines §2.5.2); wasmtime's JIT mode is completely blocked on iOS, making wasmer with Wasmi the correct choice
- Future option: wasm3 as ultra-minimal interpreter (64KB, 65% native, no async) — tracked for future ultra-low-footprint mobile scenarios
- Interface Standard: WebAssembly Component Model + WASI 0.3 (async support, rich types)
- Registry Hosting: Dual registry (GHCR for public extensions, Harbor for private group extensions)
- Public:
rg.morphee.ai/public/jira:1.0.0→ghcr.io/morphee-ai/jira:1.0.0 - Private:
rg.morphee.ai/private/techcorp-crm:1.0.0→ self-hosted Harbor
- Public:
- Permission Model: Install-time granular permissions (like Google Play Store)
- User reviews/approves permissions before extension is installed
- No runtime approval prompts (too disruptive)
- Permissions:
http:read,http:write,vault:read,vault:write,event:emit,data:read,data:write,task:create,space:read,notify:send
Runtime Architecture — BaseMorphRuntime
The WASM Extension System introduces a BaseMorphRuntime abstract class — the common contract for all execution runtimes that back dynamically-installed Integrations. This lets InterfaceManager treat WASM, JavaScript, and future Python extensions identically, just like it treats built-in Python integrations.
Python (backend/extensions/runtime.py)
class BaseMorphRuntime(ABC):
"""
Abstract runtime that loads and executes portable code as a BaseInterface.
Implementations: WasmRuntime, JSRuntime, PythonRuntime (future).
"""
@abstractmethod
async def load(self, source: str | bytes) -> "LoadedModule":
"""Load a module from a file path or bytes. Returns opaque handle."""
@abstractmethod
async def describe(self, module: "LoadedModule") -> InterfaceDefinition:
"""Returns the Integration definition declared by the module.
Mirrors BaseInterface.get_actions() + published_events + config_schema."""
@abstractmethod
async def execute(
self, module: "LoadedModule", action: str,
params: dict, context: ExecutionContext
) -> ActionResult:
"""Execute an action. Mirrors BaseInterface.execute()."""
@abstractmethod
async def teardown(self, module: "LoadedModule") -> None:
"""Clean up resources (memory, connections)."""
class WasmRuntime(BaseMorphRuntime):
"""Runs .wasm files via wasmtime-py. Used on Python backend."""
class JSRuntime(BaseMorphRuntime):
"""Runs JS canvas/UI integrations via webview bridge. Used on Tauri frontend."""
class PythonRuntime(BaseMorphRuntime): # future
"""Runs sandboxed .py scripts dynamically. Used on Python backend."""
Rust (frontend/src-tauri/src/extensions/runtime.rs)
/// Abstract runtime for executing portable code as a MorpheeIntegration.
pub trait MorphRuntime: Send + Sync {
fn load(&self, source: &[u8]) -> Result<Box<dyn LoadedModule>, MorpheeError>;
fn describe(&self, module: &dyn LoadedModule) -> Result<InterfaceDefinition, MorpheeError>;
fn execute(&self, module: &dyn LoadedModule, action: &str, params: &str, ctx: &str)
-> Result<String, MorpheeError>;
}
/// WASM backend: wasmer 5.0 (Cranelift on desktop/Android, Wasmi on iOS)
pub struct WasmRuntime { store: Store }
impl MorphRuntime for WasmRuntime { ... }
/// Frontend canvas/UI extensions via Tauri webview JS bridge
pub struct JSRuntime { app_handle: AppHandle }
impl MorphRuntime for JSRuntime { ... }
This abstraction means:
InterfaceManager
├── Python Integrations (hardcoded, direct Python code)
│ ├── LLMIntegration, TasksIntegration, CronIntegration...
│ └── (14 built-in integrations)
│
└── BaseMorphRuntime-backed Integrations (dynamic, installed)
├── WasmIntegration("jira.wasm") ← WasmRuntime(BaseMorphRuntime) → wasmtime-py
├── WasmIntegration("github.wasm") ← WasmRuntime(BaseMorphRuntime) → wasmtime-py
├── JsIntegration("canvas-ui.js") ← JSRuntime(BaseMorphRuntime) → webview JS
└── PythonIntegration("custom.py") ← PythonRuntime(BaseMorphRuntime) → sandbox [future]
WasmRuntime: Executes .wasm backend integrations (JIRA, GitHub, Notion, Slack...) using wasmtime-py on the Python backend, and wasmer 5.0 (Cranelift/Wasmi) in Tauri Rust.
JSRuntime: Executes JavaScript frontend integrations — canvas components, dynamic UI renderers, interactive widgets. These run in the browser/Tauri webview context. Every canvas Integration and dynamic UI component routes through JSRuntime, keeping UI logic sandboxed and portable alongside their WASM data counterparts.
PythonRuntime (future): Executes dynamic Python scripts loaded at runtime without backend redeployment.
WASM extension contract — each .wasm extension exports the same two methods that mirror Python's BaseInterface:
// Returns Integration definition JSON: name, version, actions[], events[], config_schema
export describe: func() -> string;
// Executes an action (mirrors BaseInterface.execute())
export execute: func(action: string, params: string, context: string) -> result<string, string>;
This ensures installed WASM extensions are first-class citizens in the InterfaceManager, identical in behavior to built-in Python integrations.
OpenMorph Integration (Phase 3i):
- Extensions are stored as
.morph/extensions/*.wasmfiles in GitStore - File Extension Association System: GitStore maps file extensions to Integrations
.wasm→WASMIntegration.can_open()returnstrue.jpg,.png→ImageIntegration.can_open()returnstrue.py→PythonIntegration.can_open()returnstrue.txt,.md→TextIntegration.can_open()returnstrue
- Multi-Integration Support: One file type can be opened by multiple Integrations (e.g.,
.pycan be opened by TextIntegration OR PythonIntegration) - Multi-File Support: One Integration can open multiple file types (e.g., ImageIntegration opens
.jpg,.png,.gif,.webp) - Discovery: GitStore scans
.morph/extensions/on Space load, registers available extensions with InterfaceManager
4. Implementation Plan
Phase 1: Foundation (2 weeks, L effort)
Goal: Core runtime infrastructure, WIT interface definition, basic host functions, Rust SDK.
Tasks:
-
Add dependencies
- Python:
wasmtime==41.0.0torequirements.txt - Rust desktop:
wasmtime = "41",wasmtime-wasi = "41"toCargo.toml - Rust mobile:
wasm3 = "0.5"toCargo.toml(conditional compilation viamobilefeature)
- Python:
-
Create
backend/extensions/modulemanager.py—ExtensionManagerclass (load, compile, cache, execute)models.py— Pydantic models (ExtensionConfig,ExtensionPermission,ResourceLimits,ExtensionManifest)permissions.py— Permission checking logic (10 granular permissions)resource_limiter.py—ResourceLimiterimplementation for Wasmtimesigning.py— Code signing/verification (RSA-PSS + SHA-256)registry.py— OCI registry client (pull fromrg.morphee.ai)
-
Create
frontend/src-tauri/src/extensions/modulewasm_runtime.rs—WasmRuntime(wasmer 5.0, platform-adaptive: Cranelift/Wasmi)js_runtime.rs—JSRuntime(webview JS bridge for canvas/UI extensions)permissions.rs— Permission checkinglimiter.rs— Resource limiter
-
Design WIT interface (
wit/morphee-extension.wit)- Define host functions:
http,vault,events,datainterfaces - Define
morphee-extensionworld (imports = host functions, exports = extension entry points) - Generate bindings:
python -m wasmtime.bindgen(Python),wit-bindgen rust(Rust)
- Define host functions:
-
Implement host functions
http::fetch(request) -> response— HTTP requests with permission checksvault::get_secret(key) -> value— Read from vault (requiresvault:read)vault::set_secret(key, value)— Write to vault (requiresvault:write)events::emit(event_name, payload)— Emit event to EventBus (requiresevent:emit)data::create_task(params) -> task_id— Create task (requiresdata:write)data::get_task(task_id) -> task— Read task (requiresdata:read)
-
Create Rust SDK crate (
sdk/morphee-sdk-rust/)- Auto-generated WIT bindings
- Helper macros:
export_extension!,permission! - Examples:
examples/echo.rs,examples/hello-world.rs - Publish to crates.io (or GitHub registry)
Deliverables:
- ✅
ExtensionManagercan load and execute WASM modules - ✅ Basic host functions (http, vault, events) working
- ✅ Example extension (
echo.wasm) runs successfully on Python + Rust - ✅ Rust SDK crate published and documented
Acceptance Criteria:
- Load
echo.wasm, callexecute("echo", [("message", "hello")]), get"hello"back - HTTP permission check: extension without
http:writecannot POST (returns error) - Resource limit: extension exceeding memory limit traps gracefully
- Mobile runtime:
wasm3interpreter works on iOS/Android
Dependencies: None
Phase 2: Security & Permissions (2 weeks, L effort)
Goal: Production-grade security, permission enforcement, audit logging.
Tasks:
-
Implement permission model
ExtensionPermissionenum (10 granular permissions):http:read— GET requests onlyhttp:write— POST/PUT/DELETE requestsvault:read— Read secretsvault:write— Write secretsevent:emit— Emit events to EventBusdata:read— Read tasks/conversations/memorydata:write— Create/update tasks/conversationstask:create— Shortcut fordata:write(tasks only)space:read— Read Space metadatanotify:send— Send notifications
- Permission checks in ALL host functions (reject if permission not granted)
- Install-time approval flow: backend endpoint + frontend dialog
-
Add resource limiters
- Memory limit (default 256MB, configurable per extension)
- CPU limit (fuel-based, Wasmtime
consume_fuel, default 10B instructions) - Rate limiting (HTTP requests: 100/min default, event emissions: 500/min)
- Timeout (max 60s execution time per action)
-
Code signing infrastructure
- Generate developer keypairs:
openssl genrsa -out developer.key 4096 - Sign extensions: RSA-PSS signature over SHA-256 hash
- Verify signatures before loading (reject unsigned/invalid extensions)
- Revocation list (future: check against revoked developer keys)
- Generate developer keypairs:
-
Audit logging
- Log all extension actions (INFO level)
- Include:
extension_id,action,user_id,group_id,result,execution_time_ms - Store in
extension_executionstable (PostgreSQL) - Never log secrets or PII (only opaque identifiers)
-
Add security tests
- Test: Extension without
http:writecannot POST - Test: Extension exceeding memory limit traps
- Test: Extension without signature is rejected
- Test: Unsigned extension is rejected
- Test: Extension with invalid signature is rejected
- Test: Extension exceeding CPU limit is terminated
- Test: Extension without
Deliverables:
- ✅ Install-time permission approval UI (frontend dialog)
- ✅ All host functions enforce permissions
- ✅ Resource limits enforced (memory, CPU, rate, timeout)
- ✅ Code signing mandatory (unsigned extensions rejected)
- ✅ Audit log tracks all extension actions
Acceptance Criteria:
- Install JIRA extension → user sees permission prompt → approves → extension installs
- JIRA extension calls
http::fetch(POST)withouthttp:write→ error returned - Extension uses 300MB RAM (limit 256MB) → traps gracefully
- Load unsigned extension → rejected with clear error message
- All extension actions logged to
extension_executionstable
Dependencies: Phase 1
Phase 3: Distribution (1 week, M effort)
Goal: OCI registry setup, manifest schema, CLI tools for install/update/remove.
Tasks:
-
Set up
rg.morphee.aidual registry- Public registry: GitHub Container Registry (free, unlimited public extensions)
- Create GitHub organization:
morphee-ai - Enable GHCR: Settings → Packages → Enable GitHub Container Registry
- DNS:
rg.morphee.ai/public/*→ proxy toghcr.io/morphee-ai/*
- Create GitHub organization:
- Private registry (Phase 6+): Self-hosted Harbor (for group-private extensions)
- Deploy Harbor on DigitalOcean/Hetzner droplet (~$20/month)
- DNS:
rg.morphee.ai/private/*→harbor.internal.morphee.ai/*
- Smart router: Nginx reverse proxy at
rg.morphee.ai
- Public registry: GitHub Container Registry (free, unlimited public extensions)
-
Design extension manifest (
manifest.json){
"name": "jira",
"version": "1.0.0",
"display_name": "JIRA Integration",
"description": "Create, update, and read JIRA issues",
"author": "Morphee Team <hello@morphee.ai>",
"homepage": "https://morphee.ai/extensions/jira",
"permissions": ["http:read", "http:write", "vault:read", "data:write"],
"resource_limits": {
"memory_mb": 128,
"cpu_fuel": 10000000000,
"http_rate_per_min": 100,
"timeout_sec": 60
},
"requires_morphee_version": ">=0.9.0",
"signature": "base64-encoded-RSA-PSS-signature"
} -
Create CLI tool (
morphee-extCLI)morphee-ext publish— Build, sign, push to registrymorphee-ext install <name>— Pull from registry, verify, installmorphee-ext list— List installed extensionsmorphee-ext update <name>— Check for updates, pull, reinstallmorphee-ext remove <name>— Uninstall extension
-
Add backend API endpoints (
backend/api/extensions.py)GET /api/extensions— List available extensions (from registry)GET /api/extensions/installed— List installed extensionsPOST /api/extensions/install— Install extension (pull from registry, verify, store)DELETE /api/extensions/{id}— Uninstall extensionPOST /api/extensions/{id}/execute— Execute extension action
Deliverables:
- ✅
rg.morphee.ailive with dual registry (GHCR for public, Harbor planned for private) - ✅ Extension manifest schema defined and documented
- ✅
morphee-extCLI tool working (publish, install, list, update, remove) - ✅ Backend REST API for extension management
Acceptance Criteria:
- Run
morphee-ext install jira→ pulls fromrg.morphee.ai/public/jira:1.0.0→ installs successfully - Run
morphee-ext list→ shows installed extensions with version, permissions, enabled status - Call
POST /api/extensions/jira/execute→ runs JIRA extension action → returns result
Dependencies: Phase 2
Phase 4: Developer Experience (2 weeks, L effort)
Goal: Excellent SDK documentation, examples, templates, local dev tools.
Tasks:
-
Write comprehensive SDK docs
- Getting Started guide
- WIT interface reference (all host functions documented)
- Permission model explained
- Resource limits explained
- Code signing workflow
- Publishing to registry
-
Create example extensions (3 examples)
examples/echo— Minimal example (accepts string, returns string)examples/hello-world— Uses http::fetch to call public APIexamples/task-creator— Uses data::create_task to create Morphee tasks
-
Create project templates
cargo generate morphee-ai/extension-template— Rust template with boilerplate- Includes: Cargo.toml, wit bindings, example actions, tests, README
-
Build local dev tools
- Hot reload: Watch
.wasmfile, auto-reload on change - Verbose logging: Log all host function calls for debugging
- Mock mode: Provide mock host functions for unit testing extensions
- Hot reload: Watch
-
Create video tutorial (optional)
- 10-minute walkthrough: "Build Your First Morphee Extension"
Deliverables:
- ✅ SDK documentation (30+ pages)
- ✅ 3 example extensions (echo, hello-world, task-creator)
- ✅ Project template (
cargo generateready) - ✅ Local dev tools (hot reload, verbose logging, mock mode)
Acceptance Criteria:
- Developer reads "Getting Started", generates template, builds extension in <30 minutes
- Example extensions compile and run successfully
- Local dev mode: Edit extension code → see changes immediately without restart
Dependencies: Phase 3
Phase 5: Built-in Extensions (3 weeks, XL effort)
Goal: Ship 5 professional integrations as WASM extensions.
Extensions to Build:
-
JIRA (1 week, M effort)
- Actions:
create_issue,update_issue,get_issue,list_issues,search_issues,add_comment - Permissions:
http:read,http:write,vault:read - OAuth: JIRA OAuth 2.0 (store tokens in vault)
- Actions:
-
GitHub (1 week, M effort)
- Actions:
create_issue,create_pr,list_prs,add_comment,merge_pr,list_commits - Permissions:
http:read,http:write,vault:read - OAuth: GitHub OAuth 2.0
- Actions:
-
Notion (3 days, S effort)
- Actions:
create_page,update_page,get_page,search - Permissions:
http:read,http:write,vault:read - OAuth: Notion OAuth 2.0
- Actions:
-
Linear (3 days, S effort)
- Actions:
create_issue,update_issue,list_issues,add_comment - Permissions:
http:read,http:write,vault:read - OAuth: Linear OAuth 2.0
- Actions:
-
Slack (3 days, S effort)
- Migrate existing
slack.pyto WASM extension - Actions:
send_message,list_channels,read_messages - Permissions:
http:read,http:write,vault:read
- Migrate existing
Tasks:
- Build each extension in Rust using
morphee-sdk-rust - Implement OAuth flows (store tokens in vault)
- Test on Python backend + Rust frontend (verify portability)
- Sign extensions with developer key
- Publish to
rg.morphee.ai/public/{name}:1.0.0 - Write user-facing docs for each extension
Deliverables:
- ✅ 5 professional integrations shipped as WASM
- ✅ All published to
rg.morphee.ai/public/* - ✅ User docs for each extension (setup guide, available actions)
Acceptance Criteria:
- User runs
morphee-ext install jira→ JIRA extension installs - User connects JIRA account (OAuth) → credentials stored in vault
- AI creates JIRA issue via chat → JIRA extension called → issue created successfully
- Same extension works on Tauri desktop app (Rust runtime)
Dependencies: Phase 4
Phase 6: Marketplace UI (2 weeks, L effort)
Goal: Frontend catalog, install flow, auto-updates, usage analytics.
Tasks:
-
Create marketplace catalog page (
frontend/src/pages/Marketplace.tsx)- List available extensions from
rg.morphee.ai - Filter by category (productivity, communication, development)
- Search extensions
- Show extension details (name, description, author, permissions, reviews)
- List available extensions from
-
Build install flow
- Extension detail page with "Install" button
- Show permission approval dialog (like Google Play Store)
- User approves → extension downloads, verifies signature, installs
- Show installation progress
-
Add Extensions settings tab (
frontend/src/pages/settings/ExtensionsTab.tsx)- List installed extensions
- Enable/disable extensions
- View extension permissions
- Uninstall extensions
-
Implement auto-updates
- Background check for updates (daily)
- Show notification: "JIRA extension update available (1.0.0 → 1.1.0)"
- User approves → downloads, verifies, updates
-
Add usage analytics
- Track: installs, executions, errors, execution time
- Dashboard: Most popular extensions, trending extensions
- Privacy-respecting (aggregate only, no user tracking)
-
Build verified publisher program
- Green checkmark for vetted developers
- Require: identity verification, code review, track record
- Display on extension cards
Deliverables:
- ✅ Marketplace catalog page (browse, search, install)
- ✅ Install flow with permission approval
- ✅ Extensions settings tab (manage installed extensions)
- ✅ Auto-update system
- ✅ Usage analytics dashboard
Acceptance Criteria:
- User browses marketplace → sees 5+ extensions
- User clicks "Install JIRA" → sees permission dialog → approves → extension installs
- User goes to Settings → Extensions → sees installed extensions → can enable/disable
- System checks for updates daily → notifies user → user approves → extension updates
Dependencies: Phase 5
5. Questions & Answers
Q1: Timeline Priority — Build WASM first or hardcode JIRA first?
Answer: Build WASM system first (Option A, 12 weeks)
Reasoning: User explicitly said "I don't want to do the work twice." Building JIRA as hardcoded Python first, then migrating to WASM later = 14 weeks total (2 weeks hardcoded + 12 weeks WASM + migration). Better to invest 12 weeks once and get the right architecture.
Q2: Marketplace Hosting — Where to host the extension registry?
Answer: Dual registry at rg.morphee.ai
Architecture:
- Public extensions:
rg.morphee.ai/public/*→ proxy to GitHub Container Registry (free, unlimited) - Private extensions:
rg.morphee.ai/private/*→ self-hosted Harbor (Phase 6+, ~$20/month)
Phase 1-3: Use GHCR only (simple, free) Phase 6+: Add Harbor for private group extensions
Why dual registry?
- Align with Morphee's privacy-first values (self-hosted option for sensitive extensions)
- Community-friendly (free public registry for community extensions)
- Scalable (GitHub handles 99% of traffic, you only pay for private extensions)
- Migration path (start simple, add complexity when needed)
Q3: Mobile Strategy — How to handle mobile?
Answer: Use wasmer 5.0+ for ALL Tauri targets — single unified API with automatic backend selection per platform.
The critical constraint discovered (Feb 2026): iOS prohibits JIT compilation (App Store Review Guidelines §2.5.2). This means:
- ✅ Wasmtime JIT: works on macOS/Windows/Linux/Android
- ❌ Wasmtime JIT: blocked by Apple on iOS — apps using JIT are rejected from the App Store
- ✅ Wasmer with Wasmi interpreter: works on iOS (interpreter-only, Apple-compliant)
Implementation (wasmer 5.0, single crate):
// Platform-adaptive backend — same API for all platforms
#[cfg(target_os = "ios")]
let store = Store::new(wasmer::Singlepass::default()); // Wasmi interpreter, iOS-safe
#[cfg(not(target_os = "ios"))]
let store = Store::new(wasmer::Cranelift::default()); // JIT on desktop/Android
Why wasmer over the original wasmtime + wasm3 plan?
- Unified API: One crate, one API — no separate wasm3 bindings, no platform-switching logic
- iOS coverage: wasmer's Wasmi backend is explicitly designed for iOS (wasmer 5.0 announcement)
- Better performance on iOS: wasmer Wasmi gives 50-60% native vs wasm3's 65% — a small trade-off for a vastly simpler codebase
- Same binary size: wasmer ~12MB (was planning wasmtime ~15MB + wasm3 ~200KB separately anyway)
wasm3 as future option: wasm3 (64KB, 65% native, sync-only) is tracked as a potential ultra-minimal mobile alternative for extremely constrained scenarios (low-end Android, microcontrollers). It is not in the current plan because wasmer covers all current use cases with a better developer experience.
Q4: Extension Approval — When should user approve permissions?
Answer: Install-time with granular permissions (like Google Play Store)
Flow:
- User clicks "Install JIRA" in marketplace
- Frontend shows permission dialog:
JIRA Integration requests:
✅ Read web content (http:read)
✅ Send web requests (http:write)
✅ Read credentials (vault:read)
✅ Create tasks (data:write)
[Cancel] [Install] - User clicks "Install" → permissions granted → extension installs
- No runtime prompts — once approved, extension has permissions until uninstalled
Why install-time?
- User reviews ALL permissions upfront (informed consent)
- No disruptive runtime prompts (better UX)
- Aligns with industry standard (Google Play Store, iOS App Store, VS Code extensions)
- User can revoke by uninstalling (or future: permission management UI)
Q5: OpenMorph Integration — When to integrate .morph/extensions/*.wasm?
Answer: Extensions work standalone now, integrate with OpenMorph in Phase 3i
File Extension Association System (Phase 3i):
- GitStore maps file extensions to Integrations via
can_open(extension: str) -> boolinterface - Examples:
.wasm→WASMIntegration.can_open(".wasm")returnstrue.jpg,.png,.gif→ImageIntegration.can_open(".jpg")returnstrue.py→PythonIntegration.can_open(".py")returnstrue.txt,.md→TextIntegration.can_open(".txt")returnstrue
- Multi-Integration Support: One file can be opened by multiple Integrations
- Example:
.pycan be opened byTextIntegration(edit as text) ORPythonIntegration(execute)
- Example:
- Multi-File Support: One Integration can open multiple file types
- Example:
ImageIntegrationopens.jpg,.png,.gif,.webp,.svg
- Example:
- Discovery: When Space loads, GitStore scans
.morph/extensions/, asks each Integration "can you open this file?", registers matches with InterfaceManager
Why defer to Phase 3i?
- WASM extension system is useful standalone (marketplace install)
- Phase 3i is already planned (Git-Native Spaces, OpenMorph spec)
- File extension association is a broader feature (benefits images, Python scripts, etc., not just WASM)
- Better to build WASM extensions first, then integrate with OpenMorph later (incremental delivery)
6. Open Items
Deferred to Future Phases
-
AssemblyScript SDK (Phase 7+)
- Provide TypeScript/AssemblyScript SDK for web developers
- Effort: M (2 weeks)
-
Extension Playground (Phase 8+)
- Web-based editor (like Rust Playground) for writing/testing extensions
- Effort: L (3 weeks)
-
Verified Publisher Program (Phase 6)
- Green checkmark for vetted developers
- Effort: S (3 days)
-
Extension Analytics Dashboard (Phase 6)
- Track installs, executions, errors
- Effort: M (1 week)
-
Hot Reload for Production (Phase 7+)
- Currently only in local dev mode
- Effort: S (3 days)
Unresolved Questions
-
Extension Pricing: Should we support paid extensions in marketplace?
- Defer to Phase 8+ (need payment infrastructure)
-
Extension Reviews: Should users be able to review/rate extensions?
- Yes, but defer to Phase 7+ (need moderation system)
-
Extension Sandboxing on Web: Can we run WASM extensions in browser?
- Yes (browser has native WASM support), but defer to Phase 7+ (need WASI polyfill)
-
Cross-Extension Communication: Can one extension call another?
- Not in Phase 1-6 (KISS), consider for Phase 7+ if demand
7. References
Research Documents (Created by Research Agent)
- Full Research (20k words) — Comprehensive comparison of runtimes, security, performance, real-world examples
- Quick Reference — TL;DR, code examples, decision matrix
- Decision Matrix — Comparison tables, metrics, recommendations
- Implementation Plan — 12-week roadmap with tasks and deliverables
- Index — Navigation hub for all docs
External Resources
WASM Runtimes:
- wasmtime-py GitHub — Python bindings for Wasmtime
- Wasmtime Rust Docs — Official Wasmtime documentation
- wasm3 GitHub — Fastest WASM interpreter (mobile-friendly)
- 2026 Runtime Benchmarks — Performance comparison
WASM Standards:
- WebAssembly Component Model — Rich types, async support
- WASI 0.3 Status (Feb 2026) — Async support announced
Real-World Examples:
- VS Code WASM Extensions — How VS Code uses WASM for extensions
- Figma Plugin System — Figma's WASM-based plugins
- Shopify WASM Functions — E-commerce extensions via WASM
- Cloudflare Workers — Edge compute with WASM
OCI Registry:
- Harbor Documentation — Self-hosted OCI registry
- GHCR Documentation — GitHub Container Registry
8. Success Metrics
Phase 1 (Foundation)
- ✅ Load and execute
echo.wasmsuccessfully - ✅ Permission check: extension without
http:writecannot POST - ✅ Resource limit: extension exceeding memory limit traps gracefully
- ✅ Mobile: wasm3 works on iOS/Android
Phase 2 (Security)
- ✅ Install-time permission approval UI working
- ✅ Unsigned extension is rejected
- ✅ All extension actions logged to audit table
- ✅ Resource limits enforced (memory, CPU, rate, timeout)
Phase 3 (Distribution)
- ✅
rg.morphee.airegistry live and accessible - ✅
morphee-ext install jira→ pulls and installs successfully - ✅ Extension manifest validated on install
Phase 4 (Developer Experience)
- ✅ Developer builds first extension in <30 minutes using docs
- ✅ 3 example extensions compile and run
- ✅ Hot reload works in local dev mode
Phase 5 (Built-in Extensions)
- ✅ 5 professional integrations published (JIRA, GitHub, Notion, Linear, Slack)
- ✅ Same extension runs on Python backend AND Rust frontend (verified)
- ✅ OAuth flows working (credentials stored in vault)
Phase 6 (Marketplace)
- ✅ User installs extension via UI (no CLI required)
- ✅ Auto-update notifies user of new versions
- ✅ Analytics dashboard shows install/usage stats
Overall Success
- ✅ Zero duplication: Same binary runs on Python + Rust (portability goal achieved)
- ✅ Community-ready: Third-party developer builds and publishes extension
- ✅ OpenMorph synergy: Extension stored in
.morph/extensions/*.wasmworks offline - ✅ Security: No security incidents (escapes, data leaks, crashes)
Appendix A: Technology Stack Summary
| Component | Technology | Version | Why Chosen |
|---|---|---|---|
| Python Runtime | wasmtime-py | 41+ | Best security (rigorously tested), 88% native, full WASI 0.3 async |
| Tauri Desktop Runtime | wasmer (Cranelift) | 5.0+ | Unified API with wasmer mobile; 80-88% native JIT |
| Tauri iOS Runtime | wasmer (Wasmi) | 5.0+ | iOS JIT restriction — Wasmi interpreter is Apple-compliant; 50-60% native |
| Tauri Android Runtime | wasmer (Cranelift) | 5.0+ | Same crate as desktop; 80-88% native JIT |
| Future Mobile | wasm3 | 0.5+ | 64KB ultra-minimal interpreter; tracked as optional future alternative |
| Frontend UI Runtime | JSRuntime (webview JS) | — | Canvas components and dynamic UI integrations run as JavaScript in Tauri webview |
| Future Python Runtime | PythonRuntime | — | Dynamic Python scripts — same BaseMorphRuntime contract as WASM |
| Interface Standard | Component Model | 2025+ | Rich types, async support (WASI 0.3), future-proof |
| Public Registry | GitHub Container Registry | — | Free, unlimited public extensions, zero maintenance |
| Private Registry | Harbor | 2.11+ | Self-hosted, CNCF graduated, privacy-first |
| Code Signing | RSA-PSS + SHA-256 | — | Industry standard, OpenSSL compatible |
| SDK Language | Rust | 1.75+ | Best WASM tooling, compile to tiny binaries |
| Alternative SDK | AssemblyScript | — | TypeScript-like, lower barrier for web devs (Phase 7+) |
Appendix B: Permission Model
10 Granular Permissions
| Permission | Description | Host Functions Gated | Example Use Case |
|---|---|---|---|
http:read | HTTP GET requests | http::fetch (GET only) | Fetch data from external API |
http:write | HTTP POST/PUT/DELETE | http::fetch (all methods) | Create JIRA issue, send Slack message |
vault:read | Read secrets from vault | vault::get_secret | Retrieve API tokens for external service |
vault:write | Write secrets to vault | vault::set_secret | Store OAuth tokens after authentication |
event:emit | Emit events to EventBus | events::emit | Notify app of external updates (e.g., new email) |
data:read | Read tasks/conversations/memory | data::get_task, data::list_tasks | Show JIRA issues in Morphee UI |
data:write | Create/update tasks/conversations | data::create_task, data::update_task | Create Morphee task from JIRA issue |
task:create | Shortcut for data:write (tasks only) | data::create_task | Simpler permission for task-only extensions |
space:read | Read Space metadata | space::get_current, space::list | Determine which Space user is in |
notify:send | Send notifications | notify::send | Notify user of critical events |
Install-Time Approval Flow
User clicks "Install JIRA Extension"
↓
Frontend fetches manifest from rg.morphee.ai
↓
Frontend shows permission dialog:
┌────────────────────────────────────────────┐
│ JIRA Integration │
│ │
│ This extension requests: │
│ ✅ Read web content (http:read) │
│ ✅ Send web requests (http:write) │
│ ✅ Read credentials (vault:read) │
│ ✅ Create tasks (data:write) │
│ │
│ The extension can: │
│ • Create JIRA issues from Morphee tasks │
│ • Sync JIRA issues to Morphee │
│ • Store your JIRA API token securely │
│ │
│ [Cancel] [Install] ← │
└────────────────────────────────────────────┘
↓
User clicks "Install"
↓
Backend downloads .wasm from registry
↓
Backend verifies signature (RSA-PSS + SHA-256)
↓
Backend stores extension + permissions in database
↓
Frontend shows "JIRA Extension installed successfully"
Appendix C: File Extension Association System (Phase 3i)
Goal: GitStore maps file types to Integrations for seamless opening of files in .morph/ directories.
Interface Addition to BaseInterface
class BaseInterface:
def can_open(self, extension: str) -> bool:
"""
Returns True if this Integration can open files with the given extension.
Examples:
WASMIntegration.can_open(".wasm") → True
ImageIntegration.can_open(".jpg") → True
ImageIntegration.can_open(".png") → True
TextIntegration.can_open(".txt") → True
TextIntegration.can_open(".md") → True
PythonIntegration.can_open(".py") → True
One file type can be opened by multiple Integrations (e.g., .py can be
opened by TextIntegration OR PythonIntegration).
One Integration can open multiple file types (e.g., ImageIntegration
opens .jpg, .png, .gif, .webp, .svg).
"""
return False # Default: cannot open any files
Example Implementations
class WASMIntegration(BaseInterface):
def can_open(self, extension: str) -> bool:
return extension == ".wasm"
async def execute(self, action_name: str, parameters: Dict) -> ActionResult:
if action_name == "load":
# Load WASM file from GitStore
wasm_path = parameters["path"]
# ... load and execute WASM module
# ...
class ImageIntegration(BaseInterface):
def can_open(self, extension: str) -> bool:
return extension in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"]
async def execute(self, action_name: str, parameters: Dict) -> ActionResult:
if action_name == "view":
# Display image from GitStore
image_path = parameters["path"]
# ... render image in FrontendIntegration
# ...
class PythonIntegration(BaseInterface):
def can_open(self, extension: str) -> bool:
return extension == ".py"
async def execute(self, action_name: str, parameters: Dict) -> ActionResult:
if action_name == "run":
# Execute Python script from GitStore
script_path = parameters["path"]
# ... run script in sandboxed environment
# ...
class TextIntegration(BaseInterface):
def can_open(self, extension: str) -> bool:
return extension in [".txt", ".md", ".log", ".json", ".yaml", ".toml", ".py"]
async def execute(self, action_name: str, parameters: Dict) -> ActionResult:
if action_name == "edit":
# Open file in text editor
file_path = parameters["path"]
# ... show editor UI via FrontendIntegration
# ...
GitStore Discovery Flow
# When Space loads
async def load_space(space_id: str):
git_store = GitStore(space_id)
files = git_store.list_files(".morph/extensions/")
for file_path in files:
extension = Path(file_path).suffix # e.g., ".wasm"
# Ask all Integrations: "Can you open this file type?"
for integration in interface_manager.list_integrations():
if integration.can_open(extension):
logger.info(f"{integration.name} can open {extension} files")
# Register file handler
file_handlers[extension].append(integration)
# User clicks on .morph/extensions/jira.wasm in UI
# → UI shows: "Open with: WASM Integration, Text Integration"
# → User chooses WASM Integration
# → Calls WASMIntegration.execute("load", {"path": ".morph/extensions/jira.wasm"})
Multi-Integration Example
File: .morph/extensions/jira.wasm
Can be opened by:
1. WASMIntegration → Load and execute extension
2. TextIntegration → View raw bytes (hex editor)
User right-clicks in UI:
┌─────────────────────────────┐
│ Open with... │
├─────────────────────────────┤
│ ▶ WASM Integration (default)│
│ Text Integration │
└─────────────────────────────┘
Appendix D: Effort Estimates Breakdown
| Phase | Task | Effort | Justification |
|---|---|---|---|
| 1. Foundation | Add dependencies | XS (1 hour) | Just update requirements.txt and Cargo.toml |
| Create backend module | M (3 days) | 6 files, ~800 lines of Python | |
| Create frontend module | M (3 days) | 4 files, ~600 lines of Rust | |
| Design WIT interface | S (1 day) | Draft interface, generate bindings | |
| Implement host functions | M (3 days) | 6 host functions, permission checks | |
| Create Rust SDK | S (2 days) | Bindings + helper macros + examples | |
| TOTAL | L (2 weeks) | ||
| 2. Security | Permission model | M (3 days) | 10 permissions, checks in all host functions |
| Resource limiters | M (3 days) | Memory, CPU, rate, timeout limiters | |
| Code signing | S (2 days) | RSA-PSS signature, verification | |
| Audit logging | S (2 days) | Log extension actions to database | |
| Security tests | S (2 days) | 5 security tests | |
| TOTAL | L (2 weeks) | ||
| 3. Distribution | Set up registry | S (2 days) | GHCR setup, DNS config |
| Design manifest | XS (1 day) | JSON schema | |
| CLI tool | M (2 days) | 5 commands (publish, install, list, update, remove) | |
| Backend API | S (2 days) | 4 endpoints | |
| TOTAL | M (1 week) | ||
| 4. Developer Experience | SDK docs | M (3 days) | 30+ pages of documentation |
| Example extensions | S (2 days) | 3 examples | |
| Project templates | XS (1 day) | Cargo template | |
| Local dev tools | S (2 days) | Hot reload, verbose logging, mock mode | |
| TOTAL | L (2 weeks) | ||
| 5. Built-in Extensions | JIRA extension | M (1 week) | 6 actions, OAuth, tests |
| GitHub extension | M (1 week) | 6 actions, OAuth, tests | |
| Notion extension | S (3 days) | 4 actions, OAuth, tests | |
| Linear extension | S (3 days) | 4 actions, OAuth, tests | |
| Slack extension (migrate) | S (3 days) | Migrate existing slack.py to WASM | |
| TOTAL | XL (3 weeks) | ||
| 6. Marketplace UI | Catalog page | M (3 days) | Browse, search, filter |
| Install flow | M (3 days) | Permission dialog, download, install | |
| Settings tab | S (2 days) | List installed, enable/disable, uninstall | |
| Auto-updates | S (2 days) | Background check, notification, update | |
| Analytics | M (2 days) | Track usage, dashboard | |
| Verified publishers | S (1 day) | Badge system | |
| TOTAL | L (2 weeks) | ||
| GRAND TOTAL | XL (12 weeks) |
Appendix E: Risk Mitigation Strategies
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Extension developer learning curve too steep | Medium | High | Excellent SDK docs, video tutorials, project templates, responsive support |
| WASM performance insufficient | Low | High | Benchmarks show 88% native (acceptable), cache compiled modules, profile and optimize |
| Security vulnerability in wasmtime | Low | Critical | Use latest stable version, subscribe to security advisories, have rollback plan |
| Binary size too large for mobile | Medium | Medium | Use wasm3 (64KB) as default on mobile, wasmtime opt-in for power users |
| Extension marketplace spam/malware | Medium | High | Mandatory code signing, verified publisher program, user reviews, moderation |
| OCI registry downtime | Low | Medium | Dual registry (GHCR + Harbor), cache extensions locally, offline mode |
| Extension compatibility breaks | Medium | Medium | Semantic versioning, extension specifies required Morphee version, deprecation policy |
| Debugging extensions too difficult | Medium | Medium | Structured error messages, logging host function, local dev verbose mode, examples |
| Community adoption too slow | Medium | Medium | Build 5 high-quality built-in extensions first (demonstrate value), marketing, incentives |
END OF INVESTIGATION DOCUMENT