Skip to content

Ambient Journal

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), the journal CLI, the encrypted store, hybrid retrieval, the answer router, consolidation, and the capture-to-store pipeline are built and tested. journald launches 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.

Read this first

  • 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.

The one-paragraph architecture

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.

Storage and privacy

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.

Build and run

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 agent

The 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.

Run it live (happy path)

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 stored

To 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.

Configuration

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.

About

Agentic everyday journal that listens to you ambiently in the background, makes a note of everything, and lets you recall info, summarise days, or ask back questions to your past, even using mcp

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages