Local-first agentic memory for Markdown vaults — with cryptographic proof on every write
Memory you can prove.
Obsidian-compatible files · FortSignal-governed mutates · HTTP + MCP for agents
Quick start · Features · Architecture · HTTP API · MCP · Remote access · Docs · Security · License
Most agent “memory” is either:
- a black-box vector DB you can’t open, or
- a raw folder write with no authorization proof
FortMemory is both human-readable and governed:
| Problem | FortMemory |
|---|---|
| Opaque embeddings-only stores | Real Markdown on disk (open in Obsidian) |
| Agents overwrite anything | Path policy + FortSignal allow/deny |
| No audit trail | Immutable signalId receipts |
| Cloud-by-default | Local-first (no domain required) |
Agent / MCP / CLI
│
▼
FortMemory (this repo) ──mutate──► FortSignal challenge / verify
│ │
│ allow + signalId │
▼ ▼
Markdown vault + FTS index Audit receipt (local + FortSignal)
| Product | Role | License / model |
|---|---|---|
| FortMemory | Local memory server · CLI · API · MCP · dashboard | Apache-2.0 (this repo) |
| FortSignal | Passports · policies · NL Composer · signalId enforcement |
Separate SaaS / enterprise |
| FortVault | Optional team cloud sync (R2) | Commercial (later) |
Open-core details: docs/OPEN-CORE.md
- Obsidian-compatible vaults — plain
.mdfiles; engine state in.fortmemory/ - Full-text search — SQLite FTS5 (Porter tokenizer)
- Optional hybrid embeddings — Ollama (
nomic-embed-text, etc.) - Vault watcher — reindex on external edits (e.g. Obsidian)
- Operator dashboard — Home · Search · Activity · Agents · Settings
- Local tokens —
fortmemory tokenmintsfm_…bearer auth for the HTTP API / dashboard
memory.write/memory.deletegated by challenge → Ed25519 sign → verify- Agent passports + passkey-approved delegations
- NL Policy Composer for path/action rules (deep-linked; not reimplemented here)
- Local receipt log + optional frontmatter
last_signal_id
- REST API on
127.0.0.1:7432 - MCP stdio tools:
memory_search,memory_read,memory_write,memory_delete - CLI for init, write, delete, serve, reindex, agent, mcp, tunnels
- Tailscale — mesh (
fortmemory tailscale) - Cloudflare Tunnel — hostname / Access / mTLS (
fortmemory cloudflare) - Default remains localhost only
| Requirement | Notes |
|---|---|
| Go 1.23+ | Build toolchain |
| Markdown folder | Existing Obsidian vault or empty path |
| FortSignal | API key + agent passport for writes/deletes |
| Optional: Ollama | Hybrid semantic search |
Reads/search need only a local FortMemory agent token. Mutates also need FortSignal.
git clone https://github.com/fortsignal/fortmemory-vault.git
cd fortmemory-vault
make test && make build
# → ./bin/fortmemory
# Optional: run as `fortmemory` from anywhere
mkdir -p ~/.local/bin
ln -sfn "$(pwd)/bin/fortmemory" ~/.local/bin/fortmemory
# ensure ~/.local/bin is on your PATH
# Or after main is on GitHub:
go install github.com/fortsignal/fortmemory-vault/cmd/fortmemory@latestfortmemoryFirst run: creates ~/Vaults/FortMemory and asks once:
Vault id [personal]:
Enter → personal. Or type your own id (work, team, …) then Enter. Starts right after.
Later: just fortmemory — no questions.
That vault id is yours alone — use the same id in FortSignal recipients: {vaultId}/Scratch/*.
Optional (custom folder):
fortmemory init ~/Vaults/MyStuff --id work
fortmemoryOpen http://127.0.0.1:7432/. Stop with Ctrl+C.
Dashboard search needs a local token (not your FortSignal key):
fortmemory token
# paste the fm_… value into Bearer → Save| Surface | URL |
|---|---|
| Dashboard | http://127.0.0.1:7432/ |
| Health | http://127.0.0.1:7432/v1/health |
fortmemory serve and fortmemory start do the same thing as bare fortmemory.
Works for any user — plug in your API key, agent id, key path, and vault id:
export FORTSIGNAL_API_KEY=fs_live_... # your FortSignal tenant key
# Dashboard: create agent → download key JSON
# Policy: memory.write · max 65536 · recipients {yourVaultId}/Scratch/*
# Delegate + passkey approve
fortmemory agent add <agentId> --key /path/to/agent-key.json
fortmemory doctor --key /path/to/agent-key.json --write-probeLocal dashboard token (not FortSignal): fortmemory token → paste fm_… in UI.
curl -sS http://127.0.0.1:7432/v1/health | jq .
curl -sS -H "Authorization: Bearer fm_…" \
"http://127.0.0.1:7432/v1/read?path=Scratch/hello.md" | jq .
curl -sS -H "Authorization: Bearer fm_…" \
-H "Content-Type: application/json" \
-d '{"q":"preferences","topK":5}' \
http://127.0.0.1:7432/v1/search | jq .fortmemory write \
--key ~/path/to/agent-key.json \
--path Scratch/hello.md \
--body $'# Hello from FortMemory\n\nGoverned write.\n'(Uses FORTMEMORY_CONFIG or --config if set.)
On allow, the note is written and annotated with last_signal_id (post-allow metadata; the FortSignal hash covers the agent payload).
./bin/fortmemory mcp \
--config ~/Vaults/Personal/.fortmemory/config.toml \
--agent research-01 \
--key ~/path/to/agent-key.json| Tool | Description |
|---|---|
memory_search |
Full-text (and hybrid) search |
memory_read |
Read a vault path |
memory_write |
FortSignal-gated write |
memory_delete |
FortSignal-gated delete |
Example client config: examples/mcp.cursor.json · Guide: docs/MCP.md
.fortmemory/config.toml (created by init):
vault_id = "personal"
vault_path = "/home/you/Vaults/Personal"
bind = "127.0.0.1"
port = 7432
[fortsignal]
api_base = "https://api.fortsignal.com"
api_key_env = "FORTSIGNAL_API_KEY"
[embeddings]
provider = "none" # or "ollama"
ollama_url = "http://127.0.0.1:11434"
model = "nomic-embed-text"
[policy]
deny_read = ["Private/**"]
deny_write = [".fortmemory/**", ".fortmemory/*"]
[security]
fail_closed_on_fortsignal = true
allow_ungated_reads = true┌──────────────┐ ┌─────────────────────┐
│ Obsidian │ │ FortSignal Dashboard│
│ (human UX) │ │ Composer · Passports│
└──────┬───────┘ └──────────▲──────────┘
│ files │ rules / verify
▼ │
┌──────────────────────────────────────────┐
│ FortMemory (local) │
│ HTTP · MCP · CLI · FTS · receipts │
│ path jail · watcher · dashboard │
└──────────────────▲───────────────────────┘
│ memory.* tools only
┌──────┴──────┐
│ Agents │
└─────────────┘
| Doc | Contents |
|---|---|
| docs/SYSTEM-ARCHITECTURE.md | Full FortSignal × FortMemory × Obsidian design |
| docs/FORTSIGNAL-INTEGRATION.md | Wire-level challenge/verify mapping |
| docs/PRODUCT-SURFACE.md | Memory-first UI (Composer is Settings, not home) |
| docs/ARCHITECTURE.md | Internal layers |
| Method | Path | Auth | Notes |
|---|---|---|---|
GET |
/v1/health |
— | Liveness, index stats |
GET |
/v1/read?path= |
Bearer | Read Markdown |
POST |
/v1/search |
Bearer | { "q", "topK?", "pathPrefix?" } |
POST |
/v1/write |
Bearer + FortSignal | Governed write |
POST |
/v1/delete |
Bearer + FortSignal | Governed delete |
GET |
/v1/receipts |
Bearer | Local audit log |
GET |
/v1/agents |
Bearer | Registered local agents |
GET |
/ |
— | Operator dashboard |
OpenAPI sketch: docs/openapi.yaml
Auth reminder
| Credential | Purpose |
|---|---|
FORTSIGNAL_API_KEY (fs_live_…) |
Tenant → FortSignal API (server process only) |
| Agent Ed25519 key file | Signs challenges (never uploaded to FortSignal private key) |
fm_… bearer |
Auth to FortMemory HTTP API |
| Situation | Command / approach |
|---|---|
| Same machine | http://127.0.0.1:7432 (default) |
| Tailscale tailnet | fortmemory tailscale print-serve |
| Hostname / Access / mTLS | fortmemory cloudflare … |
fortmemory tailscale
fortmemory tailscale print-serve
# → tailscale serve --bg http://127.0.0.1:7432
fortmemory cloudflare install
fortmemory cloudflare check
fortmemory cloudflare quick # temporary trycloud URLKeep bind = "127.0.0.1". Do not expose the API raw on the public internet.
Full guide: docs/REMOTE-ACCESS.md
fortmemory-vault/
├── cmd/fortmemory/ # CLI entrypoint
├── internal/
│ ├── server/ # HTTP API + embedded dashboard
│ ├── vault/ # Path jail + Markdown I/O
│ ├── index/ # SQLite FTS5 (+ optional vectors)
│ ├── fortsignal/ # Enforcement client
│ ├── memory/ # Write/delete orchestration
│ ├── mcpserver/ # MCP stdio tools
│ ├── agent/ # Local tokens + Ed25519 signer
│ ├── watcher/ # fsnotify reindex
│ └── tunnel/ # Cloudflare + Tailscale helpers
├── web/ # Dashboard source
├── docs/ # Architecture & product docs
├── examples/ # MCP client samples
├── LICENSE # Apache-2.0
├── NOTICE
└── SECURITY.md
| Document | Description |
|---|---|
| docs/INDEX.md | Full documentation index |
| docs/MVP.md | Scope & non-goals |
| docs/IMPLEMENTATION.md | Build status |
| docs/MCP.md | MCP setup |
| docs/SECURITY.md | Threat model |
| docs/CLI.md | CLI reference |
| CONTRIBUTING.md | Contribution guide |
- Default bind:
127.0.0.1only - Fail closed on mutates if FortSignal is unreachable
- Path jail — no escape via
../ absolute paths;.fortmemory/reserved - Private keys stay on your machine
- Report vulnerabilities: hr@fortsignal.com — subject
FortMemory security
See SECURITY.md and docs/SECURITY.md.
make test # go test ./...
make build # bin/fortmemory
make install # go install ./cmd/fortmemory
make sync-ui # copy web/index.html → embed pathMVP — local server usable for dogfood: init, serve, search, FortSignal-gated write/delete, MCP, dashboard, vault watcher.
Roadmap themes: semantic retrieval polish, Obsidian companion plugin, FortVault sync (commercial).
agent-memory local-first obsidian markdown rag mcp
fortsignal ed25519 webauthn go sqlite open-core
ai-agents privacy audit-log passkeys
(Repo → About → Topics)
Copyright © 2026 Jeffrey Walters / FortSignal.
Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.
SPDX-License-Identifier: Apache-2.0
Trademarks: FortMemory, FortVault, and FortSignal are trademarks of FortSignal / Jeffrey Walters.
The Apache License does not grant trademark rights.
| Resource | URL |
|---|---|
| Website | fortsignal.com |
| FortSignal docs | fortsignal.com/docs |
| Policy Composer | fortsignal.com/composer |
| This repository | github.com/fortsignal/fortmemory-vault |
Built for agent builders who want memory that is human-readable, local-first, and accountable.