Skip to content

Repository files navigation

Sovereign Edge

CI Native build

"Sovereign Edge" is a working codename tied to this repo's directory name — not a decided consumer-facing product name.

A privacy-first, fully offline AI companion for phones. You download a small language model (Gemma, Qwen, Phi, or similar) straight to your own device and talk to it with zero network requests from the chat itself — not as a promise, but because the chat and inference path contains no network code.

On top of that offline core sits an optional, explicitly-permissioned connector layer: a small set of agents that can reach outside the device — to search the web, or create a task in your own self-hosted software — but only once you have granted that specific connector that specific permission.

Fully standalone. No runtime dependency on sovereign, and it works with zero knowledge that sovereign exists.

This repo is a pnpm workspace: apps/mobile is the shipping product below; apps/desktop is a Tauri v2 app (see research 0010) with epics 12 (Core Port) and 13 (App Shell) done — real on-device inference, connectors, and a full navigation/settings UI, one release (v0.1.5) already published; packages/* are internal, unpublished code shared between them.

See CONCEPT.md for the full concept paper.

Current status

Early development — not a release. No store build exists yet — not because the build tooling is missing (task 0.3 is done: both platforms have declarative signing and a local/CI release pipeline), but because App Store Connect and Play Console listings (task 8.2) need a paid Apple Developer Program membership and a Google Play Console account, neither of which exists yet. That task is deliberately parked rather than blocking everything else — see its epic for what's actually missing.

The offline core and the Search connector (Tier 1 — reaches the network, with explicit per-connector permission) both work on real hardware. Phase 2's first Tier 3 connectors — Calendar and a small Device Utilities connector, both purely on-device, no network at all — are done, on mobile and desktop. Phase 3's connector SDK, plugin template, public registry, and in-app Connector Store are also done. The Sovereign Tasks connector is next. See ROADMAP.md for the exact sequence.

Area State
Offline chat (epic 1) ✅ Complete — streaming replies, model manager, writing-assist modes
Zero-network enforcement (1.5) ✅ Enforced in CI, not just intended — see network audit
Design system, app shell (7, 8.1) ✅ Theme tokens, core components, navigation, settings
Native build tooling (0.3) ✅ Declarative signing (both platforms), local + CI release scripts
Connector framework + Search connector (2, 3) ✅ Complete — manifest, permissions, routing, runtime, in-chat provenance, Tier 1 shipped
Tier 3 connectors — Calendar, Device (2.6, 10, 11) ✅ Complete — Calendar and Device (brightness, torch), mobile + desktop
Connector Store, SDK, registry (5) ✅ Complete — SDK, plugin template, public registry, in-app store
Entitlement model (6.1) ✅ Complete
Desktop app — Core Port, App Shell (12, 13) ✅ Complete — real inference, connectors, navigation, settings; v0.1.5 published
Sovereign Tasks connector (4) 📋 Planned — Phase 2, next up
Tier 2 sandboxed script runtime (5.6) 📋 Planned — Phase 3
Store release (8.2) 📋 Parked — needs a paid Apple Developer Program + Google Play Console account

Measured on an iPhone 15 Pro (Release build, Metal active): Qwen2.5 0.5B generates at 86–91 tok/s with a 233 ms cold time-to-first-token, and takes 8.7 s to load — the GPU upload that buys the generation speed.

ROADMAP.md is the canonical task status. Anything else claiming progress is a summary and may lag.

How it works

Two trust tiers, both visible to the user:

  1. Chat — fully local, fully offline, always. llama.cpp/GGUF via llama.rn. Nothing in this layer makes a network call.
  2. Connectors — an explicit, permissioned layer. Each connector requests its own permission, separately revocable, and the UI shows which connector (if any) acted for a given reply — whether it reached the network (Tier 1) or an on-device OS capability like the calendar (Tier 3).

Connectors are tiered by how much trust they require — declarative manifests (no code, open to any developer), sandboxed transform scripts, and first-party native modules. CONCEPT.md explains why that split exists and what mobile app stores actually allow.

Development

Everything below is about apps/mobile — the original shipping product. See apps/mobile/AGENTS.md for the full command list and environment quirks; see apps/desktop/AGENTS.md for the desktop equivalent — epics 12 (Core Port) and 13 (App Shell) are done.

Requirements

  • Node 24.x (see .node-version) and pnpm 11

  • iOS: macOS with Xcode 26+ and CocoaPods. Xcode 26 is a hard floor — Expo SDK 57 pulls a Swift package requiring Swift tools 6.2, and older Xcode fails at package resolution with an error naming neither.

  • Android: JDK 17 and the Android SDK (API 34+)

  • A UTF-8 locale. CocoaPods calls String#unicode_normalize on the install path, which throws Encoding::CompatibilityError when LANG is unset and Ruby falls back to ASCII-8BIT. The traceback points at verify_podfile_exists!, so it reads as a missing Podfile rather than a locale problem. If locale shows LC_CTYPE="C", add to your shell profile:

    export LANG=en_US.UTF-8   # or C.UTF-8

    CI runners set this already, so it only bites locally.

Setup

git clone https://github.com/sovereignfs/sovereign-edge.git
cd sovereign-edge
pnpm install   # installs the whole workspace
pnpm ios       # build and run on an iOS simulator
pnpm android   # build and run on an Android emulator

Both commands run expo prebuild first if needed, then compile natively. The first run takes a while; later runs are incremental.

Expo Go does not work here, by design. llama.rn is a native module, so a development build is the only supported workflow. pnpm start assumes a dev client is already installed.

Prefer pnpm ios over driving xcodebuild by hand. A bare xcodebuild -sdk iphonesimulator with no -destination leaves ONLY_ACTIVE_ARCH unable to resolve an active architecture, so it builds every standard arch including x86_64 — which fails on Apple Silicon, because Expo's prebuilt XCFrameworks ship no x86_64 simulator slice. If you do invoke it directly, pass a concrete destination:

cd apps/mobile
xcodebuild -workspace ios/SovereignEdge.xcworkspace -scheme SovereignEdge \
  -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 17' \
  -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO build

If an iOS build fails complaining about a missing workspace, check whether apps/mobile/ios/Pods exists. expo prebuild can exit 0 with its internal pod install having failed, and the .xcworkspace is created by pod install — so the first visible symptom appears one step later than the actual failure.

Scripts

Command Does
pnpm start Metro bundler for an installed dev build
pnpm ios Build and launch on an iOS simulator
pnpm android Build and launch on an Android emulator
pnpm prebuild Regenerate ios/ and android/ from scratch
pnpm test Jest
pnpm typecheck tsc --noEmit
pnpm lint ESLint
pnpm check:offline Walks imports from src/chat/ for a socket path
pnpm format Prettier (code only — Markdown is left alone)

Native projects are generated, not committed

apps/mobile/ios/ and apps/mobile/android/ are gitignored. apps/mobile/app.json plus Expo config plugins are the source of truth, and expo prebuild regenerates the native projects from them — so hand-edits inside ios/ or android/ are lost on the next prebuild. Native configuration belongs in app.json or a config plugin.

Rationale, along with why this project excludes expo-updates and EAS Build, is in research 0002.

Layout

sovereign-edge/
├── apps/
│   ├── mobile/          # the shipping product — see apps/mobile/AGENTS.md
│   │   ├── src/          # chat, models, connectors, design-system, settings
│   │   └── ...
│   └── desktop/          # Tauri v2, epics 12 + 13 + 15 done, v0.1.5 published
│                          # — see apps/desktop/AGENTS.md
├── packages/             # internal, unpublished, shared between the apps
│   ├── core/              # empty scaffold — connector manifest/permissions/
│   │   ...                # routing, eventually extracted from apps/mobile
│   ├── design-tokens/
│   ├── mobile-ui/         # empty scaffold, same as core/
│   ├── desktop-ui/
│   └── connector-sdk/     # the one package meant to be published (task 5.1)
├── docs/
│   ├── epics/            # task breakdown per work stream, tagged by Scope
│   └── research/          # decision records
└── ...

See apps/mobile/AGENTS.md for the full src/ breakdown — chat/, models/, connectors/, design-system/, settings/, shared/, one directory per epic.

models/ is a sibling of chat/ rather than a child, and that is deliberate: acquiring a model is a visible, user-initiated download, while using one never touches the network. Keeping them separate is what lets the rule below be enforced mechanically.

Chat code must not import anything that opens a socket. That is checked, not just documented — in apps/mobile, pnpm lint restricts imports and network globals inside src/chat/, and pnpm check:offline walks the import graph to catch a transitive route lint cannot see. See docs/network-audit.md for what each mechanism covers and, more importantly, what it does not.

CI

Two workflows, split so the slow native jobs don't gate every PR:

  • CI — lint, format, typecheck, and test on every PR. Nothing here downloads model weights; jest.setup.js makes fetch throw, so an accidental network call fails the run.
  • Native build — on main and manual dispatch. Builds Release for both platforms, installs, launches, and asserts the process is still alive ten seconds later. Release rather than Debug because a Debug build loads its JS from a Metro server and so proves nothing about launching standalone.

Documentation

License

AGPL-3.0-or-later, matching the wider sovereignfs ecosystem.

Releases

Packages

Contributors

Languages