Skip to content

apocryphx/XPC-Vector-Server

Repository files navigation

XPC Vector Server

A small macOS test project that wires up bidirectional NSXPCConnection between a client app and a BGE-M3 embedding server. The server exists in two flavors and the client can switch between them at runtime — useful for comparing the lifecycles, signing models, and resource ownership of an in-app NSXPCListener versus a launchd-managed system agent.

What it does

The server exposes three methods over an NSXPCConnection:

  • pingWithReply: — cheap connectivity check, no model touch.
  • performEmbeddingWithString:withReply: — returns a 1024-dim fp32 BGE-M3 embedding (max 512 tokens).
  • similarityBetweenText:andText:withReply: — cosine similarity computed server-side.

The connection is bidirectional: the server pushes status messages (serverDidUpdateStatus:) and a 1 Hz uptime heartbeat (serverDidUpdateConnectedTime:) back over the same connection.

Architecture

                          ┌────────────────────────────────┐
                          │  XPC Client.app                │
                          │  (NSXPCConnection)             │
                          └───────────────┬────────────────┘
                                          │
                  serverSwitch=ON         │         serverSwitch=OFF
                                          │
            ┌─────────────────────────────┴─────────────────────────────┐
            │                                                           │
            ▼                                                           ▼
  group.com.elarity.memory.xpc                       group.com.elarity.memory.xpc.app
            │                                                           │
            ▼                                                           ▼
  ┌──────────────────────────────┐                  ┌──────────────────────────────┐
  │  XPC Launch Agent (headless) │                  │  XPC Vector Server.app       │
  │  /usr/local/libexec/...      │                  │  (NSXPCListener instance)    │
  │  launchd-spawned on demand,  │                  │  long-lived AppDelegate      │
  │  idles out after 60s         │                  │  owns the listener           │
  └──────────────────────────────┘                  └──────────────────────────────┘

Two mach service names exist because a name can be owned by only one process at a time. Both names are subdomains of the group.com.elarity.memory App Group, so a single entitlement permits the client to look up either and either server to publish its own.

Targets

Target Role
XPC Client The GUI client. Switch toggles between system / in-app server.
XPC Vector Server App that hosts the in-app server (Cocoa NSXPCListener inside AppDelegate).
XPC Launch Agent Headless binary launched by launchd from /Library/LaunchAgents/com.elarity.XPC-Vector-Server.plist. Self-installing via CLI (--install, --uninstall, --status).

Build

You need Xcode 16+ and the BGE-M3 Core ML package. The model is not tracked (1.1 GB) — fetch it separately and drop it at:

XPC Vector Server/BGEM3Embedder/BGEM3Encoder.mlpackage

Then build the XPC Vector Server and XPC Client schemes (Cmd+R) — or XPC Launch Agent alone if you only want the headless binary.

Two ways to run the server

1. In-app server

Just run the XPC Vector Server target. It creates an NSXPCListener on group.com.elarity.memory.xpc.app for as long as the app is in the foreground. With the client's switch off, requests go here.

2. System server (launchd-managed)

cd Packaging
./build-pkg.sh                          # produces xpc-vector-server-1.0.pkg
sudo installer -pkg xpc-vector-server-1.0.pkg -target /

The installer drops the agent and model at /usr/local/libexec/xpc-vector-server/ and writes /Library/LaunchAgents/com.elarity.XPC-Vector-Server.plist. The postinstall script bootstraps the agent into the live console user's launchd session. launchd then spawns the agent on demand when the first NSXPCConnection to group.com.elarity.memory.xpc arrives; the agent exits 60 s after the last connection drops to free its ~1 GB of resident memory.

3. Signed + notarized release builds

Packaging/release-pkg.sh produces a Developer-ID-signed, Apple-notarized, ticket-stapled .pkg that installs cleanly on any Mac without Gatekeeper warnings — and can optionally upload to GitHub Releases and/or Hugging Face in the same run.

# build only (requires Developer ID certs + notarytool keychain profile)
./Packaging/release-pkg.sh 1.0.0

# build + publish to GitHub Releases (tag v1.0.0)
./Packaging/release-pkg.sh 1.0.0 --gh-release

# build + upload to a Hugging Face model repo
./Packaging/release-pkg.sh 1.0.0 --hf=apocryphx/xpc-vector-server-macos

# both at once
./Packaging/release-pkg.sh 1.0.0 --gh-release --hf=apocryphx/xpc-vector-server-macos

One-time setup — Developer ID Application + Developer ID Installer certs in your login keychain, and a notarytool keychain profile — is documented in Packaging/SIGNING.md.

Verify:

launchctl print gui/$UID/com.elarity.XPC-Vector-Server | head

Uninstall:

sudo launchctl bootout gui/$UID/com.elarity.XPC-Vector-Server
sudo rm /Library/LaunchAgents/com.elarity.XPC-Vector-Server.plist
sudo rm -rf /usr/local/libexec/xpc-vector-server

Notes worth knowing

  • NSXPCListener.delegate is a weak property. The agent's main.m keeps the listener and its delegate alive by virtue of dispatch_main() running inside an @autoreleasepool that never drains. Restructuring that without re-rooting the strong references will cause peer connections to be silently cancelled by the listener (no shouldAcceptNewConnection ever fires).
  • The launch agent's idle timer guards against arming while a connection is live. Without that guard, a connection arriving in the brief window between [listener resume] and armInitialIdleTimer could land out of order on the state queue and leave a timer counting down mid-session, then exit(0) on a live client.
  • SMAppService.register is intentionally not called from the App. The pkg installer owns the system mach name; calling register from the App would conflict. If you ever want app-managed registration instead, switch the App's listener back to the system mach name and remove the pkg.
  • BGE-M3 is initialized with maxLength=512 (matching this mlpackage's model description). BGEM3Embedder defaultEmbedderWithError: hardcodes 1024, which Core ML rejects — AppDelegate.applicationDidFinishLaunching constructs the embedder explicitly to avoid that.

Acknowledgments

This project bundles the BGE-M3 multilingual embedding model from BAAI (MIT-licensed). Thanks to the BAAI team for releasing it openly.

License

MIT — see LICENSE.

About

Test project: XPC communication between a client app and a BGE-M3 embedding server (in-app + launchd-managed system server)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors