secubox-openwrt/package/secubox/secubox-localrecall/files/usr/lib/localrecall/memory.sh
CyberMind-FR f2dfb5c144 feat(ai): Add v0.19 AI agent packages
Network Anomaly Agent (secubox-network-anomaly):
- 5 detection modules: bandwidth, connection flood, port scan, DNS, protocol
- EMA-based baseline comparison
- LocalAI integration for threat assessment
- network-anomalyctl CLI

LocalRecall Memory System (secubox-localrecall):
- Persistent memory for AI agents
- Categories: threats, decisions, patterns, configs, conversations
- EMA-based importance scoring
- LocalAI integration for summarization
- localrecallctl CLI with 13 commands

AI Insights Dashboard (luci-app-ai-insights):
- Unified view across all AI agents
- Security posture scoring (0-100)
- Agent status grid with alert counts
- Aggregated alerts from all agents
- Run All Agents and AI Analysis actions

LuCI Dashboards:
- luci-app-network-anomaly with real-time stats
- luci-app-localrecall with memory management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:58:08 +01:00

302 lines
7.0 KiB
Bash

#!/bin/sh
# LocalRecall Memory Library
# Persistent memory storage for AI agents
STORAGE_DIR="/var/lib/localrecall"
MEMORIES_FILE="$STORAGE_DIR/memories.json"
INDEX_FILE="$STORAGE_DIR/index.json"
# Initialize storage
init_storage() {
mkdir -p "$STORAGE_DIR"
[ -f "$MEMORIES_FILE" ] || echo '[]' > "$MEMORIES_FILE"
[ -f "$INDEX_FILE" ] || echo '{}' > "$INDEX_FILE"
}
# Generate unique ID
gen_id() {
head -c 8 /dev/urandom | md5sum | head -c 16
}
# Add a memory
# $1 = category (threats|decisions|patterns|configs|conversations)
# $2 = agent (threat_analyst|dns_guard|network_anomaly|cve_triage|user)
# $3 = content
# $4 = metadata JSON (optional)
# $5 = importance (1-10, optional, default 5)
add_memory() {
local category="$1"
local agent="$2"
local content="$3"
local metadata="${4:-{}}"
local importance="${5:-5}"
init_storage
local id=$(gen_id)
local timestamp=$(date -Iseconds)
# Escape content for JSON
local escaped_content=$(printf '%s' "$content" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
# Create memory entry
local memory=$(cat <<EOF
{
"id": "$id",
"category": "$category",
"agent": "$agent",
"content": "$escaped_content",
"metadata": $metadata,
"importance": $importance,
"timestamp": "$timestamp",
"accessed": "$timestamp",
"access_count": 0
}
EOF
)
# Append to memories file
local tmp_file="$MEMORIES_FILE.tmp"
if [ -s "$MEMORIES_FILE" ] && [ "$(cat "$MEMORIES_FILE")" != "[]" ]; then
# Remove closing bracket, add comma and new entry
head -c -2 "$MEMORIES_FILE" > "$tmp_file"
printf ',\n%s\n]' "$memory" >> "$tmp_file"
else
# First entry
printf '[\n%s\n]' "$memory" > "$tmp_file"
fi
mv "$tmp_file" "$MEMORIES_FILE"
# Update category index
update_index "$category" "$id"
echo "$id"
}
# Update category index
update_index() {
local category="$1"
local id="$2"
local index=$(cat "$INDEX_FILE" 2>/dev/null || echo '{}')
# Simple JSON manipulation for index
if echo "$index" | grep -q "\"$category\""; then
# Add ID to existing category array
local tmp_file="$INDEX_FILE.tmp"
sed "s/\"$category\":\[/\"$category\":[\"$id\",/" "$INDEX_FILE" > "$tmp_file"
mv "$tmp_file" "$INDEX_FILE"
else
# Create new category entry
local tmp_file="$INDEX_FILE.tmp"
if [ "$index" = "{}" ]; then
echo "{\"$category\":[\"$id\"]}" > "$tmp_file"
else
# Add to existing object
sed "s/^{/{\"$category\":[\"$id\"],/" "$INDEX_FILE" > "$tmp_file"
fi
mv "$tmp_file" "$INDEX_FILE"
fi
}
# Get memory by ID
get_memory() {
local id="$1"
[ ! -f "$MEMORIES_FILE" ] && return 1
# Extract memory with matching ID
local memory=$(jsonfilter -i "$MEMORIES_FILE" -e "@[@.id='$id']" 2>/dev/null)
if [ -n "$memory" ]; then
# Update access timestamp and count
update_access "$id"
echo "$memory"
return 0
fi
return 1
}
# Update access stats
update_access() {
local id="$1"
local timestamp=$(date -Iseconds)
# Update accessed timestamp (simplified - full impl would use proper JSON parsing)
local tmp_file="$MEMORIES_FILE.tmp"
sed "s/\"id\":\"$id\",\\(.*\\)\"accessed\":\"[^\"]*\"/\"id\":\"$id\",\\1\"accessed\":\"$timestamp\"/" \
"$MEMORIES_FILE" > "$tmp_file"
mv "$tmp_file" "$MEMORIES_FILE"
}
# Search memories by category
search_category() {
local category="$1"
local limit="${2:-20}"
[ ! -f "$MEMORIES_FILE" ] && echo '[]' && return
# Filter by category
jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='$category']" 2>/dev/null | head -n "$limit"
}
# Search memories by agent
search_agent() {
local agent="$1"
local limit="${2:-20}"
[ ! -f "$MEMORIES_FILE" ] && echo '[]' && return
jsonfilter -i "$MEMORIES_FILE" -e "@[@.agent='$agent']" 2>/dev/null | head -n "$limit"
}
# Search memories by content (simple grep)
search_content() {
local query="$1"
local limit="${2:-20}"
[ ! -f "$MEMORIES_FILE" ] && echo '[]' && return
grep -i "$query" "$MEMORIES_FILE" 2>/dev/null | head -n "$limit"
}
# Get recent memories
get_recent() {
local limit="${1:-20}"
[ ! -f "$MEMORIES_FILE" ] && echo '[]' && return
# Return last N entries (file is append-only so last = recent)
jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | tail -n "$limit"
}
# Get important memories (importance >= 7)
get_important() {
local limit="${1:-50}"
[ ! -f "$MEMORIES_FILE" ] && echo '[]' && return
# Filter by importance
local result='['
local first=1
jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | while read -r mem; do
local imp=$(echo "$mem" | jsonfilter -e '@.importance' 2>/dev/null)
if [ "${imp:-0}" -ge 7 ]; then
[ $first -eq 0 ] && result="${result},"
first=0
result="${result}${mem}"
fi
done
echo "${result}]"
}
# Delete memory by ID
delete_memory() {
local id="$1"
[ ! -f "$MEMORIES_FILE" ] && return 1
# Filter out the memory with this ID
local tmp_file="$MEMORIES_FILE.tmp"
local result='['
local first=1
jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | while read -r mem; do
local mem_id=$(echo "$mem" | jsonfilter -e '@.id' 2>/dev/null)
if [ "$mem_id" != "$id" ]; then
[ $first -eq 0 ] && result="${result},"
first=0
result="${result}${mem}"
fi
done
echo "${result}]" > "$tmp_file"
mv "$tmp_file" "$MEMORIES_FILE"
}
# Cleanup old memories
cleanup_old() {
local retention_days="${1:-90}"
local keep_important="${2:-1}"
[ ! -f "$MEMORIES_FILE" ] && return
local cutoff_date=$(date -d "-${retention_days} days" -Iseconds 2>/dev/null || date -Iseconds)
local deleted=0
local tmp_file="$MEMORIES_FILE.tmp"
local result='['
local first=1
jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | while read -r mem; do
local ts=$(echo "$mem" | jsonfilter -e '@.timestamp' 2>/dev/null)
local imp=$(echo "$mem" | jsonfilter -e '@.importance' 2>/dev/null)
# Keep if recent or important
local keep=0
[ "$ts" \> "$cutoff_date" ] && keep=1
[ "$keep_important" = "1" ] && [ "${imp:-0}" -ge 7 ] && keep=1
if [ $keep -eq 1 ]; then
[ $first -eq 0 ] && result="${result},"
first=0
result="${result}${mem}"
else
deleted=$((deleted + 1))
fi
done
echo "${result}]" > "$tmp_file"
mv "$tmp_file" "$MEMORIES_FILE"
echo "$deleted"
}
# Count memories
count_memories() {
[ ! -f "$MEMORIES_FILE" ] && echo 0 && return
jsonfilter -i "$MEMORIES_FILE" -e '@[*]' 2>/dev/null | wc -l
}
# Count by category
count_category() {
local category="$1"
[ ! -f "$MEMORIES_FILE" ] && echo 0 && return
jsonfilter -i "$MEMORIES_FILE" -e "@[@.category='$category']" 2>/dev/null | wc -l
}
# Export memories to file
export_memories() {
local output_file="$1"
[ -f "$MEMORIES_FILE" ] && cp "$MEMORIES_FILE" "$output_file"
}
# Import memories from file
import_memories() {
local input_file="$1"
[ ! -f "$input_file" ] && return 1
# Validate JSON
jsonfilter -i "$input_file" -e '@[0]' >/dev/null 2>&1 || return 1
# Merge with existing (simple concat for now)
init_storage
if [ -s "$MEMORIES_FILE" ] && [ "$(cat "$MEMORIES_FILE")" != "[]" ]; then
# Merge arrays
local tmp_file="$MEMORIES_FILE.tmp"
head -c -1 "$MEMORIES_FILE" > "$tmp_file"
printf ','
tail -c +2 "$input_file" >> "$tmp_file"
mv "$tmp_file" "$MEMORIES_FILE"
else
cp "$input_file" "$MEMORIES_FILE"
fi
}