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.
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.
┌────────────────────────────────┐
│ 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.
| 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). |
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.
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.
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.
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-macosOne-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 | headUninstall:
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-serverNSXPCListener.delegateis a weak property. The agent'smain.mkeeps the listener and its delegate alive by virtue ofdispatch_main()running inside an@autoreleasepoolthat never drains. Restructuring that without re-rooting the strong references will cause peer connections to be silently cancelled by the listener (noshouldAcceptNewConnectionever 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]andarmInitialIdleTimercould land out of order on the state queue and leave a timer counting down mid-session, thenexit(0)on a live client. SMAppService.registeris intentionally not called from the App. The pkg installer owns the system mach name; callingregisterfrom 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 thismlpackage's model description).BGEM3Embedder defaultEmbedderWithError:hardcodes 1024, which Core ML rejects —AppDelegate.applicationDidFinishLaunchingconstructs the embedder explicitly to avoid that.
This project bundles the BGE-M3 multilingual embedding model from BAAI (MIT-licensed). Thanks to the BAAI team for releasing it openly.
MIT — see LICENSE.