Files
blackroad/scripts/memory-system.sh
Alexa Amundson 78fbe80f2a Initial monorepo — everything BlackRoad in one place
bin/       230 CLI tools (ask-*, br-*, agent-*, roadid, carpool)
scripts/   99 automation scripts
fleet/     Node configs and deployment
workers/   Cloudflare Worker sources (roadpay, road-search, squad webhooks)
roadc/     RoadC programming language
roadnet/   Mesh network (5 APs, WireGuard)
operator/  Memory system scripts
config/    System configs
dotfiles/  Shell configs
docs/      Documentation

BlackRoad OS — Pave Tomorrow.

RoadChain-SHA2048: d1a24f55318d338b
RoadChain-Identity: alexa@sovereign
RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
2026-03-14 17:08:41 -05:00

434 lines
14 KiB
Bash
Executable File

#!/bin/bash
# BlackRoad Memory System Controller
# Provides continuous memory for Claude Code sessions via PS-SHA∞ journals
set -e
VERSION="1.0.0"
# Configuration
MEMORY_DIR="$HOME/.blackroad/memory"
SESSION_DIR="$MEMORY_DIR/sessions"
JOURNAL_DIR="$MEMORY_DIR/journals"
LEDGER_DIR="$MEMORY_DIR/ledger"
CONTEXT_DIR="$MEMORY_DIR/context"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# Helper functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Initialize memory system
init_memory() {
log_info "Initializing BlackRoad Memory System..."
mkdir -p "$SESSION_DIR" "$JOURNAL_DIR" "$LEDGER_DIR" "$CONTEXT_DIR"
# Create genesis entry if not exists
if [ ! -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
local timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
local genesis_hash="$(echo -n "genesis" | shasum -a 256 | cut -d' ' -f1)"
echo "{\"timestamp\":\"${timestamp}\",\"action\":\"genesis\",\"entity\":\"memory-system\",\"details\":\"BlackRoad Memory System initialized\",\"sha256\":\"${genesis_hash}\",\"parent_hash\":\"0000000000000000\"}" > "$JOURNAL_DIR/master-journal.jsonl"
echo "{\"hash\":\"${genesis_hash}\",\"timestamp\":\"${timestamp}\",\"parent\":\"0000000000000000\",\"action_count\":0}" > "$LEDGER_DIR/memory-ledger.jsonl"
log_success "Memory system initialized with genesis hash: ${genesis_hash:0:8}..."
else
log_warning "Memory system already initialized"
fi
# Create config
cat > "$MEMORY_DIR/config.json" <<EOF
{
"version": "${VERSION}",
"initialized": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"hash_function": "sha256",
"encoding": "utf-8"
}
EOF
log_success "Memory system ready at: $MEMORY_DIR"
}
# Create new session
new_session() {
local session_name="${1:-default}"
local session_id="$(date +%Y-%m-%d-%H%M)-${session_name}"
local session_file="$SESSION_DIR/${session_id}.json"
local timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Get last action
local last_action="N/A"
if [ -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
last_action="$(tail -1 "$JOURNAL_DIR/master-journal.jsonl" | jq -r '.action + ": " + .entity' 2>/dev/null || echo "N/A")"
fi
# Count git repos
local git_repos=$(find ~/projects -name .git -type d 2>/dev/null | wc -l | tr -d ' ')
cat > "$session_file" <<EOF
{
"session_id": "${session_id}",
"timestamp": "${timestamp}",
"type": "session_start",
"context": {
"working_directory": "$(pwd)",
"git_repos": ${git_repos},
"last_action": "${last_action}",
"hostname": "$(hostname)",
"user": "$(whoami)"
}
}
EOF
# Create symlink to current session
ln -sf "$session_file" "$SESSION_DIR/current-session.json"
# Log session start
log_action "session_start" "$session_id" "Working directory: $(pwd)"
log_success "Session created: $session_id"
}
# Log action to journal (with lock-free concurrent write support)
log_action() {
local action="$1"
local entity="$2"
local details="${3:-}"
if [ ! -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
log_error "Memory system not initialized. Run: $0 init"
return 1
fi
# Use high-precision timestamp with nanoseconds for uniqueness
local timestamp="$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
# Lock-free read: get parent hash (atomic tail operation)
local parent_hash="$(tail -1 "$JOURNAL_DIR/master-journal.jsonl" | jq -r '.sha256' 2>/dev/null || echo '0000000000000000')"
# Add process ID and random nonce for uniqueness in concurrent writes
local nonce="$$-$(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')"
local hash_input="${timestamp}${action}${entity}${details}${parent_hash}${nonce}"
local sha256="$(echo -n "$hash_input" | shasum -a 256 | cut -d' ' -f1)"
local action_count=$(wc -l < "$JOURNAL_DIR/master-journal.jsonl")
# Get agent SHA-2048 identity if available
local agent_sha2048="${CLAUDE_SHA2048_SHORT:-}"
local agent_id="${MY_CLAUDE:-}"
# Prepare JSON entry (use jq to properly escape multiline strings, -c for compact)
local temp_file="$JOURNAL_DIR/.temp-${sha256:0:16}.jsonl"
jq -nc \
--arg timestamp "$timestamp" \
--arg action "$action" \
--arg entity "$entity" \
--arg details "$details" \
--arg sha256 "$sha256" \
--arg parent_hash "$parent_hash" \
--arg nonce "$nonce" \
--arg agent_id "$agent_id" \
--arg agent_sha2048 "$agent_sha2048" \
'{timestamp: $timestamp, action: $action, entity: $entity, details: $details, sha256: $sha256, parent_hash: $parent_hash, nonce: $nonce, agent_id: $agent_id, agent_sha2048: $agent_sha2048}' \
> "$temp_file"
# Lock-free atomic append (>> is atomic at filesystem level for small writes)
cat "$temp_file" >> "$JOURNAL_DIR/master-journal.jsonl"
rm -f "$temp_file"
# Update ledger (also atomic append)
local ledger_entry="{\"hash\":\"${sha256}\",\"timestamp\":\"${timestamp}\",\"parent\":\"${parent_hash}\",\"action_count\":${action_count},\"nonce\":\"${nonce}\"}"
echo "$ledger_entry" >> "$LEDGER_DIR/memory-ledger.jsonl"
echo -e "${PURPLE}[MEMORY]${NC} Logged: ${action}${entity} (hash: ${sha256:0:8}...)"
}
# Synthesize context from journals
synthesize_context() {
log_info "Synthesizing context from memory journals..."
if [ ! -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
log_error "No journal found. Run: $0 init"
return 1
fi
# Recent actions
echo "# Recent Actions" > "$CONTEXT_DIR/recent-actions.md"
echo "" >> "$CONTEXT_DIR/recent-actions.md"
tail -50 "$JOURNAL_DIR/master-journal.jsonl" | \
jq -r '"- [" + .timestamp[0:19] + "] **" + .action + "**: " + .entity + " — " + .details' \
>> "$CONTEXT_DIR/recent-actions.md"
# Infrastructure state
echo "# Infrastructure State" > "$CONTEXT_DIR/infrastructure-state.md"
echo "" >> "$CONTEXT_DIR/infrastructure-state.md"
grep -E '"action":"(deployed|configured|allocated)"' "$JOURNAL_DIR/master-journal.jsonl" 2>/dev/null | \
tail -30 | \
jq -r '"- **" + .entity + "**: " + .details + " (" + .timestamp[0:10] + ")"' \
>> "$CONTEXT_DIR/infrastructure-state.md" || echo "No infrastructure changes yet" >> "$CONTEXT_DIR/infrastructure-state.md"
# Decisions made
echo "# Decisions Made" > "$CONTEXT_DIR/decisions.md"
echo "" >> "$CONTEXT_DIR/decisions.md"
grep -E '"action":"decided"' "$JOURNAL_DIR/master-journal.jsonl" 2>/dev/null | \
jq -r '"- **" + .entity + "**: " + .details + " (" + .timestamp[0:10] + ")"' \
>> "$CONTEXT_DIR/decisions.md" || echo "No decisions logged yet" >> "$CONTEXT_DIR/decisions.md"
log_success "Context synthesized to: $CONTEXT_DIR/"
}
# Show session summary
show_summary() {
echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ 🧠 BlackRoad Memory System Status ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
echo ""
if [ ! -f "$SESSION_DIR/current-session.json" ]; then
log_warning "No active session. Run: $0 new [name]"
return 0
fi
# Current session
local session_id=$(jq -r '.session_id' "$SESSION_DIR/current-session.json" 2>/dev/null || echo "unknown")
local session_time=$(jq -r '.timestamp' "$SESSION_DIR/current-session.json" 2>/dev/null || echo "unknown")
local working_dir=$(jq -r '.context.working_directory' "$SESSION_DIR/current-session.json" 2>/dev/null || echo "unknown")
echo -e " ${GREEN}Session:${NC} $session_id"
echo -e " ${GREEN}Started:${NC} $session_time"
echo -e " ${GREEN}Directory:${NC} $working_dir"
echo ""
# Journal stats
if [ -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
local total_entries=$(wc -l < "$JOURNAL_DIR/master-journal.jsonl")
local last_hash=$(tail -1 "$JOURNAL_DIR/master-journal.jsonl" | jq -r '.sha256' | cut -c1-16)
local last_action=$(tail -1 "$JOURNAL_DIR/master-journal.jsonl" | jq -r '.action + ": " + .entity')
echo -e " ${GREEN}Total entries:${NC} $total_entries"
echo -e " ${GREEN}Last hash:${NC} ${last_hash}..."
echo -e " ${GREEN}Last action:${NC} $last_action"
echo ""
# Recent changes
echo -e "${BLUE}Recent changes:${NC}"
tail -5 "$JOURNAL_DIR/master-journal.jsonl" | \
jq -r '" [" + .timestamp[11:19] + "] " + .action + ": " + .entity' | \
sed "s/^/ /"
else
log_warning "No journal entries yet"
fi
echo ""
}
# Verify integrity
verify_integrity() {
log_info "Verifying memory integrity..."
if [ ! -f "$JOURNAL_DIR/master-journal.jsonl" ]; then
log_error "No journal found"
return 1
fi
local entries=$(wc -l < "$JOURNAL_DIR/master-journal.jsonl")
local valid=0
local invalid=0
local line_num=0
while IFS= read -r line; do
line_num=$((line_num + 1))
# Support both old format (prev_hash/hash) and new format (parent_hash/sha256)
local parent_hash=$(echo "$line" | jq -r 'if .parent_hash then .parent_hash elif .prev_hash then .prev_hash else "0000000000000000" end')
local stored_hash=$(echo "$line" | jq -r 'if .sha256 then .sha256 elif .hash then .hash else "unknown" end')
# Skip genesis entries and session roots (entries with no valid parent)
if [ "$parent_hash" = "0000000000000000" ] || [ "$parent_hash" = "null" ] || [ -z "$parent_hash" ]; then
valid=$((valid + 1))
continue
fi
# Verify parent exists in previous entries (check both hash field names)
if [ $line_num -le 1 ]; then
invalid=$((invalid + 1))
log_warning "Broken chain at entry $line_num (hash: ${stored_hash:0:8}...)"
elif head -$((line_num - 1)) "$JOURNAL_DIR/master-journal.jsonl" | grep -qE "\"(sha256|hash)\":\"$parent_hash\""; then
valid=$((valid + 1))
else
invalid=$((invalid + 1))
log_warning "Broken chain at entry $line_num (hash: ${stored_hash:0:8}...)"
fi
done < "$JOURNAL_DIR/master-journal.jsonl"
echo ""
if [ $invalid -eq 0 ]; then
log_success "Memory integrity verified ✅ ($valid entries, 0 broken)"
else
log_error "Found $invalid broken entries ❌ ($valid valid entries)"
return 1
fi
}
# Export context for Claude Code
export_context() {
log_info "Exporting context for Claude Code..."
synthesize_context
cat > "$CONTEXT_DIR/session-restore-context.md" <<EOF
# 🧠 BlackRoad Session Context
**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
---
## Session Summary
$(show_summary 2>&1)
---
## Recent Infrastructure Changes
$(grep -E '"action":"(deployed|configured|allocated)"' "$JOURNAL_DIR/master-journal.jsonl" 2>/dev/null | tail -20 | jq -r '"- [" + .timestamp[0:19] + "] **" + .action + "**: " + .entity + " — " + .details' || echo "No infrastructure changes yet")
---
## Recent Decisions
$(grep -E '"action":"decided"' "$JOURNAL_DIR/master-journal.jsonl" 2>/dev/null | tail -10 | jq -r '"- [" + .timestamp[0:19] + "] **" + .entity + "**: " + .details' || echo "No decisions logged yet")
---
## Active Deployments
$(ssh pi@aria64 "docker ps --format '{{.Names}} → {{.Ports}}'" 2>/dev/null || echo "Cannot reach aria64")
---
## Current Working State
**Directory:** $(pwd)
**Git Status:**
\`\`\`
$(git status -s 2>/dev/null || echo "Not in git repository")
\`\`\`
---
**Memory integrity:** $(verify_integrity &>/dev/null && echo "✅ Verified" || echo "⚠️ Check needed")
EOF
log_success "Context exported to: $CONTEXT_DIR/session-restore-context.md"
echo ""
echo "View with: cat $CONTEXT_DIR/session-restore-context.md"
}
# Show help
show_help() {
cat <<EOF
BlackRoad Memory System v${VERSION}
USAGE:
memory-system.sh <command> [options]
COMMANDS:
init Initialize memory system
new [session-name] Create new session
log <action> <entity> [details] Log action to journal
summary Show current session summary
synthesize Synthesize context from journals
verify Verify memory integrity
export Export context for Claude Code
help Show this help
EXAMPLES:
# Initialize
memory-system.sh init
# Start new session
memory-system.sh new infrastructure-audit
# Log actions
memory-system.sh log deployed "api.blackroad.io" "Port 8080, FastAPI"
memory-system.sh log created "new-script.sh" "Automated deployment"
memory-system.sh log decided "architecture" "Using Headscale for mesh"
# View state
memory-system.sh summary
memory-system.sh export
# Verify
memory-system.sh verify
MEMORY LOCATIONS:
Sessions: $SESSION_DIR/
Journals: $JOURNAL_DIR/
Ledger: $LEDGER_DIR/
Context: $CONTEXT_DIR/
EOF
}
# Main command handler
case "${1:-help}" in
init)
init_memory
;;
new)
new_session "${2:-default}"
;;
log)
if [ -z "$2" ] || [ -z "$3" ]; then
log_error "Usage: $0 log <action> <entity> [details]"
exit 1
fi
log_action "$2" "$3" "${4:-}"
;;
synthesize)
synthesize_context
;;
summary)
show_summary
;;
verify)
verify_integrity
;;
export)
export_context
;;
help|--help|-h)
show_help
;;
*)
log_error "Unknown command: $1"
echo ""
show_help
exit 1
;;
esac