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 (a prebuilt release or a source build) is the
recommended way to run ai-memory. 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 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); it uses the local event spool + OIDC-token fallback.posix—shruns the bundled.shscript. The Docker wrapper's default.
Set
AI_MEMORY_HOOK_PLATFORMbefore wiring hooks to override the default.
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.
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.
Run as a Login Service (launchd)
Every scenario above leaves the server in the foreground: close that terminal
and capture stops. 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.
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).
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.