The intentional memory layer for your AI agents.
Noema gives AI agents — and the humans working alongside them — a persistent, structured place to record what they know, decide, observe, and intend. Every memory is a plain markdown file. The index is a local SQLite database. Nothing lives in the cloud; nothing requires an API key.
| Term | Meaning |
|---|---|
| Trace | A single memory — one markdown file + its database row |
| Cortex | A named collection of Traces, stored in a directory you control |
A Trace has a type that describes its intent:
| Type | Meaning |
|---|---|
fact |
A discrete thing that is true |
decision |
A choice made and why |
preference |
A behavioral or stylistic lean |
context |
Situational background |
skill |
A learned capability or procedure |
intent |
Something that needs to happen |
observation |
Something witnessed but not yet verified |
note |
Anything else |
divergence |
A concurrent edit conflict, auto-created by federation sync |
The fastest path. One command taps Fail-Safe/homebrew-noema and
installs the cross-platform formula covering darwin/{amd64,arm64}
and linux/{amd64,arm64}:
brew install Fail-Safe/noema/noemaOn macOS a cask is also published for users who prefer the cask ecosystem:
brew install --cask Fail-Safe/noema/noemaThe formula path is the default on macOS — when a tap contains both a formula and a cask of the same name,
brew install(without--cask) resolves the formula first. Pass--caskto opt into the cask. Linux users get the formula automatically.
Prefer the two-step form? It works the same:
brew tap Fail-Safe/noema
brew install noema # formula (macOS + Linux)
brew install --cask noema # cask (macOS only)Grab the archive for your OS/arch from the
Releases page, verify it
against checksums.txt, and put noema somewhere on your $PATH:
# macOS (Apple Silicon) — adjust VERSION and Arch as needed.
# Pre-built binaries start at v0.3.0; earlier tags (v0.1.x, v0.2.x)
# exist in git history but were never published as downloadable
# releases.
VERSION=0.3.0
curl -LO https://github.com/Fail-Safe/Noema/releases/download/v${VERSION}/noema_${VERSION}_darwin_arm64.tar.gz
curl -LO https://github.com/Fail-Safe/Noema/releases/download/v${VERSION}/checksums.txt
shasum -a 256 -c checksums.txt --ignore-missing
tar -xzf noema_${VERSION}_darwin_arm64.tar.gz
sudo mv noema /usr/local/bin/
noema versionRelease archives are fully static (pure-Go SQLite, CGO_ENABLED=0) so
there's nothing to install alongside the binary. Supported targets:
darwin/amd64, darwin/arm64, linux/amd64, linux/arm64,
windows/amd64, windows/arm64.
If you already have Go 1.25+ installed:
go install github.com/Fail-Safe/Noema/cmd/noema@latestgit clone https://github.com/Fail-Safe/Noema.git
cd Noema
make build # dev build with debug info -> ./noema
make release # stripped build for this host -> dist/noema-<os>-<arch>
make release-linux # stripped build for linux/amd64 -> dist/noema-linux-amd64Dev builds keep the symbol table and DWARF info for debugging (~19 MB).
Release builds strip both and run with -trimpath for a ~13 MB static
binary with the git version embedded via -ldflags. make help lists
all targets.
Pre-1.0 notice. Noema is currently on the
v0.xline. Expect breaking changes between minor releases until v1.0. Cortex data on disk is forward-compatible via non-destructive migrations; anycortex.mdversion bump that requires a manual step ships with an explicitnoema migratecommand.
# Create a Cortex
noema init --name my-cortex
# Add a Trace interactively
noema add
# Add a Trace with flags
noema add --title "We chose Go" --type decision --tag go --body "Pure-Go SQLite, fast iteration."
# List Traces
noema list
# Search
noema search "sqlite"
# View a Trace
noema get 20260329-we-chose-gonoema init --name <name> [--path <dir>] Create a new Cortex
noema use <name> Set the default Cortex
noema cortex list List all known Cortexes
noema cortex remove <name> [--purge] [--force]
Unregister a Cortex (--purge also deletes its directory)
noema cortex backup <name> [-o <path>] [--force]
Write a gzipped tarball of a Cortex
noema cortex restore <tarball> [--name <n>] [--path <dir>] [--force]
Restore a Cortex from a backup tarball
noema add [flags] Add a Trace (interactive if flags omitted)
noema list [flags] List Traces
noema get <id> Show a Trace
noema edit <id> Edit a Trace in $EDITOR
noema remove <id> Move a Trace to trash (--force to hard-delete)
noema recover <id> Restore a Trace from trash
noema purge [--days N] Permanently delete all trashed Traces older than N days
noema search <query> [flags] Full-text search (FTS5)
noema archive <id> Archive a Trace
noema unarchive <id> Restore an archived Trace
noema sync [--recover] Re-index trace files; --recover rebuilds missing files from the event log
noema events [trace-id] [--since] [--limit]
Show the event log (audit trail) for a trace, or recent events across all traces
noema events backfill [--dry-run] [--yes]
Synthesize create events for active traces missing one (e.g. traces added via `noema sync`)
noema resolve <divergence-id> --accept <origin> | --custom <body>
Resolve a divergence (concurrent edit conflict)
noema verify [--backfill] Check trace content hashes for integrity; --backfill populates
hashes for old traces
noema drift Check federated traces for drift from their source hash
noema federation status Show federation config, MCP access posture, peer sync state, and vector clock
noema federation peers List configured federation peers
noema federation add-peer <name> <endpoint>
Add a federation peer to cortex.md
noema federation reset-peer <name>... Clear stored state for a peer (forces a fresh handshake; use after a peer
ran `noema migrate cortex-id --reset` and the syncer is now reporting an
identity mismatch)
noema federation set-mode <sync|publish|subscribe>
Set the cortex-level federation mode
noema federation pause-peer <name> Pause syncing with a peer (preserves cursor + identity)
noema federation resume-peer <name> Resume syncing with a paused peer
noema federation key fingerprint Print the SHA-256 fingerprint of the active MCP shared key (safe to
say aloud over an out-of-band channel to confirm a pairing)
noema serve [--transport stdio|http] [--host <addr>] [--tls-cert <file> --tls-key <file>]
Start the MCP server (http requires --host; endpoint is /mcp)
noema serve --print-config Print a ready-to-use .mcp.json snippet and exit
noema serve ... --print-systemd-unit Print a systemd service unit for the current serve flags
noema serve ... --print-launchd-plist Print a launchd LaunchAgent plist for the current serve flags
noema tui [--theme auto|dark|light] Open the interactive TUI
noema config get <key> Print a user-level setting (ui.theme, trash_days)
noema config set <key> <value> Update and persist a user-level setting
noema config list List every known config key with its current value
noema completion [bash|zsh|fish|install] Generate shell completions
noema version Print version, commit, and build date
TUI theme priority (highest wins):
--themeflag onnoema tuiNOEMA_THEMEenvironment variableui.themein~/.config/noema/config.yaml(noema config set ui.theme dark)auto— detected from the terminal's reported background color
Common flags:
--cortex <name> Target a specific Cortex (overrides NOEMA_CORTEX env and config default)
--type <type> Filter by Trace type
--author <name> Filter by author
--tag <tag> Filter by tag
--archived Show only archived Traces
--trashed Show only trashed Traces
--all Show active and archived Traces
Cortex selection priority (highest wins):
--cortexflagNOEMA_CORTEXenvironment variable- Default set via
noema use <name>
Noema can run as an MCP server, giving any MCP-compatible AI tool direct access to your Cortex.
Tools exposed:
| Tool | Purpose |
|---|---|
get_instructions |
Live reference guide for this Cortex (call first in any new session) |
list_traces |
List traces, filterable by type, author, tag, origin, archived, all |
get_trace |
Fetch a trace's full body, origin, and lineage |
create_trace |
Create a new trace (supports derived_from, origin) |
update_trace |
Update any subset of fields on an existing trace |
search_traces |
FTS5 full-text search |
archive_trace / unarchive_trace |
Archive a trace or restore it |
delete_trace / recover_trace |
Soft-delete (move to trash) or restore from trash |
trace_history |
Event log (audit trail) for a trace |
trace_lineage |
Derivation graph: derived_from + derived_by |
resolve_divergence |
Resolve a concurrent edit conflict by accepting an origin or supplying a merged body |
sync_events |
Pull events for federation sync (called by remote peers) |
federation_status |
Federation config, peer sync state, vector clock, unresolved divergences |
announce_peer |
Accept a peer announcement for mutual discovery |
delete_trace moves a trace to trash (soft-delete, recoverable). Use recover_trace to restore it.
Call get_instructions first in any new session — it returns a live reference guide covering Trace types, field definitions, filtering options, and tool usage, with the active Cortex's name and purpose already filled in.
Generate a ready-to-use config snippet for the current machine and cortex:
noema serve --print-configThis prints a .mcp.json block with the correct binary path and cortex already filled in. Pipe it to a file to use it:
# Claude Code (project-level)
noema serve --print-config > .mcp.json
# Claude Desktop — merge the "noema" block into ~/Library/Application Support/Claude/claude_desktop_config.json
noema serve --print-configThe --cortex flag, NOEMA_CORTEX env, and config default are all respected, so --print-config always reflects the cortex you would actually use.
Noema speaks the Streamable HTTP transport from the MCP 2025-03-26 spec — a single endpoint at /mcp that handles JSON-RPC requests and optional SSE streaming on the same path. This is the transport native MCP clients (Zed, Claude Desktop's HTTP support, GitHub Copilot's MCP integration) speak today; the older two-endpoint legacy SSE transport has been removed.
# Local-only listener
noema serve --cortex my-cortex --transport http --host 127.0.0.1 --port 3000
# LAN-reachable listener (for federation peers)
noema serve --cortex my-cortex --transport http --host 10.0.0.5 --port 3000
# HTTPS
noema serve --cortex my-cortex --transport http --host 10.0.0.5 --port 3000 \
--tls-cert /path/server.crt --tls-key /path/server.key--host is required in HTTP mode and must be an explicit address — 0.0.0.0/:: are rejected to avoid accidentally exposing a Cortex on every interface. Pair --tls-cert with --tls-key to serve over HTTPS. The endpoint is /mcp (not configurable).
Add the running endpoint to Zed's settings.json:
{
"context_servers": {
"noema-my-cortex": {
"url": "https://10.0.0.5:3000/mcp"
}
}
}Any MCP client that supports Streamable HTTP works the same way — point its url field at <scheme>://<host>:<port>/mcp.
The HTTP endpoint can be gated behind a shared bearer key so only clients that know the secret can reach it. This is the recommended posture for any non-local deployment — federation peers, remote IDE clients, multi-host clusters. The HTTP endpoint runs in open mode by default, so existing deployments keep working until you opt in. Stdio is unaffected (stdio implies local-process trust).
Two ways to configure a key (in priority order):
NOEMA_MCP_KEYenvironment variable — the simplest form. Ideal forsystemdwith anEnvironmentFile=drop-in.access.shared_key_fileincortex.md— a path (absolute or relative to the cortex directory) pointing at a sidecar file that contains the key on its first non-empty line. The file must be mode0600; Noema refuses to load a file that's group- or world-readable. Useful when you want the key to travel with the cortex directory rather than with the service environment.
If both are set, the env var wins and the server logs a warning so operators notice the override.
TLS is required. Keyed mode refuses to start over plaintext HTTP — a bearer token sent without TLS is stolen by the first adversary on the network path. Pair --tls-cert with --tls-key, or run in open mode.
Sidecar-file example:
# cortex.md
name: my-cortex
purpose: Primary memory
owner: mark
created: 2026-03-29
version: 2
access:
shared_key_file: .access.secretopenssl rand -base64 32 > /path/to/my-cortex/.access.secret
chmod 600 /path/to/my-cortex/.access.secret
noema serve --cortex my-cortex --transport http --host 10.0.0.5 \
--tls-cert /path/server.crt --tls-key /path/server.keyOn startup the server logs the active posture:
[serve] access=keyed source=file fingerprint=SHA256:8e:76:62:80:f0:85:9c:05:...
Verifying a pairing. The fingerprint is a non-secret SHA-256 of the key, safe to say aloud over an out-of-band channel. Every host in a federation ring should produce the same fingerprint:
noema federation key fingerprintIf two hosts report different fingerprints, they have different keys and will 401 each other on federation sync. If a host reports access=open while its peers are keyed, it will be fully isolated.
MCP clients talking to a keyed endpoint must send Authorization: Bearer <key>. The .mcp.json snippet emitted by noema serve --print-config already uses "Bearer ${NOEMA_MCP_KEY}" — clients that support env interpolation (Claude Code) resolve it at runtime; clients that don't will produce a searchable 401.
For ad-hoc use, backgrounding with nohup works fine:
nohup noema serve --cortex agentbrain --transport http --host 127.0.0.1 \
> ~/noema.log 2>&1 &
disownFor a real federation host you probably want a process supervisor — restart on crash, start at boot, logs aggregated. Noema can print a ready-to-install unit/plist that mirrors the serve command you've already validated:
Linux (systemd)
noema serve --cortex agentbrain --transport http --host 192.168.1.10 --print-systemd-unit | sudo tee /etc/systemd/system/noema-agentbrain.service
sudo systemctl daemon-reload
sudo systemctl enable --now noema-agentbrain
sudo journalctl -u noema-agentbrain -fmacOS (launchd)
noema serve --cortex agentbrain --transport http --host 127.0.0.1 --print-launchd-plist > ~/Library/LaunchAgents/com.fail-safe.noema.agentbrain.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.fail-safe.noema.agentbrain.plist
tail -f ~/Library/Logs/noema-agentbrain.logBoth flags require --transport http (stdio has no endpoint to supervise) and an explicit --cortex (the unit/plist pins exactly one cortex — NOEMA_CORTEX and the config default aren't carried into the service environment). All the usual HTTP flag invariants (--host not 0.0.0.0, TLS pair symmetry) are validated at preview time, so you catch misconfigurations before installing.
The emitted unit filename convention is noema-<cortex>.service / com.fail-safe.noema.<cortex>.plist, so running multiple cortexes on one host never collides.
Keyed mode under a supervisor. When NOEMA_MCP_KEY gates the endpoint, the unit/plist needs a way to reach the secret without embedding it in a world-readable file. The emitted templates don't inline keys — they leave you a seam:
- systemd — the unit already contains
EnvironmentFile=-%h/.config/noema/<cortex>.env(the leading-makes the file optional, so open-mode installs keep working). Create the env file withNOEMA_MCP_KEY=..., mode0600, owned by the user the unit runs as, andsystemctl restartpicks it up. - launchd — the plist includes an
EnvironmentVariablesdict with a commentedNOEMA_MCP_KEYplaceholder. Uncomment and fill it in, or prefer theaccess.shared_key_filesidecar path insidecortex.mdso the secret travels with the cortex directory instead of the plist.
Either way you still need --tls-cert/--tls-key on the serve command that generated the template — the preview flag validates the TLS-for-keyed-mode rule before emitting, so you catch the footgun at install time. See Shared-key authentication above for the full rollout story.
A Cortex can sync with peer Cortexes over Streamable HTTP: every mutation is recorded in an immutable event log, peers pull each other's events, and concurrent edits surface as divergence traces instead of silently overwriting. Federation is fully opt-in — a Cortex with no federation block in cortex.md runs exactly as before.
name: alpha
purpose: Primary research cortex
owner: mark
created: 2026-03-29
version: 1
federation:
interval: 30s
peers:
- name: beta
endpoint: http://192.168.1.10:3000
- name: gamma
endpoint: https://192.168.1.11:3000
ca: /etc/noema/beta-ca.pem # optional, for self-signed TLSOr add a peer from the CLI:
noema federation add-peer beta http://192.168.1.10:3000The federation.mode field controls how a Cortex participates in the ring:
| Mode | Syncer runs? | sync_events serves? |
HTTP write tools? |
|---|---|---|---|
sync (default) |
Yes | Yes | Yes |
publish |
No | Yes | Blocked |
subscribe |
Yes | Blocked | Yes |
Publish mode is for source-of-truth cortexes: a company knowledgebase, a curated dataset, a reference corpus. Content is managed locally via stdio; remote peers pull events via sync_events but cannot write back. Subscribe mode is the complement: pull everything, share nothing.
federation:
mode: publish
interval: 30s
peers:
- name: consumer-1
endpoint: https://consumer-1.example:3000
- name: consumer-2
endpoint: https://consumer-2.example:3000
mode: paused # temporarily skip this peerIndividual peers can be paused without affecting the rest of the ring:
noema federation pause-peer consumer-2 # skip until resumed
noema federation resume-peer consumer-2 # re-enable
noema federation set-mode subscribe # switch cortex modeChanges take effect on the next noema serve restart.
Every trace carries a content_hash (SHA-256 of the body, recomputed on every write). This enables integrity verification and federation sync optimization.
Publishers can mark traces as source-locked — immutable on the consumer side:
---
id: 20260329-api-rate-limits
title: API Rate Limits
type: fact
origin: company-kb
source_hash: sha256:a3f2b8c...
source_locked: true
---Source-locked traces refuse update, delete, and remove operations when the local cortex is not the origin. archive/unarchive remain allowed (non-destructive). Use --force on CLI commands to override in emergencies.
noema verify # check all trace hashes for integrity
noema verify --backfill # populate hashes for old traces
noema drift # check federated traces against source hashes
noema edit <id> --force # override source-lockFederation peers share a single bearer key — the same NOEMA_MCP_KEY / access.shared_key_file described in Shared-key authentication above. When the syncer polls a peer's sync_events tool, it automatically attaches Authorization: Bearer <key> from the local host's active key; nothing peer-specific lives in cortex.md. This means:
- Every host in a federation ring must produce the same fingerprint. Verify on each box with
noema federation key fingerprintand compare out-of-band. - If one host rotates its key without the others, the rotated host will 401 its peers on the next sync tick. Federation status on both sides reports the failure, and the syncer falls back to exponential backoff (
2m → 4m → 8m) until the mismatch is resolved. - A host running in open mode while its peers are keyed — or vice versa — is effectively isolated: keyed peers reject its unauthenticated requests, and it rejects theirs. Mixed-mode rings aren't supported; roll the whole ring in one window.
Because the bearer key is required for every MCP call on a keyed endpoint, the same key also gates any human or tooling that wants to call federation_status, sync_events, or any other MCP tool over HTTP — there is no federation-only carve-out.
When noema serve --transport http starts and cortex.md has peers, a background syncer polls each peer's sync_events MCP tool (over the /mcp Streamable HTTP endpoint) on the configured interval (default 30s). New events are replayed locally — files are written, the DB is updated, and the event is stored in the local log with its original ID and origin (no event amplification).
Every event carries a vector clock snapshot. Each Cortex tracks one counter per peer it has heard from; on every local mutation it bumps its own counter, and on every replayed remote event it merges the remote clock into its own.
When two peers update the same trace concurrently (their vector clocks neither dominate nor are dominated), neither edit overwrites the other. Instead, Noema creates a divergence trace with type: divergence, tags [divergence, needs-resolution], and derived_from: [<original-id>]. Its body lists every conflicting version under ### Version from <origin> headers, deterministically rendered (origins sorted by name) so every replica produces identical content.
Find unresolved divergences:
noema list --type divergence
noema federation status # also shows the countResolve a divergence by picking a side or supplying a custom merge:
noema resolve <divergence-id> --accept beta
noema resolve <divergence-id> --custom "merged body content"Either form updates the original trace, federates the resolution, and trashes the divergence trace. The MCP equivalent is the resolve_divergence tool.
Every create / update / archive / unarchive / trash / recover / purge is recorded as an event with a ULID, timestamp, origin, and JSON snapshot. Inspect from the CLI:
noema events 20260329-why-we-chose-go # full history for one trace
noema events --limit 50 # recent events across all traces
noema events --since 01JQXYZ... # cursor-based paginationIf a trace file is lost from disk but its event log survives, noema sync --recover rebuilds the file from the most recent create/update event snapshot.
Traces are plain markdown files with YAML frontmatter. The markdown file is the source of truth; the SQLite database is a derived index that enables fast filtering and full-text search.
---
id: 20260329-why-we-chose-go
title: Why we chose Go
type: decision
author: research-agent-1
tags: [go, architecture]
derived_from: [20260328-language-candidates]
origin: research-cortex
created: 2026-03-29T14:23:00Z
updated: 2026-03-29T14:23:00Z
---
Go gives us pure-Go SQLite (no CGo), best-in-class TUI tooling, and fast
iteration. We can revisit Rust if the MCP server demands higher concurrency.derived_from records which traces informed this one (used by trace_lineage to build a knowledge graph). origin is the name of the Cortex that created the trace — set automatically and used by federation to attribute remote traces. Both fields are optional; existing traces without them parse unchanged.
Cortex layout on disk:
my-cortex/
AGENT.md ← agent guide (generated by noema init, see below)
cortex.md ← manifest: name, purpose, owner, created
traces/ ← active Traces
archive/
traces/ ← archived Traces (hidden by default, fully reversible)
trash/
traces/ ← soft-deleted Traces (auto-purged after 30 days by default)
db/
noema.db ← SQLite index (metadata, tags, FTS5)
The author field is free-form — a human username, an agent name, or omitted. Multi-agent systems use it to track which peer wrote a given Trace.
Noema supports three access patterns, depending on what tooling an agent has available:
Connect via noema serve and use the MCP tools. Call get_instructions at the start of a session for a live reference guide — it includes the Cortex name, purpose, Trace type definitions, and a full tool reference. Changes are indexed immediately; no manual sync needed.
Use the CLI commands directly. noema sync re-indexes any files written directly to disk by other agents or humans.
Read and write markdown files directly. AGENT.md at the Cortex root (generated by noema init) explains the file format, directory layout, Trace types, and naming conventions for agents that arrive with only file access. After making changes, run noema sync when the binary next becomes available to reconcile the database.
Each agent identifies itself via the author field when creating Traces. Filter by author to read only a specific agent's prior work. Because Traces are plain markdown files, a human can inspect, edit, or audit the Cortex at any time without any special tooling.
MIT — see LICENSE.