Skip to content

Fail-Safe/Noema

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Noema.

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.


Concepts

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

Installation

With Homebrew (macOS + Linux)

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/noema

On macOS a cask is also published for users who prefer the cask ecosystem:

brew install --cask Fail-Safe/noema/noema

The 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 --cask to 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)

Download a pre-built binary

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 version

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

With the Go toolchain

If you already have Go 1.25+ installed:

go install github.com/Fail-Safe/Noema/cmd/noema@latest

Build from source

git 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-amd64

Dev 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.x line. Expect breaking changes between minor releases until v1.0. Cortex data on disk is forward-compatible via non-destructive migrations; any cortex.md version bump that requires a manual step ships with an explicit noema migrate command.


Quick Start

# 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-go

CLI Reference

noema 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):

  1. --theme flag on noema tui
  2. NOEMA_THEME environment variable
  3. ui.theme in ~/.config/noema/config.yaml (noema config set ui.theme dark)
  4. 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):

  1. --cortex flag
  2. NOEMA_CORTEX environment variable
  3. Default set via noema use <name>

MCP Server

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.

stdio (Claude Desktop, Claude Code, any MCP client)

Generate a ready-to-use config snippet for the current machine and cortex:

noema serve --print-config

This 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-config

The --cortex flag, NOEMA_CORTEX env, and config default are all respected, so --print-config always reflects the cortex you would actually use.

Streamable HTTP (remote clients, GitHub Copilot, federation peers)

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

Connecting from Zed

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.

Shared-key authentication

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):

  1. NOEMA_MCP_KEY environment variable — the simplest form. Ideal for systemd with an EnvironmentFile= drop-in.
  2. access.shared_key_file in cortex.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 mode 0600; 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.secret
openssl 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.key

On 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 fingerprint

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

Running as a persistent service

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 &
disown

For 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 -f

macOS (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.log

Both 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 with NOEMA_MCP_KEY=..., mode 0600, owned by the user the unit runs as, and systemctl restart picks it up.
  • launchd — the plist includes an EnvironmentVariables dict with a commented NOEMA_MCP_KEY placeholder. Uncomment and fill it in, or prefer the access.shared_key_file sidecar path inside cortex.md so 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.


Federation

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.

Configure peers in cortex.md

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 TLS

Or add a peer from the CLI:

noema federation add-peer beta http://192.168.1.10:3000

Federation modes

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

Individual 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 mode

Changes take effect on the next noema serve restart.

Content hashing and source-locking

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-lock

Authentication

Federation 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 fingerprint and 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.

How sync works

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.

Divergence traces

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 count

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

Audit trail and recovery

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 pagination

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


Data Model

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.


Agent Access

Noema supports three access patterns, depending on what tooling an agent has available:

MCP (preferred)

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.

noema binary

Use the CLI commands directly. noema sync re-indexes any files written directly to disk by other agents or humans.

Filesystem only (no binary, no MCP)

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.


License

MIT — see LICENSE.

About

The intentional memory layer for your AI agents.

Topics

Resources

License

Stars

Watchers

Forks

Contributors