An always-on, fully-local, privacy-first ambient journaling app for macOS. It listens in the background (with consent) and, instead of storing raw audio, continually transcribes, summarizes, and keywords your spoken day into a searchable, ask-able journal. Nothing leaves the device unless you explicitly route a hard question through your own Claude or Codex.
Open source, not for sale. Apache-2.0, so anyone can read exactly what it does and build and install it themselves. It targets macOS 26+ on Apple Silicon and uses Apple's on-device models (no cloud, no API keys required for the core experience).
Status: implementation. The Go core daemon (
journald), thejournalCLI, the encrypted store, hybrid retrieval, the answer router, consolidation, and the capture-to-store pipeline are built and tested.journaldlaunches and supervises the Swift helper, segments its speech stream, distills each segment through a pluggable backend, and stores the result. Producing entries needs a distiller available on your machine: Apple Intelligence turned on (native), or a local Ollama model, or your own Claude/Codex. Live audio capture in the helper and its signed bundle are the remaining on-device piece.
- DESIGN.md: full vision, platform research, data model, and the privacy and efficiency analysis.
- PRIVACY.md: exactly what is captured, stored, never stored, and what can leave the device.
- THREAT-MODEL.md: assets, threats, mitigations, and residual risks.
- CLAUDE.md: architecture map, invariants, code standards, and testing ideology.
- CONTRIBUTING.md: how to build, the invariants a change must uphold, and the pull request workflow. See also the Code of Conduct and the security policy.
A Swift helper owns all audio and the native AI: it captures microphone and system audio, gates on speech, runs Apple SpeechAnalyzer/SpeechTranscriber for transcription, and Apple FoundationModels to turn each speech segment into a structured summary (highlights, decisions, action items, people, keywords) plus an NLContextualEmbedding. It emits only text and structured JSON to the Go core daemon over a local socket, so raw audio never leaves the helper. The daemon stores distilled records in an encrypted SQLite file, rolls them up into hour/day/week entries, and answers questions via hybrid keyword-plus-semantic retrieval. You query it with the journal CLI or by pointing your own agent at its MCP endpoint. Answers are on-device by default; a hard question can opt in to your own Claude or Codex, sending only the distilled context, never a transcript.
Pure-Go SQLite (ncruces/go-sqlite3) encrypted at rest with the Adiantum VFS (key generated on first run, kept 0600, Keychain-backed later). Embeddings are stored as float32 blobs and searched by cosine in Go; keyword search is a ranked scan; the two fuse with recency and salience. There is no raw-transcript column by design, and no telemetry.
Requires Go 1.26+ and, for the helper, Swift 6.3+ with the macOS 26 SDK (Command Line Tools are sufficient).
make tools # install pinned dev tools (golangci-lint), once
make check # gofmt, go vet, golangci-lint, tests, and the Swift self-test
make build # build journald, journal, and the Swift helper
./bin/journald # start the daemon (encrypted store under your config dir)
./bin/journal today # ask the daemon (over its unix socket)
./bin/journal ask "what did I decide about pricing?"
./bin/journal search pricing
./bin/journald -mcp # instead serve the MCP endpoint over stdio for your agentThe daemon stores data under your user config directory (~/Library/Application Support/ambient-journal by default); override with --data-dir. On an empty journal it honestly reports it has nothing recorded rather than inventing an answer.
This runs the always-listening flow: capture your microphone, distill each stretch of speech, store it, and answer questions. Data goes in a local ./.data directory. The repo ships a ready-to-run config.toml (microphone only, Claude as the distiller so it works with no extra install, local offline answers).
# 1. One-time build: the binaries and the SIGNED helper bundle (needed so macOS
# can prompt for microphone access). Also creates config.toml if missing.
make setup
# 2. Start the daemon (foreground). It launches the helper and begins capturing.
make run
# On first run, macOS prompts to allow Microphone access for "AmbientHelper".
# Click Allow. If no prompt appears, add it under
# System Settings > Privacy & Security > Microphone.
# 3. In ANOTHER terminal (leave the daemon running), talk near your Mac for a
# minute, then ask:
./bin/journal ask "what did I decide?"
./bin/journal today
./bin/journal search launch
./bin/journal ingest # capture state and how many segments are storedTo stop, press Ctrl-C in the daemon terminal; it shuts down cleanly.
Backfill or test without the mic: ingest a recorded WAV through the same pipeline.
./bin/journal ingest test/fixtures/speech.wav
./bin/journal ask "what did I decide about the launch?"Privacy note for this config: distiller = "claude" streams the raw transcript of what you say to your Claude account continuously. To go fully private and on-device, enable Apple Intelligence in System Settings, then set distiller = "native" and both cloud flags to false in config.toml (see config.example.toml). Ollama is the other local option.
Pass a TOML file with journald --config config.toml. Everything has a local-only default.
data_dir = "~/Library/Application Support/ambient-journal"
helper_path = "/path/to/ambient-helper" # enables live capture; empty = query only
[capture]
sources = ["mic", "system"] # opt out of either
excluded_apps = [] # sources never captured
# private_window: recurring daily spans when nothing is captured or stored
# [[capture.private_window]]
# start = "21:00"
# end = "09:00"
[provider]
distiller = "native" # native (Apple), ollama, claude, codex, generic
answerer = "local" # local, ollama, claude, codex
allow_cloud = false # required before any off-device call
acknowledge_cloud_distill = false # additionally required for a cloud distiller,
# which streams raw transcript continuously
ollama_url = "http://127.0.0.1:11434"
ollama_model = "gemma3"Distillation is on-device by default and auto-selects native when Apple Intelligence is available, falling back to a local Ollama model. See PRIVACY.md before enabling any cloud option.