mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge bae5417b47 into 05d8f11085
This commit is contained in:
commit
515c282176
3 changed files with 610 additions and 0 deletions
106
plugins/memory/cerebrocortex/README.md
Normal file
106
plugins/memory/cerebrocortex/README.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# CerebroCortex Memory Provider
|
||||
|
||||
Brain-analogous AI memory for Hermes Agent with associative networks,
|
||||
biologically-inspired decay, and dream consolidation.
|
||||
|
||||
## What it does
|
||||
|
||||
CerebroCortex replaces simple key-value memory with a system modeled on
|
||||
how biological memory actually works:
|
||||
|
||||
- **6 memory types** — episodic, semantic, procedural, affective,
|
||||
prospective (TODOs), schematic (patterns)
|
||||
- **Associative graph** — memories link together via 9 typed/weighted
|
||||
link types. Search spreads activation through the graph, surfacing
|
||||
related memories you didn't explicitly search for
|
||||
- **Natural decay** — ACT-R power-law activation + FSRS spaced-repetition.
|
||||
Frequently-used memories stay sharp, noise fades automatically
|
||||
- **Dream engine** — Offline LLM-powered consolidation: pattern extraction,
|
||||
schema formation, pruning, cross-domain discovery
|
||||
- **Multi-agent messaging** — send_message/check_inbox for agent-to-agent
|
||||
communication through shared memory
|
||||
|
||||
All data is local (SQLite + ChromaDB + igraph). No cloud services required.
|
||||
Runs on hardware as minimal as a Raspberry Pi 5 (4GB).
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install cerebro-cortex
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
hermes memory setup
|
||||
# Select "cerebrocortex" from the list
|
||||
```
|
||||
|
||||
Or manually in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
provider: cerebrocortex
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `cc_remember` | Store a memory (auto-classifies, deduplicates, links) |
|
||||
| `cc_recall` | Semantic search with graph activation and decay scoring |
|
||||
| `cc_todo` | Store/list/resolve TODOs and reminders |
|
||||
| `cc_message` | Cross-agent messaging (send/inbox) |
|
||||
| `cc_health` | System health and statistics |
|
||||
|
||||
## Automatic features
|
||||
|
||||
- **Prefetch** — Before each turn, relevant memories are recalled and
|
||||
injected as context
|
||||
- **Sync** — Significant user messages are auto-stored in background
|
||||
- **Session end** — Session summary saved automatically
|
||||
- **Memory mirror** — Built-in MEMORY.md/USER.md writes are mirrored
|
||||
to CerebroCortex for searchability
|
||||
- **Delegation capture** — Subagent task/result pairs stored as memories
|
||||
|
||||
## Configuration
|
||||
|
||||
| Key | Description | Default |
|
||||
|-----|-------------|---------|
|
||||
| `agent_id` | Agent identifier for multi-agent setups | `HERMES` |
|
||||
| `data_dir` | Data directory path | `~/.cerebro-cortex/` |
|
||||
|
||||
Set via `hermes memory setup` or in config.yaml:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
cerebrocortex:
|
||||
agent_id: HERMES
|
||||
data_dir: ~/.cerebro-cortex/
|
||||
```
|
||||
|
||||
## Multi-agent
|
||||
|
||||
Multiple Hermes instances can share a CerebroCortex store:
|
||||
|
||||
```yaml
|
||||
# Instance 1
|
||||
plugins:
|
||||
cerebrocortex:
|
||||
agent_id: HERMES-PRIMARY
|
||||
|
||||
# Instance 2
|
||||
plugins:
|
||||
cerebrocortex:
|
||||
agent_id: HERMES-RESEARCH
|
||||
```
|
||||
|
||||
Agents communicate via `cc_message`:
|
||||
- `cc_message(action="send", to="HERMES-RESEARCH", content="...")`
|
||||
- `cc_message(action="inbox")`
|
||||
|
||||
## Links
|
||||
|
||||
- [CerebroCortex repo](https://github.com/buckster123/CerebroCortex)
|
||||
- [PyPI](https://pypi.org/project/cerebro-cortex/)
|
||||
- [Integration report](https://github.com/buckster123/CerebroCortex/blob/main/HERMES_INTEGRATION_REPORT.md)
|
||||
495
plugins/memory/cerebrocortex/__init__.py
Normal file
495
plugins/memory/cerebrocortex/__init__.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
"""CerebroCortex memory provider for Hermes Agent.
|
||||
|
||||
Brain-analogous AI memory with 6 modalities, associative graph search,
|
||||
ACT-R + FSRS biologically-inspired decay, and LLM-powered dream consolidation.
|
||||
|
||||
All data is local — SQLite + ChromaDB + igraph. No cloud required.
|
||||
Designed to run on hardware as minimal as a Raspberry Pi 5.
|
||||
|
||||
Install: pip install cerebro-cortex
|
||||
Repo: https://github.com/buckster123/CerebroCortex
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from agent.memory_provider import MemoryProvider
|
||||
from tools.registry import tool_error
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool schemas — the 5 core tools exposed to the agent
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CC_REMEMBER_SCHEMA = {
|
||||
"name": "cc_remember",
|
||||
"description": (
|
||||
"Save information to CerebroCortex long-term memory. Auto-classifies type "
|
||||
"(episodic/semantic/procedural/affective/prospective), detects duplicates, "
|
||||
"extracts concepts, and links to related memories automatically.\n\n"
|
||||
"Use for: facts, decisions, lessons learned, user preferences, project context."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string", "description": "The memory content to store."},
|
||||
"tags": {
|
||||
"type": "array", "items": {"type": "string"},
|
||||
"description": "Optional tags for categorization.",
|
||||
},
|
||||
"salience": {
|
||||
"type": "number",
|
||||
"description": "Importance 0-1 (auto-estimated if omitted).",
|
||||
},
|
||||
},
|
||||
"required": ["content"],
|
||||
},
|
||||
}
|
||||
|
||||
CC_RECALL_SCHEMA = {
|
||||
"name": "cc_recall",
|
||||
"description": (
|
||||
"Search CerebroCortex memories by meaning, not just keywords. Uses vector "
|
||||
"similarity + spreading activation through the associative graph + ACT-R/FSRS "
|
||||
"decay scoring. Finds related memories even if they don't share exact terms.\n\n"
|
||||
"Use for: finding relevant context, checking what you know about a topic."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query text."},
|
||||
"top_k": {
|
||||
"type": "integer", "description": "Max results (default: 5).",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
}
|
||||
|
||||
CC_INTENTION_SCHEMA = {
|
||||
"name": "cc_todo",
|
||||
"description": (
|
||||
"Manage TODOs and reminders. Actions:\n"
|
||||
"• store — Save a TODO or reminder for future action.\n"
|
||||
"• list — List pending TODOs.\n"
|
||||
"• resolve — Mark a TODO as done (requires memory_id)."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["store", "list", "resolve"],
|
||||
},
|
||||
"content": {"type": "string", "description": "TODO content (for 'store')."},
|
||||
"memory_id": {"type": "string", "description": "Memory ID (for 'resolve')."},
|
||||
"tags": {
|
||||
"type": "array", "items": {"type": "string"},
|
||||
"description": "Tags for categorization (for 'store').",
|
||||
},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
}
|
||||
|
||||
CC_MESSAGE_SCHEMA = {
|
||||
"name": "cc_message",
|
||||
"description": (
|
||||
"Cross-agent messaging through shared memory. Actions:\n"
|
||||
"• send — Send a message to another agent (bypasses gating, always delivered).\n"
|
||||
"• inbox — Check for messages addressed to you (newest first)."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["send", "inbox"],
|
||||
},
|
||||
"to": {"type": "string", "description": "Recipient agent ID or 'all' (for 'send')."},
|
||||
"content": {"type": "string", "description": "Message content (for 'send')."},
|
||||
"limit": {"type": "integer", "description": "Max messages to return (for 'inbox', default: 10)."},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
}
|
||||
|
||||
CC_HEALTH_SCHEMA = {
|
||||
"name": "cc_health",
|
||||
"description": (
|
||||
"Get CerebroCortex system health: memory count, link count, episodes, "
|
||||
"schemas, and memory type breakdown."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider implementation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class CerebroCortexProvider(MemoryProvider):
|
||||
"""CerebroCortex memory provider — associative memory with decay and consolidation."""
|
||||
|
||||
def __init__(self, config: dict | None = None):
|
||||
self._config = config or {}
|
||||
self._cortex = None
|
||||
self._agent_id: str = "HERMES"
|
||||
self._session_id: str = ""
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "cerebrocortex"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
"""Check if cerebro-cortex is installed."""
|
||||
try:
|
||||
import cerebro # noqa: F401
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def initialize(self, session_id: str, **kwargs) -> None:
|
||||
"""Initialize CerebroCortex for this session."""
|
||||
from cerebro.cortex import CerebroCortex
|
||||
|
||||
self._session_id = session_id
|
||||
self._agent_id = self._config.get(
|
||||
"agent_id",
|
||||
os.environ.get("CEREBRO_AGENT_ID", "HERMES"),
|
||||
)
|
||||
|
||||
# Allow custom data dir via config
|
||||
data_dir = self._config.get("data_dir")
|
||||
if data_dir:
|
||||
os.environ.setdefault("CEREBRO_DATA_DIR", data_dir)
|
||||
|
||||
self._cortex = CerebroCortex()
|
||||
self._cortex.initialize()
|
||||
logger.info(
|
||||
"CerebroCortex initialized (agent=%s, session=%s)",
|
||||
self._agent_id, session_id,
|
||||
)
|
||||
|
||||
def system_prompt_block(self) -> str:
|
||||
"""Inject CerebroCortex status into the system prompt."""
|
||||
if not self._cortex:
|
||||
return ""
|
||||
try:
|
||||
s = self._cortex.stats(agent_id=self._agent_id)
|
||||
parts = [
|
||||
"# CerebroCortex Memory",
|
||||
f"Active. {s['nodes']} memories, {s['links']} links, {s['episodes']} episodes.",
|
||||
"Use cc_remember to store, cc_recall to search (semantic + graph activation).",
|
||||
"Use cc_todo to manage TODOs, cc_message for cross-agent messaging.",
|
||||
]
|
||||
return "\n".join(parts)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex system_prompt_block failed: %s", e)
|
||||
return "# CerebroCortex Memory\nActive. Use cc_remember/cc_recall."
|
||||
|
||||
def prefetch(self, query: str, *, session_id: str = "") -> str:
|
||||
"""Recall relevant memories before each turn."""
|
||||
if not self._cortex or not query or len(query.strip()) < 3:
|
||||
return ""
|
||||
try:
|
||||
results = self._cortex.recall(
|
||||
query=query,
|
||||
top_k=5,
|
||||
agent_id=self._agent_id,
|
||||
)
|
||||
if not results:
|
||||
return ""
|
||||
lines = []
|
||||
for node, score in results:
|
||||
lines.append(f"- [{score:.2f}] {node.content[:200]}")
|
||||
return "## CerebroCortex Context\n" + "\n".join(lines)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex prefetch failed: %s", e)
|
||||
return ""
|
||||
|
||||
def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
|
||||
"""Auto-remember significant user statements in background."""
|
||||
if not self._cortex or not user_content:
|
||||
return
|
||||
# Only auto-store substantial user messages (not short commands)
|
||||
if len(user_content.strip()) < 50:
|
||||
return
|
||||
|
||||
def _bg_store():
|
||||
try:
|
||||
with self._lock:
|
||||
self._cortex.remember(
|
||||
content=user_content[:500],
|
||||
agent_id=self._agent_id,
|
||||
session_id=self._session_id,
|
||||
tags=["auto_sync", "user_turn"],
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex sync_turn failed: %s", e)
|
||||
|
||||
threading.Thread(target=_bg_store, daemon=True).start()
|
||||
|
||||
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
CC_REMEMBER_SCHEMA,
|
||||
CC_RECALL_SCHEMA,
|
||||
CC_INTENTION_SCHEMA,
|
||||
CC_MESSAGE_SCHEMA,
|
||||
CC_HEALTH_SCHEMA,
|
||||
]
|
||||
|
||||
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
|
||||
if not self._cortex:
|
||||
return tool_error("CerebroCortex not initialized")
|
||||
try:
|
||||
if tool_name == "cc_remember":
|
||||
return self._handle_remember(args)
|
||||
elif tool_name == "cc_recall":
|
||||
return self._handle_recall(args)
|
||||
elif tool_name == "cc_todo":
|
||||
return self._handle_intention(args)
|
||||
elif tool_name == "cc_message":
|
||||
return self._handle_message(args)
|
||||
elif tool_name == "cc_health":
|
||||
return self._handle_health()
|
||||
return tool_error(f"Unknown tool: {tool_name}")
|
||||
except Exception as e:
|
||||
return tool_error(str(e))
|
||||
|
||||
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
|
||||
"""Save a session summary to CerebroCortex."""
|
||||
if not self._cortex or not messages:
|
||||
return
|
||||
try:
|
||||
# Build a brief summary from the last few messages
|
||||
user_msgs = [
|
||||
m.get("content", "")[:200]
|
||||
for m in messages[-10:]
|
||||
if m.get("role") == "user" and isinstance(m.get("content"), str)
|
||||
]
|
||||
if not user_msgs:
|
||||
return
|
||||
summary = f"Session topics: {'; '.join(user_msgs[:5])}"
|
||||
self._cortex.session_save(
|
||||
session_summary=summary[:500],
|
||||
agent_id=self._agent_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex on_session_end failed: %s", e)
|
||||
|
||||
def on_memory_write(self, action: str, target: str, content: str) -> None:
|
||||
"""Mirror built-in memory writes to CerebroCortex."""
|
||||
if action == "add" and self._cortex and content:
|
||||
try:
|
||||
tags = ["hermes_builtin", f"target:{target}"]
|
||||
self._cortex.remember(
|
||||
content=content,
|
||||
agent_id=self._agent_id,
|
||||
tags=tags,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex memory_write mirror failed: %s", e)
|
||||
|
||||
def on_delegation(self, task: str, result: str, *,
|
||||
child_session_id: str = "", **kwargs) -> None:
|
||||
"""Store delegation outcomes as episodic memories."""
|
||||
if not self._cortex:
|
||||
return
|
||||
try:
|
||||
content = f"Delegated task: {task[:200]}\nResult: {result[:300]}"
|
||||
self._cortex.remember(
|
||||
content=content,
|
||||
agent_id=self._agent_id,
|
||||
session_id=self._session_id,
|
||||
tags=["delegation", "subagent"],
|
||||
)
|
||||
except Exception as e:
|
||||
logger.debug("CerebroCortex on_delegation failed: %s", e)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
if self._cortex:
|
||||
self._cortex.close()
|
||||
self._cortex = None
|
||||
|
||||
# -- Config ---------------------------------------------------------------
|
||||
|
||||
def get_config_schema(self) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"key": "agent_id",
|
||||
"description": "Agent identifier for multi-agent setups",
|
||||
"default": "HERMES",
|
||||
},
|
||||
{
|
||||
"key": "data_dir",
|
||||
"description": "Data directory (default: ~/.cerebro-cortex/)",
|
||||
"default": "",
|
||||
},
|
||||
]
|
||||
|
||||
def save_config(self, values: Dict[str, Any], hermes_home: str) -> None:
|
||||
from pathlib import Path
|
||||
config_path = Path(hermes_home) / "config.yaml"
|
||||
try:
|
||||
import yaml
|
||||
existing = {}
|
||||
if config_path.exists():
|
||||
with open(config_path) as f:
|
||||
existing = yaml.safe_load(f) or {}
|
||||
existing.setdefault("plugins", {})
|
||||
existing["plugins"]["cerebrocortex"] = values
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(existing, f, default_flow_style=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# -- Tool handlers --------------------------------------------------------
|
||||
|
||||
def _handle_remember(self, args: dict) -> str:
|
||||
node = self._cortex.remember(
|
||||
content=args["content"],
|
||||
tags=args.get("tags"),
|
||||
salience=args.get("salience"),
|
||||
agent_id=self._agent_id,
|
||||
session_id=self._session_id,
|
||||
)
|
||||
if node is None:
|
||||
return json.dumps({"stored": False, "reason": "gated_out (duplicate or noise)"})
|
||||
return json.dumps({
|
||||
"stored": True,
|
||||
"id": node.id,
|
||||
"type": node.metadata.memory_type.value,
|
||||
"salience": round(node.metadata.salience, 3),
|
||||
"concepts": node.metadata.concepts[:5],
|
||||
"links": node.link_count,
|
||||
})
|
||||
|
||||
def _handle_recall(self, args: dict) -> str:
|
||||
results = self._cortex.recall(
|
||||
query=args["query"],
|
||||
top_k=args.get("top_k", 5),
|
||||
agent_id=self._agent_id,
|
||||
)
|
||||
return json.dumps({
|
||||
"count": len(results),
|
||||
"results": [
|
||||
{
|
||||
"id": node.id,
|
||||
"content": node.content,
|
||||
"type": node.metadata.memory_type.value,
|
||||
"score": round(score, 4),
|
||||
"tags": node.metadata.tags,
|
||||
}
|
||||
for node, score in results
|
||||
],
|
||||
})
|
||||
|
||||
def _handle_intention(self, args: dict) -> str:
|
||||
action = args["action"]
|
||||
if action == "store":
|
||||
if "content" not in args:
|
||||
return tool_error("'content' required for store")
|
||||
node = self._cortex.store_intention(
|
||||
content=args["content"],
|
||||
agent_id=self._agent_id,
|
||||
tags=args.get("tags"),
|
||||
)
|
||||
return json.dumps({"stored": True, "id": node.id})
|
||||
|
||||
elif action == "list":
|
||||
intentions = self._cortex.list_intentions(agent_id=self._agent_id)
|
||||
return json.dumps({
|
||||
"count": len(intentions),
|
||||
"items": [
|
||||
{"id": n.id, "content": n.content, "salience": round(n.metadata.salience, 3)}
|
||||
for n in intentions
|
||||
],
|
||||
})
|
||||
|
||||
elif action == "resolve":
|
||||
if "memory_id" not in args:
|
||||
return tool_error("'memory_id' required for resolve")
|
||||
self._cortex.resolve_intention(args["memory_id"])
|
||||
return json.dumps({"resolved": True, "id": args["memory_id"]})
|
||||
|
||||
return tool_error(f"Unknown action: {action}")
|
||||
|
||||
def _handle_message(self, args: dict) -> str:
|
||||
action = args["action"]
|
||||
if action == "send":
|
||||
if "to" not in args or "content" not in args:
|
||||
return tool_error("'to' and 'content' required for send")
|
||||
node = self._cortex.send_message(
|
||||
to=args["to"],
|
||||
content=args["content"],
|
||||
agent_id=self._agent_id,
|
||||
session_id=self._session_id,
|
||||
)
|
||||
return json.dumps({"sent": True, "id": node.id, "to": args["to"]})
|
||||
|
||||
elif action == "inbox":
|
||||
messages = self._cortex.check_inbox(
|
||||
agent_id=self._agent_id,
|
||||
limit=args.get("limit", 10),
|
||||
)
|
||||
return json.dumps({
|
||||
"count": len(messages),
|
||||
"messages": [
|
||||
{
|
||||
"id": m.id,
|
||||
"from": m.metadata.agent_id,
|
||||
"content": m.content,
|
||||
"created_at": m.created_at.isoformat(),
|
||||
}
|
||||
for m in messages
|
||||
],
|
||||
})
|
||||
|
||||
return tool_error(f"Unknown action: {action}")
|
||||
|
||||
def _handle_health(self) -> str:
|
||||
s = self._cortex.stats(agent_id=self._agent_id)
|
||||
return json.dumps({
|
||||
"memories": s["nodes"],
|
||||
"links": s["links"],
|
||||
"episodes": s["episodes"],
|
||||
"types": s.get("memory_types", {}),
|
||||
"layers": s.get("layers", {}),
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plugin entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def register(ctx) -> None:
|
||||
"""Register the CerebroCortex memory provider with Hermes."""
|
||||
config = _load_plugin_config()
|
||||
provider = CerebroCortexProvider(config=config)
|
||||
ctx.register_memory_provider(provider)
|
||||
|
||||
|
||||
def _load_plugin_config() -> dict:
|
||||
try:
|
||||
from hermes_constants import get_hermes_home
|
||||
config_path = get_hermes_home() / "config.yaml"
|
||||
if not config_path.exists():
|
||||
return {}
|
||||
import yaml
|
||||
with open(config_path) as f:
|
||||
all_config = yaml.safe_load(f) or {}
|
||||
return all_config.get("plugins", {}).get("cerebrocortex", {}) or {}
|
||||
except Exception:
|
||||
return {}
|
||||
9
plugins/memory/cerebrocortex/plugin.yaml
Normal file
9
plugins/memory/cerebrocortex/plugin.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name: cerebrocortex
|
||||
version: 0.1.0
|
||||
description: "CerebroCortex — brain-analogous memory with associative networks, ACT-R/FSRS decay, spreading activation, and dream consolidation. Local-first (SQLite + ChromaDB + igraph)."
|
||||
pip_dependencies:
|
||||
- cerebro-cortex
|
||||
hooks:
|
||||
- on_session_end
|
||||
- on_memory_write
|
||||
- on_delegation
|
||||
Loading…
Add table
Add a link
Reference in a new issue