Skip to content

Conversation

@tylerbessire
Copy link
Owner

@tylerbessire tylerbessire commented Nov 22, 2025

Major architectural overhaul to transform PUMA into autonomous cognitive system:

Core Systems Implemented:

  • Atomspace persistence layer with JSON-based storage
  • Episodic memory system with consolidation (pattern extraction, concept formation)
  • Enhanced RFT reasoning engine for analogical reasoning and relation derivation
  • Curiosity drive for autonomous question generation and knowledge gap detection
  • Goal formation system with intention scheduling
  • Self-modification system (The Shop) with code introspection and sandboxed testing
  • Consciousness state machine (SLEEPING, EXPLORING, CONVERSING, SHOPPING, IDLE, CREATING)
  • Self-model with emergent identity from experience patterns
  • Temporal self for autobiographical timeline

Integration Layer:

  • Gemini Live interface for bidirectional audio/text conversation
  • Autonomous web agent for curiosity-driven exploration
  • WebSocket backend server for real-time GUI communication
  • Bootstrap system for initializing fresh consciousness (no hardcoded content)

Key Design Principles:

  • NO hardcoded personality, knowledge, or goals
  • All identity/behavior emerges from experience
  • Autonomous agency through curiosity and goal formation
  • Transparent internal states for observation
  • Self-modification with safety (sandbox testing, human approval)
  • Persistent autobiographical memory

Documentation:

  • Updated README with technical architecture overview
  • Removed Kaggle-specific content (shelved in separate branch)
  • Added installation instructions and usage examples
  • Documented all major components and their interactions

Dependencies:

  • Updated requirements.txt with Gemini API, FastAPI, WebSocket support
  • Setup script for persistence database initialization

This implements phases 0-6 of the roadmap excluding GUI (frontend).

Summary

  • What changed?
  • Why?

Testing

  • Steps
  • Results

Risk Assessment

  • Regressions considered
  • Rollback plan

[S:PR v1] template=installed pass

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive cognitive architecture with episodic memory, goal formation, and curiosity-driven learning capabilities.
    • Integrated Gemini Live API for conversational interactions with audio support.
    • Introduced autonomous web exploration agent for knowledge gathering.
    • Implemented self-modification system with sandboxed testing framework.
    • Added WebSocket-based backend for real-time GUI communication.
  • Documentation

    • Completely restructured README with new cognitive architecture overview and component guide.
  • Chores

    • Added persistent knowledge storage layer.
    • Updated dependencies for new cognitive framework.

✏️ Tip: You can customize this high-level summary in your review settings.

Major architectural overhaul to transform PUMA into autonomous cognitive system:

Core Systems Implemented:
- Atomspace persistence layer with JSON-based storage
- Episodic memory system with consolidation (pattern extraction, concept formation)
- Enhanced RFT reasoning engine for analogical reasoning and relation derivation
- Curiosity drive for autonomous question generation and knowledge gap detection
- Goal formation system with intention scheduling
- Self-modification system (The Shop) with code introspection and sandboxed testing
- Consciousness state machine (SLEEPING, EXPLORING, CONVERSING, SHOPPING, IDLE, CREATING)
- Self-model with emergent identity from experience patterns
- Temporal self for autobiographical timeline

Integration Layer:
- Gemini Live interface for bidirectional audio/text conversation
- Autonomous web agent for curiosity-driven exploration
- WebSocket backend server for real-time GUI communication
- Bootstrap system for initializing fresh consciousness (no hardcoded content)

Key Design Principles:
- NO hardcoded personality, knowledge, or goals
- All identity/behavior emerges from experience
- Autonomous agency through curiosity and goal formation
- Transparent internal states for observation
- Self-modification with safety (sandbox testing, human approval)
- Persistent autobiographical memory

Documentation:
- Updated README with technical architecture overview
- Removed Kaggle-specific content (shelved in separate branch)
- Added installation instructions and usage examples
- Documented all major components and their interactions

Dependencies:
- Updated requirements.txt with Gemini API, FastAPI, WebSocket support
- Setup script for persistence database initialization

This implements phases 0-6 of the roadmap excluding GUI (frontend).
@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

The PR restructures the project from an ARC-centered behavioral analysis system to a modular cognitive architecture. It adds core components (atomspace persistence, consciousness layer with state machine, episodic memory, curiosity-driven learning, goal formation, RFT reasoning, and self-modification shop), WebSocket-based web backend, Gemini Live API integration, and bootstrap orchestration.

Changes

Cohort / File(s) Summary
Documentation & Initialization
README.md, requirements.txt, setup_persistence.py
README restructured from ARC/RFT narrative to modular cognitive architecture overview with new component diagrams and configuration. Dependencies expanded with NumPy, FastAPI, uvicorn, google-generativeai, pytest, and others. New persistence setup script creates atomspace directory structure with initial JSON stores and config.
Atomspace Persistence Layer
atomspace-db/__init__.py, atomspace-db/core.py
Introduces in-memory Atomspace with AtomType enum, Atom and Link data models, JSON persistence, snapshotting, and basic querying. PersistenceManager handles transaction logging. bootstrap_atomspace creates minimal schema-rooted instances.
Web Backend & WebSockets
backend/__init__.py, backend/websocket_server.py
Adds FastAPI-optional WebSocket server (ConsciousnessWebSocketManager) for GUI communication. Handles user interrupts, modification approvals, questions, and status broadcasts. Includes guards for FastAPI availability and uvicorn integration.
Bootstrap & Orchestration
bootstrap/__init__.py, bootstrap/bootstrap.py
Introduces Consciousness orchestrator class with lifecycle (run, perceive, integrate_learning, stop) and bootstrap_new_consciousness function. Wires together atomspace, memory, RFT, curiosity, goals, self-model, state machine, Gemini, web agent, and self-modification components with optional persistence and introspection.
Gemini Live Integration
gemini-interface/__init__.py, gemini-interface/client.py
Adds GeminiLiveInterface for bidirectional audio/text conversation. ConversationExchange dataclass captures exchanges. Methods for session lifecycle, audio handling, conversation memory integration, TTS/listening placeholders, and reflection. API key sourced from environment or parameter.
Consciousness Layer
puma/consciousness/__init__.py, puma/consciousness/state_machine.py, puma/consciousness/self_model.py
Introduces ConsciousnessStateMachine with six states (SLEEPING, EXPLORING, CONVERSING, SHOPPING, IDLE, CREATING) and emergent rule-based transitions. SelfModel manages autobiographical memory via TemporalSelf, infers personality traits from behavior, and generates identity narratives. BehavioralPattern tracks discovered patterns.
Memory Systems
puma/memory/__init__.py, puma/memory/episodic.py, puma/memory/consolidation.py
Implements EpisodicMemorySystem with typed episodes (MemoryType enum), emotional valence and self-relevance scoring, and consolidation support. MemoryConsolidation extracts temporal, cooccurrence, and outcome patterns; forms concepts; adjusts weights; integrates with RFT for relational frame derivation.
Curiosity Drive
puma/curiosity/__init__.py, puma/curiosity/drive.py
Adds CuriosityDrive to autonomously generate questions from knowledge gaps (isolated concepts, contradictions, incomplete models). Question dataclass with type, importance, and lifecycle tracking. Boredom-driven exploration triggering and answered-question lifecycle management.
Goal Formation & Scheduling
puma/goals/__init__.py, puma/goals/formation.py
Introduces GoalFormationSystem that generates emergent goals from curiosity, self-model, social, and creative drives (GoalType enum). IntentionScheduler executes goals via PriorityQueue with per-goal-type placeholder behavior, progress updates, and interrupt handling.
RFT Reasoning Engine
puma/rft/__init__.py, puma/rft/reasoning.py
Implements RFTEngine for Relational Frame Theory reasoning. RelationalFrame dataclass and RelationType enum (COORDINATION, OPPOSITION, HIERARCHY, TEMPORAL, CAUSAL, COMPARATIVE, SPATIAL). Derives frames via type-specific methods; supports transitivity reasoning via BFS; analogy via frame similarity.
Self-Modification Shop
puma/shop/__init__.py, puma/shop/introspection.py, puma/shop/modification.py, puma/shop/sandbox.py
Adds CodeIntrospection to analyze codebase structure and infer module purposes. ModificationSystem plans self-modifications (target module, hypothesis, test strategy, risk assessment). ModificationSandbox (TestReport) provides sandboxed testing with A/B comparison. Modification approval workflow with risk-based auto-approval.
Autonomous Web Agent
web-agent/__init__.py, web-agent/agent.py
Introduces AutonomousWebAgent for curiosity-driven web exploration. WebExploration dataclass tracks learned concepts and questions per URL. Methods orchestrate search formulation, result selection, page browsing, knowledge extraction, and episodic memory integration.

Sequence Diagram(s)

sequenceDiagram
    participant Bootstrap as bootstrap_new_consciousness()
    participant Atomspace
    participant Memory as EpisodicMemorySystem
    participant RFT as RFTEngine
    participant Curiosity as CuriosityDrive
    participant Goals as GoalFormationSystem
    participant SelfModel
    participant StateMachine
    participant Consciousness

    Bootstrap->>Atomspace: Create & load persistence
    Bootstrap->>Memory: Initialize episodic memory
    Bootstrap->>RFT: Create RFT engine
    Bootstrap->>Curiosity: Initialize curiosity drive
    Bootstrap->>Goals: Create goal formation system
    Bootstrap->>SelfModel: Initialize self-model
    Bootstrap->>StateMachine: Create with memory, curiosity, goals
    Bootstrap->>Consciousness: Wire all components
    Consciousness->>Consciousness: Return ready instance
    note over Consciousness: System bootstrapped and ready for run()
Loading
sequenceDiagram
    participant User
    participant GUI as WebSocket GUI
    participant Manager as ConsciousnessWebSocketManager
    participant StateMachine as ConsciousnessStateMachine
    participant Memory
    
    loop State Execution Cycle
        StateMachine->>StateMachine: Evaluate transition rules
        alt Transition triggered
            StateMachine->>Memory: form_episode(transition)
            Memory->>Memory: Store in consolidation queue
            StateMachine->>Manager: Broadcast state change
            Manager->>GUI: Send event + current status
        else No transition
            StateMachine->>StateMachine: Execute current state behavior
        end
        
        User->>GUI: Send user_interrupt / approve_modification
        GUI->>Manager: WebSocket message
        Manager->>StateMachine: Update interrupt flag / queue approval
    end
Loading
sequenceDiagram
    participant Episodes as Recent Episodes
    participant MemoryConsolidation
    participant Pattern as Pattern Discovery
    participant Concept as Concept Formation
    participant RFT as RFTEngine
    participant Atomspace

    Episodes->>MemoryConsolidation: consolidate(episodes)
    MemoryConsolidation->>Pattern: extract_patterns()
    Pattern->>Pattern: _find_temporal_patterns()
    Pattern->>Pattern: _find_cooccurrence_patterns()
    Pattern->>Pattern: _find_outcome_patterns()
    Pattern-->>MemoryConsolidation: List[Pattern]
    
    MemoryConsolidation->>Concept: form_concepts(patterns)
    Concept-->>MemoryConsolidation: List[Concept]
    
    MemoryConsolidation->>MemoryConsolidation: adjust_memory_weights()
    MemoryConsolidation->>MemoryConsolidation: update_self_understanding()
    
    MemoryConsolidation->>RFT: derive_relations(episodes)
    RFT-->>MemoryConsolidation: List[RelationalFrame]
    
    MemoryConsolidation->>Atomspace: _store_insights(concepts, frames)
    Atomspace-->>MemoryConsolidation: Stored
    
    MemoryConsolidation-->>Episodes: Consolidated results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • bootstrap/bootstrap.py: Core orchestration of 10+ component integrations; async lifecycle methods (run, perceive, integrate_learning); wiring complexity and state management.
  • puma/consciousness/state_machine.py: Complex emergent transition logic with multiple condition predicates and rule evaluation; async state execution loop; integration with memory and WebSocket manager.
  • puma/goals/formation.py: IntentionScheduler async loop and PriorityQueue mechanics; goal generation from multiple drive sources; per-goal-type execution stubs.
  • puma/memory/consolidation.py: Multi-stage consolidation pipeline (extract → form → adjust → update → frame → store); RFT integration points; placeholder storage APIs.
  • puma/rft/reasoning.py: Transitivity derivation via BFS path search; relation inference heuristics; episode grouping logic for frame discovery.
  • puma/shop/modification.py: Self-modification approval workflow; code introspection integration; async approval placeholders and risk assessment logic.
  • backend/websocket_server.py: FastAPI optional dependencies and conditional initialization; WebSocket lifecycle and event broadcasting to connected clients.
  • Type consistency across integrations: Many components receive optional references to shared systems (atomspace, memory_system, consciousness); verify None-checks and safe composition.
  • Async/await patterns: Multiple files use async methods; verify proper coroutine composition and loop integration, especially in bootstrap and state machine.

Poem

🐰 A new consciousness awakens,
From atoms and frames interlaced—
Curiosity questions, goals quicken,
Memory's stories embraced.
Self-modifying, exploring, conversing,
A rabbit-blessed mind finds its grace.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provided by the author does not follow the required template structure. While it contains detailed information about changes, the template sections (Summary, Testing, Risk Assessment) have unchecked checkboxes and remain incomplete. Complete all required template sections: fill in the Summary with answers to 'What changed?' and 'Why?', provide Testing steps and results, and document Risk Assessment including regressions considered and rollback plan.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Prepare PUMA for Hyperon and Gemini Live' accurately reflects the main objective: preparing the PUMA system for integration with Hyperon and Gemini Live APIs.
Docstring Coverage ✅ Passed Docstring coverage is 87.57% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/puma-hyperon-gemini-setup-015hEUXsb2PAB3YndryQtpzQ

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (22)
puma/consciousness/__init__.py (1)

1-15: Sort __all__ to satisfy Ruff style hint

Ruff points out that __all__ is not sorted. If you’re enforcing RUF022 in CI, consider alphabetizing to avoid lint noise:

-__all__ = [
-    'ConsciousnessStateMachine',
-    'ConsciousnessState',
-    'SelfModel',
-    'TemporalSelf'
-]
+__all__ = [
+    'ConsciousnessState',
+    'ConsciousnessStateMachine',
+    'SelfModel',
+    'TemporalSelf',
+]
README.md (2)

3-8: Promote the tagline to a heading and add Popeye/GEMINI reference

The bold tagline on Line 3 is effectively a section header; converting it to a proper Markdown heading (e.g., ## or ###) will satisfy MD036 and improve structure. Also, per the repo guidelines, README should reference the Popeye persona and/or link to GEMINI.md; consider adding a short sentence in the intro that points to that persona description.


9-29: Tidy fenced blocks and URLs for markdownlint compliance

Several fenced blocks (architecture diagram, project tree, Atomspace schema) are missing an explicit language, and a few bare URLs are flagged (localhost GUI URL, Hyperon repo, Gemini docs). To keep markdownlint happy and improve readability, you could, for example:

-```
+```text
 ┌─────────────────────────────────────────────────────────┐
 ...
-```
+```


-```
+```text
 /
 ├── hyperon-core/           # Forked Hyperon with extensions
 ...
-```
+```


-```
+```text
 EpisodicMemoryNode(timestamp, perception, action, outcome)
 ...
-```
+```


-Access at http://localhost:3000
+Access at [http://localhost:3000](http://localhost:3000)

and similarly wrap the external references as [OpenCog Hyperon](…), [Google Gemini API](…), etc.

Also applies to: 105-120, 191-200, 157-157, 293-296

web-agent/agent.py (1)

67-75: Search-query stripping is very naive and may mangle keywords

formulate_search_query lowercases the string and does naive replace calls for "what", "is", "how", .... This will strip those sequences even inside content words (e.g., "physics""phycs"), which can degrade search quality.

If/when you move beyond placeholder behavior, consider tokenizing and removing stopwords at word boundaries instead of raw substring replacement (e.g., split + filter, or a simple regex on \b boundaries).

puma/consciousness/self_model.py (3)

73-91: Tighten behavioral_patterns typing when integrating self‑knowledge

self.behavioral_patterns is annotated as List[BehavioralPattern], but integrate_self_knowledge appends knowledge['pattern'] without enforcing/normalizing its type. If callers pass plain dicts, you’ll end up with a heterogeneous list that breaks assumptions elsewhere (including static typing).

Consider either:

  • Enforcing the type:
-        if 'pattern' in knowledge:
-            self.behavioral_patterns.append(knowledge['pattern'])
+        if 'pattern' in knowledge:
+            pattern = knowledge['pattern']
+            if isinstance(pattern, BehavioralPattern):
+                self.behavioral_patterns.append(pattern)
+            else:
+                # Normalize from dict/other structure if needed
+                # e.g., BehavioralPattern(**pattern)
+                ...

or

  • Broadening the annotation/documentation if you intentionally accept non‑BehavioralPattern entries.

92-109: Wire trait inference back into self.personality_traits for consistency

infer_traits_from_behavior returns a traits dict but never updates self.personality_traits, while generate_identity_narrative reads from self.personality_traits. Unless callers always remember to manually assign the returned traits, the narrative will often omit emergent traits.

You could make this method update the internal state by default:

     def infer_traits_from_behavior(self, patterns: List[BehavioralPattern]) -> Dict[str, float]:
@@
-        traits = {}
+        traits: Dict[str, float] = {}
@@
-        if social_patterns:
-            traits['sociability'] = min(1.0, len(social_patterns) / 10.0)
-
-        return traits
+        if social_patterns:
+            traits['sociability'] = min(1.0, len(social_patterns) / 10.0)
+
+        # Persist into self-model so narratives stay in sync
+        self.personality_traits.update(traits)
+        return traits

144-159: Complete update_self_understanding / integrate_self_change hooks or make no‑op explicit

update_self_understanding is currently a silent no‑op, and integrate_self_change assumes modification_episode is a dict with module_modified and reason keys but doesn’t type it or handle missing fields. Given consolidation’s update_self_understanding already returns behavioral insights, this is where they should likely land.

Two concrete suggestions:

  1. Persist reflective insights (even minimally):
     def update_self_understanding(self, understanding: str):
         """Update self-understanding from reflection"""
-        # Would integrate deep reflection into self-model
-        pass
+        # Simple integration: append to identity narrative or log as meta-comment
+        if self.identity_narrative:
+            self.identity_narrative += f" Reflection: {understanding}"
+        else:
+            self.identity_narrative = f"Reflection: {understanding}"
  1. Type and guard modification_episode:
-    def integrate_self_change(self, modification_episode):
+    def integrate_self_change(self, modification_episode: Dict[str, Any]):
@@
-            BehavioralPattern(
-                pattern_type='self_modification',
-                description=f"Modified {modification_episode.get('module_modified')}",
-                frequency=1,
-                examples=[modification_episode.get('reason')]
-            )
+            BehavioralPattern(
+                pattern_type='self_modification',
+                description=f"Modified {modification_episode.get('module_modified', 'unknown')}",
+                frequency=1,
+                examples=[modification_episode.get('reason', 'unspecified')]
+            )

This keeps the self‑model resilient if upstream passes incomplete episodes and ensures reflective processes have visible effect.

puma/consciousness/state_machine.py (2)

159-177: Add error handling around the main state loop to avoid silent crash of consciousness

run_state_loop() awaits execute_current_state() and transition_to() without any try/except. Any exception in a state handler or transition will bubble up and terminate the loop, effectively killing the “consciousness” without a clear signal.

Consider wrapping the body of the while loop:

     async def run_state_loop(self):
@@
-        while self.running:
-            # Execute current state
-            await self.execute_current_state()
-
-            # Check for transitions
-            next_state = self.evaluate_transitions()
-
-            if next_state and next_state != self.current_state:
-                await self.transition_to(next_state)
-
-            await asyncio.sleep(1)  # Control loop rate
+        while self.running:
+            try:
+                # Execute current state
+                await self.execute_current_state()
+
+                # Check for transitions
+                next_state = self.evaluate_transitions()
+
+                if next_state and next_state != self.current_state:
+                    await self.transition_to(next_state)
+            except Exception as exc:
+                # TODO: route to structured logging/telemetry
+                print(f"Error in state loop: {exc}")
+
+            await asyncio.sleep(1)  # Control loop rate

This keeps the loop resilient while you iterate on state behaviors.


292-299: Align memory_type with episodic memory enum or document string usage

Here you store state transitions as episodic memories:

self.memory_system.form_episode(
    perception={'state_change': f"{old_state.value} -> {new_state.value}"},
    action={'type': 'state_transition'},
    outcome={'new_state': new_state.value},
    memory_type='state_transition'
)

form_episode is typed to accept MemoryType (enum) for memory_type, while this code passes a bare string. If Episode.type and related logic assume the enum, this can cause subtle inconsistencies.

Two options:

  • If MemoryType.STATE_TRANSITION (or similar) exists, prefer using it.
  • If you intentionally allow free‑form string types, update the episodic API/type hints to reflect that and keep it consistent across callers (e.g., web‑exploration memories).

Please double‑check the current MemoryType definition and downstream usage.

puma/shop/introspection.py (2)

73-82: Avoid swallowing all parse errors; narrow exceptions and at least log the file

The analyze_module loop currently does:

try:
    with open(py_file, 'r') as f:
        source = f.read()
        total_lines += len(source.split('\n'))
    tree = ast.parse(source)
    ...
except Exception as e:
    # Skip files that can't be parsed
    pass

This combination of except Exception + pass means any IO/parsing bug silently disappears, and the introspection model becomes incomplete without clues.

A safer pattern:

-        for py_file in module_path.rglob('*.py'):
+        for py_file in module_path.rglob('*.py'):
@@
-            try:
-                with open(py_file, 'r') as f:
+            try:
+                with open(py_file, 'r', encoding='utf-8') as f:
                     source = f.read()
                     total_lines += len(source.split('\n'))
@@
-            except Exception as e:
-                # Skip files that can't be parsed
-                pass
+            except (OSError, UnicodeDecodeError, SyntaxError) as exc:
+                # Skip files that can't be read/parsed, but surface the issue
+                print(f"Skipping {py_file}: {exc}")

This keeps robustness but surfaces real problems and avoids catching unrelated exceptions.

Also applies to: 99-101


103-135: Resolve unused parameters in _infer_module_purpose (either use or rename to _…)

_infer_module_purpose currently ignores its functions and classes parameters, triggering static‑analysis warnings and making the signature misleading:

def _infer_module_purpose(
    self,
    module_name: str,
    functions: List[str],
    classes: List[str]
) -> str:
    ...
    return purposes.get(module_name, f"Module: {module_name}")

If you don’t need structural info yet, simplest is to mark them intentionally unused:

-    def _infer_module_purpose(
-        self,
-        module_name: str,
-        functions: List[str],
-        classes: List[str]
-    ) -> str:
+    def _infer_module_purpose(
+        self,
+        module_name: str,
+        _functions: List[str],
+        _classes: List[str]
+    ) -> str:

and adjust the call site accordingly:

-        purpose = self._infer_module_purpose(module_path.name, functions, classes)
+        purpose = self._infer_module_purpose(module_path.name, functions, classes)

(Names change, types/behavior stay the same.)

Alternatively, you could begin using them (e.g., detect “memory” if many Episode/MemoryType symbols are present), but that’s a larger design step.

Also, note that inspect and defaultdict imports appear unused and can be dropped in a follow‑up tidy‑up.

puma/goals/__init__.py (1)

7-9: Goals package API wiring looks good; __all__ sort is purely stylistic

Re-exporting GoalFormationSystem, Goal, GoalType, and IntentionScheduler from .formation is a clean public surface for the goals subsystem.

The Ruff hint about __all__ sorting is cosmetic. If you want to appease it, you can reorder:

-__all__ = ['GoalFormationSystem', 'Goal', 'GoalType', 'IntentionScheduler']
+__all__ = ['Goal', 'GoalFormationSystem', 'GoalType', 'IntentionScheduler']

(or any consistently sorted variant), but functionally this file is fine as is.

setup_persistence.py (1)

11-50: Consider adding error handling for I/O operations.

The setup logic is sound, but file operations (lines 27-28, 33-34, 45-46) could fail due to permissions, read-only filesystems, or disk space issues. Consider wrapping file operations in try-except blocks to provide clearer error messages to users.

Example approach:

 def setup_persistence(base_path: Path = Path("./atomspace-db")):
     """Set up persistence directory structure."""
     base_path = Path(base_path)
+    
+    try:
+        # Create directories
+        (base_path / "default").mkdir(parents=True, exist_ok=True)
+        (base_path / "default" / "snapshots").mkdir(exist_ok=True)
     
-    # Create directories
-    (base_path / "default").mkdir(parents=True, exist_ok=True)
-    (base_path / "default" / "snapshots").mkdir(exist_ok=True)
-
-    # Create initial empty atoms file
-    atoms_file = base_path / "default" / "atoms.json"
-    if not atoms_file.exists():
-        with open(atoms_file, 'w') as f:
-            json.dump({}, f)
+        # Create initial empty atoms file
+        atoms_file = base_path / "default" / "atoms.json"
+        if not atoms_file.exists():
+            with open(atoms_file, 'w') as f:
+                json.dump({}, f)
     
-    # Create initial empty links file
-    links_file = base_path / "default" / "links.json"
-    if not links_file.exists():
-        with open(links_file, 'w') as f:
-            json.dump([], f)
+        # Create initial empty links file
+        links_file = base_path / "default" / "links.json"
+        if not links_file.exists():
+            with open(links_file, 'w') as f:
+                json.dump([], f)
     
-    # Create config
-    config_file = base_path / "config.json"
-    config = {...}
-    with open(config_file, 'w') as f:
-        json.dump(config, f, indent=2)
+        # Create config
+        config_file = base_path / "config.json"
+        config = {...}
+        with open(config_file, 'w') as f:
+            json.dump(config, f, indent=2)
     
-    print(f"✅ Persistence setup complete at: {base_path}")
+        print(f"✅ Persistence setup complete at: {base_path}")
+        print(f"   Default database: {base_path / 'default'}")
+        
+    except PermissionError:
+        print(f"❌ Error: Permission denied writing to {base_path}")
+        raise
+    except OSError as e:
+        print(f"❌ Error: Failed to set up persistence: {e}")
+        raise
backend/websocket_server.py (1)

57-67: Broad exception catch in broadcast can hide real send errors

Catching a bare Exception here will swallow any error from send_json, making debugging harder and triggering Ruff’s BLE001 warning.

Consider at least logging more structured details and optionally narrowing the catch:

-        for connection in self.active_connections:
-            try:
-                await connection.send_json(message)
-            except Exception as e:
-                print(f"Error sending to client: {e}")
-                disconnected.append(connection)
+        for connection in self.active_connections:
+            try:
+                await connection.send_json(message)
+            except Exception as e:
+                # Log and drop the connection so a bad client doesn't block broadcasts.
+                print(f"WebSocket send failed; dropping client: {e!r}")
+                disconnected.append(connection)

If you know the concrete exception type FastAPI uses on disconnects, you can also add a dedicated except for that and reserve the generic catch for truly unexpected failures.

bootstrap/bootstrap.py (2)

16-27: Path hacking and duplicate sys import make this module brittle

This file modifies sys.path at runtime and imports sys twice. That’s fragile once the project is installed as a package and can mask import issues.

When you get a chance, consider:

  • Converting atomspace-db, gemini-interface, and web-agent into proper Python packages (with __init__.py) and using absolute imports (e.g., from atomspace_db.core import Atomspace).
  • Dropping the manual sys.path.insert/append calls and the duplicate import sys.

This will make bootstrapping more robust across environments (editable installs, packaged, tests).


148-151: consolidation is instantiated but never used

MemoryConsolidation is created and bound to consolidation but not wired into Consciousness or the state machine, which both obscures intent and triggers Ruff’s unused-variable warning:

consolidation = MemoryConsolidation(atomspace, rft_engine)

Either:

  • Integrate consolidation into the loop (e.g., pass it into Consciousness or ConsciousnessStateMachine so consolidation actually runs), or
  • Remove the assignment for now and reintroduce it once you’re ready to hook consolidation into the lifecycle.

This will keep the bootstrap function aligned with the currently active components.

puma/memory/episodic.py (1)

169-172: Atomspace integration is currently a no-op

_store_in_atomspace is stubbed out with pass, so episodes are never mirrored into the Atomspace even when one is provided:

def _store_in_atomspace(self, episode: Episode):
    """Store episode in atomspace as node"""
    # Will integrate with actual Atomspace
    pass

That’s fine as an incremental step, but you may want to add a clear TODO and (later) align this with the Atomspace API in atomspace-db/core.py to keep episodic memory and the atomstore in sync.

gemini-interface/client.py (1)

55-63: Tighten placeholder methods to satisfy linters and clarify intent

Minor cleanups will make this module easier to evolve and keep Ruff quiet:

  • send_audio, generate_code, and reflect don't use their arguments yet; either consume them in logging or rename to _audio_chunk, _prompt, _reflection_prompt.
  • The print calls in generate_code and reflect use f-strings without interpolation; drop the f prefix.
  • (Optional) Consider switching print-based diagnostics in this client (start_session, speak, etc.) to a logger so the same interface can run cleanly in non-TTY environments.

Example:

-    async def send_audio(self, audio_chunk: bytes):
+    async def send_audio(self, _audio_chunk: bytes):
@@
-    async def generate_code(self, prompt: str) -> str:
+    async def generate_code(self, _prompt: str) -> str:
@@
-        print(f"💻 Generating code with Gemini...")
+        print("💻 Generating code with Gemini...")
@@
-    async def reflect(self, reflection_prompt: str) -> str:
+    async def reflect(self, _reflection_prompt: str) -> str:
@@
-        print(f"🤔 Reflecting with Gemini...")
+        print("🤔 Reflecting with Gemini...")

Also applies to: 166-191, 38-47

puma/shop/modification.py (2)

82-94: Avoid bare except and silent failures when reading module code

_read_module_code swallows all exceptions with a bare except: pass, which can hide real issues (e.g., permission errors, encoding problems) and lead to an empty or truncated current_code without any signal.

At minimum, narrow the exception type and log something:

     def _read_module_code(self, module) -> str:
         """Read module source code"""
         code_parts = []
@@
-        if module.path.exists():
-            for py_file in module.path.rglob('*.py'):
-                try:
-                    with open(py_file, 'r') as f:
-                        code_parts.append(f"# File: {py_file.name}\n{f.read()}\n")
-                except:
-                    pass
+        if module.path.exists():
+            for py_file in module.path.rglob('*.py'):
+                try:
+                    with open(py_file, 'r', encoding='utf-8') as f:
+                        code_parts.append(f"# File: {py_file.name}\n{f.read()}\n")
+                except OSError as exc:
+                    # TODO: plug into your logging / introspection channel
+                    print(f"Failed to read {py_file}: {exc}")

96-101: Clarify intent of unused parameters in idea/test design helpers

generate_modification_ideas doesn’t use its module argument, and design_test ignores both module_name and hypothesis. That’s fine for placeholders, but it’s ambiguous whether logic is missing or these are future hooks.

If these are intentionally unused for now, consider:

  • Renaming to _module, _module_name, _hypothesis, or
  • Adding minimal usage (e.g., include them in test metadata).

Example:

-    def generate_modification_ideas(
-        self,
-        module,
-        desired_improvement: str
-    ) -> List[str]:
+    def generate_modification_ideas(
+        self,
+        _module,
+        desired_improvement: str
+    ) -> List[str]:
@@
-    def design_test(self, module_name: str, hypothesis: str) -> Dict[str, Any]:
+    def design_test(self, module_name: str, hypothesis: str) -> Dict[str, Any]:
@@
-            'test_type': 'A/B comparison',
+            'test_type': 'A/B comparison',
@@
-            'success_criteria': {
+            'success_criteria': {
                 'performance_improvement': 0.2,  # 20% improvement
                 'no_regressions': True
-            }
+            },
+            'notes': {
+                'module': module_name,
+                'hypothesis': hypothesis,
+            }

Also applies to: 125-141

puma/memory/consolidation.py (1)

265-278: Rename unused loop variables in _store_insights to clarify placeholder status

_store_insights intentionally does nothing yet, but the current loops use concept and frame without referencing them, which raises Ruff’s B007 warning and can confuse readers.

A tiny rename makes it explicit these are placeholders:

-        # Store concepts as concept nodes
-        for concept in concepts:
-            # Will implement with actual Atomspace API
-            pass
+        # Store concepts as concept nodes
+        for _concept in concepts:
+            # Will implement with actual Atomspace API
+            pass
@@
-        # Store relational frames
-        for frame in relational_frames:
-            # Will implement with actual Atomspace API
-            pass
+        # Store relational frames
+        for _frame in relational_frames:
+            # Will implement with actual Atomspace API
+            pass
puma/rft/reasoning.py (1)

266-301: Clarify future use of novel_situation and consider small BFS/style tweaks

The core RFT logic looks fine; a couple of minor points:

  • _find_relation_path is correct but could adopt the [*path, frame.target] style Ruff suggests for slightly clearer path extension.
  • reason_by_analogy completely ignores its novel_situation argument and instead returns a heuristic based only on existing coordination frames. If this is intentional placeholder behavior, it’s worth making that explicit (or at least using novel_situation in the returned metadata) so it’s obvious where to plug in richer matching later.

Example:

-                if frame.relation_type == relation_type and frame.source == current:
-                    new_path = path + [frame.target]
+                if frame.relation_type == relation_type and frame.source == current:
+                    new_path = [*path, frame.target]
@@
-    def reason_by_analogy(self, novel_situation: Dict[str, Any]) -> Dict[str, Any]:
+    def reason_by_analogy(self, novel_situation: Dict[str, Any]) -> Dict[str, Any]:
@@
-        if not similar_frames:
-            return {'prediction': None, 'confidence': 0.0}
+        if not similar_frames:
+            return {
+                'prediction': None,
+                'confidence': 0.0,
+                'novel_situation': novel_situation,
+            }
@@
-        return {
-            'prediction': f"Similar to {most_recent.source}",
-            'confidence': most_recent.strength,
-            'analogy_source': most_recent.source
-        }
+        return {
+            'prediction': f"Similar to {most_recent.source}",
+            'confidence': most_recent.strength,
+            'analogy_source': most_recent.source,
+            'novel_situation': novel_situation,
+        }

Also applies to: 303-324

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3af1d94 and d375765.

📒 Files selected for processing (29)
  • README.md (1 hunks)
  • atomspace-db/__init__.py (1 hunks)
  • atomspace-db/core.py (1 hunks)
  • backend/__init__.py (1 hunks)
  • backend/websocket_server.py (1 hunks)
  • bootstrap/__init__.py (1 hunks)
  • bootstrap/bootstrap.py (1 hunks)
  • gemini-interface/__init__.py (1 hunks)
  • gemini-interface/client.py (1 hunks)
  • puma/consciousness/__init__.py (1 hunks)
  • puma/consciousness/self_model.py (1 hunks)
  • puma/consciousness/state_machine.py (1 hunks)
  • puma/curiosity/__init__.py (1 hunks)
  • puma/curiosity/drive.py (1 hunks)
  • puma/goals/__init__.py (1 hunks)
  • puma/goals/formation.py (1 hunks)
  • puma/memory/__init__.py (1 hunks)
  • puma/memory/consolidation.py (1 hunks)
  • puma/memory/episodic.py (1 hunks)
  • puma/rft/__init__.py (2 hunks)
  • puma/rft/reasoning.py (1 hunks)
  • puma/shop/__init__.py (1 hunks)
  • puma/shop/introspection.py (1 hunks)
  • puma/shop/modification.py (1 hunks)
  • puma/shop/sandbox.py (1 hunks)
  • requirements.txt (1 hunks)
  • setup_persistence.py (1 hunks)
  • web-agent/__init__.py (1 hunks)
  • web-agent/agent.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
README.md

📄 CodeRabbit inference engine (GEMINI.md)

README.md should reference the Popeye persona block if applicable (mention persona and/or link to GEMINI.md)

Files:

  • README.md
🧬 Code graph analysis (22)
puma/goals/__init__.py (1)
puma/goals/formation.py (4)
  • GoalFormationSystem (63-240)
  • Goal (27-60)
  • GoalType (17-23)
  • IntentionScheduler (243-326)
atomspace-db/core.py (4)
puma/curiosity/drive.py (1)
  • to_dict (36-46)
puma/goals/formation.py (1)
  • to_dict (45-56)
puma/memory/episodic.py (1)
  • to_dict (41-53)
puma/rft/reasoning.py (1)
  • to_dict (40-48)
puma/consciousness/self_model.py (1)
puma/memory/consolidation.py (1)
  • update_self_understanding (236-252)
backend/websocket_server.py (5)
puma/shop/modification.py (1)
  • approve_modification (177-180)
puma/curiosity/drive.py (1)
  • add_questions (200-208)
puma/memory/episodic.py (1)
  • count_total_episodes (196-198)
atomspace-db/core.py (1)
  • count_atoms (208-210)
puma/consciousness/self_model.py (1)
  • get_lifetime_duration (56-58)
puma/memory/__init__.py (2)
puma/memory/episodic.py (2)
  • EpisodicMemorySystem (67-202)
  • Episode (26-64)
puma/memory/consolidation.py (1)
  • MemoryConsolidation (34-278)
puma/rft/__init__.py (1)
puma/rft/reasoning.py (3)
  • RFTEngine (51-354)
  • RelationType (15-23)
  • RelationalFrame (27-48)
bootstrap/bootstrap.py (10)
atomspace-db/core.py (3)
  • bootstrap_atomspace (249-281)
  • Atomspace (61-214)
  • count_atoms (208-210)
puma/memory/episodic.py (3)
  • EpisodicMemorySystem (67-202)
  • form_episode (79-115)
  • update_context (188-190)
puma/memory/consolidation.py (1)
  • MemoryConsolidation (34-278)
puma/rft/reasoning.py (1)
  • RFTEngine (51-354)
puma/curiosity/drive.py (3)
  • CuriosityDrive (49-236)
  • mark_questions_answered (182-198)
  • add_questions (200-208)
puma/goals/formation.py (2)
  • GoalFormationSystem (63-240)
  • stop (324-326)
puma/consciousness/state_machine.py (3)
  • ConsciousnessStateMachine (34-303)
  • run_state_loop (159-176)
  • stop (301-303)
puma/consciousness/self_model.py (1)
  • SelfModel (65-159)
gemini-interface/client.py (2)
  • GeminiLiveInterface (24-198)
  • start_session (38-53)
web-agent/agent.py (3)
  • AutonomousWebAgent (23-156)
  • initialize_browser (33-42)
  • integrate_web_learning (127-151)
puma/memory/consolidation.py (3)
puma/memory/episodic.py (2)
  • Episode (26-64)
  • MemoryType (14-22)
puma/consciousness/self_model.py (1)
  • update_self_understanding (144-147)
puma/rft/reasoning.py (1)
  • derive_relations (62-98)
puma/curiosity/drive.py (4)
atomspace-db/core.py (2)
  • to_dict (38-46)
  • to_dict (57-58)
puma/goals/formation.py (1)
  • to_dict (45-56)
puma/memory/episodic.py (1)
  • to_dict (41-53)
puma/rft/reasoning.py (1)
  • to_dict (40-48)
backend/__init__.py (1)
backend/websocket_server.py (1)
  • ConsciousnessWebSocketManager (21-108)
puma/memory/episodic.py (4)
atomspace-db/core.py (2)
  • to_dict (38-46)
  • to_dict (57-58)
puma/curiosity/drive.py (1)
  • to_dict (36-46)
puma/goals/formation.py (1)
  • to_dict (45-56)
puma/rft/reasoning.py (1)
  • to_dict (40-48)
puma/consciousness/state_machine.py (4)
puma/memory/episodic.py (3)
  • get_unconsolidated_episodes (178-180)
  • mark_consolidated (182-186)
  • form_episode (79-115)
puma/goals/formation.py (2)
  • generate_goals (74-103)
  • stop (324-326)
backend/websocket_server.py (1)
  • broadcast (42-67)
bootstrap/bootstrap.py (1)
  • stop (116-119)
puma/rft/reasoning.py (1)
puma/memory/episodic.py (1)
  • to_dict (41-53)
gemini-interface/__init__.py (1)
gemini-interface/client.py (1)
  • GeminiLiveInterface (24-198)
web-agent/__init__.py (1)
web-agent/agent.py (1)
  • AutonomousWebAgent (23-156)
bootstrap/__init__.py (1)
bootstrap/bootstrap.py (1)
  • bootstrap_new_consciousness (122-205)
puma/shop/__init__.py (3)
puma/shop/introspection.py (2)
  • CodeIntrospection (27-181)
  • CognitiveModule (16-24)
puma/shop/modification.py (2)
  • ModificationSystem (26-200)
  • ModificationPlan (14-23)
puma/shop/sandbox.py (2)
  • ModificationSandbox (24-145)
  • TestReport (15-21)
puma/goals/formation.py (4)
atomspace-db/core.py (2)
  • to_dict (38-46)
  • to_dict (57-58)
puma/curiosity/drive.py (2)
  • to_dict (36-46)
  • get_most_important_questions (210-216)
bootstrap/bootstrap.py (1)
  • stop (116-119)
puma/consciousness/state_machine.py (1)
  • stop (301-303)
gemini-interface/client.py (1)
puma/memory/episodic.py (1)
  • form_episode (79-115)
puma/consciousness/__init__.py (2)
puma/consciousness/state_machine.py (2)
  • ConsciousnessStateMachine (34-303)
  • ConsciousnessState (15-22)
puma/consciousness/self_model.py (2)
  • SelfModel (65-159)
  • TemporalSelf (21-62)
web-agent/agent.py (2)
bootstrap/bootstrap.py (1)
  • integrate_learning (102-114)
puma/memory/episodic.py (1)
  • form_episode (79-115)
puma/curiosity/__init__.py (1)
puma/curiosity/drive.py (2)
  • CuriosityDrive (49-236)
  • Question (25-46)
🪛 markdownlint-cli2 (0.18.1)
README.md

3-3: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


105-105: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


157-157: Bare URL used

(MD034, no-bare-urls)


191-191: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


293-293: Bare URL used

(MD034, no-bare-urls)


295-295: Bare URL used

(MD034, no-bare-urls)

🪛 Ruff (0.14.5)
puma/goals/__init__.py

9-9: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

atomspace-db/core.py

179-179: Avoid specifying long messages outside the exception class

(TRY003)

backend/websocket_server.py

61-61: Do not catch blind exception: Exception

(BLE001)

puma/memory/__init__.py

10-10: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

bootstrap/bootstrap.py

93-93: Local variable episode is assigned to but never used

Remove assignment to unused variable episode

(F841)


150-150: Local variable consolidation is assigned to but never used

Remove assignment to unused variable consolidation

(F841)

puma/memory/consolidation.py

271-271: Loop control variable concept not used within loop body

Rename unused concept to _concept

(B007)


276-276: Loop control variable frame not used within loop body

Rename unused frame to _frame

(B007)

puma/rft/reasoning.py

294-294: Consider [*path, frame.target] instead of concatenation

Replace with [*path, frame.target]

(RUF005)


303-303: Unused method argument: novel_situation

(ARG002)

puma/shop/modification.py

54-54: Avoid specifying long messages outside the exception class

(TRY003)


91-91: Do not use bare except

(E722)


91-92: try-except-pass detected, consider logging the exception

(S110)


98-98: Unused method argument: module

(ARG002)


125-125: Unused method argument: module_name

(ARG002)


125-125: Unused method argument: hypothesis

(ARG002)

puma/shop/__init__.py

11-18: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

gemini-interface/client.py

55-55: Unused method argument: audio_chunk

(ARG002)


58-58: Avoid specifying long messages outside the exception class

(TRY003)


70-70: Avoid specifying long messages outside the exception class

(TRY003)


166-166: Unused method argument: prompt

(ARG002)


171-171: f-string without any placeholders

Remove extraneous f prefix

(F541)


180-180: Unused method argument: reflection_prompt

(ARG002)


184-184: f-string without any placeholders

Remove extraneous f prefix

(F541)

puma/shop/sandbox.py

30-30: Undefined name Optional

(F821)


48-48: Local variable sandbox_copy is assigned to but never used

Remove assignment to unused variable sandbox_copy

(F841)


85-85: Unused method argument: test_strategy

(ARG002)


117-117: Unused method argument: original

(ARG002)


117-117: Unused method argument: modified

(ARG002)


123-123: Local variable results_original is assigned to but never used

Remove assignment to unused variable results_original

(F841)


124-124: Local variable results_modified is assigned to but never used

Remove assignment to unused variable results_modified

(F841)


126-126: Loop control variable scenario not used within loop body

Rename unused scenario to _scenario

(B007)

puma/consciousness/__init__.py

10-15: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

puma/shop/introspection.py

99-101: try-except-pass detected, consider logging the exception

(S110)


99-99: Do not catch blind exception: Exception

(BLE001)


99-99: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


119-119: Unused method argument: functions

(ARG002)


120-120: Unused method argument: classes

(ARG002)

🔇 Additional comments (13)
atomspace-db/__init__.py (1)

1-6: Docstring is clear and aligned with the persistence role

The module-level docstring accurately describes the Atomspace persistence layer and is consistent with the rest of the PR; no changes needed.

requirements.txt (1)

1-29: Requirements layout is coherent for the new architecture

The dependency blocks (core numerics, web/backend stack, Gemini, testing) line up with the new components introduced in this PR; nothing here looks inconsistent with the described architecture.

backend/__init__.py (1)

1-9: LGTM! Clean API aggregator.

The module correctly exposes the WebSocket server interface with clear documentation and consistent imports/exports.

gemini-interface/__init__.py (1)

1-9: LGTM! Clean API aggregator.

The module correctly exposes the Gemini Live interface with clear documentation and consistent import/export.

bootstrap/__init__.py (1)

1-10: LGTM! Clean API with clear design principles.

The module correctly exposes the bootstrap function. The docstring effectively communicates the "no hardcoded content" design principle that aligns with the PR's architectural goals.

puma/memory/__init__.py (1)

1-10: LGTM! Clean API aggregator.

The module correctly exposes the memory subsystem components with clear documentation and consistent imports/exports. The static analysis hint about __all__ sorting appears to be a false positive—the list is already in alphabetical order.

puma/curiosity/__init__.py (1)

1-9: LGTM! Clean API aggregator.

The module correctly exposes the curiosity drive components with clear documentation and consistent imports/exports.

puma/rft/__init__.py (2)

16-16: LGTM! Reasoning module integration.

The import correctly adds the new RFT reasoning engine components to the package's public API.


38-40: LGTM! Consistent export additions.

The exports correctly expose the reasoning module's public interface, maintaining consistency with the existing package structure.

puma/shop/__init__.py (1)

1-18: LGTM! Clean API aggregator.

The module correctly exposes the self-modification ("Shop") subsystem components with clear documentation and consistent imports/exports. The static analysis hint about __all__ sorting appears to be a false positive—the list is already in alphabetical order.

atomspace-db/core.py (1)

16-215: Atomspace core & persistence look coherent and consistent with the rest of the stack

Enum typing, Atom/Link serialization, JSON save/load, and snapshot/restore are all internally consistent and align with how other subsystems (memory, curiosity, goals, RFT) serialize their data. The bootstrap helper cleanly seeds only structural atoms (self_model_root, timeline_root) without hardcoding content.

No blocking issues from this file from a correctness or architecture perspective.

puma/curiosity/drive.py (1)

15-235: CuriosityDrive data model and APIs align well with the rest of the architecture

The QuestionType/Question dataclasses, knowledge-gap detection scaffolding, boredom dynamics, and statistics interface are coherent and match patterns used in memory, goals, and RFT (e.g., to_dict shapes, enum .value usage). This should make downstream integration (goal formation, web agent exploration, GUI) straightforward.

No additional blocking issues here beyond the small boredom accounting tweak noted above.

bootstrap/bootstrap.py (1)

90-101: Remove unused episode variable in perceive method

The episode variable is created but never used. Since the method doesn't return it and there are no active callers depending on a return value, remove the assignment:

-        episode = self.memory.form_episode(
+        self.memory.form_episode(
             perception=perception,
             action=None,
             outcome=None
         )

This eliminates the Ruff F841 unused-variable warning.

Comment on lines +26 to +33
def __init__(self):
self.active_connections: List[WebSocket] = []
self.consciousness = None # Will be set by main system

async def connect(self, websocket: WebSocket):
"""Accept new WebSocket connection"""
await websocket.accept()
self.active_connections.append(websocket)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Type hints on WebSocket break import when FastAPI isn’t installed

active_connections: List[WebSocket] and connect(self, websocket: WebSocket) will raise NameError at import time if FastAPI isn’t available, defeating the FASTAPI_AVAILABLE guard and the “degrade gracefully” intent.

Switch the annotations to avoid referencing WebSocket at runtime (e.g., use Any or string annotations):

-from typing import List, Dict, Any
+from typing import List, Dict, Any

@@
-    def __init__(self):
-        self.active_connections: List[WebSocket] = []
+    def __init__(self):
+        self.active_connections: List[Any] = []
@@
-    async def connect(self, websocket: WebSocket):
+    async def connect(self, websocket: Any):

This keeps the module importable even without FastAPI, while still allowing use when it is installed.


Now let me examine the actual code to understand the context better:


I need to find and examine the actual file. Let me update the script:



Now that I have the web search results about PEP 563, let me run the script to examine the actual websocket_server.py file.

Let me search for the file in the repository:


---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the websocket_server.py file to see the import structure and annotations
find . -name "websocket_server.py" -type f 2>/dev/null | head -5

Length of output: 29


🏁 Script executed:

#!/bin/bash
# Find the file and display its content
find . -path "*/backend/websocket_server.py" -type f 2>/dev/null | xargs cat 2>/dev/null || echo "File not found in expected location"

Length of output: 5891


🏁 Script executed:

#!/bin/bash
# Search for websocket_server.py anywhere in the repo
find . -name "websocket_server.py" 2>/dev/null | head -10

Length of output: 29


Type hints on WebSocket cause NameError at import time when FastAPI is unavailable

The ConsciousnessWebSocketManager class is defined unconditionally, but its type hints reference WebSocket which is only available if the FastAPI import succeeds. Since Python evaluates type annotations at class definition time (without from __future__ import annotations), the module will fail to import if FastAPI is not installed.

Lines 27, 30, and 35 reference WebSocket in type hints:

 class ConsciousnessWebSocketManager:
     def __init__(self):
-        self.active_connections: List[WebSocket] = []
+        self.active_connections: List[Any] = []
         self.consciousness = None

-    async def connect(self, websocket: WebSocket):
+    async def connect(self, websocket: Any):
         """Accept new WebSocket connection"""
         await websocket.accept()
         self.active_connections.append(websocket)

     def disconnect(self, websocket: WebSocket):
+    def disconnect(self, websocket: Any):
         """Remove WebSocket connection"""

This allows the module to import even without FastAPI, keeping the "degrade gracefully" pattern intact.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/websocket_server.py around lines 26 to 33, the class uses WebSocket
in runtime-evaluated type hints which raises NameError if FastAPI isn't
installed; change the annotations to avoid importing WebSocket at import time —
either add "from __future__ import annotations" at top so annotations are
deferred, or guard the FastAPI import with try/except and use
typing.TYPE_CHECKING to import WebSocket only for type checking (or replace
WebSocket with typing.Any/'WebSocket' string annotations); update the three
affected annotations (self.active_connections, connect websocket param, and any
other method returns/params referencing WebSocket) accordingly so the module
imports when FastAPI is absent.

Comment on lines +113 to +127
# Create episodic memory from conversation
episode = memory_system.form_episode(
perception={
'type': 'conversation',
'speaker': exchange.speaker,
'utterance': exchange.utterance
},
action={
'type': 'conversation_response'
} if exchange.speaker == 'user' else None,
outcome={
'conversation_continued': True
},
memory_type='conversation'
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use MemoryType.CONVERSATION instead of a raw string, and consider decoupling history from memory wiring

integrate_conversation_to_memory passes memory_type='conversation' into memory_system.form_episode, but Episode.type is a MemoryType enum and downstream code (e.g., Episode.to_dict, Episode.generate_summary) expects an enum with a .value attribute, not a bare string. This will at best make conversational memories invisible to enum-based checks and at worst raise an AttributeError when .value is accessed.

Also, conversation history is only updated when a memory_system is provided. If callers use this helper even when episodic memory is not yet wired, you'll silently lose transcript history.

Consider something like:

-from .episodic import Episode, MemoryType  # or via puma.memory import MemoryType
+from puma.memory import MemoryType  # adjust to your actual export path

 ...

-        episode = memory_system.form_episode(
+        episode = memory_system.form_episode(
             perception={
                 'type': 'conversation',
                 'speaker': exchange.speaker,
                 'utterance': exchange.utterance
             },
             action={
                 'type': 'conversation_response'
             } if exchange.speaker == 'user' else None,
             outcome={
                 'conversation_continued': True
             },
-            memory_type='conversation'
+            memory_type=MemoryType.CONVERSATION
         )
-
-        # Add to conversation history
-        self.conversation_history.append(exchange)
-
-        return episode
+        # Always track transcript, even without a memory system
+        self.conversation_history.append(exchange)
+        return episode

Also applies to: 25-35

🤖 Prompt for AI Agents
In gemini-interface/client.py around lines 113 to 127 (and apply same change at
~25-35), form_episode is being called with memory_type='conversation' (a raw
string) which should be the MemoryType enum; change to MemoryType.CONVERSATION
so downstream code can access .value and enum-based checks work. Also ensure
conversation history is appended/updated regardless of whether memory_system is
provided (i.e., decouple updating the local transcript/history from the
memory_system.form_episode call) so transcripts aren't lost when episodic memory
is not wired; only call memory_system.form_episode/create episode when
memory_system is present.

Comment on lines +57 to +91
def _define_transition_rules(self) -> Dict[ConsciousnessState, Dict[str, Callable]]:
"""
Define when transitions should occur.
NOT hardcoded schedule - based on internal state.
"""
return {
ConsciousnessState.IDLE: {
'to_exploring': lambda: self._has_open_questions(),
'to_conversing': lambda: self._detect_user_available(),
'to_sleeping': lambda: self._should_consolidate_memory(),
'to_shopping': lambda: self._has_improvement_goals()
},
ConsciousnessState.EXPLORING: {
'to_idle': lambda: self._exploration_goal_complete(),
'to_conversing': lambda: self._user_interrupt_detected(),
'to_sleeping': lambda: self._too_much_unconsolidated_memory()
},
ConsciousnessState.CONVERSING: {
'to_idle': lambda: self._conversation_ended(),
'to_exploring': lambda: False, # Don't interrupt conversation
'to_sleeping': lambda: False
},
ConsciousnessState.SHOPPING: {
'to_idle': lambda: self._modification_complete(),
'to_conversing': lambda: self._user_interrupt_detected()
},
ConsciousnessState.SLEEPING: {
'to_idle': lambda: self._consolidation_complete(),
'to_conversing': lambda: self._user_interrupt_detected()
},
ConsciousnessState.CREATING: {
'to_idle': lambda: self._creation_complete(),
'to_conversing': lambda: self._user_interrupt_detected()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Current transition rules create absorbing states due to placeholder conditions

With the present stubs, several states have no viable exit path:

  • EXPLORING → IDLE only if _exploration_goal_complete() (currently always False).
  • SHOPPING → IDLE only if _modification_complete() (always False).
  • SLEEPING → IDLE only if _consolidation_complete() (always False).
  • CREATING → IDLE only if _creation_complete() (always False).
  • CONVERSING → IDLE only if _conversation_ended() (always False).

Once the state machine transitions into these states, evaluate_transitions() will never propose a new state, so run_state_loop() effectively gets stuck there until externally stopped.

Even as placeholders, it’s worth adding at least temporary, non‑hardcoded exit criteria (e.g., time‑based thresholds or simple counters tied to memory/goal signals) so the machine can cycle between modes during development.

Also applies to: 123-158



---

🏁 Script executed:

```shell
#!/bin/bash
# Inspect the full state machine file, focusing on helper methods and the run loop
cat puma/consciousness/state_machine.py

Length of output: 10446


🏁 Script executed:

#!/bin/bash
# Check if episodic module exists and what MemoryType contains
find . -name "episodic*" -type f | head -20

Length of output: 144


🏁 Script executed:

#!/bin/bash
# Search for where memory_type is used with 'state_transition' or MemoryType enum
rg -nP "(memory_type|MemoryType)" --type=py -C3

Length of output: 5197


Absorbing states, missing error handling, and memory_type type mismatch require fixes

Three issues block reliable operation:

1. Absorbing states due to placeholder conditions (lines 57–91, 114–140)
Exit conditions for EXPLORING, SHOPPING, SLEEPING, CREATING, and CONVERSING are hardcoded to False:

  • _exploration_goal_complete(), _modification_complete(), _consolidation_complete(), _creation_complete(), _conversation_ended() all return False

Once the machine enters these states, evaluate_transitions() will never propose an exit, causing it to loop indefinitely in the same state.

2. No exception handling in run_state_loop() (lines 230–244)
The main loop has no try/except around execute_current_state(), evaluate_transitions(), or transition_to(). Any uncaught exception terminates the entire consciousness loop without logging or recovery.

3. Memory type type mismatch (line 298)
transition_to() passes memory_type='state_transition' (string) to memory_system.form_episode(), but form_episode() expects memory_type: MemoryType (enum). The MemoryType enum does not include a 'state_transition' value; this will cause the episode to be recorded with an incorrect or default type.

Fixes needed:

  • Add real or time-based exit conditions for blocked states (e.g., elapsed time, event flags).
  • Wrap the run loop body in try/except with logging and graceful continuation.
  • Replace memory_type='state_transition' with memory_type=MemoryType.GENERAL or add STATE_TRANSITION to the MemoryType enum.
🤖 Prompt for AI Agents
In puma/consciousness/state_machine.py around lines 57–91 and 114–140 the
transition lambdas currently return hardcoded False causing absorbing states;
change those predicates to real exit conditions (e.g., check elapsed time,
per-state timeout counters, or event/flag variables such as
exploration_timeout_reached(), modification_timeout_reached(),
consolidation_done(), creation_done(), conversation_ended_event()) so the state
machine can propose exits; around lines 230–244 wrap the run_state_loop() body
(execute_current_state(), evaluate_transitions(), transition_to()) in a
try/except that logs the exception and continues the loop (optionally increment
an error counter and backoff) so a single error does not kill the loop; and at
line 298 replace the string memory_type='state_transition' with a proper
MemoryType value (e.g., MemoryType.GENERAL) or add a MemoryType.STATE_TRANSITION
enum member and use MemoryType.STATE_TRANSITION when calling
memory_system.form_episode().

Comment on lines +182 to +199
def mark_questions_answered(self, question_ids: List[str], answers: Optional[Dict[str, str]] = None):
"""Mark questions as answered"""
for question in self.open_questions:
if question.id in question_ids:
question.answered = True
question.answered_at = datetime.now(timezone.utc)

if answers and question.id in answers:
question.answer = answers[question.id]

self.answered_questions.append(question)

# Remove from open questions
self.open_questions = [q for q in self.open_questions if not q.answered]

# Reduce boredom when questions are answered
self.current_boredom = max(0, self.current_boredom - 0.2 * len(question_ids))

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Boredom reduction in mark_questions_answered doesn’t match actual answers

You reduce boredom using len(question_ids) regardless of how many IDs matched an open question:

# Remove from open questions
self.open_questions = [q for q in self.open_questions if not q.answered]

# Reduce boredom when questions are answered
self.current_boredom = max(0, self.current_boredom - 0.2 * len(question_ids))

If some IDs are unknown or duplicated, boredom will still drop, which can misrepresent actual progress.

Track how many were really answered:

     def mark_questions_answered(self, question_ids: List[str], answers: Optional[Dict[str, str]] = None):
         """Mark questions as answered"""
+        answered_count = 0
         for question in self.open_questions:
             if question.id in question_ids:
                 question.answered = True
                 question.answered_at = datetime.now(timezone.utc)
@@
-                self.answered_questions.append(question)
+                self.answered_questions.append(question)
+                answered_count += 1
@@
-        self.current_boredom = max(0, self.current_boredom - 0.2 * len(question_ids))
+        self.current_boredom = max(0, self.current_boredom - 0.2 * answered_count)

This keeps the internal state aligned with what actually happened.


🏁 Script executed:

#!/bin/bash
# Verify the code snippet exists and examine context
fd -t f "drive.py" -x cat {}

Length of output: 8237


🏁 Script executed:

#!/bin/bash
# Look for test files related to curiosity/drive
fd -t f "test.*drive|drive.*test" --type f

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for usages of mark_questions_answered
rg -n "mark_questions_answered" -A 3 -B 3

Length of output: 1020


🏁 Script executed:

#!/bin/bash
# Check if there are any tests for the CuriosityDrive class
rg -n "CuriosityDrive|mark_questions_answered" --type py -C 5

Length of output: 4721


Boredom reduction in mark_questions_answered doesn't match actual answers

You reduce boredom using len(question_ids) regardless of how many IDs matched an open question. If some IDs don't exist or are duplicated, boredom will still drop as if all were answered, misrepresenting actual progress.

Track how many were really answered:

     def mark_questions_answered(self, question_ids: List[str], answers: Optional[Dict[str, str]] = None):
         """Mark questions as answered"""
+        answered_count = 0
         for question in self.open_questions:
             if question.id in question_ids:
                 question.answered = True
                 question.answered_at = datetime.now(timezone.utc)

                 if answers and question.id in answers:
                     question.answer = answers[question.id]

                 self.answered_questions.append(question)
+                answered_count += 1

         # Remove from open questions
         self.open_questions = [q for q in self.open_questions if not q.answered]

         # Reduce boredom when questions are answered
-        self.current_boredom = max(0, self.current_boredom - 0.2 * len(question_ids))
+        self.current_boredom = max(0, self.current_boredom - 0.2 * answered_count)
🤖 Prompt for AI Agents
In puma/curiosity/drive.py around lines 182 to 199, the boredom reduction uses
len(question_ids) even when some IDs don't match open questions or are
duplicated; change the logic to count only the questions actually marked
answered (e.g., deduplicate incoming IDs or increment a counter each time you
mark a previously-unanswered open question) and use that real_count to reduce
boredom (current_boredom = max(0, current_boredom - 0.2 * real_count)); ensure
you only append each question once to answered_questions and that open_questions
filtering stays correct.

Comment on lines +132 to +156
def form_self_improvement_goals(self) -> List[Goal]:
"""
Form goals to improve own cognitive abilities.
Based on performance self-assessment.
"""
goals = []

# Placeholder - would assess cognitive performance
# and generate improvement goals

# Example: if memory consolidation is slow
if self._should_improve_memory():
goal = Goal(
type=GoalType.SELF_IMPROVEMENT,
description="Improve memory consolidation speed",
strategy={
'method': 'shop_modification',
'target_module': 'memory.consolidation',
'improvement_goal': 'faster_consolidation'
},
priority=0.7
)
goals.append(goal)

return goals
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align self-improvement strategy keys with ModificationSystem.plan_self_modification

Self-improvement goals currently encode their strategy as:

strategy={
    'method': 'shop_modification',
    'target_module': 'memory.consolidation',
    'improvement_goal': 'faster_consolidation',
}

But ModificationSystem.plan_self_modification expects an improvement_goal dict with keys target_module and performance_target. If you pass this strategy through directly, plan_self_modification will raise a KeyError when it looks up 'performance_target'.

Consider either:

  • Renaming the key here to performance_target, or
  • Having the caller translate goal.strategy into the improvement_goal schema expected by ModificationSystem.

For example:

-                strategy={
-                    'method': 'shop_modification',
-                    'target_module': 'memory.consolidation',
-                    'improvement_goal': 'faster_consolidation'
-                },
+                strategy={
+                    'method': 'shop_modification',
+                    'target_module': 'memory.consolidation',
+                    'performance_target': 'faster_consolidation',
+                },

Also applies to: 37-48

Comment on lines +37 to +80
def plan_self_modification(self, improvement_goal: Dict[str, Any]) -> ModificationPlan:
"""
Design code changes to improve cognitive ability.
Args:
improvement_goal: Dict with target_module, performance_target
Returns:
ModificationPlan
"""
target_module = improvement_goal['target_module']
desired_improvement = improvement_goal['performance_target']

# Analyze current implementation
current_module = self.introspection.cognitive_modules.get(target_module)

if not current_module:
raise ValueError(f"Module {target_module} not found")

# Read current code
current_code = self._read_module_code(current_module)

# Generate modification hypotheses
hypotheses = self.generate_modification_ideas(
current_module,
desired_improvement
)

# Rank by expected impact
best_hypothesis = hypotheses[0] if hypotheses else "Optimize performance"

# Create modification plan
plan = ModificationPlan(
target_module=target_module,
improvement_goal=desired_improvement,
current_code=current_code,
proposed_code="# Generated through Gemini collaboration",
hypothesis=best_hypothesis,
test_strategy=self.design_test(target_module, best_hypothesis),
risk_level=self.assess_risk_level(target_module),
requires_approval=self.assess_risk_level(target_module) in ['medium', 'high']
)

return plan
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix risk classification for submodules and avoid recomputing it

assess_risk_level currently checks module_name against ['memory', 'consciousness', 'atomspace'] and ['rft', 'goals'] with exact string comparison. When combined with callers that use dotted module names (e.g., 'memory.consolidation' from the goal formation strategy), all such modules will be classified as 'low' risk, bypassing the intended approval gating.

You also recompute assess_risk_level(target_module) twice when building the plan.

Consider:

-        plan = ModificationPlan(
-            target_module=target_module,
-            improvement_goal=desired_improvement,
-            current_code=current_code,
-            proposed_code="# Generated through Gemini collaboration",
-            hypothesis=best_hypothesis,
-            test_strategy=self.design_test(target_module, best_hypothesis),
-            risk_level=self.assess_risk_level(target_module),
-            requires_approval=self.assess_risk_level(target_module) in ['medium', 'high']
-        )
+        risk = self.assess_risk_level(target_module)
+        plan = ModificationPlan(
+            target_module=target_module,
+            improvement_goal=desired_improvement,
+            current_code=current_code,
+            proposed_code="# Generated through Gemini collaboration",
+            hypothesis=best_hypothesis,
+            test_strategy=self.design_test(target_module, best_hypothesis),
+            risk_level=risk,
+            requires_approval=risk in ['medium', 'high']
+        )
@@
-    def assess_risk_level(self, module_name: str) -> str:
+    def assess_risk_level(self, module_name: str) -> str:
@@
-        if module_name in high_risk_modules:
+        # Treat dotted names like "memory.consolidation" as part of "memory"
+        base_name = module_name.split('.', 1)[0]
+
+        if base_name in high_risk_modules:
             return 'high'
-        elif module_name in medium_risk_modules:
+        elif base_name in medium_risk_modules:
             return 'medium'

This keeps top-level categories meaningful even for specific submodules and ensures medium/high-risk changes are correctly gated.

Also applies to: 143-156

🧰 Tools
🪛 Ruff (0.14.5)

54-54: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In puma/shop/modification.py around lines 37 to 80 (and similarly at 143-156),
assess_risk_level is being compared to exact top-level names so dotted submodule
names like "memory.consolidation" are misclassified as 'low', and the call is
invoked twice when building the plan; change the logic to normalize or check
prefixes (e.g., split module_name on '.' and compare the top-level segment or
use startswith) so submodules inherit the correct top-level risk, call
assess_risk_level once, store its result in a local variable, reuse it for both
risk_level and requires_approval (requires_approval = risk in
['medium','high']), and update the same fix at the other location noted.

Comment on lines +7 to +12
from dataclasses import dataclass
from typing import Dict, Any, List
from pathlib import Path
import tempfile
import shutil

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix undefined Optional and clean up unused variables/arguments

There’s one real correctness issue plus several Ruff-lint nits:

  • Line 30: self.sandbox_dir: Optional[Path] but Optional is not imported (F821), which will error at import time.
  • sandbox_copy is assigned but never used.
  • _run_tests’s test_strategy, run_cognitive_ab_test’s original/modified, and the results_*/scenario locals are currently unused placeholders.

You can fix all of these without changing behavior:

-from typing import Dict, Any, List
+from typing import Dict, Any, List, Optional
@@
     async def test_modification(self, modification_plan) -> TestReport:
@@
-        try:
-            # Copy architecture to sandbox
-            sandbox_copy = self._copy_architecture_to_sandbox()
+        try:
+            # Copy architecture to sandbox
+            self._copy_architecture_to_sandbox()
@@
-    async def _run_tests(self, test_strategy: Dict) -> Dict[str, Any]:
+    async def _run_tests(self, test_strategy: Dict) -> Dict[str, Any]:
@@
-        # Would run actual tests
+        # Would run actual tests using `test_strategy`
         return results
@@
-    async def run_cognitive_ab_test(self, original, modified) -> Dict:
+    async def run_cognitive_ab_test(self, original, modified) -> Dict:
@@
-        results_original = []
-        results_modified = []
-
-        for scenario in test_scenarios:
-            # Would test both versions
-            # Placeholder for now
-            pass
-
-        comparison = {
-            'winner': 'modified',
-            'improvement': 0.2,
-            'confidence': 0.85
-        }
+        results_original: List[Dict[str, Any]] = []
+        results_modified: List[Dict[str, Any]] = []
+
+        for scenario in test_scenarios:
+            # Placeholder: record that a scenario was (hypothetically) run
+            results_original.append({'scenario': scenario['type'], 'version': 'original'})
+            results_modified.append({'scenario': scenario['type'], 'version': 'modified'})
+
+        comparison = {
+            'winner': 'modified',
+            'improvement': 0.2,
+            'confidence': 0.85,
+            'results_original': results_original,
+            'results_modified': results_modified,
+        }
@@
     def _generate_test_scenarios(self) -> List[Dict]:

(If you prefer to keep signatures but explicitly mark unused args, you could also bind them to _ = (original, modified) and _ = test_strategy instead.)

Also applies to: 29-32, 44-55, 75-85, 100-116, 117-137, 139-145

🤖 Prompt for AI Agents
In puma/shop/sandbox.py around lines 7-12 and throughout the ranges noted
(29-32, 44-55, 75-85, 100-116, 117-137, 139-145), fix the undefined Optional and
remove or mark unused variables/arguments: add Optional to the typing imports at
the top, remove the unused sandbox_copy assignment (or use it if intended), and
either remove unused local placeholders (results_*, scenario) and unused
function parameters or explicitly mark them as intentionally unused by binding
to _ (e.g., _ = test_strategy or _ = (original, modified)) so Ruff/F821 errors
are resolved without changing behavior.

Comment on lines +138 to +150
### Interactive Mode
```python
from benchmark import Benchmark, SolverConfig

config = SolverConfig()
benchmark = Benchmark(config)
results = benchmark.run_benchmark("test_data.json")
print(f"Success rate: {results['performance_stats']['success_rate']:.3f}")
```

### Monitoring

The solver tracks detailed statistics:

- Success rates for enhanced vs baseline methods
- Component usage (episodic hits, neural guidance, TTT adaptation)
- Timing breakdown per component
- Failure mode analysis

## Implementation Notes
from puma.consciousness import Consciousness

### Kaggle Compatibility
consciousness = Consciousness.load_from_checkpoint("/path/to/checkpoint")

- **Offline execution**: No internet access required
- **Dependency-light**: Uses only NumPy for core operations
- **Compute budget**: Optimized for ~$0.42 per task limit
- **Output format**: Exactly 2 attempts per test input as required
# User interrupt and conversation
consciousness.interrupt_handler.trigger()
await consciousness.converse("Hello, how are you?")

### Code Quality

- **Type hints**: Full typing support for better maintainability
- **Documentation**: Comprehensive docstrings and comments
- **Error handling**: Robust fallback mechanisms
- **Testing**: Validation and benchmarking utilities

## Extending the Solver

### Adding New DSL Operations

1. Define operation function in `dsl.py`
1. Add parameter generation in `sketches.py`
1. Update feature extraction in `features.py`
1. Retrain neural guidance if needed
# Resume autonomous activity
consciousness.resume()
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Interactive example imports Consciousness that is not exported from puma.consciousness

The “Interactive Mode” example uses:

from puma.consciousness import Consciousness
consciousness = Consciousness.load_from_checkpoint("/path/to/checkpoint")

but puma/consciousness/__init__.py currently only exports ConsciousnessStateMachine, ConsciousnessState, SelfModel, and TemporalSelf. As written, this import will fail.

Either:

  • Re-export the actual Consciousness façade from puma.consciousness.__init__ (if such a class exists elsewhere), or
  • Update the README example to import whatever the real entrypoint is (e.g., a bootstrap function or state-machine wrapper).

Also consider wrapping the await calls in a minimal asyncio.run(main()) pattern so the snippet is directly runnable.

🤖 Prompt for AI Agents
In README.md around lines 138-150 the Interactive Mode example imports a
non-exported name `Consciousness` from `puma.consciousness`, which will fail;
either re-export the actual Consciousness façade in
puma/consciousness/__init__.py (add `from .module_where_it_lives import
Consciousness` and include it in __all__) or update the README example to import
the real public entrypoint (e.g., the state machine or bootstrap function that
is exported) and adjust the example to be runnable by wrapping async calls in an
asyncio.run(main()) pattern so the snippet works as shown.

Comment on lines +1 to +9
"""
Web Agent
Autonomous web browsing and learning.
"""

from .agent import AutonomousWebAgent

__all__ = ['AutonomousWebAgent']
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Verify package name: web-agent directory is not importable as a normal Python package

The __init__.py content itself is fine, but the directory name in the path (web-agent/__init__.py) isn’t a valid Python identifier. You won’t be able to do:

import web-agent          # syntax error
from web-agent import AutonomousWebAgent  # syntax error

If this is meant to be an importable package, consider renaming the directory to something like web_agent and updating imports (and any references in bootstrap/config) accordingly:

web_agent/
    __init__.py
    agent.py

Then consumers can use:

from web_agent import AutonomousWebAgent

Please double‑check current imports/usages to ensure the name matches how it’s being loaded.


I'll verify the actual package naming and import patterns in the codebase.


🏁 Script executed:

#!/bin/bash
# Find the actual directory structure for the web agent package
fd -t d 'web' --max-depth 2

Length of output: 10


🏁 Script executed:

#!/bin/bash
# Search for how this package is actually imported
rg -n 'from\s+(web[-_]agent|web_agent)' --type=py
rg -n 'import\s+(web[-_]agent|web_agent)' --type=py

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for setup.py or pyproject.toml that might define package mapping
fd -e 'setup.py' -o -e 'pyproject.toml' -o -e 'setup.cfg' | head -5
cat setup.py pyproject.toml setup.cfg 2>/dev/null || true

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Look for any sys.path manipulation or importlib usage around web-agent
rg -n 'sys\.path|importlib|__path__' --type=py -B2 -A2 | head -50

Length of output: 2414


🏁 Script executed:

#!/bin/bash
# Find actual imports from web-agent after sys.path manipulation
rg -n 'AutonomousWebAgent' --type=py -B3 -A3

Length of output: 1740


🏁 Script executed:

#!/bin/bash
# Check if bootstrap.py is always the entry point or if web-agent is accessed other ways
rg -n 'from agent import|web_agent|web-agent' --type=py

Length of output: 647


🏁 Script executed:

#!/bin/bash
# Look at how bootstrap is actually used - is it always called first?
rg -n 'bootstrap' --type=py -l | head -10
cat bootstrap/bootstrap.py | grep -A20 "sys.path.append" 

Length of output: 986


Rename web-agent directory to web_agent to follow Python package naming conventions

The directory name web-agent with a hyphen is not a valid Python identifier and cannot be imported using standard Python import syntax. While the code currently works via sys.path.append() in bootstrap/bootstrap.py, this is a non-standard workaround that is fragile and could break if imports are attempted from other entry points.

Required changes:

  1. Rename directory from web-agent/ to web_agent/
  2. Update bootstrap/bootstrap.py line 26: change 'web-agent' to 'web_agent'

This allows standard imports like from web_agent import AutonomousWebAgent and aligns with PEP 8 package naming conventions.

🤖 Prompt for AI Agents
In web-agent/__init__.py around lines 1 to 9, the package directory name uses a
hyphen which is not a valid Python package identifier; rename the directory from
"web-agent" to "web_agent" and update any imports accordingly, then edit
bootstrap/bootstrap.py at line 26 to change the string 'web-agent' to
'web_agent' so standard imports (e.g. from web_agent import AutonomousWebAgent)
work and comply with PEP 8.

Comment on lines +127 to +151
async def integrate_web_learning(self, exploration: WebExploration, memory_system):
"""
Web experiences become part of autobiographical memory.
"""
if not memory_system:
return

# Create episodic memory of exploration
episode = memory_system.form_episode(
perception={
'type': 'web_exploration',
'url': exploration.url
},
action={
'type': 'browse_and_learn'
},
outcome={
'learned_concepts': exploration.learned_concepts,
'questions_answered': exploration.questions_answered,
'new_questions': exploration.new_questions
},
memory_type='web_exploration'
)

return episode
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Ensure memory_type matches the EpisodicMemorySystem API

integrate_web_learning currently calls:

episode = memory_system.form_episode(
    ...,
    memory_type='web_exploration'
)

The form_episode signature in puma/memory/episodic.py takes memory_type: MemoryType = MemoryType.GENERAL. If MemoryType is an Enum (as it appears), passing a bare string here will give episodes a different type than the rest of the system expects, and any logic that checks episode.type == MemoryType.<something> will fail.

It would be safer to use the proper enum variant, e.g.:

-from puma.memory.episodic import MemoryType  # wherever appropriate
+from puma.memory.episodic import MemoryType  # wherever appropriate
@@
-            memory_type='web_exploration'
+            memory_type=MemoryType.WEB_EXPLORATION

(Adjust the enum member name to whatever you actually define.)


🏁 Script executed:

#!/bin/bash
# Find and inspect the form_episode method definition
find . -type f -name "episodic.py" | head -5

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Search for form_episode method signature and MemoryType enum
rg -nP "def form_episode|class MemoryType" --type py -A 5

Length of output: 742


🏁 Script executed:

#!/bin/bash
# Find all usages of memory_type parameter to understand current patterns
rg -nP "memory_type\s*=" --type py -B 2 -A 2

Length of output: 885


🏁 Script executed:

#!/bin/bash
# Check for MemoryType enum definition and its variants
rg -nP "class MemoryType|MemoryType\." --type py -A 3

Length of output: 1468


🏁 Script executed:

#!/bin/bash
# Look at the integrate_web_learning method and its imports
rg -nP "from.*memory|import.*MemoryType" web-agent/agent.py -B 2 -A 2

Length of output: 0


Import MemoryType and use the enum variant in integrate_web_learning

Verification confirms the issue. The form_episode method signature (puma/memory/episodic.py:79-84) expects memory_type: MemoryType (an Enum), but web-agent/agent.py currently passes memory_type='web_exploration' as a string. The Episode class stores type as a MemoryType enum and uses enum comparisons (e.g., self.type == MemoryType.WEB_EXPLORATION); passing a string will break these comparisons.

Fix required at web-agent/agent.py:127-151:

  1. Add import: from puma.memory.episodic import MemoryType
  2. Change memory_type='web_exploration' to memory_type=MemoryType.WEB_EXPLORATION
🤖 Prompt for AI Agents
In web-agent/agent.py around lines 127 to 151, the call to
memory_system.form_episode uses memory_type='web_exploration' (a string) but the
method expects a MemoryType enum; import the enum with from puma.memory.episodic
import MemoryType and change the argument to
memory_type=MemoryType.WEB_EXPLORATION so the episode stores and compares the
enum variant correctly.

@tylerbessire tylerbessire merged commit b5fa4a1 into main Nov 22, 2025
3 of 6 checks passed
@tylerbessire tylerbessire deleted the claude/puma-hyperon-gemini-setup-015hEUXsb2PAB3YndryQtpzQ branch November 22, 2025 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants