feat: complete multi-graph support across CLI, scripts, and systemd timers
Non-default graphs were second-class citizens — timers only maintained the default graph, git sync ignored named graphs, there was no way to create a graph without editing config manually, cross-graph edge errors were confusing, and utility scripts were hardcoded to the default graph. - Add `graph-create` CLI command + `create_graph()` in common.py, with custom path registration written to the default graph's _config.json - Add `scripts/maintain-all-graphs.sh` to loop decay/core/embed/reflect over all discovered graphs; update systemd services to call it - Refactor `memory-git-sync.sh` into sync_repo() function that iterates default + all named graphs with .git directories - Improve cross-graph edge ValueError to explain the same-graph constraint - Add --graph flag to edge-proposer.py and session_memory.py - Update systemd/README.md with portable paths and new architecture Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
adc9a64c8d
commit
d8dd1f35a5
19
cli.py
19
cli.py
@ -8,6 +8,7 @@ Command-line interface for the cognitive memory system.
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from client import CognitiveMemoryClient
|
||||
from common import (
|
||||
@ -15,6 +16,7 @@ from common import (
|
||||
VALID_RELATION_TYPES,
|
||||
VALID_TYPES,
|
||||
_load_memory_config,
|
||||
create_graph,
|
||||
resolve_graph_path,
|
||||
list_graphs,
|
||||
)
|
||||
@ -224,6 +226,19 @@ def main():
|
||||
# graphs
|
||||
subparsers.add_parser("graphs", help="List available memory graphs")
|
||||
|
||||
# graph-create
|
||||
sp = subparsers.add_parser("graph-create", help="Create a new named memory graph")
|
||||
sp.add_argument("name", help="Graph name (alphanumeric, hyphens OK)")
|
||||
sp.add_argument(
|
||||
"--path",
|
||||
default=None,
|
||||
help=(
|
||||
"Custom directory path for the graph. "
|
||||
"If omitted, uses the convention path (~/.local/share/cognitive-memory-graphs/<name>). "
|
||||
"Custom paths are registered in the default graph's _config.json."
|
||||
),
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
@ -467,6 +482,10 @@ def main():
|
||||
elif args.command == "graphs":
|
||||
result = list_graphs()
|
||||
|
||||
elif args.command == "graph-create":
|
||||
custom_path = Path(args.path) if args.path else None
|
||||
result = create_graph(args.name, path=custom_path)
|
||||
|
||||
elif args.command == "config":
|
||||
config_path = client.memory_dir / "_config.json"
|
||||
config = _load_memory_config(config_path)
|
||||
|
||||
44
common.py
44
common.py
@ -557,6 +557,50 @@ def resolve_graph_path(
|
||||
return GRAPHS_BASE_DIR / graph_name
|
||||
|
||||
|
||||
def create_graph(name: str, path: Optional[Path] = None) -> Dict[str, Any]:
|
||||
"""Create a new named graph directory structure.
|
||||
|
||||
If path is None, uses the convention path (GRAPHS_BASE_DIR / name) and
|
||||
does NOT modify any config file — the convention path is auto-discovered.
|
||||
|
||||
If a custom path is given, the mapping is written to the default graph's
|
||||
_config.json under graphs.<name>.path so resolve_graph_path() can find it.
|
||||
|
||||
Returns a dict with keys: name, path, created (bool), registered (bool).
|
||||
"""
|
||||
if path is None:
|
||||
graph_path = GRAPHS_BASE_DIR / name
|
||||
register = False
|
||||
else:
|
||||
graph_path = Path(path).expanduser()
|
||||
register = True
|
||||
|
||||
# Track whether this is a new graph or already exists
|
||||
already_existed = graph_path.exists()
|
||||
|
||||
# Create the standard subdirectory layout that CognitiveMemoryClient expects
|
||||
for type_dir in TYPE_DIRS.values():
|
||||
(graph_path / "graph" / type_dir).mkdir(parents=True, exist_ok=True)
|
||||
(graph_path / "graph" / EDGES_DIR_NAME).mkdir(parents=True, exist_ok=True)
|
||||
(graph_path / "episodes").mkdir(parents=True, exist_ok=True)
|
||||
(graph_path / "vault").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Register custom path in the default graph's _config.json
|
||||
if register:
|
||||
cfg = _load_memory_config(CONFIG_PATH)
|
||||
graphs_section = cfg.setdefault("graphs", {})
|
||||
graphs_section[name] = {"path": str(graph_path)}
|
||||
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
CONFIG_PATH.write_text(json.dumps(cfg, indent=2))
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"path": str(graph_path),
|
||||
"created": not already_existed,
|
||||
"registered": register,
|
||||
}
|
||||
|
||||
|
||||
def list_graphs(config_path: Optional[Path] = None) -> List[Dict[str, Any]]:
|
||||
"""List all known graphs: default + configured + discovered on disk."""
|
||||
result = [{"name": "default", "path": str(MEMORY_DIR)}]
|
||||
|
||||
6
edges.py
6
edges.py
@ -43,7 +43,11 @@ class EdgesMixin:
|
||||
from_path = self._resolve_memory_path(from_id)
|
||||
to_path = self._resolve_memory_path(to_id)
|
||||
if not from_path or not to_path:
|
||||
raise ValueError(f"Memory not found: {from_id if not from_path else to_id}")
|
||||
missing_id = from_id if not from_path else to_id
|
||||
raise ValueError(
|
||||
f"Memory not found: {missing_id}. "
|
||||
f"Note: edges can only connect memories within the same graph."
|
||||
)
|
||||
|
||||
# Read source memory
|
||||
fm, body = self._read_memory_file(from_path)
|
||||
|
||||
@ -33,6 +33,7 @@ First run: 2026-02-19 — produced 5186 candidates from 473 memories,
|
||||
20 high-quality edges were manually selected and created.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -40,20 +41,53 @@ from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from itertools import combinations
|
||||
|
||||
# Resolve data directory: COGNITIVE_MEMORY_DIR > XDG_DATA_HOME > default
|
||||
# Resolve base data directory: COGNITIVE_MEMORY_DIR > XDG_DATA_HOME > default
|
||||
_env_dir = os.environ.get("COGNITIVE_MEMORY_DIR", "")
|
||||
if _env_dir:
|
||||
MEMORY_DIR = Path(_env_dir).expanduser()
|
||||
_BASE_MEMORY_DIR = Path(_env_dir).expanduser()
|
||||
else:
|
||||
_xdg_data = os.environ.get("XDG_DATA_HOME", "") or str(
|
||||
Path.home() / ".local" / "share"
|
||||
)
|
||||
MEMORY_DIR = Path(_xdg_data) / "cognitive-memory"
|
||||
_BASE_MEMORY_DIR = Path(_xdg_data) / "cognitive-memory"
|
||||
|
||||
# These are set at runtime in main() after --graph is resolved
|
||||
MEMORY_DIR = _BASE_MEMORY_DIR
|
||||
STATE_FILE = MEMORY_DIR / "_state.json"
|
||||
GRAPH_DIR = MEMORY_DIR / "graph"
|
||||
EDGES_DIR = GRAPH_DIR / "edges"
|
||||
|
||||
|
||||
def _resolve_graph_dir(graph_name: str | None) -> Path:
|
||||
"""Resolve a graph name to its directory path.
|
||||
|
||||
Mirrors the logic in common.resolve_graph_path without importing the full
|
||||
client package (which would pull in heavy dependencies).
|
||||
|
||||
None / 'default' -> MEMORY_DIR (the default graph).
|
||||
Named graph -> sibling directory next to MEMORY_DIR, e.g.
|
||||
~/.local/share/cognitive-memory-<name>.
|
||||
"""
|
||||
if not graph_name or graph_name == "default":
|
||||
return _BASE_MEMORY_DIR
|
||||
|
||||
# Check _config.json for a registered path
|
||||
config_path = _BASE_MEMORY_DIR / "_config.json"
|
||||
if config_path.exists():
|
||||
try:
|
||||
cfg = json.loads(config_path.read_text())
|
||||
graphs = cfg.get("graphs", {})
|
||||
if graph_name in graphs:
|
||||
p = graphs[graph_name].get("path", "")
|
||||
if p:
|
||||
return Path(p).expanduser()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Convention: ~/.local/share/cognitive-memory-graphs/<name>
|
||||
return _BASE_MEMORY_DIR.parent / "cognitive-memory-graphs" / graph_name
|
||||
|
||||
|
||||
# Type-based heuristics: (type_a, type_b) -> (suggested_rel, direction, base_score)
|
||||
# direction: "ab" means a->b, "ba" means b->a
|
||||
TYPE_HEURISTICS = {
|
||||
@ -349,6 +383,31 @@ def score_pair(mem_a: dict, mem_b: dict) -> dict | None:
|
||||
|
||||
|
||||
def main():
|
||||
global MEMORY_DIR, STATE_FILE, GRAPH_DIR, EDGES_DIR
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Analyze cognitive memories and propose high-quality edges."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--graph",
|
||||
default=None,
|
||||
metavar="NAME",
|
||||
help=(
|
||||
"Named memory graph to analyze (default: the default graph). "
|
||||
"Use 'claude-memory graphs' to list available graphs."
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Resolve graph directory and update module-level path globals
|
||||
MEMORY_DIR = _resolve_graph_dir(args.graph)
|
||||
STATE_FILE = MEMORY_DIR / "_state.json"
|
||||
GRAPH_DIR = MEMORY_DIR / "graph"
|
||||
EDGES_DIR = GRAPH_DIR / "edges"
|
||||
|
||||
if args.graph:
|
||||
print(f"Using graph: {args.graph} ({MEMORY_DIR})")
|
||||
|
||||
print("Loading memories...")
|
||||
memories = load_memories()
|
||||
print(f" Found {len(memories)} memories")
|
||||
|
||||
135
scripts/maintain-all-graphs.sh
Executable file
135
scripts/maintain-all-graphs.sh
Executable file
@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
# Run cognitive memory maintenance (decay, core, embed, reflect) for ALL graphs.
|
||||
#
|
||||
# Called by systemd service units instead of individual claude-memory commands,
|
||||
# so that named graphs receive the same maintenance as the default graph.
|
||||
#
|
||||
# Usage:
|
||||
# maintain-all-graphs.sh [--daily | --embed | --weekly]
|
||||
#
|
||||
# --daily (default) Run decay + core for every graph
|
||||
# --embed Run embed --if-changed for every graph
|
||||
# --weekly Run reflect for every graph
|
||||
#
|
||||
# Named graph directories that do not exist on disk are silently skipped.
|
||||
# The default graph is always processed first regardless of disk discovery.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── configuration ──────────────────────────────────────────────────────────────
|
||||
|
||||
CLAUDE_MEMORY="${CLAUDE_MEMORY_BIN:-/home/cal/.local/bin/claude-memory}"
|
||||
export PATH="/home/cal/.local/bin:$PATH"
|
||||
|
||||
MODE="${1:---daily}"
|
||||
|
||||
# ── helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
log() {
|
||||
echo "maintain-all-graphs [$(date '+%H:%M:%S')]: $*"
|
||||
}
|
||||
|
||||
# Run a claude-memory command for one graph.
|
||||
# $1 = graph name ("default" omits --graph flag, others pass --graph <name>)
|
||||
# remaining args = claude-memory subcommand + flags
|
||||
run_for_graph() {
|
||||
local graph_name="$1"
|
||||
shift
|
||||
local graph_flag=()
|
||||
if [ "$graph_name" != "default" ]; then
|
||||
graph_flag=(--graph "$graph_name")
|
||||
fi
|
||||
|
||||
log "[$graph_name] $*"
|
||||
"$CLAUDE_MEMORY" "${graph_flag[@]}" "$@"
|
||||
}
|
||||
|
||||
# ── discover graphs ────────────────────────────────────────────────────────────
|
||||
#
|
||||
# claude-memory graphs outputs a JSON array:
|
||||
# [{"name": "default", "path": "/home/cal/.local/share/cognitive-memory"}, ...]
|
||||
#
|
||||
# We parse it with only sh/awk — no jq dependency.
|
||||
# Strategy: extract "name" and "path" values line by line from pretty-printed JSON.
|
||||
|
||||
graphs_json=$("$CLAUDE_MEMORY" graphs 2>/dev/null) || {
|
||||
log "ERROR: 'claude-memory graphs' failed; aborting"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build parallel arrays: GRAPH_NAMES and GRAPH_PATHS
|
||||
GRAPH_NAMES=()
|
||||
GRAPH_PATHS=()
|
||||
|
||||
# State machine: after seeing "name": capture value; after "path": capture value.
|
||||
# Each object ends when we collect both; append to arrays.
|
||||
_name=""
|
||||
_path=""
|
||||
while IFS= read -r line; do
|
||||
# Strip leading/trailing whitespace
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
line="${line%"${line##*[![:space:]]}"}"
|
||||
|
||||
# Match: "name": "value"
|
||||
if [[ "$line" =~ ^\"name\":\ *\"([^\"]+)\" ]]; then
|
||||
_name="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
# Match: "path": "value" (path may contain slashes — [^"]+ is fine)
|
||||
if [[ "$line" =~ ^\"path\":\ *\"([^\"]+)\" ]]; then
|
||||
_path="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
# When we have both, flush to arrays (object boundary)
|
||||
if [ -n "$_name" ] && [ -n "$_path" ]; then
|
||||
GRAPH_NAMES+=("$_name")
|
||||
GRAPH_PATHS+=("$_path")
|
||||
_name=""
|
||||
_path=""
|
||||
fi
|
||||
done <<< "$graphs_json"
|
||||
|
||||
if [ "${#GRAPH_NAMES[@]}" -eq 0 ]; then
|
||||
log "ERROR: no graphs discovered; aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "discovered ${#GRAPH_NAMES[@]} graph(s): ${GRAPH_NAMES[*]}"
|
||||
|
||||
# ── main loop ─────────────────────────────────────────────────────────────────
|
||||
|
||||
ERRORS=0
|
||||
|
||||
for i in "${!GRAPH_NAMES[@]}"; do
|
||||
name="${GRAPH_NAMES[$i]}"
|
||||
path="${GRAPH_PATHS[$i]}"
|
||||
|
||||
# Skip graphs whose directory doesn't exist on disk (named graphs may not
|
||||
# be initialised yet on this machine).
|
||||
if [ "$name" != "default" ] && [ ! -d "$path" ]; then
|
||||
log "[$name] skipping — directory not found: $path"
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$MODE" in
|
||||
--daily)
|
||||
run_for_graph "$name" decay || { log "[$name] decay FAILED"; ERRORS=$((ERRORS+1)); }
|
||||
run_for_graph "$name" core || { log "[$name] core FAILED"; ERRORS=$((ERRORS+1)); }
|
||||
;;
|
||||
--embed)
|
||||
run_for_graph "$name" embed --if-changed || { log "[$name] embed FAILED"; ERRORS=$((ERRORS+1)); }
|
||||
;;
|
||||
--weekly)
|
||||
run_for_graph "$name" reflect || { log "[$name] reflect FAILED"; ERRORS=$((ERRORS+1)); }
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [--daily | --embed | --weekly]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
log "completed with $ERRORS error(s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "done (mode: $MODE)"
|
||||
@ -1,29 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Commit and push cognitive memory changes to Gitea (cal/claude-memory)
|
||||
# Commit and push cognitive memory changes to Gitea.
|
||||
#
|
||||
# Called daily by cognitive-memory-daily.service after decay/core/symlinks.
|
||||
# Only commits if there are actual changes. Safe to run multiple times.
|
||||
# Syncs the default graph and any named graphs whose directory is a git repo.
|
||||
# Called daily by cognitive-memory-daily.service after decay/core runs.
|
||||
# Safe to run multiple times — only commits when there are actual changes.
|
||||
#
|
||||
# Default graph: COGNITIVE_MEMORY_DIR > XDG_DATA_HOME > ~/.local/share/cognitive-memory
|
||||
# Named graphs: ~/.local/share/cognitive-memory-graphs/<name>/ (any with .git/)
|
||||
#
|
||||
# Location: ~/.claude/skills/cognitive-memory/scripts/memory-git-sync.sh
|
||||
# Repo: cognitive-memory data dir -> https://git.manticorum.com/cal/claude-memory.git
|
||||
|
||||
set -euo pipefail
|
||||
set -uo pipefail
|
||||
# Note: -e intentionally omitted at the top level so that one graph's failure
|
||||
# does not prevent the remaining graphs from being synced. Each function call
|
||||
# is checked explicitly.
|
||||
|
||||
# ── resolve directories ────────────────────────────────────────────────────────
|
||||
|
||||
# Resolve data directory: COGNITIVE_MEMORY_DIR > XDG_DATA_HOME > default
|
||||
MEMORY_DIR="${COGNITIVE_MEMORY_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/cognitive-memory}"
|
||||
|
||||
cd "$MEMORY_DIR"
|
||||
# Named graphs live in a sibling directory: cognitive-memory-graphs/<name>/
|
||||
GRAPHS_BASE_DIR="${MEMORY_DIR%cognitive-memory}cognitive-memory-graphs"
|
||||
|
||||
# Check if there are any changes to commit
|
||||
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
||||
echo "memory-git-sync: no changes to commit"
|
||||
# ── sync function ──────────────────────────────────────────────────────────────
|
||||
# sync_repo <dir> <label>
|
||||
# Commits and pushes any pending changes in the given git repo directory.
|
||||
# Returns 0 on success or "nothing to commit", non-zero on error.
|
||||
sync_repo() {
|
||||
local dir="$1"
|
||||
local label="$2"
|
||||
|
||||
# Directory must exist and be a git repo
|
||||
if [ ! -d "$dir/.git" ]; then
|
||||
echo "memory-git-sync [$label]: not a git repo, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
(
|
||||
# Run everything inside a subshell so cd is scoped and set -e is safe
|
||||
set -euo pipefail
|
||||
cd "$dir"
|
||||
|
||||
# Nothing to commit?
|
||||
if git diff --quiet && git diff --cached --quiet \
|
||||
&& [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
||||
echo "memory-git-sync [$label]: no changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stage all changes (.gitignore handles exclusions)
|
||||
git add -A
|
||||
|
||||
# Build a commit message from what changed
|
||||
# Build a descriptive commit message
|
||||
ADDED=$(git diff --cached --name-only --diff-filter=A | wc -l)
|
||||
MODIFIED=$(git diff --cached --name-only --diff-filter=M | wc -l)
|
||||
DELETED=$(git diff --cached --name-only --diff-filter=D | wc -l)
|
||||
@ -35,13 +63,41 @@ if [ "$EDGES" -gt 0 ]; then
|
||||
fi
|
||||
|
||||
git commit -m "$MSG" --no-gpg-sign 2>/dev/null || {
|
||||
echo "memory-git-sync: commit failed (pre-commit hook?)"
|
||||
echo "memory-git-sync [$label]: commit failed (pre-commit hook?)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
git push origin main 2>/dev/null || {
|
||||
echo "memory-git-sync: push failed"
|
||||
echo "memory-git-sync [$label]: push failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "memory-git-sync: pushed to origin/main"
|
||||
echo "memory-git-sync [$label]: pushed to origin/main"
|
||||
)
|
||||
}
|
||||
|
||||
# ── sync default graph ─────────────────────────────────────────────────────────
|
||||
|
||||
ERRORS=0
|
||||
|
||||
sync_repo "$MEMORY_DIR" "default" || ERRORS=$((ERRORS + 1))
|
||||
|
||||
# ── sync named graphs ──────────────────────────────────────────────────────────
|
||||
# Iterate subdirectories of GRAPHS_BASE_DIR; skip non-git directories silently.
|
||||
|
||||
if [ -d "$GRAPHS_BASE_DIR" ]; then
|
||||
for graph_dir in "$GRAPHS_BASE_DIR"/*/; do
|
||||
# glob may yield literal "*/'" if directory is empty
|
||||
[ -d "$graph_dir" ] || continue
|
||||
|
||||
graph_name="$(basename "$graph_dir")"
|
||||
sync_repo "$graph_dir" "$graph_name" || ERRORS=$((ERRORS + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
# ── exit status ───────────────────────────────────────────────────────────────
|
||||
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo "memory-git-sync: completed with $ERRORS error(s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -7,6 +7,7 @@ architecture decisions, new patterns, configurations), and stores them as
|
||||
cognitive memories via claude-memory CLI.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
@ -480,8 +481,13 @@ def build_title(summary: dict) -> str:
|
||||
return f"[{project}] Session: {work}"
|
||||
|
||||
|
||||
def store_memory(summary: dict):
|
||||
"""Store the session memory via claude-memory CLI."""
|
||||
def store_memory(summary: dict, graph: str | None = None):
|
||||
"""Store the session memory via claude-memory CLI.
|
||||
|
||||
Args:
|
||||
summary: Session summary dict from build_session_summary().
|
||||
graph: Named memory graph to store into, or None for the default graph.
|
||||
"""
|
||||
title = build_title(summary)
|
||||
content = build_memory_content(summary)
|
||||
mem_type = determine_memory_type(summary)
|
||||
@ -501,8 +507,11 @@ def store_memory(summary: dict):
|
||||
tags.append("session-log")
|
||||
tag_str = ",".join(tags)
|
||||
|
||||
cmd = [
|
||||
"claude-memory",
|
||||
# Base command: optionally target a named graph
|
||||
cmd = ["claude-memory"]
|
||||
if graph:
|
||||
cmd += ["--graph", graph]
|
||||
cmd += [
|
||||
"store",
|
||||
"--type",
|
||||
mem_type,
|
||||
@ -544,6 +553,24 @@ def store_memory(summary: dict):
|
||||
def main():
|
||||
log_separator()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Session-end memory hook: store session events as cognitive memories.",
|
||||
add_help=False, # keep --help available but don't conflict with hook stdin
|
||||
)
|
||||
parser.add_argument(
|
||||
"--graph",
|
||||
default=None,
|
||||
metavar="NAME",
|
||||
help=(
|
||||
"Named memory graph to store memories into (default: the default graph). "
|
||||
"Use 'claude-memory graphs' to list available graphs."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--help", "-h", action="help", help="Show this help message and exit."
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
hook_input = read_stdin()
|
||||
transcript_path = hook_input.get("transcript_path", "")
|
||||
cwd = hook_input.get("cwd", "")
|
||||
@ -579,7 +606,7 @@ def main():
|
||||
log(f"[main] ABORT: build_session_summary returned '{summary}'")
|
||||
sys.exit(0)
|
||||
|
||||
store_memory(summary)
|
||||
store_memory(summary, graph=args.graph)
|
||||
log("[main] Done")
|
||||
|
||||
|
||||
|
||||
@ -6,16 +6,31 @@ Reference copies of the systemd user units that automate memory maintenance.
|
||||
|
||||
| Unit | Schedule | What it does |
|
||||
|------|----------|-------------|
|
||||
| `cognitive-memory-daily` | daily | Decay scores, regenerate CORE.md, refresh MEMORY.md symlinks |
|
||||
| `cognitive-memory-embed` | hourly | Refresh embeddings (skips if unchanged) |
|
||||
| `cognitive-memory-weekly` | weekly | Run reflection cycle |
|
||||
| `cognitive-memory-daily` | daily | Decay scores, regenerate CORE.md, git sync — for ALL graphs |
|
||||
| `cognitive-memory-embed` | hourly | Refresh embeddings (skips if unchanged) — for ALL graphs |
|
||||
| `cognitive-memory-weekly` | weekly | Run reflection cycle — for ALL graphs |
|
||||
|
||||
All three services now call `scripts/maintain-all-graphs.sh` instead of
|
||||
individual `claude-memory` commands. The script discovers every graph via
|
||||
`claude-memory graphs` (default graph + named graphs under
|
||||
`~/.local/share/cognitive-memory-graphs/`) and runs the appropriate command
|
||||
for each one, passing `--graph <name>` for non-default graphs.
|
||||
|
||||
Named graph directories that do not exist on disk are silently skipped.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `scripts/maintain-all-graphs.sh` | Loop decay/core/embed/reflect over all graphs |
|
||||
| `scripts/memory-git-sync.sh` | Git commit+push for default graph and all named git repos |
|
||||
|
||||
## Install / Update
|
||||
|
||||
```bash
|
||||
# Copy units into place
|
||||
cp ~/.claude/skills/cognitive-memory/systemd/*.service \
|
||||
~/.claude/skills/cognitive-memory/systemd/*.timer \
|
||||
# Copy units into place (adjust source path to your cognitive-memory install)
|
||||
CMDIR=~/.claude/skills/cognitive-memory # or /mnt/NV2/Development/cognitive-memory
|
||||
cp "$CMDIR"/systemd/*.service "$CMDIR"/systemd/*.timer \
|
||||
~/.config/systemd/user/
|
||||
|
||||
# Reload and enable
|
||||
@ -32,3 +47,16 @@ systemctl --user list-timers 'cognitive-memory-*'
|
||||
systemctl --user start cognitive-memory-daily.service # manual test run
|
||||
journalctl --user -u cognitive-memory-daily.service --since today
|
||||
```
|
||||
|
||||
## Manual test (single graph)
|
||||
|
||||
```bash
|
||||
# Default graph only
|
||||
claude-memory decay && claude-memory core
|
||||
|
||||
# Named graph
|
||||
claude-memory --graph paper-dynasty decay && claude-memory --graph paper-dynasty core
|
||||
|
||||
# All graphs at once (adjust path to your install)
|
||||
/path/to/cognitive-memory/scripts/maintain-all-graphs.sh --daily
|
||||
```
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Cognitive Memory daily maintenance (decay, core, git sync)
|
||||
Description=Cognitive Memory daily maintenance (decay, core, git sync) for all graphs
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c 'export PATH="/home/cal/.local/bin:$PATH" && /home/cal/.local/bin/claude-memory decay && /home/cal/.local/bin/claude-memory core && /mnt/NV2/Development/cognitive-memory/scripts/memory-git-sync.sh'
|
||||
ExecStart=/bin/bash -c 'export PATH="/home/cal/.local/bin:$PATH" && /mnt/NV2/Development/cognitive-memory/scripts/maintain-all-graphs.sh --daily && /mnt/NV2/Development/cognitive-memory/scripts/memory-git-sync.sh'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Cognitive Memory hourly embedding refresh (skips if unchanged)
|
||||
Description=Cognitive Memory hourly embedding refresh (skips if unchanged) for all graphs
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash -c 'export PATH="/home/cal/.local/bin:$PATH" && /home/cal/.local/bin/claude-memory embed --if-changed'
|
||||
ExecStart=/bin/bash -c 'export PATH="/home/cal/.local/bin:$PATH" && /mnt/NV2/Development/cognitive-memory/scripts/maintain-all-graphs.sh --embed'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Cognitive Memory weekly reflection
|
||||
Description=Cognitive Memory weekly reflection for all graphs
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/home/cal/.local/bin/claude-memory reflect
|
||||
ExecStart=/bin/bash -c 'export PATH="/home/cal/.local/bin:$PATH" && /mnt/NV2/Development/cognitive-memory/scripts/maintain-all-graphs.sh --weekly'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user