#!/bin/bash #=============================================================================== # active_learner.sh - Active Learning with Information-Theoretic Selection # # Implements: # - Information gain computation for topic selection # - Uncertainty sampling # - Curiosity-driven exploration with UCB # - Spaced repetition (SM-2 algorithm) # - Knowledge graph integration #=============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/../orchestrator/lib/supervisor.sh" 2>/dev/null || true #=============================================================================== # CONFIGURATION #=============================================================================== LEARNER_DB="${LEARNER_DB:-${SCRIPT_DIR}/knowledge_graph.db}" LEARNER_STATE="${LEARNER_STATE:-${SCRIPT_DIR}/learner_state.json}" LEARNER_LOG="${LEARNER_LOG:-${SCRIPT_DIR}/learner.log}" # Learning parameters (golden ratio scaled) PHI=1.6180339887 ALPHA=$(echo "scale=4; 0.1 * $PHI" | bc) BETA=$(echo "scale=4; 0.05 * $PHI" | bc) GAMMA=$(echo "scale=4; 0.02 * $PHI" | bc) # UCB parameters UCB_C=2.0 # Exploration constant # Spaced repetition (SM-2) SM2_INITIAL_INTERVAL=1 # days SM2_INITIAL_EF=2.5 #=============================================================================== # KNOWLEDGE CATEGORIES AND SOURCES #=============================================================================== declare -A CATEGORIES=( [science]="physics chemistry biology astronomy neuroscience" [technology]="ai ml programming systems networks" [philosophy]="ethics metaphysics epistemology consciousness" [mathematics]="algebra calculus topology statistics" [arts]="music literature visual_arts" [history]="ancient modern contemporary" ) declare -A SOURCES=( [wikipedia]="https://en.wikipedia.org/api/rest_v1/page/random/summary" [arxiv]="http://export.arxiv.org/api/query?search_query=cat:cs.AI&max_results=1&sortBy=submittedDate" [semantic_scholar]="https://api.semanticscholar.org/graph/v1/paper/search?query=" ) #=============================================================================== # DATABASE OPERATIONS #=============================================================================== db_init() { sqlite3 "$LEARNER_DB" <<'SQL' CREATE TABLE IF NOT EXISTS concepts ( id INTEGER PRIMARY KEY, concept TEXT UNIQUE, domain TEXT, category TEXT, confidence REAL DEFAULT 0.5, uncertainty REAL DEFAULT 1.0, access_count INTEGER DEFAULT 0, info_gain REAL DEFAULT 1.0, created_at TEXT DEFAULT (datetime('now')), last_accessed TEXT ); CREATE TABLE IF NOT EXISTS repetition ( concept_id INTEGER PRIMARY KEY, repetition INTEGER DEFAULT 0, easiness REAL DEFAULT 2.5, interval REAL DEFAULT 1, next_review TEXT, last_review TEXT, last_quality INTEGER DEFAULT 3, FOREIGN KEY (concept_id) REFERENCES concepts(id) ); CREATE TABLE IF NOT EXISTS learning_history ( id INTEGER PRIMARY KEY, concept_id INTEGER, action TEXT, reward REAL, timestamp TEXT DEFAULT (datetime('now')), FOREIGN KEY (concept_id) REFERENCES concepts(id) ); CREATE INDEX IF NOT EXISTS idx_concepts_domain ON concepts(domain); CREATE INDEX IF NOT EXISTS idx_concepts_uncertainty ON concepts(uncertainty DESC); CREATE INDEX IF NOT EXISTS idx_repetition_next ON repetition(next_review); SQL } db_query() { sqlite3 -separator '|' "$LEARNER_DB" "$1" } #=============================================================================== # INFORMATION GAIN COMPUTATION #=============================================================================== # Compute information gain for learning a new concept compute_info_gain() { local concept="$1" local domain="$2" # Check if already known local existing=$(db_query "SELECT confidence FROM concepts WHERE concept='$concept' LIMIT 1") if [[ -n "$existing" ]]; then # Low gain for already-known concepts echo "0.1" return fi # Get domain statistics local domain_count=$(db_query "SELECT COUNT(*) FROM concepts WHERE domain='$domain'") local domain_avg_conf=$(db_query "SELECT COALESCE(AVG(confidence), 0.5) FROM concepts WHERE domain='$domain'") if [[ -z "$domain_count" ]] || [[ "$domain_count" == "0" ]]; then # New domain = high information gain echo "1.0" return fi # Information gain = entropy reduction # More uncertain domains have higher gain local uncertainty=$(echo "scale=4; 1 - $domain_avg_conf" | bc) # Bonus for connecting to existing knowledge local connectivity_bonus=$(echo "scale=4; l($domain_count + 1) / 10" | bc -l 2>/dev/null || echo "0.1") local gain=$(echo "scale=4; $uncertainty * (1 + $connectivity_bonus)" | bc) # Clamp to [0, 1] if (( $(echo "$gain > 1" | bc -l) )); then gain="1.0" fi echo "$gain" } #=============================================================================== # UCB (Upper Confidence Bound) SELECTION #=============================================================================== # UCB score: exploitation + exploration compute_ucb_score() { local concept_id="$1" local total_selections="$2" # Get concept statistics local stats=$(db_query "SELECT confidence, access_count, info_gain FROM concepts WHERE id=$concept_id") local confidence=$(echo "$stats" | cut -d'|' -f1) local access_count=$(echo "$stats" | cut -d'|' -f2) local info_gain=$(echo "$stats" | cut -d'|' -f3) # Exploitation: confidence * info_gain local exploitation=$(echo "scale=4; $confidence * $info_gain" | bc) # Exploration: sqrt(2 * ln(t) / n) local exploration=1.0 if [[ "$access_count" -gt 0 ]]; then exploration=$(echo "scale=4; sqrt($UCB_C * l($total_selections + 1) / $access_count)" | bc -l 2>/dev/null || echo "1") fi # UCB score echo "scale=4; $exploitation + $exploration" | bc } # Select best concept using UCB select_concept_ucb() { local domain="${1:-}" local limit="${2:-10}" local where_clause="" if [[ -n "$domain" ]]; then where_clause="WHERE domain='$domain'" fi # Get total selections local total=$(db_query "SELECT COUNT(*) FROM learning_history") # Get candidate concepts local candidates=$(db_query "SELECT id, concept, uncertainty FROM concepts $where_clause ORDER BY uncertainty DESC LIMIT $limit") local best_id="" local best_score="-999" while IFS='|' read -r id concept uncertainty; do [[ -z "$id" ]] && continue local score=$(compute_ucb_score "$id" "$total") if (( $(echo "$score > $best_score" | bc -l) )); then best_score="$score" best_id="$id" fi done <<< "$candidates" echo "$best_id" } #=============================================================================== # SPACED REPETITION (SM-2 ALGORITHM) #=============================================================================== # Get concepts due for review get_due_for_review() { local limit="${1:-10}" db_query " SELECT c.id, c.concept, c.domain, r.repetition, r.easiness FROM concepts c JOIN repetition r ON c.id = r.concept_id WHERE r.next_review <= datetime('now') ORDER BY r.next_review ASC LIMIT $limit " } # Record review result and update schedule record_review() { local concept_id="$1" local quality="$2" # 0-5 # Get current state local state=$(db_query "SELECT repetition, easiness, interval FROM repetition WHERE concept_id=$concept_id") local rep=$(echo "$state" | cut -d'|' -f1) local ef=$(echo "$state" | cut -d'|' -f2) local interval=$(echo "$state" | cut -d'|' -f3) # SM-2 algorithm if [[ "$quality" -lt 3 ]]; then # Failed: reset rep=0 interval=1 else # Success ((rep++)) if [[ "$rep" -eq 1 ]]; then interval=1 elif [[ "$rep" -eq 2 ]]; then interval=6 else interval=$(echo "scale=1; $interval * $ef" | bc) fi fi # Update easiness factor local ef_delta=$(echo "scale=4; 0.1 - (5 - $quality) * (0.08 + (5 - $quality) * 0.02)" | bc) ef=$(echo "scale=4; $ef + $ef_delta" | bc) if (( $(echo "$ef < 1.3" | bc -l) )); then ef="1.3" fi # Calculate next review date local next_review=$(date -d "+${interval%.*} days" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || \ date -v "+${interval%.*}d" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || \ echo "$(date '+%Y-%m-%d %H:%M:%S')") # Update database db_query " UPDATE repetition SET repetition = $rep, easiness = $ef, interval = $interval, next_review = '$next_review', last_review = datetime('now'), last_quality = $quality WHERE concept_id = $concept_id " # Update concept confidence if [[ "$quality" -ge 3 ]]; then local conf_boost=$(echo "scale=4; $ALPHA * $quality / 5" | bc) db_query "UPDATE concepts SET confidence = MIN(1.0, confidence + $conf_boost * (1 - confidence)) WHERE id = $concept_id" else db_query "UPDATE concepts SET confidence = MAX(0.1, confidence * (1 - $GAMMA)) WHERE id = $concept_id" fi # Log db_query "INSERT INTO learning_history (concept_id, action, reward) VALUES ($concept_id, 'review', $quality)" echo "Updated: rep=$rep, ef=$ef, interval=$interval, next=$next_review" } #=============================================================================== # ACTIVE LEARNING STRATEGIES #=============================================================================== # Strategy: EXPLORE - Random exploration strategy_explore() { local category="${1:-science}" local topics="${CATEGORIES[$category]:-physics}" # Pick random topic from category local topic=$(echo "$topics" | tr ' ' '\n' | shuf -n 1) echo "EXPLORE: Fetching from $topic in $category" # Fetch from Wikipedia local result=$(curl -s "${SOURCES[wikipedia]}" 2>/dev/null | jq -r '.title // empty') if [[ -n "$result" ]]; then local gain=$(compute_info_gain "$result" "$category") echo "Learned: $result (gain: $gain)" # Add to database db_query "INSERT OR IGNORE INTO concepts (concept, domain, category, info_gain) VALUES ('$result', '$category', '$topic', $gain)" local concept_id=$(db_query "SELECT id FROM concepts WHERE concept='$result'") db_query "INSERT OR IGNORE INTO repetition (concept_id, next_review) VALUES ($concept_id, datetime('now', '+1 day'))" fi } # Strategy: UNCERTAINTY - Sample most uncertain concepts strategy_uncertainty() { echo "UNCERTAINTY: Selecting most uncertain concept" local uncertain=$(db_query "SELECT id, concept, uncertainty FROM concepts ORDER BY uncertainty DESC LIMIT 1") if [[ -n "$uncertain" ]]; then local id=$(echo "$uncertain" | cut -d'|' -f1) local concept=$(echo "$uncertain" | cut -d'|' -f2) echo "Reinforcing: $concept" # Simulate learning (reduce uncertainty) db_query "UPDATE concepts SET uncertainty = uncertainty * 0.9, access_count = access_count + 1 WHERE id = $id" db_query "INSERT INTO learning_history (concept_id, action, reward) VALUES ($id, 'uncertainty', 0.1)" fi } # Strategy: UCB - Upper Confidence Bound selection strategy_ucb() { local domain="${1:-}" echo "UCB: Selecting by exploration-exploitation balance" local concept_id=$(select_concept_ucb "$domain") if [[ -n "$concept_id" ]]; then local concept=$(db_query "SELECT concept FROM concepts WHERE id=$concept_id") echo "Selected: $concept (id: $concept_id)" db_query "UPDATE concepts SET access_count = access_count + 1, last_accessed = datetime('now') WHERE id = $concept_id" db_query "INSERT INTO learning_history (concept_id, action, reward) VALUES ($concept_id, 'ucb', 0.5)" fi } # Strategy: REINFORCE - Review due concepts strategy_reinforce() { echo "REINFORCE: Reviewing due concepts" local due=$(get_due_for_review 5) if [[ -z "$due" ]]; then echo "No concepts due for review" return fi while IFS='|' read -r id concept domain rep ef; do [[ -z "$id" ]] && continue echo "Reviewing: $concept (repetition: $rep)" # Simulate review (random quality 3-5 for now) local quality=$((3 + RANDOM % 3)) record_review "$id" "$quality" done <<< "$due" } #=============================================================================== # MAIN LEARNING LOOP #=============================================================================== # Initialize if needed init_learner() { db_init # Initialize state file if [[ ! -f "$LEARNER_STATE" ]]; then cat > "$LEARNER_STATE" </dev/null || echo "0.5") # Update state file if command -v jq &>/dev/null; then local ticks=$(jq -r '.total_ticks' "$LEARNER_STATE") ((ticks++)) jq --arg ticks "$ticks" \ --arg learned "$total_concepts" \ --arg strategy "$strategy" \ --arg phi "$phi" \ '.total_ticks = ($ticks | tonumber) | .total_learned = ($learned | tonumber) | .current_strategy = $strategy | .phi = ($phi | tonumber)' \ "$LEARNER_STATE" > "${LEARNER_STATE}.tmp" && mv "${LEARNER_STATE}.tmp" "$LEARNER_STATE" fi echo "Tick complete: strategy=$strategy, concepts=$total_concepts, phi=$phi" } # Run learning daemon run_learner_daemon() { init_learner echo "Starting Active Learner Daemon" echo "Database: $LEARNER_DB" echo "State: $LEARNER_STATE" while true; do learning_tick sleep 60 # 1 tick per minute done } #=============================================================================== # CLI #=============================================================================== case "${1:-}" in init) init_learner echo "Initialized active learner" ;; tick) init_learner learning_tick ;; explore) init_learner strategy_explore "${2:-science}" ;; review) init_learner strategy_reinforce ;; status) init_learner echo "=== Active Learner Status ===" echo "Total concepts: $(db_query 'SELECT COUNT(*) FROM concepts')" echo "Avg confidence: $(db_query 'SELECT COALESCE(AVG(confidence), 0) FROM concepts')" echo "Due for review: $(db_query 'SELECT COUNT(*) FROM repetition WHERE next_review <= datetime("now")')" if [[ -f "$LEARNER_STATE" ]]; then echo "State: $(cat "$LEARNER_STATE")" fi ;; daemon) run_learner_daemon ;; *) echo "Usage: $0 {init|tick|explore|review|status|daemon}" ;; esac