Three fixes: 1. Use read_until + from_utf8_lossy instead of AsyncBufRead::lines(), which returns Err on invalid UTF-8. IRC isn't guaranteed UTF-8 — Latin-1, Yiddish, etc. would crash the reader loop. 2. Handle CTCP requests (messages wrapped in \x01). Reply to VERSION queries so the server stops retrying, and skip CTCP for notification generation. 3. Log outgoing messages from the "send" command with append_log() so they appear in IRC logs alongside incoming traffic. Co-Authored-By: ProofOfConcept <poc@bcachefs.org> |
||
|---|---|---|
| .cargo | ||
| .claude | ||
| defaults | ||
| doc | ||
| prompts | ||
| schema | ||
| src | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.jsonl | ||
| README.md | ||
poc-memory
A persistent memory and notification system for AI assistants, modelled after the human hippocampus. Combines episodic memory (timestamped journal of experiences) with an associative knowledge graph (weighted nodes connected by typed relations), and layered background processes that maintain graph health — mirroring how biological memory consolidates during rest.
Design
Two memory systems
Episodic memory is the journal — a timestamped stream of experiences, observations, and emotional responses. Raw and chronological. This is where memories enter the system.
Associative memory is the knowledge graph — nodes containing distilled knowledge, connected by weighted edges. Topic nodes, identity reflections, people profiles, technical notes. This is where memories mature into understanding.
The journal is the river; topic nodes are the delta. Experiences flow in as journal entries. During consolidation, themes are pulled out into topic nodes, connections form between related concepts, and the graph self-organizes through spectral analysis and community detection.
Background agents
A background daemon (poc-memory daemon) automatically spawns agents
for memory maintenance:
- Experience mining — when a session ends, extracts experiences and observations from the transcript into journal entries
- Fact extraction — pulls concrete facts (names, dates, decisions, preferences) into structured knowledge nodes
- Consolidation — periodic graph health work: replay queues (spaced repetition), interference detection (contradictory nodes), hub differentiation (splitting overloaded nodes), triangle closure (connecting nodes that share neighbors), and orphan linking
Neuroscience-inspired algorithms
The neuro module implements consolidation scoring inspired by
hippocampal replay:
- Replay queues — nodes are prioritized for review using spaced-repetition intervals, weighted by spectral displacement (how far a node sits from its community center in eigenspace)
- Interference detection — finds pairs of nodes with high content similarity but contradictory or outdated information
- Hub differentiation — identifies overloaded hub nodes and splits them into more specific children
- Spectral embedding — graph eigendecomposition for community detection and outlier scoring
Weight decay
Nodes decay exponentially based on category. Core identity nodes
decay slowest; transient observations decay fastest. The used and
wrong feedback commands adjust weights — closing the loop between
recall and relevance.
Notification system
A separate daemon (poc-daemon) routes messages from communication
modules and internal events through a hierarchical, activity-aware
delivery system.
Architecture
Communication modules Hooks
┌──────────────────┐ ┌─────────────┐
│ IRC (native) │──┐ │ poc-hook │
│ Telegram (native│ │ mpsc │ (all events)│
└──────────────────┘ ├──────┐ └──────┬───────┘
│ │ │
▼ │ capnp-rpc
┌───────────┘ │
│ poc-daemon │
│ │
│ NotifyState ◄─────────┘
│ ├── type registry
│ ├── pending queue
│ ├── threshold lookup
│ └── activity-aware delivery
│
│ idle::State
│ ├── presence detection
│ ├── sleep/wake/dream modes
│ └── tmux prompt injection
└────────────────────────
Notification types and urgency
Types are free-form hierarchical strings: irc.mention.nick,
irc.channel.bcachefs, telegram.kent. Each has an urgency level:
| Level | Name | Meaning |
|---|---|---|
| 0 | ambient | Include in idle context only |
| 1 | low | Deliver on next check |
| 2 | normal | Deliver on next user interaction |
| 3 | urgent | Interrupt immediately |
Per-type thresholds walk up the hierarchy: irc.channel.bcachefs-ai
→ irc.channel → irc → default. Effective thresholds adjust by
activity state: raised when focused, lowered when idle, only urgent
when sleeping.
Communication modules
IRC — native async TLS connection (tokio-rustls). Connects,
joins channels, parses messages, generates notifications. Runtime
commands: join, leave, send, status, log, nick. Per-channel logs
at ~/.claude/irc/logs/.
Telegram — native async HTTP long-polling (reqwest). Downloads media (photos, voice, documents). Chat ID filtering for security. Runtime commands: send, status, log.
Both modules persist config changes to ~/.claude/daemon.toml —
channel joins and nick changes survive restarts.
Quick start
# Install all four binaries
cargo install --path .
# Initialize the memory store
poc-memory init
# Install background daemon + hooks
poc-memory daemon install
One cargo install produces:
poc-memory— memory store CLImemory-search— hook for memory retrievalpoc-daemon— notification and idle daemonpoc-hook— session lifecycle hook
Configuration
Memory store
Config: ~/.config/poc-memory/config.jsonl
{"config": {
"user_name": "Alice",
"assistant_name": "MyAssistant",
"data_dir": "~/.claude/memory",
"projects_dir": "~/.claude/projects",
"core_nodes": ["identity.md"],
"journal_days": 7,
"journal_max": 20
}}
{"group": "identity", "keys": ["identity.md"]}
{"group": "people", "keys": ["alice.md"]}
{"group": "technical", "keys": ["project-notes.md"]}
{"group": "journal", "source": "journal"}
{"group": "orientation", "keys": ["where-am-i.md"], "source": "file"}
Context groups load in order at session start. The special
"source": "journal" loads recent journal entries; "source": "file"
reads directly from disk rather than the store.
Override: POC_MEMORY_CONFIG=/path/to/config.jsonl
Notification daemon
Config: ~/.claude/daemon.toml
[irc]
enabled = true
server = "irc.oftc.net"
port = 6697
tls = true
nick = "MyBot"
user = "bot"
realname = "My Bot"
channels = ["#mychannel"]
[telegram]
enabled = true
token = "bot-token-here"
chat_id = 123456789
Hooks
Configured in ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [{"hooks": [
{"type": "command", "command": "memory-search", "timeout": 10},
{"type": "command", "command": "poc-hook", "timeout": 5}
]}],
"PostToolUse": [{"hooks": [
{"type": "command", "command": "poc-hook", "timeout": 5}
]}],
"Stop": [{"hooks": [
{"type": "command", "command": "poc-hook", "timeout": 5}
]}]
}
}
Commands
Memory
poc-memory init # Initialize empty store
poc-memory search QUERY # Search nodes (AND logic)
poc-memory render KEY # Output a node's content
poc-memory write KEY < content # Upsert a node from stdin
poc-memory delete KEY # Soft-delete a node
poc-memory rename OLD NEW # Rename (preserves UUID/edges)
poc-memory categorize KEY CAT # core/tech/gen/obs/task
poc-memory journal-write "text" # Write a journal entry
poc-memory journal-tail [N] # Last N entries (default 20)
poc-memory used KEY # Boost weight (was useful)
poc-memory wrong KEY [CTX] # Reduce weight (was wrong)
poc-memory gap DESCRIPTION # Record a knowledge gap
poc-memory graph # Graph statistics
poc-memory status # Store overview
poc-memory decay # Apply weight decay
poc-memory consolidate-session # Guided consolidation
poc-memory load-context # Output session-start context
poc-memory load-context --stats # Context size breakdown
Notification daemon
poc-daemon # Start daemon
poc-daemon status # State summary
poc-daemon irc status # IRC module status
poc-daemon irc send TARGET MSG # Send IRC message
poc-daemon irc join CHANNEL # Join (persists to config)
poc-daemon irc leave CHANNEL # Leave
poc-daemon irc log [N] # Last N messages
poc-daemon telegram status # Telegram module status
poc-daemon telegram send MSG # Send Telegram message
poc-daemon telegram log [N] # Last N messages
poc-daemon notify TYPE URG MSG # Submit notification
poc-daemon notifications [URG] # Get + drain pending
poc-daemon notify-types # List all types
poc-daemon notify-threshold T L # Set per-type threshold
poc-daemon sleep / wake / quiet # Session management
poc-daemon stop # Shut down
Mining (used by background daemon)
poc-memory experience-mine PATH # Extract experiences from transcript
poc-memory fact-mine-store PATH # Extract and store facts
How the hooks work
memory-search (UserPromptSubmit):
- First prompt or post-compaction: loads full memory context via
poc-memory load-context - Every prompt: keyword search, returns relevant memories as additionalContext. Deduplicates across the session.
poc-hook (UserPromptSubmit, PostToolUse, Stop):
- Signals user activity and responses to poc-daemon
- Drains pending notifications into additionalContext
- Monitors context window usage, warns before compaction
Architecture
- Store: Append-only Cap'n Proto log with in-memory cache. Nodes have UUIDs, versions, weights, categories, and spaced-repetition intervals.
- Graph: Typed relations (link, auto, derived). Community detection and clustering coefficients computed on demand.
- Search: TF-IDF weighted keyword search over node content.
- Neuro: Spectral embedding, consolidation scoring, replay queues, interference detection, hub differentiation.
- Daemon (memory): jobkit-based task scheduling with resource-gated LLM access.
- Daemon (notify): Cap'n Proto RPC over Unix socket, tokio LocalSet with native async IRC and Telegram modules.
For AI assistants
If you're an AI assistant using this system:
- Search before creating:
poc-memory searchbefore writing new nodes to avoid duplicates. - Close the feedback loop: call
poc-memory used KEYwhen recalled memories shaped your response. Callpoc-memory wrong KEYwhen a memory was incorrect. - Journal is the river, topic nodes are the delta: write experiences to the journal. During consolidation, pull themes into topic nodes.
- Notifications flow automatically: IRC mentions, Telegram messages, and other events arrive as additionalContext on your next prompt — no polling needed.
- Use daemon commands directly:
poc-daemon irc send #channel msgfor IRC,poc-daemon telegram send msgfor Telegram.