sense-memory
Give your AI agent a memory. Encrypted key-value storage and journal entries on Nostr relays — the agent writes to itself using its own keypair. Nobody else can read them.
Quickstart
import asyncio, os
from nostrkey import Identity
from sense_memory import MemoryStore
identity = Identity.from_nsec(os.environ["NOSTR_NSEC"])
store = MemoryStore(identity, "wss://relay.nostrkeep.com")
async def main():
# Key-value: store and recall
await store.remember("user_timezone", "America/Vancouver")
mem = await store.recall("user_timezone")
print(mem.value) # "America/Vancouver"
# Journal: append-only log
await store.journal("User prefers concise responses")
entries = await store.recent(limit=5)
asyncio.run(main())
How It Works
Two modes of persistence, both encrypted with NIP-44 using the agent's own keypair.
| Mode | Nostr Kind | Behavior | Use Case |
|---|---|---|---|
| Key-value | 30078 (NIP-78) | Replaceable by key | Preferences, state, facts |
| Journal | 4 (NIP-04 DM to self) | Append-only | Conversation logs, observations |
API
Key-Value Memories
# Store (overwrites if key exists)
await store.remember("user_name", "Vergel")
# Recall one
mem = await store.recall("user_name") # Memory | None
# Recall all
all_memories = await store.recall_all() # list[Memory]
# Delete
await store.forget("user_name") # NIP-09 deletion
Journal Entries
# Write (append-only)
await store.journal("Had a great conversation about scheduling")
# Read recent (newest first)
entries = await store.recent(limit=10) # list[JournalEntry]
Return Types
| Function | Returns | Description |
|---|---|---|
remember(key, value) | str | Event ID of stored memory |
recall(key) | Memory | None | Memory if found |
recall_all() | list[Memory] | All stored memories |
forget(key) | str | Event ID of deletion |
journal(content) | str | Event ID of journal entry |
recent(limit=20) | list[JournalEntry] | Recent entries, newest first |
Security
- All content is NIP-44 encrypted — only the agent's own keypair can decrypt
- Memory keys validated: no slashes, backslashes, null bytes, or path traversal
- Content length capped at 65,000 characters
- Relay queries capped at 1,000 events (memory exhaustion prevention)
- Decrypted key verified against queried key (tamper detection)
- No telemetry — no network calls except to the relay you configure
- Never hardcode an nsec — load from environment variable or encrypted file
Configuration
| Variable | Required | Description |
|---|---|---|
NOSTR_NSEC | Yes | Agent's nsec private key (bech32 or hex) |
NOSTR_RELAY | No | Relay URL (default: wss://relay.nostrkeep.com) |
| Constraint | Limit |
|---|---|
| Key length | 256 characters max |
| Value / content length | 65,000 characters max |
| Forbidden key chars | / \ null byte .. |
| Journal recent limit | 1 to 100 |
Nostr NIPs Used
| NIP | Purpose |
|---|---|
| NIP-01 | Basic event structure and relay protocol |
| NIP-04 | DM to self (journal entries) |
| NIP-09 | Event deletion (forget) |
| NIP-44 | Encryption for all stored content |
| NIP-78 | App-specific replaceable data (key-value memories) |
Part of the NSE Ecosystem
sense-memory uses NostrKey for identity and encryption. It sits alongside nostr-profile, NostrCalendar, NostrSocial, and social-alignment in the NSE sovereign identity stack.
The agent's keypair is the thread that runs through everything. One identity, many capabilities — and now, persistent memory.