20 KiB
macOS Support
macOS is a supported platform: the workspace test suite runs on macOS CI and
tagged releases publish native ai-memory-macos-aarch64.tar.gz (Apple Silicon)
and ai-memory-macos-x86_64.tar.gz (Intel) binaries.
On macOS the native binary is the recommended way to run ai-memory. Three native installs exist:
- Scenario D — menu bar app — self-contained
.appthat bundles the binary, starts the LaunchAgent, and opens/web, status, and config. The GUI path if you are building from source. - Scenario A — prebuilt tarball —
no Rust toolchain; run
servein a terminal or install the LaunchAgent by hand. - Scenario B — source build — developing ai-memory itself.
It binds the server on 127.0.0.1:49374, and both the MCP endpoint and the
lifecycle hooks talk to that loopback address — which the native agent can
reach and which is already in the default Host-header allowlist. The Docker
wrapper (Scenario C) is also supported when you
prefer a containerised server.
Unlike Windows there is only one "path world" on macOS: POSIX paths and POSIX
.sh hooks throughout. There is no WSL-vs-native split to get wrong.
Rule Of Thumb
Run install-mcp / install-hooks from the same shell that launches Claude
Code, Codex, Cursor, Gemini CLI, or another agent — on macOS that is just your
normal Terminal.
-
The agent runs as a native macOS process, so its config must point at a host-reachable server URL. Native installs and Docker-wrapper
install-mcp/install-hookscommands renderhttp://127.0.0.1:49374, which works from the host agent. -
Hooks are rendered for one of two platforms:
posix-native— a directai-memory hook --event …call. The default for native macOS/Linux Claude Code installs (cargo / release binary) and the Docker wrapper's checksum-verified host client; it uses the local event spool + OIDC-token fallback and enforces capture policy v1.posix—shruns the bundled.shscript. This is an explicit compatibility fallback for the Docker wrapper.
Set
AI_MEMORY_HOOK_PLATFORMbefore wiring hooks to override the default. -
The menu bar app still needs
install-mcp/install-hooksfrom a shell; use the bundled binary inside the.appso hook discovery sees the siblinghooks/tree.
Scenario A: Prebuilt Release Binary (Recommended, No Toolchain)
Use this when you want a local server plus native hooks without a Rust toolchain or Docker. Each tagged release publishes a macOS tarball per architecture.
# 1. Download the archive for your chip and extract it to a stable location.
# aarch64 = Apple Silicon (M-series); x86_64 = Intel.
mkdir -p ~/Applications/ai-memory && cd ~/Applications/ai-memory
curl -fsSL -O https://github.com/akitaonrails/ai-memory/releases/latest/download/ai-memory-macos-aarch64.tar.gz
tar -xzf ai-memory-macos-aarch64.tar.gz
# `curl` downloads are not Gatekeeper-quarantined, so the binary runs as-is.
# If you downloaded via a browser instead, clear the quarantine flag once:
# xattr -d com.apple.quarantine ./ai-memory
# 2. Initialise the data dir (defaults to
# ~/Library/Application Support/ai-memory; override with AI_MEMORY_DATA_DIR).
./ai-memory init
# 3. Start the server (loopback only).
./ai-memory serve --transport http --bind 127.0.0.1:49374
The server from step 3 must stay running for every other command.
ai-memory initonly creates the data dir — it does not start a server.bootstrap,install-hooks,install-mcp, andstatusare all clients that talk to the running server over HTTP, so running them while nothing is serving fails withConnection refused (os error 61)/could not reach http://localhost:49374. Leaveserverunning in its own terminal (or set it up as a login service, below), then run the rest in a second terminal.
In a second terminal, wire the agent:
cd ~/Applications/ai-memory
# `install-hooks` auto-discovers the bundled hooks/ directory beside the binary.
./ai-memory install-hooks --agent claude-code --apply
./ai-memory install-mcp --client claude-code --apply
Optionally, once hooks are wired, put the binary on PATH so later commands
(ai-memory status, a fresh terminal tab, the checklist below) don't need
cd/./:
sudo ln -sf ~/Applications/ai-memory/ai-memory /usr/local/bin/ai-memory
As of v1.39.0, running install-hooks through the symlink works: hook
discovery canonicalises the running binary's path before walking up to
the sibling hooks/ directory
(#546, fixed in
v1.39.0). On v1.38.x or older, the walk did not resolve through a
symlink — running install-hooks via /usr/local/bin/ai-memory sent
discovery to the wrong parent directories, failing outright on a clean
machine:
Error: could not locate hooks directory. Tried: ["/…/hooks/claude-code",
"/usr/local/share/ai-memory/hooks/claude-code", "/usr/share/ai-memory/hooks/claude-code",
"…/Library/Application Support/ai-memory/hooks/claude-code"]
— or, worse, silently wiring a stale hooks cache from
~/Library/Application Support/ai-memory on a machine with an earlier
install. If you are on an older release, run install-hooks via the
extracted ./ai-memory path (or upgrade).
Notes:
- The MCP endpoint, capture hooks, and
ai-memory statuswork without a token in this single-user loopback setup. If you explicitly configureAI_MEMORY_AUTH_TOKENfor the server, pass the same token with--auth-tokenor export it for CLI commands. - Keep the extracted
ai-memoryat a stable path; the hook commands (and the symlink, if you made one) reference it. Re-runinstall-hooksand re-point the symlink if you move it. - Later updates: with the release binary on
PATH(or invoked as./ai-memory), runai-memory upgradeto download the latest matching macOS tarball, verify its.sha256, replace the binary (and siblinghooks/when present), and refresh staged agent hooks. Seedocs/install.md#keeping-ai-memory-up-to-date.
Scenario B: Source Build
Use this when developing ai-memory itself. Requires Rust 1.95
(rust-toolchain.toml) plus the Xcode Command Line Tools
(xcode-select --install); SQLite is bundled and libgit2 is vendored, so no
extra system libraries are needed.
git clone https://github.com/akitaonrails/ai-memory
cd ai-memory
cargo build --release --workspace
./target/release/ai-memory init
./target/release/ai-memory serve --transport http --bind 127.0.0.1:49374
From another shell in the repo, install-hooks finds the bundled hooks/
automatically (no --source needed from the repo root):
./target/release/ai-memory install-hooks --agent claude-code --apply
./target/release/ai-memory install-mcp --client claude-code --apply
If you symlink the built binary onto PATH for convenience (e.g.
ln -sf "$(pwd)/target/release/ai-memory" ~/.local/bin/ai-memory), do it
after the install-hooks call above, not before — see the install-hooks
symlink caution in Scenario A
(#546); it applies
here too and is the exact layout that bug was filed against.
Scenario C: Docker Wrapper
Use this when you want the server data in a Docker volume while the agent still
runs as a native macOS process. The wrapper renders host-side agent config with
http://127.0.0.1:49374, but its own thin-client commands reach the server from
inside a helper container via Docker Desktop's host.docker.internal alias.
This assumes the ai-memory thin-client wrapper is already on PATH; if
ai-memory --version doesn't resolve yet, install it first via the
README Docker quick-start (downloads a small shell
script to ~/.local/bin/ai-memory). On a stock macOS Terminal ~/.local/bin
is not on PATH by default — add
export PATH="$HOME/.local/bin:$PATH" to ~/.zshrc if which ai-memory
comes up empty after installing the wrapper.
# Start the server. The image default allowlist includes host.docker.internal so
# wrapper thin-client commands (status, search, …) are not rejected with 403.
docker run -d --name ai-memory --restart unless-stopped \
-p 127.0.0.1:49374:49374 -v ai-memory-data:/data \
akitaonrails/ai-memory:latest
# Wire the native host agent. The wrapper keeps these rendered URLs on loopback.
ai-memory install-mcp --client claude-code --apply
ai-memory install-hooks --agent claude-code --apply
The wrapper is a shell script, not the native binary, so the install-hooks
symlink caution above (#546) does not apply here.
The published Docker image includes both linux/amd64 and linux/arm64, so
Apple Silicon pulls the native arm64 image without --platform linux/amd64.
Scenario D: Menu bar app
Use this when you want a self-contained macOS install: one .app that contains
the ai-memory binary and hooks/, starts the existing LaunchAgent, and opens
the surfaces the tool already has (/web, ai-memory status, config.toml,
logs). It does not replace those tools with a second dashboard.
From a source checkout (Rust 1.95 + Xcode / Swift 6):
./companions/ai-memory-macos/build.sh
open "companions/ai-memory-macos/dist/AI Memory.app"
Drag AI Memory.app to /Applications so the LaunchAgent path stays stable
across rebuilds. The menu extra has no Dock icon.
- Install & Start Server — runs bundled
ai-memory initif~/Library/Application Support/ai-memory/config.tomlis missing, renderspackaging/launchd/com.github.akitaonrails.ai-memory.plist, and bootstraps the samecom.github.akitaonrails.ai-memorylabel as the hand-installed LaunchAgent below. Do not skip this click; the app does not start the server on first launch by itself. - The status item is green when
GET /admin/statussucceeds, yellow when the server is up but the LLM/embedding role is in error or the write queue is non-empty, red when the server is unreachable. - Open Web UI, Show Status…, Open Config, Open Data Directory, and Open Logs call the existing browser UI, the bundled CLI, and Finder.
Wire an agent with the bundled binary so install-hooks finds the sibling
hooks/ tree (#546):
BIN="/Applications/AI Memory.app/Contents/Resources/runtime/ai-memory"
"$BIN" install-mcp --client claude-code --apply
"$BIN" install-hooks --agent claude-code --apply
Memory, config, models, and logs stay outside the bundle
(~/Library/Application Support/ai-memory and ~/Library/Logs/ai-memory).
Replacing the .app is an update; it does not rewrite that tree. Details:
companions/ai-memory-macos/README.md.
Notarization and a Homebrew cask are not part of this companion yet.
Run as a Login Service (launchd)
Scenarios A–C leave the server in the foreground: close that terminal and
capture stops. Scenario D already installs this LaunchAgent from Install &
Start Server. The macOS counterpart of a systemd user unit is a
LaunchAgent — a plist in ~/Library/LaunchAgents/ that the per-user
launchd domain starts at login and restarts on failure. The repo ships one at
packaging/launchd/com.github.akitaonrails.ai-memory.plist, and the macOS
release tarballs include it.
launchd expands nothing. A plist has no home specifier and no
EnvironmentFile, so every path in it is a literal and the template carries
two placeholders you substitute at install time. Run this from the extracted
tarball (Scenario A) or the repo root (Scenario B):
# launchd creates the log files but not their parent directory, and a missing
# one is a silent redirect failure.
mkdir -p ~/Library/Logs/ai-memory
# Wherever you keep the binary: ~/Applications/ai-memory/ai-memory for a
# release tarball, ./target/release/ai-memory for a source build.
AI_MEMORY_BIN=~/Applications/ai-memory/ai-memory
sed -e "s|__AI_MEMORY_BIN__|$AI_MEMORY_BIN|" \
-e "s|__HOME__|$HOME|" \
packaging/launchd/com.github.akitaonrails.ai-memory.plist \
> ~/Library/LaunchAgents/com.github.akitaonrails.ai-memory.plist
launchctl bootstrap gui/$(id -u) \
~/Library/LaunchAgents/com.github.akitaonrails.ai-memory.plist
The agent runs ai-memory serve --transport http --enable-web and passes
neither --data-dir nor --config: on macOS the binary already defaults to
~/Library/Application Support/ai-memory with the config file inside it, so
naming them would only add two more paths to substitute. bind comes from that
config, defaulting to 127.0.0.1:49374. Re-render and reload the plist if you
move the binary.
Verify it came up:
launchctl print gui/$(id -u)/com.github.akitaonrails.ai-memory | grep state
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:49374/mcp # 405
tail -f ~/Library/Logs/ai-memory/stderr.log
Coming from systemd
systemd --user |
launchd (per-user domain) |
|---|---|
systemctl --user enable --now ai-memory |
launchctl bootstrap gui/$(id -u) <plist> |
systemctl --user disable --now ai-memory |
launchctl bootout gui/$(id -u)/<label> |
systemctl --user status ai-memory |
launchctl print gui/$(id -u)/<label> |
systemctl --user restart ai-memory |
launchctl kickstart -k gui/$(id -u)/<label> |
journalctl --user -u ai-memory -f |
tail -f ~/Library/Logs/ai-memory/stderr.log |
loginctl enable-linger $USER |
no equivalent — a LaunchAgent stops at logout |
EnvironmentFile= |
no equivalent — see the token note below |
<label> is com.github.akitaonrails.ai-memory. After editing the plist,
bootout then bootstrap again; kickstart -k only restarts the process and
does not re-read the definition.
If you configure a bearer token
AI_MEMORY_AUTH_TOKEN is read from the process environment only — it is not a
config.toml key, and launchd has no EnvironmentFile. A single-user loopback
setup needs no token at all. If you do set one, add it to your rendered plist
and tighten the file, because ~/Library/LaunchAgents is not private:
<key>EnvironmentVariables</key>
<dict>
<key>AI_MEMORY_AUTH_TOKEN</key>
<string>…</string>
</dict>
chmod 600 ~/Library/LaunchAgents/com.github.akitaonrails.ai-memory.plist
Removing the agent
launchctl bootout gui/$(id -u)/com.github.akitaonrails.ai-memory
rm ~/Library/LaunchAgents/com.github.akitaonrails.ai-memory.plist
Nothing rotates the two log files; they grow without bound. Add a
newsyslog.d entry or truncate them periodically if that matters to you.
Validated on macOS 26.6.2 (build 25G83, Apple Silicon) with ai-memory v1.38.0 installed per Scenario A, and separately with v1.21.0 to confirm the agent does not depend on a recently added flag. Confirmed:
launchctl bootstrap; the jobrunningwithlast exit code = (never exited)rather than crash-looping; the launchd child (PPID 1) owning127.0.0.1:49374, so the reply came from the agent rather than a foreground server left over on the same port;~/Library/Application Support/ai-memoryresolved and logged as the data dir with no--data-dirpassed;405fromGET /mcpon the bound port;KeepAlive— the served process wasSIGKILLed and a replacement was answering about a second later, withrunsincrementing; and a cleanlaunchctl bootout. Start-at-login was configured but not independently exercised, since that needs a logout.ThrottleIntervalis left at its 10s default, so a crash within 10s of startup is respawned after that delay rather than immediately. Corrections from anyone on a different macOS version are welcome.
Hook Platform on macOS
AI_MEMORY_HOOK_PLATFORM selects how hook commands are rendered. On macOS the
two relevant values are posix-native (direct binary call; the native default)
and posix (the bundled .sh scripts; the Docker-wrapper default). Set it
before running install-hooks so the choice is baked into the rendered
commands. The native hook spools events locally, does short session-start
cleanup, and starts a detached session-end hook-drain helper; the whole-minute
spool-timing overrides are shared with Windows and documented in
docs/windows.md.
Native posix-native ai-memory hook commands enforce the nearest-marker
[capture] ignore_paths policy before spool or network delivery. The Docker
wrapper's posix shell-script path does not. Re-run install-hooks --agent <agent> --apply after upgrading to refresh an existing native install; see
Capture exclusions.
Troubleshooting on macOS
403 forbidden hostfrom Docker-wrapper CLI commands: update the Docker image and wrapper script. Current images allowlisthost.docker.internalfor loopback-published Docker Desktop servers.- Agent config points at
host.docker.internal: re-runai-memory install-mcp --client <client> --applyandai-memory install-hooks --agent <agent> --applywith the current wrapper. Host-side agent config should usehttp://127.0.0.1:49374. - Hooks bundle not found from a release archive: ensure you extracted the
whole tarball, not just the binary. Current
install-hooksprobes the siblinghooks/directory automatically. - Platform-mismatch warning on Apple Silicon: update to a current Docker
tag. Tagged releases publish a multi-arch manifest with
linux/arm64. ai-memory: command not foundin a new terminal tab: Scenario A/B's./ai-memory/./target/release/ai-memoryis a relative path, so it only resolves from inside the install/build directory. Either keepcd-ing there first, or symlink the binary ontoPATHonce you're done wiring hooks (see the Scenario A/B notes above) so plainai-memoryworks everywhere.install-hookswires the wrong (or no)hooks/bundle even though the tarball was extracted whole: if the binary is reached through a symlink (e.g. you put it onPATHbefore runninginstall-hooks), macOS discovery does not resolve the symlink and searches the wrong parent directories — see #546. On a clean machine this fails outright withError: could not locate hooks directory. Tried: [...]; if a hooks cache from an earlier install already exists under~/Library/Application Support/ai-memory, it can silently reuse that stale copy instead and report success. Runinstall-hooksvia the real extracted/built path instead of the symlink until that's fixed.- No Dock icon after opening AI Memory.app: that is the menu extra. Look in the menu bar (brain / status symbol), not the Dock.
- Menu extra says "Runtime not bundled":
swift runfrom the package does not stageai-memory+hooks/. Use./companions/ai-memory-macos/build.sh. - Install & Start does not turn the item green: check
~/Library/Logs/ai-memory/stderr.logandlaunchctl print gui/$(id -u)/com.github.akitaonrails.ai-memory. The app never writes into its own bundle; a missingconfig.tomlis created under~/Library/Application Support/ai-memoryby bundledai-memory init.
Suggested Test Checklist
ai-memory serve --bind 127.0.0.1:49374starts and logsbind=127.0.0.1:49374(./ai-memory serve …, or./target/release/ai-memory serve …for Scenario B, if you haven't put it onPATHyet).curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:49374/mcpreturns405(reachable; GET not allowed), confirming the loopback server is up.install-hooks --agent claude-code --applywrites hook commands that referencehttp://127.0.0.1:49374and host-side paths.install-mcp --client claude-coderendershttp://127.0.0.1:49374/mcp.- Launch the agent, call
memory_status, send a prompt, then confirm capture (ai-memory statusshows non-zero observations, or query the SQLiteobservationstable). - Scenario D: after Install & Start Server, the menu extra is green;
Open Web UI loads
http://127.0.0.1:49374/web;launchctl print gui/$(id -u)/com.github.akitaonrails.ai-memoryshowsstate = running; replacing the.appleaves~/Library/Application Support/ai-memoryuntouched.
Report which scenario you used, your chip (Apple Silicon / Intel), the agent and version, and whether hooks executed or failed with a connect/resolve error.