{"id":48450550,"url":"https://github.com/fail-safe/noema","last_synced_at":"2026-04-24T21:02:50.467Z","repository":{"id":347921037,"uuid":"1195596131","full_name":"Fail-Safe/Noema","owner":"Fail-Safe","description":"The intentional memory layer for your AI agents.","archived":false,"fork":false,"pushed_at":"2026-04-20T18:47:54.000Z","size":513,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-20T20:03:30.478Z","etag":null,"topics":["agent-memory","agents","ai-tools","claude-code","copilot","federation","golang","hermes","local-first","markdown","mcp","mcp-server","memory","memory-management","model-context-protocol","obsidian","peer-to-peer","sqlite"],"latest_commit_sha":null,"homepage":"https://noemacortex.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Fail-Safe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T21:10:45.000Z","updated_at":"2026-04-20T18:47:44.000Z","dependencies_parsed_at":"2026-04-20T20:00:36.364Z","dependency_job_id":null,"html_url":"https://github.com/Fail-Safe/Noema","commit_stats":null,"previous_names":["fail-safe/noema"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/Fail-Safe/Noema","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fail-Safe%2FNoema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fail-Safe%2FNoema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fail-Safe%2FNoema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fail-Safe%2FNoema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Fail-Safe","download_url":"https://codeload.github.com/Fail-Safe/Noema/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fail-Safe%2FNoema/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32240614,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-memory","agents","ai-tools","claude-code","copilot","federation","golang","hermes","local-first","markdown","mcp","mcp-server","memory","memory-management","model-context-protocol","obsidian","peer-to-peer","sqlite"],"created_at":"2026-04-06T20:05:14.452Z","updated_at":"2026-04-24T21:02:50.453Z","avatar_url":"https://github.com/Fail-Safe.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\".github/assets/brand/noema-dark.svg\"\u003e\n    \u003cimg alt=\"Noema.\" src=\".github/assets/brand/noema-light.svg\" width=\"600\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n**The intentional memory layer for your AI agents.**\n\nNoema 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.\n\n---\n\n## Concepts\n\n| Term | Meaning |\n|---|---|\n| **Trace** | A single memory — one markdown file + its database row |\n| **Cortex** | A named collection of Traces, stored in a directory you control |\n\nA Trace has a **type** that describes its intent:\n\n| Type | Meaning |\n|---|---|\n| `fact` | A discrete thing that is true |\n| `decision` | A choice made and why |\n| `preference` | A behavioral or stylistic lean |\n| `context` | Situational background |\n| `skill` | A learned capability or procedure |\n| `intent` | Something that needs to happen |\n| `observation` | Something witnessed but not yet verified |\n| `note` | Anything else |\n| `divergence` | A concurrent edit conflict, auto-created by federation sync |\n\n---\n\n## Installation\n\n### With Homebrew (macOS + Linux)\n\nThe fastest path. One command taps `Fail-Safe/homebrew-noema` and\ninstalls the cross-platform formula covering `darwin/{amd64,arm64}`\nand `linux/{amd64,arm64}`:\n\n```bash\nbrew install Fail-Safe/noema/noema\n```\n\nOn macOS a cask is also published for users who prefer the cask\necosystem:\n\n```bash\nbrew install --cask Fail-Safe/noema/noema\n```\n\n\u003e The formula path is the default on macOS — when a tap contains both a\n\u003e formula and a cask of the same name, `brew install` (without `--cask`)\n\u003e resolves the formula first. Pass `--cask` to opt into the cask. Linux\n\u003e users get the formula automatically.\n\nPrefer the two-step form? It works the same:\n\n```bash\nbrew tap Fail-Safe/noema\nbrew install noema          # formula (macOS + Linux)\nbrew install --cask noema   # cask (macOS only)\n```\n\n### Download a pre-built binary\n\nGrab the archive for your OS/arch from the\n[Releases page](https://github.com/Fail-Safe/Noema/releases), verify it\nagainst `checksums.txt`, and put `noema` somewhere on your `$PATH`:\n\n```bash\n# macOS (Apple Silicon) — adjust VERSION and Arch as needed.\n# Pre-built binaries start at v0.3.0; earlier tags (v0.1.x, v0.2.x)\n# exist in git history but were never published as downloadable\n# releases.\nVERSION=0.3.0\ncurl -LO https://github.com/Fail-Safe/Noema/releases/download/v${VERSION}/noema_${VERSION}_darwin_arm64.tar.gz\ncurl -LO https://github.com/Fail-Safe/Noema/releases/download/v${VERSION}/checksums.txt\nshasum -a 256 -c checksums.txt --ignore-missing\ntar -xzf noema_${VERSION}_darwin_arm64.tar.gz\nsudo mv noema /usr/local/bin/\nnoema version\n```\n\nRelease archives are fully static (pure-Go SQLite, `CGO_ENABLED=0`) so\nthere's nothing to install alongside the binary. Supported targets:\n`darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`,\n`windows/amd64`, `windows/arm64`.\n\n### With the Go toolchain\n\nIf you already have Go 1.25+ installed:\n\n```bash\ngo install github.com/Fail-Safe/Noema/cmd/noema@latest\n```\n\n### Build from source\n\n```bash\ngit clone https://github.com/Fail-Safe/Noema.git\ncd Noema\nmake build                # dev build with debug info  -\u003e ./noema\nmake release              # stripped build for this host -\u003e dist/noema-\u003cos\u003e-\u003carch\u003e\nmake release-linux        # stripped build for linux/amd64 -\u003e dist/noema-linux-amd64\n```\n\nDev builds keep the symbol table and DWARF info for debugging (~19 MB).\nRelease builds strip both and run with `-trimpath` for a ~13 MB static\nbinary with the git version embedded via `-ldflags`. `make help` lists\nall targets.\n\n\u003e **Pre-1.0 notice.** Noema is currently on the `v0.x` line. Expect\n\u003e breaking changes between minor releases until v1.0. Cortex data on\n\u003e disk is forward-compatible via non-destructive migrations; any\n\u003e `cortex.md` version bump that requires a manual step ships with an\n\u003e explicit `noema migrate` command.\n\n---\n\n## Quick Start\n\n```bash\n# Create a Cortex\nnoema init --name my-cortex\n\n# Add a Trace interactively\nnoema add\n\n# Add a Trace with flags\nnoema add --title \"We chose Go\" --type decision --tag go --body \"Pure-Go SQLite, fast iteration.\"\n\n# List Traces\nnoema list\n\n# Search\nnoema search \"sqlite\"\n\n# View a Trace\nnoema get 20260329-we-chose-go\n```\n\n---\n\n## CLI Reference\n\n```\nnoema init --name \u003cname\u003e [--path \u003cdir\u003e]   Create a new Cortex\nnoema use \u003cname\u003e                          Set the default Cortex\nnoema cortex list                         List all known Cortexes\nnoema cortex remove \u003cname\u003e [--purge] [--force]\n                                          Unregister a Cortex (--purge also deletes its directory)\nnoema cortex backup \u003cname\u003e [-o \u003cpath\u003e] [--force]\n                                          Write a gzipped tarball of a Cortex\nnoema cortex restore \u003ctarball\u003e [--name \u003cn\u003e] [--path \u003cdir\u003e] [--force]\n                                          Restore a Cortex from a backup tarball\n\nnoema add [flags]                         Add a Trace (interactive if flags omitted)\nnoema list [flags]                        List Traces\nnoema get \u003cid\u003e                            Show a Trace\nnoema edit \u003cid\u003e                           Edit a Trace in $EDITOR\nnoema remove \u003cid\u003e                         Move a Trace to trash (--force to hard-delete)\nnoema recover \u003cid\u003e                        Restore a Trace from trash\nnoema purge [--days N]                    Permanently delete all trashed Traces older than N days\nnoema search \u003cquery\u003e [flags]              Full-text search (FTS5)\n\nnoema archive \u003cid\u003e                        Archive a Trace\nnoema unarchive \u003cid\u003e                      Restore an archived Trace\nnoema sync [--recover]                    Re-index trace files; --recover rebuilds missing files from the event log\nnoema events [trace-id] [--since] [--limit]\n                                          Show the event log (audit trail) for a trace, or recent events across all traces\nnoema events backfill [--dry-run] [--yes]\n                                          Synthesize create events for active traces missing one (e.g. traces added via `noema sync`)\nnoema resolve \u003cdivergence-id\u003e --accept \u003corigin\u003e | --custom \u003cbody\u003e\n                                          Resolve a divergence (concurrent edit conflict)\nnoema verify [--backfill]                 Check trace content hashes for integrity; --backfill populates\n                                          hashes for old traces\nnoema drift                               Check federated traces for drift from their source hash\n\nnoema federation status                   Show federation config, MCP access posture, peer sync state, and vector clock\nnoema federation peers                    List configured federation peers\nnoema federation add-peer \u003cname\u003e \u003cendpoint\u003e\n                                          Add a federation peer to cortex.md\nnoema federation reset-peer \u003cname\u003e...     Clear stored state for a peer (forces a fresh handshake; use after a peer\n                                          ran `noema migrate cortex-id --reset` and the syncer is now reporting an\n                                          identity mismatch)\nnoema federation set-mode \u003csync|publish|subscribe\u003e\n                                          Set the cortex-level federation mode\nnoema federation pause-peer \u003cname\u003e        Pause syncing with a peer (preserves cursor + identity)\nnoema federation resume-peer \u003cname\u003e       Resume syncing with a paused peer\nnoema federation key fingerprint          Print the SHA-256 fingerprint of the active MCP shared key (safe to\n                                          say aloud over an out-of-band channel to confirm a pairing)\n\nnoema serve [--transport stdio|http] [--host \u003caddr\u003e] [--tls-cert \u003cfile\u003e --tls-key \u003cfile\u003e]\n                                          Start the MCP server (http requires --host; endpoint is /mcp)\nnoema serve --print-config                Print a ready-to-use .mcp.json snippet and exit\nnoema serve ... --print-systemd-unit      Print a systemd service unit for the current serve flags\nnoema serve ... --print-launchd-plist     Print a launchd LaunchAgent plist for the current serve flags\nnoema tui [--theme auto|dark|light]       Open the interactive TUI\nnoema config get \u003ckey\u003e                    Print a user-level setting (ui.theme, trash_days)\nnoema config set \u003ckey\u003e \u003cvalue\u003e            Update and persist a user-level setting\nnoema config list                         List every known config key with its current value\nnoema completion [bash|zsh|fish|install]  Generate shell completions\nnoema version                             Print version, commit, and build date\n```\n\n**TUI theme priority** (highest wins):\n\n1. `--theme` flag on `noema tui`\n2. `NOEMA_THEME` environment variable\n3. `ui.theme` in `~/.config/noema/config.yaml` (`noema config set ui.theme dark`)\n4. `auto` — detected from the terminal's reported background color\n\n**Common flags:**\n\n```\n--cortex \u003cname\u003e       Target a specific Cortex (overrides NOEMA_CORTEX env and config default)\n--type \u003ctype\u003e         Filter by Trace type\n--author \u003cname\u003e       Filter by author\n--tag \u003ctag\u003e           Filter by tag\n--archived            Show only archived Traces\n--trashed             Show only trashed Traces\n--all                 Show active and archived Traces\n```\n\n**Cortex selection priority** (highest wins):\n\n1. `--cortex` flag\n2. `NOEMA_CORTEX` environment variable\n3. Default set via `noema use \u003cname\u003e`\n\n---\n\n## MCP Server\n\nNoema can run as an [MCP](https://modelcontextprotocol.io) server, giving any MCP-compatible AI tool direct access to your Cortex.\n\n**Tools exposed:**\n\n| Tool | Purpose |\n|---|---|\n| `get_instructions` | Live reference guide for this Cortex (call first in any new session) |\n| `list_traces` | List traces, filterable by `type`, `author`, `tag`, `origin`, `archived`, `all` |\n| `get_trace` | Fetch a trace's full body, origin, and lineage |\n| `create_trace` | Create a new trace (supports `derived_from`, `origin`) |\n| `update_trace` | Update any subset of fields on an existing trace |\n| `search_traces` | FTS5 full-text search |\n| `archive_trace` / `unarchive_trace` | Archive a trace or restore it |\n| `delete_trace` / `recover_trace` | Soft-delete (move to trash) or restore from trash |\n| `trace_history` | Event log (audit trail) for a trace |\n| `trace_lineage` | Derivation graph: `derived_from` + `derived_by` |\n| `resolve_divergence` | Resolve a concurrent edit conflict by accepting an origin or supplying a merged body |\n| `sync_events` | Pull events for federation sync (called by remote peers) |\n| `federation_status` | Federation config, peer sync state, vector clock, unresolved divergences |\n| `announce_peer` | Accept a peer announcement for mutual discovery |\n\n`delete_trace` moves a trace to trash (soft-delete, recoverable). Use `recover_trace` to restore it.\n\nCall `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.\n\n### stdio (Claude Desktop, Claude Code, any MCP client)\n\nGenerate a ready-to-use config snippet for the current machine and cortex:\n\n```bash\nnoema serve --print-config\n```\n\nThis prints a `.mcp.json` block with the correct binary path and cortex already filled in. Pipe it to a file to use it:\n\n```bash\n# Claude Code (project-level)\nnoema serve --print-config \u003e .mcp.json\n\n# Claude Desktop — merge the \"noema\" block into ~/Library/Application Support/Claude/claude_desktop_config.json\nnoema serve --print-config\n```\n\nThe `--cortex` flag, `NOEMA_CORTEX` env, and config default are all respected, so `--print-config` always reflects the cortex you would actually use.\n\n### Streamable HTTP (remote clients, GitHub Copilot, federation peers)\n\nNoema speaks the **Streamable HTTP** transport from the [MCP 2025-03-26 spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) — 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.\n\n```bash\n# Local-only listener\nnoema serve --cortex my-cortex --transport http --host 127.0.0.1 --port 3000\n\n# LAN-reachable listener (for federation peers)\nnoema serve --cortex my-cortex --transport http --host 10.0.0.5 --port 3000\n\n# HTTPS\nnoema serve --cortex my-cortex --transport http --host 10.0.0.5 --port 3000 \\\n            --tls-cert /path/server.crt --tls-key /path/server.key\n```\n\n`--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).\n\n#### Connecting from Zed\n\nAdd the running endpoint to Zed's `settings.json`:\n\n```json\n{\n  \"context_servers\": {\n    \"noema-my-cortex\": {\n      \"url\": \"https://10.0.0.5:3000/mcp\"\n    }\n  }\n}\n```\n\nAny MCP client that supports Streamable HTTP works the same way — point its `url` field at `\u003cscheme\u003e://\u003chost\u003e:\u003cport\u003e/mcp`.\n\n### Shared-key authentication\n\nThe 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).\n\n**Two ways to configure a key** (in priority order):\n\n1. **`NOEMA_MCP_KEY` environment variable** — the simplest form. Ideal for `systemd` with an `EnvironmentFile=` drop-in.\n2. **`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.\n\nIf both are set, the env var wins and the server logs a warning so operators notice the override.\n\n**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.\n\n**Sidecar-file example:**\n\n```yaml\n# cortex.md\nname: my-cortex\npurpose: Primary memory\nowner: mark\ncreated: 2026-03-29\nversion: 2\naccess:\n  shared_key_file: .access.secret\n```\n\n```bash\nopenssl rand -base64 32 \u003e /path/to/my-cortex/.access.secret\nchmod 600 /path/to/my-cortex/.access.secret\n\nnoema serve --cortex my-cortex --transport http --host 10.0.0.5 \\\n            --tls-cert /path/server.crt --tls-key /path/server.key\n```\n\nOn startup the server logs the active posture:\n\n```\n[serve] access=keyed source=file fingerprint=SHA256:8e:76:62:80:f0:85:9c:05:...\n```\n\n**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:\n\n```bash\nnoema federation key fingerprint\n```\n\nIf 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.\n\nMCP clients talking to a keyed endpoint must send `Authorization: Bearer \u003ckey\u003e`. 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.\n\n### Running as a persistent service\n\nFor ad-hoc use, backgrounding with `nohup` works fine:\n\n```bash\nnohup noema serve --cortex agentbrain --transport http --host 127.0.0.1 \\\n  \u003e ~/noema.log 2\u003e\u00261 \u0026\ndisown\n```\n\nFor 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:\n\n**Linux (systemd)**\n\n```bash\nnoema serve --cortex agentbrain --transport http --host 192.168.1.10 --print-systemd-unit | sudo tee /etc/systemd/system/noema-agentbrain.service\nsudo systemctl daemon-reload\nsudo systemctl enable --now noema-agentbrain\nsudo journalctl -u noema-agentbrain -f\n```\n\n**macOS (launchd)**\n\n```bash\nnoema serve --cortex agentbrain --transport http --host 127.0.0.1 --print-launchd-plist \u003e ~/Library/LaunchAgents/com.fail-safe.noema.agentbrain.plist\nlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.fail-safe.noema.agentbrain.plist\ntail -f ~/Library/Logs/noema-agentbrain.log\n```\n\nBoth 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.\n\nThe emitted unit filename convention is `noema-\u003ccortex\u003e.service` / `com.fail-safe.noema.\u003ccortex\u003e.plist`, so running multiple cortexes on one host never collides.\n\n**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:\n\n- **systemd** — the unit already contains `EnvironmentFile=-%h/.config/noema/\u003ccortex\u003e.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.\n- **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.\n\nEither 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](#shared-key-authentication) above for the full rollout story.\n\n---\n\n## Federation\n\nA 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.\n\n### Configure peers in `cortex.md`\n\n```yaml\nname: alpha\npurpose: Primary research cortex\nowner: mark\ncreated: 2026-03-29\nversion: 1\nfederation:\n  interval: 30s\n  peers:\n    - name: beta\n      endpoint: http://192.168.1.10:3000\n    - name: gamma\n      endpoint: https://192.168.1.11:3000\n      ca: /etc/noema/beta-ca.pem    # optional, for self-signed TLS\n```\n\nOr add a peer from the CLI:\n\n```bash\nnoema federation add-peer beta http://192.168.1.10:3000\n```\n\n### Federation modes\n\nThe `federation.mode` field controls how a Cortex participates in the ring:\n\n| Mode | Syncer runs? | `sync_events` serves? | HTTP write tools? |\n|------|-------------|----------------------|-------------------|\n| `sync` (default) | Yes | Yes | Yes |\n| `publish` | No | Yes | Blocked |\n| `subscribe` | Yes | Blocked | Yes |\n\n**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.\n\n```yaml\nfederation:\n  mode: publish\n  interval: 30s\n  peers:\n    - name: consumer-1\n      endpoint: https://consumer-1.example:3000\n    - name: consumer-2\n      endpoint: https://consumer-2.example:3000\n      mode: paused    # temporarily skip this peer\n```\n\nIndividual peers can be paused without affecting the rest of the ring:\n\n```bash\nnoema federation pause-peer consumer-2   # skip until resumed\nnoema federation resume-peer consumer-2  # re-enable\nnoema federation set-mode subscribe      # switch cortex mode\n```\n\nChanges take effect on the next `noema serve` restart.\n\n### Content hashing and source-locking\n\nEvery trace carries a `content_hash` (SHA-256 of the body, recomputed on every write). This enables integrity verification and federation sync optimization.\n\nPublishers can mark traces as **source-locked** — immutable on the consumer side:\n\n```yaml\n---\nid: 20260329-api-rate-limits\ntitle: API Rate Limits\ntype: fact\norigin: company-kb\nsource_hash: sha256:a3f2b8c...\nsource_locked: true\n---\n```\n\nSource-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.\n\n```bash\nnoema verify               # check all trace hashes for integrity\nnoema verify --backfill     # populate hashes for old traces\nnoema drift                 # check federated traces against source hashes\nnoema edit \u003cid\u003e --force     # override source-lock\n```\n\n### Authentication\n\nFederation peers share a single bearer key — the same `NOEMA_MCP_KEY` / `access.shared_key_file` described in [Shared-key authentication](#shared-key-authentication) above. When the syncer polls a peer's `sync_events` tool, it automatically attaches `Authorization: Bearer \u003ckey\u003e` from the local host's active key; nothing peer-specific lives in `cortex.md`. This means:\n\n- 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.\n- 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.\n- 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.\n\nBecause 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.\n\n### How sync works\n\nWhen `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).\n\nEvery 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.\n\n### Divergence traces\n\nWhen 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: [\u003coriginal-id\u003e]`. Its body lists every conflicting version under `### Version from \u003corigin\u003e` headers, deterministically rendered (origins sorted by name) so every replica produces identical content.\n\nFind unresolved divergences:\n\n```bash\nnoema list --type divergence\nnoema federation status     # also shows the count\n```\n\nResolve a divergence by picking a side or supplying a custom merge:\n\n```bash\nnoema resolve \u003cdivergence-id\u003e --accept beta\nnoema resolve \u003cdivergence-id\u003e --custom \"merged body content\"\n```\n\nEither form updates the original trace, federates the resolution, and trashes the divergence trace. The MCP equivalent is the `resolve_divergence` tool.\n\n### Audit trail and recovery\n\nEvery create / update / archive / unarchive / trash / recover / purge is recorded as an event with a ULID, timestamp, origin, and JSON snapshot. Inspect from the CLI:\n\n```bash\nnoema events 20260329-why-we-chose-go     # full history for one trace\nnoema events --limit 50                   # recent events across all traces\nnoema events --since 01JQXYZ...           # cursor-based pagination\n```\n\nIf 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.\n\n---\n\n## Data Model\n\nTraces 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.\n\n```markdown\n---\nid: 20260329-why-we-chose-go\ntitle: Why we chose Go\ntype: decision\nauthor: research-agent-1\ntags: [go, architecture]\nderived_from: [20260328-language-candidates]\norigin: research-cortex\ncreated: 2026-03-29T14:23:00Z\nupdated: 2026-03-29T14:23:00Z\n---\n\nGo gives us pure-Go SQLite (no CGo), best-in-class TUI tooling, and fast\niteration. We can revisit Rust if the MCP server demands higher concurrency.\n```\n\n`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.\n\n**Cortex layout on disk:**\n\n```\nmy-cortex/\n  AGENT.md            ← agent guide (generated by noema init, see below)\n  cortex.md           ← manifest: name, purpose, owner, created\n  traces/             ← active Traces\n  archive/\n    traces/           ← archived Traces (hidden by default, fully reversible)\n  trash/\n    traces/           ← soft-deleted Traces (auto-purged after 30 days by default)\n  db/\n    noema.db          ← SQLite index (metadata, tags, FTS5)\n```\n\nThe `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.\n\n---\n\n## Agent Access\n\nNoema supports three access patterns, depending on what tooling an agent has available:\n\n### MCP (preferred)\n\nConnect 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.\n\n### `noema` binary\n\nUse the CLI commands directly. `noema sync` re-indexes any files written directly to disk by other agents or humans.\n\n### Filesystem only (no binary, no MCP)\n\nRead 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.\n\n---\n\nEach 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.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffail-safe%2Fnoema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffail-safe%2Fnoema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffail-safe%2Fnoema/lists"}