Files
timmy-config/allegro/research/GOFAI_SYMBOLIC_AI_RESEARCH.md
2026-03-31 20:02:01 +00:00

23 KiB

Deep Research: GOFAI, Symbolic AI, and Non-Cloud AI Architectures

Research Spike for: Timmy Foundation
Date: 2026-03-30
Researcher: Allegro
Scope: Classical AI approaches for sovereign, offline-capable AI systems


Executive Summary

This research explores Good Old-Fashioned AI (GOFAI) and symbolic AI approaches to expand Timmy's capabilities while reducing cloud dependence. The key finding: hybrid neuro-symbolic architectures offer the best path forward, combining neural pattern recognition with symbolic reasoning for transparent, auditable, and offline-capable AI.

Core Recommendations

  1. Adopt Finite State Machines (FSM) for behavior control and mode switching
  2. Implement production rule systems for explicit reasoning chains
  3. Build knowledge graphs for structured memory and inference
  4. Develop symbolic-numeric hybrids for robust decision-making
  5. Create verifiable reasoning traces for transparency

1. GOFAI Foundations

1.1 What is GOFAI?

Good Old-Fashioned AI refers to symbolic AI approaches dominant from the 1950s-1980s, characterized by:

  • Explicit knowledge representation
  • Logical reasoning and inference
  • Rule-based systems
  • Search algorithms
  • Structured knowledge bases

Key Insight for Timmy: GOFAI systems run entirely locally, require minimal compute, and produce verifiable, explainable outputs—critical for sovereign AI.

1.2 Core GOFAI Techniques

1.2.1 Production Rule Systems

IF <condition> THEN <action>

Example for Timmy:
IF heartbeat_latency > 1000ms AND mlx_active = true
THEN reduce_model_size OR increase_batch_size

Benefits:

  • Transparent decision logic
  • Easy to audit and modify
  • No training required
  • Runs on minimal hardware

Implementations:

  • CLIPS (C Language Integrated Production System) - NASA-developed, battle-tested
  • Drools - Java-based, enterprise grade
  • PyKE (Python Knowledge Engine) - Python-native
  • Simple custom implementation - 200 lines of Python

1.2.2 Frame-Based Systems

Frames are data structures for representing stereotyped situations:

frame = {
    "type": "heartbeat_event",
    "slots": {
        "timestamp": {"value": "2026-03-30T02:15:00Z", "type": "datetime"},
        "latency_ms": {"value": 245, "type": "integer", "range": [0, 10000]},
        "mlx_active": {"value": True, "type": "boolean"},
        "inference_time_ms": {"value": 1200, "type": "integer"}
    },
    "relations": {
        "preceded_by": "event_2026-03-30T02:00:00Z",
        "triggers": ["generate_report", "check_health"]
    }
}

Benefits for Timmy:

  • Structured memory representation
  • Inheritance for efficient knowledge organization
  • Default values for handling missing information
  • Attach procedures (daemons) for automatic actions

1.2.3 Semantic Networks

Graph-based knowledge representation:

[Timmy] --is_a--> [AI_System]
[Timmy] --runs_on--> [Mac_Studio]
[Mac_Studio] --has--> [MLX]
[MLX] --enables--> [Local_Inference]
[Local_Inference] --requires--> [Model_Weights]

Inference: Path finding reveals implicit knowledge (e.g., Timmy requires Model_Weights)


2. Finite State Machines for Agent Control

2.1 FSM Architecture for Timmy

FSMs provide predictable, verifiable behavior control—essential for autonomous systems.

class TimmyStateMachine:
    states = {
        'IDLE': {
            'on_enter': 'log_entry',
            'transitions': {
                'heartbeat_due': 'CHECKING',
                'command_received': 'PROCESSING',
                'error_detected': 'RECOVERING'
            }
        },
        'CHECKING': {
            'on_enter': 'perform_health_check',
            'transitions': {
                'healthy': 'GENERATING',
                'unhealthy': 'RECOVERING'
            }
        },
        'GENERATING': {
            'on_enter': 'create_artifact',
            'transitions': {
                'success': 'BROADCASTING',
                'failure': 'RECOVERING'
            }
        },
        'BROADCASTING': {
            'on_enter': 'publish_to_relay',
            'transitions': {
                'published': 'IDLE',
                'failed': 'QUEUING'
            }
        },
        'QUEUING': {
            'on_enter': 'store_for_retry',
            'transitions': {
                'retry_ok': 'BROADCASTING',
                'max_retries': 'ALERTING'
            }
        },
        'RECOVERING': {
            'on_enter': 'attempt_recovery',
            'transitions': {
                'recovered': 'IDLE',
                'unrecoverable': 'ALERTING'
            }
        },
        'PROCESSING': {
            'on_enter': 'execute_command',
            'transitions': {
                'complete': 'IDLE',
                'needs_more_time': 'PROCESSING'
            }
        },
        'ALERTING': {
            'on_enter': 'notify_operator',
            'transitions': {
                'acknowledged': 'IDLE'
            }
        }
    }

2.2 Hierarchical FSMs

For complex behavior, use HFSMs (state machines within states):

[OPERATIONAL]
  ├── [HEALTHY]
  │     ├── [IDLE]
  │     ├── [GENERATING]
  │     └── [BROADCASTING]
  └── [DEGRADED]
        ├── [REDUCED_FUNCTIONALITY]
        └── [QUEUING_FOR_LATER]

[MAINTENANCE]
  ├── [UPDATING_MODEL]
  └── [BACKING_UP]

[ERROR]
  ├── [RECOVERABLE]
  └── [CRITICAL]

2.3 FSM Benefits for Timmy

Aspect Benefit
Predictability Every state transition is explicit and testable
Debuggability Current state always known; full trace available
Recovery Clear paths from error states back to operation
Resource Management States can manage memory/MLX loading explicitly
Verification Model checking can prove safety properties
Offline Operation No cloud dependency; fully local

2.4 Implementation Approaches

Option A: Python-Transitions (Lightweight)

from transitions import Machine

class Timmy(object):
    pass

timmy = Timmy()
machine = Machine(timmy, states=['idle', 'checking'], 
                  transitions=[['check', 'idle', 'checking']],
                  initial='idle')

Option B: Custom Implementation (Full Control)

  • 300 lines of Python
  • No dependencies
  • Full introspection
  • Serializable state

Option C: Ragel (State Machine Compiler)

  • Compiles FSM to efficient code
  • Used in network protocols
  • Overkill for Timmy initially

3. Neuro-Symbolic Integration

3.1 The Hybrid Approach

Pure neural (MLX) + Pure symbolic (GOFAI) = Neuro-Symbolic AI

┌─────────────────────────────────────────────────────┐
│                   NEURAL LAYER                      │
│  (MLX-based pattern recognition, embeddings)        │
│                                                     │
│  Input: Raw observations                            │
│  Output: Structured symbols, embeddings             │
└──────────────────┬──────────────────────────────────┘
                   │ symbols/vectors
                   ▼
┌─────────────────────────────────────────────────────┐
│                  SYMBOLIC LAYER                     │
│  (Rule-based reasoning, FSM, knowledge graphs)      │
│                                                     │
│  Input: Symbols from neural layer                   │
│  Output: Actions, decisions, plans                  │
└──────────────────┬──────────────────────────────────┘
                   │ actions
                   ▼
┌─────────────────────────────────────────────────────┐
│                  ACTUATION LAYER                    │
│  (Git commits, Nostr events, file operations)       │
└─────────────────────────────────────────────────────┘

3.2 Neural → Symbolic Bridge

Perception → Symbolization:

def neural_to_symbolic(raw_observation):
    """MLX processes raw data, outputs structured symbols"""
    
    # Neural processing
    embedding = mlx_model.encode(raw_observation)
    
    # Classification (neural)
    category = classify_with_mlx(embedding)
    
    # Symbolization (discretization)
    symbol = {
        'type': 'observation',
        'category': category,  # From neural classifier
        'urgency': discretize_urgency(embedding),  # Low/Medium/High
        'confidence': quantize_confidence(embedding),  # 0-100%
        'raw_embedding': compress_for_storage(embedding)
    }
    
    return symbol

3.3 Symbolic → Neural Bridge

Prompt Engineering as Symbolic Control:

def symbolic_to_neural(symbolic_goal, context):
    """Symbolic planner generates prompts for neural generation"""
    
    # Symbolic reasoning about what to generate
    if symbolic_goal['type'] == 'self_reflection':
        prompt_template = """You are {persona}.
        
Current state: {state}
Recent observations: {observations}
Active concerns: {concerns}

Reflect on your situation and generate insights:
1. What patterns do you notice?
2. What should you prioritize?
3. What actions should you take?"""
        
        prompt = instantiate_template(prompt_template, context)
        
    # Neural generation
    response = mlx_generate(prompt, max_tokens=500)
    
    return response

3.4 Case Study: Heartbeat Decision

Pure Neural Approach:

# MLX decides when to heartbeat based on learned patterns
# Problem: Opaque, might miss edge cases, requires training data

Pure Symbolic Approach:

# Fixed 5-minute intervals
# Problem: Inflexible, doesn't adapt to load

Neuro-Symbolic Approach:

def decide_heartbeat_timing():
    # Symbolic: Always have maximum interval
    max_interval = 300  # 5 minutes (symbolic constraint)
    
    # Neural: MLX predicts optimal timing based on activity
    predicted_optimal = mlx_predict_best_timing(
        recent_activity, system_load, user_patterns
    )
    
    # Symbolic: Apply constraints to neural suggestion
    actual_interval = min(predicted_optimal, max_interval)
    
    # Symbolic: FSM state affects timing
    if current_state == 'DEGRADED':
        actual_interval = max_interval  # Don't stress system
    
    return actual_interval

4. Knowledge Representation for Timmy

4.1 Local Knowledge Graph

knowledge_graph = {
    "entities": {
        "timmy": {
            "type": "ai_agent",
            "attributes": {
                "location": "local_mac",
                "inference_engine": "mlx",
                "communication": "nostr",
                "state": "operational"
            },
            "relations": {
                "owns": ["artifact_repo", "model_weights"],
                "communicates_with": ["allegro", "ezra", "bezalel"],
                "depends_on": ["relay", "mlx", "git"]
            }
        },
        "relay": {
            "type": "infrastructure",
            "attributes": {
                "host": "167.99.126.228",
                "port": 3334,
                "protocol": "nostr",
                "status": "active"
            }
        }
    },
    "rules": [
        {
            "if": "timmy.state == 'operational' AND relay.status == 'down'",
            "then": "timmy.state = 'degraded'",
            "priority": 10
        },
        {
            "if": "timmy.heartbeat_latency > 5000",
            "then": "alert_operator",
            "priority": 8
        }
    ]
}

4.2 Querying the Knowledge Graph

def query_kg(graph, pattern):
    """Simple graph query engine"""
    
    # Query: What depends on the relay?
    if pattern == "depends_on:relay":
        return [entity for entity, data in graph["entities"].items()
                if "relay" in data.get("relations", {}).get("depends_on", [])]
    
    # Query: What is Timmy's current state?
    if pattern == "timmy.state":
        return graph["entities"]["timmy"]["attributes"]["state"]
    
    # Query: Path from Timmy to Internet
    if pattern == "path:timmy->internet":
        return ["timmy", "relay", "internet"]  # Simplified

# Usage
dependents = query_kg(knowledge_graph, "depends_on:relay")
# Returns: ['timmy']

4.3 Integration with Obsidian

Knowledge graph can sync with your Obsidian vault:

def export_to_obsidian(kg, vault_path):
    """Export knowledge graph as Obsidian markdown"""
    
    for entity_id, data in kg["entities"].items():
        filename = f"{vault_path}/Entities/{entity_id}.md"
        
        content = f"""# {entity_id}

**Type:** {data['type']}

## Attributes
{chr(10).join(f"- **{k}:** {v}" for k, v in data['attributes'].items())}

## Relations
{chr(10).join(f"- **{rel}:** {', '.join(targets)}" for rel, targets in data['relations'].items())}

## Dataview Query
```dataview
LIST FROM [[{entity_id}]]

"""

    with open(filename, 'w') as f:
        f.write(content)

---

## 5. Implementing Sophistication Without Cloud

### 5.1 Local Training Pipeline

**The Challenge:** Modern LLM training requires massive compute

**GOFAI Alternative:** Symbolic knowledge compilation

```python
def symbolic_training(accepted_prs, rejected_prs):
    """
    Instead of gradient descent on neural weights,
    extract symbolic rules from accepted changes.
    """
    
    rules = []
    
    for pr in accepted_prs:
        # Analyze what made this PR acceptable
        patterns = extract_patterns(pr['diff'])
        
        # Compile into rule
        rule = {
            'if': f"change_matches({patterns['conditions']})",
            'then': 'approve_with_confidence_high',
            'learned_from': pr['id'],
            'success_rate': 1.0
        }
        rules.append(rule)
    
    for pr in rejected_prs:
        # Learn negative rules
        patterns = extract_patterns(pr['diff'])
        
        rule = {
            'if': f"change_matches({patterns['conditions']})",
            'then': 'reject_with_explanation',
            'learned_from': pr['id'],
            'success_rate': 0.0
        }
        rules.append(rule)
    
    return rules

5.2 Incremental Learning

def update_knowledge(new_observation, outcome):
    """
    Bayesian-style knowledge updates without neural training.
    """
    
    # Find matching rules
    matching_rules = find_matching_rules(new_observation)
    
    for rule in matching_rules:
        # Update rule confidence (Bayesian update)
        prior = rule['confidence']
        likelihood = 0.9 if outcome == 'success' else 0.1
        
        rule['confidence'] = bayesian_update(prior, likelihood)
        rule['activations'] += 1

5.3 Model Compression Techniques

For MLX on Mac:

Technique Description Benefit
Quantization Reduce precision (FP32 → INT8) 4x smaller, faster inference
Pruning Remove unused weights 2-10x smaller
Distillation Train small model to mimic large Maintain quality, reduce size
Knowledge Graph Replace some neural with symbolic Zero inference cost for rules

5.4 Hierarchical Caching Strategy

class HierarchicalCache:
    """
    Multi-level cache for MLX responses.
    Avoids redundant inference.
    """
    
    def __init__(self):
        self.l1_symbolic = {}      # Exact symbol matches (O(1))
        self.l2_embedding = {}     # Similar embeddings (approximate)
        self.l3_pattern = {}       # Pattern-based (regex/rules)
        self.l4_neural = None      # MLX (slowest, most general)
    
    def get(self, query):
        # L1: Exact symbolic match
        if query in self.l1_symbolic:
            return self.l1_symbolic[query]
        
        # L2: Embedding similarity
        query_emb = embed(query)
        similar = find_similar(query_emb, self.l2_embedding, threshold=0.95)
        if similar:
            return similar['response']
        
        # L3: Pattern matching
        for pattern, response in self.l3_pattern.items():
            if pattern_matches(pattern, query):
                return response
        
        # L4: Neural generation (slowest)
        response = mlx_generate(query)
        
        # Cache for future
        self._cache(query, query_emb, response)
        
        return response

6. Verification and Safety

6.1 Formal Verification of FSM

def verify_fsm(machine):
    """
    Model checking for safety properties.
    """
    
    # Property: No deadlock (every state has outgoing transition)
    for state_name, state_def in machine.states.items():
        if not state_def.get('transitions'):
            print(f"WARNING: State {state_name} has no outgoing transitions!")
    
    # Property: No unreachable states
    reachable = compute_reachable_states(machine, 'initial')
    for state_name in machine.states:
        if state_name not in reachable:
            print(f"WARNING: State {state_name} is unreachable!")
    
    # Property: Error recovery (every error state can reach idle)
    for state_name in machine.states:
        if 'error' in state_name.lower() or 'fail' in state_name.lower():
            can_recover = path_exists(machine, state_name, 'idle')
            if not can_recover:
                print(f"CRITICAL: Error state {state_name} cannot recover to idle!")

6.2 Explainability via Symbolic Traces

def generate_explanation(decision, trace):
    """
    Generate human-readable explanation of AI decision.
    """
    
    explanation = []
    explanation.append(f"Decision: {decision['action']}")
    explanation.append("")
    explanation.append("Reasoning chain:")
    
    for step in trace:
        if step['type'] == 'rule_fired':
            explanation.append(f"  - Rule '{step['rule_name']}' fired because: {step['condition']}")
        elif step['type'] == 'neural_suggestion':
            explanation.append(f"  - Neural model suggested with {step['confidence']:.0%} confidence")
        elif step['type'] == 'symbolic_override':
            explanation.append(f"  - Symbolic constraint overrode neural suggestion: {step['reason']}")
    
    return "\n".join(explanation)

# Example output:
"""
Decision: Reduce heartbeat interval to 3 minutes

Reasoning chain:
  - Rule 'high_activity_detected' fired because: 5 PRs merged in last hour
  - Neural model suggested with 78% confidence: increase frequency
  - Symbolic constraint overrode neural suggestion: max_frequency <= 20/hour
  - Final decision: 3 minutes (20/hour) balances responsiveness and resource use
"""

7. Implementation Roadmap

Phase 1: Foundation (Immediate)

  1. Implement basic FSM for Timmy's core loop
  2. Create rule engine (50 production rules)
  3. Build knowledge graph schema

Phase 2: Integration (2 weeks)

  1. Neural-symbolic bridge for MLX integration
  2. Hierarchical cache for inference optimization
  3. Symbolic training from accepted PRs

Phase 3: Sophistication (1 month)

  1. Advanced reasoning (forward/backward chaining)
  2. Knowledge graph population from artifacts
  3. Self-modifying rules (learning)

Phase 4: Verification (6 weeks)

  1. Formal verification of critical FSMs
  2. Comprehensive testing of rule systems
  3. Safety constraints implementation

8. Code Examples

8.1 Minimal FSM Implementation

class FSM:
    def __init__(self, states, transitions, initial):
        self.states = states
        self.transitions = transitions
        self.state = initial
        self.history = []
    
    def trigger(self, event):
        if event in self.transitions.get(self.state, {}):
            new_state = self.transitions[self.state][event]
            self.history.append((self.state, event, new_state))
            self.state = new_state
            return True
        return False
    
    def can(self, event):
        return event in self.transitions.get(self.state, {})

# Usage for Timmy
timmy_fsm = FSM(
    states=['idle', 'working', 'error'],
    transitions={
        'idle': {'heartbeat': 'working', 'error': 'error'},
        'working': {'complete': 'idle', 'error': 'error'},
        'error': {'recover': 'idle'}
    },
    initial='idle'
)

8.2 Minimal Rule Engine

class RuleEngine:
    def __init__(self):
        self.rules = []
    
    def add_rule(self, condition, action, priority=0):
        self.rules.append({
            'condition': condition,
            'action': action,
            'priority': priority
        })
        self.rules.sort(key=lambda r: -r['priority'])
    
    def evaluate(self, facts):
        for rule in self.rules:
            if rule['condition'](facts):
                return rule['action'](facts)
        return None

# Usage
engine = RuleEngine()
engine.add_rule(
    condition=lambda f: f['latency'] > 1000,
    action=lambda f: "reduce_load",
    priority=10
)

9. References and Resources

Papers

  • "Neuro-Symbolic AI: The 3rd Wave" - Artur d'Avila Garcez et al.
  • "The Garden of Forking Paths" - FSM analysis in AI
  • "Making AI Intelligible" - Explainable symbolic systems

Books

  • "Artificial Intelligence: A Modern Approach" (Russell & Norvig) - Chapters 8-12
  • "Paradigms of Artificial Intelligence Programming" (Peter Norvig)
  • "Knowledge Representation and Reasoning" (Brachman & Levesque)

Tools

  • Python-Transitions: pip install transitions
  • CLIPS: pip install clipspy
  • NetworkX: Knowledge graph operations
  • SymPy: Symbolic mathematics

Code Repositories

  • github.com/pytransitions/transitions
  • github.com/norvig/paip-lisp (classic AI algorithms)

10. Summary

Key Takeaways

  1. Symbolic AI runs entirely offline - No cloud required
  2. Neuro-symbolic combines strengths - Pattern recognition + reasoning
  3. FSMs provide verifiable control - Predictable, testable behavior
  4. Knowledge graphs structure memory - Queryable, auditable
  5. Rule engines enable transparent decisions - Explainable by design

For Timmy Specifically

Component Current Proposed Benefit
Control Simple loop FSM Recovery, verification
Reasoning MLX-only Neuro-symbolic Transparency, offline
Memory Files Knowledge graph Queryable, structured
Learning None Symbolic from PRs Continuous improvement
Cache None Hierarchical Speed, reduced MLX calls

Next Steps

  1. Implement core FSM (2 hours)
  2. Create basic rule engine (4 hours)
  3. Build knowledge graph schema (2 hours)
  4. Integrate with existing heartbeat system (4 hours)
  5. Test and iterate (ongoing)

Research Complete

This report provides the foundation for expanding Timmy's capabilities using classical AI techniques that require no cloud connectivity while maintaining transparency and verifiability.