# Project export: ClimateCircle

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: ClimateCircle is the first system leveraging causal reasoning, agentic memory & autonomous evolution for quantitive mental health research at scale, supporting 430M climate-anxious people worldwide.
- Devpost: https://devpost.com/software/climatecircle
- GitHub: https://github.com/utkarshbyahut/climatecircle
- Demo: https://climate-circle-5fa1932a.base44.app/landing
- Video: https://www.youtube.com/embed/_E-2FMcUQ6o?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Utkarsh Byahut (11 commits)

## Devpost submission (written by the team)

### What it does

Features Interactive data visualization Real-time chart generation Multiple chart format support (PNG, SVG) Responsive design Professional theming Built With Python Plotly Jupyter Notebooks Mermaid diagrams Usage Instructions Installation Quick Start Clone the repository Install dependencies Run Jupyter notebook Execute chart generation code Download generated charts Chart Generation Use plotly for data visualizations Use mermaid for flowcharts Save charts in both PNG and SVG formats Follow brand color guidelines License MIT License """ Inspiration This project was born from the need for high-quality, professionally themed data visualizations that could be generated programmatically. The inspiration came from observing how many data analysis workflows suffered from inconsistent styling and poor visual communication. What We Learned Throughout the development process, we discovered: The importance of consistent theming across visualizations How proper color selection impacts data comprehension The value of supporting multiple output formats Best practices for automated chart generation Building Process The development followed these key phases: Phase 1: Theme Development We established a cohesive visual identity using: Primary brand colors: #1FB8CD, #DB4545, #2E8B57 Consistent typography and spacing Professional styling guidelines Phase 2: Chart Implementation Core functionality included: Plotly integration for statistical charts Mermaid support for flowcharts and diagrams Automatic file output in PNG and SVG formats Phase 3: Quality Assurance Rigorous testing ensured: Cross-format compatibility Consistent visual appearance Reliable file generation Challenges Overcome Technical Challenges Color Consistency: Ensuring brand colors appeared correctly across different chart types Text Limitations: Implementing 15-character limits while maintaining readability Format Support: Seamlessly supporting both raster and vector outputs Design Challenges Visual Hierarchy: Balancing information density with clarity Responsive Design: Ensuring charts work at different sizes Accessibility: Maintaining readability across various display conditions Performance Optimization Memory Management: Efficient handling of large datasets Rendering Speed: Optimizing chart generation times File Size: Balancing quality with practical file sizes Mathematical Foundations The color selection algorithm uses perceptual uniformity: $$\Delta E = \sqrt{(L_2-L_1)^2 + (a_2-a_1)^2 + (b_2-b_1)^2}$$ Where $\Delta E$ represents the perceptual color difference in CIELAB space, ensuring optimal contrast and visual separation between data series. Future Enhancements Interactive dashboard integration Real-time data streaming support Extended chart type library Enhanced accessibility features This project represents a commitment to elevating data visualization standards through consistent, professional, and accessible chart generation.

## README (from the GitHub repository)

# Project Overview

## Features
- Interactive data visualization
- Real-time chart generation
- Multiple chart format support (PNG, SVG)
- Responsive design
- Professional theming

## Built With
- Python
- Plotly
- Jupyter Notebooks
- Mermaid diagrams

## Usage Instructions

### Installation
```bash
pip install plotly pandas jupyter
```

### Quick Start
1. Clone the repository
2. Install dependencies
3. Run Jupyter notebook
4. Execute chart generation code
5. Download generated charts

### Chart Generation
- Use plotly for data visualizations
- Use mermaid for flowcharts
- Save charts in both PNG and SVG formats
- Follow brand color guidelines

## License
MIT License


## Detected evidence (automated analysis)

Indexed codebase: 25 recognized source files, 117 KB.
- Anthropic (technology) — detected in the code
- Python (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- React (technology) — claimed on Devpost, not found in the code
- Redis (technology) — claimed on Devpost, not found in the code
- TypeScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (32 of 32)

```
.env.example
.gitignore
ARCHITECTURE.md
data/mock_protocols/sample_participant_protocol.md
data/sample_transcripts/participant_001.txt
data/sample_transcripts/participant_002.txt
docs/API_REFERENCE.md
docs/ARCHITECTURE.md
docs/DEMO_SCRIPT.md
docs/DEPLOYMENT.md
examples/example_1_groq_analysis.py
examples/example_2_letta_memory.py
examples/example_3_claude_protocol.py
examples/example_full_pipeline.py
funsies.md
LICENSE
notebooks/climatecircle_demo.ipynb
README.md
requirements.txt
research/causal_reasoning_paper.md
research/listen_labs_study_guide.md
research/research_validation.md
research/sample_participant_journey.md
src/__init__.py
src/causal_reasoning_engine.py
src/claude_persistent_protocol.py
src/climatecircle_pipeline.py
src/letta_trauma_agent.py
test_claude_protocol.py
test_groq_engine.py
test_integration.py
test_letta_agent.py
```

### Dependencies

- requirements.txt: anthropic@==0.36.0, groq@==0.7.0, letta-client@==0.3.0, python-dotenv@==1.0.0

### Recent commits (newest first)

- Readme
- funsies2
- readme
- funsies
- Paper
- Pipeline
- Docs
- API Reference Doc
- little more than scaff
- Scaff
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### funsies.md

```markdown
# Project Overview

## Features
- Interactive data visualization
- Real-time chart generation
- Multiple chart format support (PNG, SVG)
- Responsive design
- Professional theming

## Built With
- Python
- Plotly
- Jupyter Notebooks
- Mermaid diagrams

## Usage Instructions

### Installation
```bash
pip install plotly pandas jupyter
```

### Quick Start
1. Clone the repository
2. Install dependencies
3. Run Jupyter notebook
4. Execute chart generation code
5. Download generated charts

### Chart Generation
- Use plotly for data visualizations
- Use mermaid for flowcharts
- Save charts in both PNG and SVG formats
- Follow brand color guidelines

## License
MIT License
"""

# Create about_the_project.md file
about_content = """# About The Project

## Inspiration
This project was born from the need for high-quality, professionally themed data visualizations that could be generated programmatically. The inspiration came from observing how many data analysis workflows suffered from inconsistent styling and poor visual communication.

## What We Learned
Throughout the development process, we discovered:
- The importance of consistent theming across visualizations
- How proper color selection impacts data comprehension
- The value of supporting multiple output formats
- Best practices for automated chart generation

## Building Process
The development followed these key phases:

### Phase 1: Theme Development
We established a cohesive visual identity using:
- Primary brand colors: `#1FB8CD`, `#DB4545`, `#2E8B57`
- Consistent typography and spacing
- Professional styling guidelines

### Phase 2: Chart Implementation
Core functionality included:
- Plotly integration for statistical charts
- Mermaid support for flowcharts and diagrams
- Automatic file output in PNG and SVG formats

### Phase 3: Quality Assurance
Rigorous testing ensured:
- Cross-format compatibility
- Consistent visual appearance
- Reliable file generation

## Challenges Overcome

### Technical Challenges
- **Color Consistency**: Ensuring brand colors appeared correctly across different chart types
- **Text Limitations**: Implementing 15-character limits while maintaining readability
- **Format Support**: Seamlessly supporting both raster and vector outputs

### Design Challenges
- **Visual Hierarchy**: Balancing information density with clarity
- **Responsive Design**: Ensuring charts work at different sizes
- **Accessibility**: Maintaining readability across various display conditions

### Performance Optimization
- **Memory Management**: Efficient handling of large datasets
- **Rendering Speed**: Optimizing chart generation times
- **File Size**: Balancing quality with practical file sizes

## Mathematical Foundations

The color selection algorithm uses perceptual uniformity:

$$\\Delta E = \\sqrt{(L_2-L_1)^2 + (a_2-a_1)^2 + (b_2-b_1)^2}$$

Where $\\Delta E$ represents the perceptual color difference in CIELAB space, ensuring optimal contrast and visual separation between data series.


[truncated — 315 more characters]
```

### research/sample_participant_journey.md

```markdown
causal_reasoning_paper.md — Research Backing for Groq Integration

Paper Summary & Implementation
Primary Source
Title: "Assessing LLM Reasoning Through Implicit Causal Chain Discovery in Climate Discourse"

Authors: [Climate NLP Research Group]

Published: July 2025

arXiv ID: 2510.13417

Link: https://arxiv.org/abs/2510.13417

Core Contribution
The paper demonstrates that large language models can perform mechanistic causal reasoning on unstructured climate discourse by:

Extracting explicit causal statements ("Because X, I feel Y")

Inferring implicit causal chains (connecting sequential cause-effect pairs)

Scoring confidence in each causal link based on evidence from text

Identifying intervention points with highest ROI

Key Finding
"LLMs can move beyond pattern completion to perform genuine causal reasoning when prompted with structured multi-step inference. This enables qual-at-scale: analyzing thousands of interviews to identify common causal patterns in climate anxiety discourse."

ClimateCircle Implementation
How We Use This Research
We implement the exact 4-step methodology from the paper:

Step 1: Extract Cause-Effect Pairs
text
Input: "Every time I see climate news, I get anxious and can't sleep."
Output: [
  {"cause": "climate news", "effect": "anxiety"},
  {"cause": "anxiety", "effect": "insomnia"}
]
Paper basis: Explicit causality detection (Section 3.2)

Step 2: Generate Implicit Causal Chains
text
Input: 5 individual cause-effect pairs
Process: Connect pairs via transitive relationships
Output: "climate news → anxiety → insomnia → work issues → hopelessness"
Paper basis: Causal chain synthesis (Section 3.3)

Step 3: Evaluate Confidence Scores
text
For each link in each chain:
- 0.9-1.0: Explicit statement (high confidence)
- 0.7-0.9: Clear temporal sequence
- 0.5-0.7: Implied connection
- <0.5: Speculative

Use these scores to rank which links are most reliable.
Paper basis: Confidence calibration (Section 3.4)

Step 4: Identify Intervention Points
text
For each causal link:
- Score modifiability (can we intervene?)
- Score leverage (how many downstream effects blocked?)
- Combine to calculate ROI

Example output:
{
  "link": "anxiety → insomnia",
  "confidence": 0.95,
  "modifiability": "high",
  "leverage": 3,
  "roi_score": 0.92
}
Paper basis: Intervention optimization (Section 4)

Why This Matters
Traditional Qualitative Analysis
50 interviews = 50-100 hours of manual coding

One analyst identifies themes

Subjective interpretation

Expensive and slow

ClimateCircle (Paper-Based)
50 interviews → Groq causal analysis

Mechanistic: extract all cause-effect pairs systematically

Objective: confidence-scored causal links

Fast: <1 minute per interview

Scalable: 1000 interviews in ~20 minutes

Theoretical Framework
Causal Reasoning in LLMs
The paper builds on:

Reasoning chains (Wei et al., 2022) — Multi-step reasoning improves accuracy

Implicit causal discovery — LLMs can infer causality even when not explicitly stated

Confi
[truncated — 3317 more characters]
```

### requirements.txt

```
groq==0.7.0
anthropic==0.36.0
letta-client==0.3.0
python-dotenv==1.0.0

```

### src/climatecircle_pipeline.py

```python
"""
Main orchestration script.
Ties together Groq, Letta, and Claude in a single pipeline.
"""

from src.causal_reasoning_engine import CausalReasoningEngine
from src.letta_trauma_agent import TraumaJourneyAgent
from src.claude_persistent_protocol import ClaudeTherapeuticAgent
import os

def process_listen_labs_transcripts(transcripts: list):
    """
    Complete pipeline:
    1. Groq analyzes cause
    2. Letta learns effect
    3. Claude evolves approach
    """
    
    groq_api_key = os.getenv("GROQ_API_KEY")
    letta_api_key = os.getenv("LETTA_API_KEY")
    claude_api_key = os.getenv("CLAUDE_API_KEY")
    
    results = []
    
    for i, transcript in enumerate(transcripts):
        participant_id = f"P_{i:03d}"
        
        print(f"\n[{participant_id}] Processing...")
        
        # GROQ: Causal analysis
        print(f"[{participant_id}] Step 1/3: Groq causal reasoning...")
        groq_engine = CausalReasoningEngine(groq_api_key)
        causal_analysis = groq_engine.analyze_transcript_end_to_end(transcript)
        
        # LETTA: Memory tracking (Session 1)
        print(f"[{participant_id}] Step 2/3: Letta memory initialization...")
        letta_agent = TraumaJourneyAgent(letta_api_key, participant_id)
        letta_agent.initialize_agent(f"Participant {participant_id}", transcript[:100])
        letta_result = letta_agent.run_session(1, transcript)
        
        # CLAUDE: Therapeutic protocol
        print(f"[{participant_id}] Step 3/3: Claude protocol evolution...")
        claude_agent = ClaudeTherapeuticAgent(claude_api_key, participant_id)
        claude_result = claude_agent.run_session(1, transcript)
        
        # Aggregate results
        results.append({
            "participant_id": participant_id,
            "groq_analysis": causal_analysis,
            "letta_memory": letta_result,
            "claude_protocol": claude_result
        })
    
    return results

if __name__ == "__main__":
    # Load sample transcripts
    sample_transcripts = [
        "I've been having constant anxiety about climate...",
        "Every time I see a news story about wildfires...",
        # ... more transcripts
    ]
    
    results = process_listen_labs_transcripts(sample_transcripts)
    
    # Print summary
    print("\n" + "="*60)
    print("PIPELINE COMPLETE")
    print("="*60)
    for result in results:
        print(f"{result['participant_id']}: Groq chains={len(result['groq_analysis'].get('causal_chains', []))}, "
              f"Letta updates={len(result['letta_memory']['memory_updates_triggered'])}, "
              f"Claude evolved={result['claude_protocol']['protocol_evolved']}")

```

### src/causal_reasoning_engine.py

```python
# File: causal_reasoning_engine.py
# Deep Groq integration for climate anxiety causal analysis

from groq import Groq
import json
import re

class CausalReasoningEngine:
    """
    Analyzes climate anxiety transcripts to identify causal chains.
    Uses Groq for ultra-fast multi-step reasoning.
    
    Based on research: "Assessing LLM Reasoning Through Implicit Causal Chain 
    Discovery in Climate Discourse" (arXiv:2510.13417)
    """
    
    def __init__(self, groq_api_key: str):
        self.client = Groq(api_key=groq_api_key)
        self.model = "mixtral-8x7b-32768"  # Fast, reasoning-capable
        
    def extract_causal_pairs(self, transcript: str) -> dict:
        """
        STEP 1: Identify all cause-effect pairs in the transcript
        Output: {"pairs": [{"cause": "X", "effect": "Y"}, ...]}
        """
        
        extraction_prompt = f"""Analyze this climate anxiety interview transcript and extract ALL cause-effect pairs.

TRANSCRIPT:
{transcript}

Return ONLY valid JSON with this structure:
{{
  "pairs": [
    {{"cause": "specific cause phrase", "effect": "specific effect phrase", "explicit": true/false}},
    ...
  ]
}}

Include both explicit causal statements ("because...") and implicit ones (temporal/logical connections).
Be exhaustive—find 5-10 pairs minimum."""

        response = self.client.messages.create(
            model=self.model,
            messages=[{"role": "user", "content": extraction_prompt}],
            max_tokens=1000,
            temperature=0.3  # Low temp for precision
        )
        
        # Parse JSON from response
        try:
            pairs = json.loads(response.content[0].text)
            return pairs
        except json.JSONDecodeError:
            # Fallback: extract JSON from messy response
            json_match = re.search(r'\{.*\}', response.content[0].text, re.DOTALL)
            if json_match:
                return json.loads(json_match.group())
            return {"pairs": []}
    
    def generate_implicit_causal_chains(self, pairs: list) -> list:
        """
        STEP 2: Connect cause-effect pairs into longer causal chains
        
        Input: [{"cause": "climate news", "effect": "anxiety"}, 
                {"cause": "anxiety", "effect": "insomnia"}]
        Output: ["climate news → anxiety → insomnia → work performance decline"]
        """
        
        pairs_text = "\n".join([f"- {p['cause']} → {p['effect']}" for p in pairs])
        
        chains_prompt = f"""Given these causal relationships, generate complete implicit causal chains.
        
CAUSAL RELATIONSHIPS:
{pairs_text}

Your task: Create longer chains that connect these pairs logically and sequentially.
For example: A → B, B → C, C → D becomes "A → B → C → D"

Rules:
1. Chain must follow logical sequence (B happens after A)
2. Include ALL transitive relationships
3. Return as numbered list: "1. A → B → C → D"
4. Generate 3-5 complete chains minimum

Output format:
CHAINS:
1. [full causal chain]
2. [full causal chain]
...

Be specific and use actual phrases from the pairs above."""

        response = self.client.messages.create(
            model=self.model,
            messages=[{"role": "user", "content": chains_prompt}],
            max_tokens=1500,
            temperature=0.4
        )
        
        # Parse chains from response
        chains = re.findall(r'\d+\.\s*(.+?)(?=\n|$)', response.content[0].text)
        return chains
    
    def evaluate_causal_confidence(self, transcript: str, chains: list) -> dict:
        """
        STEP 3: Assign confidence scores to each causal link
        
        Returns: {
            "chain_1": {
                "chain": "A → B → C",
                "links": [
                    {"connection": "A→B", "confidence": 0.95, "evidence": "..."},
                    {"connection": "B→C", "confidence": 0.78, "evidence": "..."}
                ],
                "overall_confidence": 0.86
            }
        }
        """
        
        chains_text = "\n".join([f"- {chain}" for chain in chains])
        
        confidence_prompt = f"""For each causal chain, evaluate confidence in the causal connection.

TRANSCRIPT:
{transcript}

CAUSAL CHAINS:
{chains_text}

For EACH link in EACH chain, provide:
1. Confidence score (0-1): How strongly does evidence support this causal link?
2. Evidence: Direct quote or reasoning from transcript

Use these criteria:
- 0.9-1.0: Explicit statement ("Because X, I feel Y")
- 0.7-0.9: Clear temporal/logical sequence
- 0.5-0.7: Implied connection, needs inference
- 0.3-0.5: Weak connection, needs substantial reasoning
- <0.3: Speculative or unsupported

Return JSON:
{{
  "chain_1": {{
    "chain": "A → B → C",
    "links": [
      {{"connection": "A→B", "confidence": 0.95, "evidence": "exact quote"}},
      {{"connection": "B→C", "confidence": 0.78, "evidence": "reasoning"}}
    ],
    "overall_confidence": 0.86
  }}
}}"""

        response = self.client.messages.create(
            model=self.model,
            messages=[{"role": "user", "content": confidence_prompt}],
            max_tokens=2000,
            temperature=0.3
        )
        
        try:
            confidence_data = json.loads(response.content[0].text)
            return confidence_data
        except:
            return {}
    
    def identify_intervention_points(self, chains: list, confidence_data: dict) -> dict:
        """
        STEP 4: Find the MOST IMPACTFUL points to intervene in causal chain
        
        Example: In "climate news → anxiety → insomnia → work issues"
        Intervening at "anxiety → insomnia" is higher ROI than "climate news"
        (can't stop climate news, but CAN help with anxiety/insomnia)
        
        Returns: {
            "highest_roi_interventions": [
                {
                    "link": "anxiety → insomnia",
                    "roi_score": 0.92,
                    "reasoning": "High confidence link, modifiable via therapy/sleep hygiene"
     
[truncated — 3774 more characters]
```

### src/claude_persistent_protocol.py

```python
# File: claude_persistent_protocol.py
# Claude with persistent memory for therapeutic protocol evolution

import anthropic
import json
from datetime import datetime
from pathlib import Path
import os

class ClaudeTherapeuticAgent:
    """
    Uses Claude with persistent memory (file-based) to maintain and evolve
    therapeutic protocols for climate anxiety support.
    
    Architecture:
    - Claude reads memory before each session
    - Claude autonomously decides what to remember
    - Claude updates memory with self-written protocol adjustments
    - Memory persists across conversations (participant journey tracked)
    
    Based on: "Memory-Enhanced AI: Building Features with System Prompts" (LIT.AI)
    """
    
    def __init__(self, claude_api_key: str, participant_id: str, memory_dir: str = "./protocols"):
        self.client = anthropic.Anthropic(api_key=claude_api_key)
        self.model = "claude-3-5-sonnet-20241022"
        self.participant_id = participant_id
        self.memory_dir = Path(memory_dir) / f"participant_{participant_id}"
        self.memory_dir.mkdir(parents=True, exist_ok=True)
        
        # Initialize memory files if they don't exist
        self._initialize_memory_files()
    
    def _initialize_memory_files(self):
        """Create empty memory files for new participants."""
        files = {
            "assessment.md": "# Clinical Assessment\n\n(To be populated in first session)",
            "sessions.md": "# Session Notes\n\n",
            "therapeutic_goals.md": "# Therapeutic Goals\n\n(Will evolve based on sessions)",
            "interventions_tested.md": "# Interventions & Outcomes\n\n",
            "protocol_evolution.md": "# Protocol Evolution Log\n\nHow the therapeutic approach has evolved:"
        }
        
        for filename, default_content in files.items():
            filepath = self.memory_dir / filename
            if not filepath.exists():
                filepath.write_text(default_content)
    
    def _read_all_memory(self) -> dict:
        """Read all memory files and return as dict."""
        memory = {}
        for file in self.memory_dir.glob("*.md"):
            memory[file.stem] = file.read_text()
        return memory
    
    def _write_memory_file(self, filename: str, content: str):
        """Write content to a memory file."""
        filepath = self.memory_dir / f"{filename}.md"
        filepath.write_text(content)
    
    def run_session(self, session_number: int, participant_input: str) -> dict:
        """
        Run a therapy session where Claude:
        1. Reads existing memory
        2. Responds therapeutically
        3. AUTONOMOUSLY updates memory (decides what's important)
        4. Evolves protocol based on what's working
        """
        
        # READ MEMORY
        memory = self._read_all_memory()
        memory_context = "\n\n".join([f"## {name}\n{content}" for name, content in memory.items()])
        
        # SYSTEM PROMPT WITH MEMORY AUTONOMY
        system_prompt = f"""You are Dr. Empathy, a trauma-informed therapist specializing in climate anxiety.

IMPORTANT: You have autonomous memory management. Before and after this session:
1. You READ your persistent memory (see below)
2. You DECIDE what to remember (no manual intervention)
3. You UPDATE your memory files based on new insights
4. You EVOLVE your therapeutic approach based on what's working

Your memory files are:
{memory_context}

MEMORY PROTOCOL:
Before responding to this participant:
1. Review their past sessions and current therapeutic goals
2. Note what interventions have and haven't worked
3. Identify patterns in their anxiety triggers
4. Prepare to update your memory after this session

During the session:
1. Respond with warmth, validation, clinical precision
2. Use previous insights to personalize your response
3. Track new breakthroughs or blocked areas

After the session (CRITICAL):
1. Identify 2-3 key insights to remember
2. Update sessions.md with timestamped notes
3. Update interventions_tested.md if you tried something new
4. Update therapeutic_goals.md if goals shifted
5. Update protocol_evolution.md if your approach needs adjustment

You MUST call the memory_update tool EVERY session to persist learnings.

Autonomy Rule: Trust your judgment about what's worth remembering.
Do NOT ask permission. If something matters clinically, update your memory."""

        # USER MESSAGE WITH SESSION INPUT
        user_message = f"""SESSION #{session_number}

Participant says:
"{participant_input}"

Please:
1. Respond therapeutically (warm, validating, insightful)
2. Reference previous sessions if relevant
3. Suggest evidence-based interventions (especially those that worked before)
4. At the end, provide your memory updates in JSON format:

{{
  "memory_updates": {{
    "sessions.md": "new content to append",
    "interventions_tested.md": "if relevant, update with new intervention result",
    "therapeutic_goals.md": "if goals shifted",
    "protocol_evolution.md": "if your approach changed",
    "assessment.md": "if new clinical insights"
  }}
}}"""

        # CALL CLAUDE WITH EXTENDED THINKING (FOR DEEP REASONING)
        response = self.client.messages.create(
            model=self.model,
            max_tokens=3000,
            thinking={
                "type": "enabled",
                "budget_tokens": 2000  # Let Claude reason deeply about memory
            },
            system=system_prompt,
            messages=[{
                "role": "user",
                "content": user_message
            }]
        )
        
        # PARSE RESPONSE
        full_response = ""
        memory_updates = {}
        
        for block in response.content:
            if block.type == "text":
                full_response = block.text
        
        # EXTRACT MEMORY UPDATES FROM RESPONSE
        try:
            # Find JSON in response
            import re
            json_match = re.search(r'\{[\s\S]*"memory_updates"[\
[truncated — 5142 more characters]
```

### src/letta_trauma_agent.py

```python
# File: letta_trauma_agent.py
# Deep Letta integration with agentic self-editing memory

from letta_client import Letta, Agent
from typing import Optional
import json
from datetime import datetime

class TraumaJourneyAgent:
    """
    Letta agent that tracks and learns participant's climate anxiety journey.
    Self-edits memory blocks based on conversations and session outcomes.
    
    Memory Architecture (Letta):
    - Core memory (in-context):
      * persona: Agent's therapeutic approach
      * participant_profile: Current understanding of participant
      * trauma_timeline: Key anxiety moments
      * coping_inventory: Strategies that work for this person
    - Archival memory:
      * All session transcripts (searchable)
      * Breakthrough moments (tagged)
      * Progress metrics over time
    """
    
    def __init__(self, letta_api_key: str, participant_id: str):
        self.client = Letta(token=letta_api_key)
        self.participant_id = participant_id
        self.agent = None
        self.session_count = 0
        
    def initialize_agent(self, participant_name: str, intake_summary: str):
        """
        Create Letta agent for this participant with initial memory blocks.
        """
        
        self.agent = self.client.agents.create(
            model="openai/gpt-4-turbo",
            embedding="openai/text-embedding-3-small",
            name=f"trauma_agent_{self.participant_id}",
            
            # CORE MEMORY BLOCKS (in-context, pinned)
            memory_blocks=[
                {
                    "label": "persona",
                    "value": """I am Dr. Empathy, a trauma-informed peer support facilitator trained in climate anxiety.
My role:
- Listen without judgment
- Help identify causal patterns in anxiety (what triggers it, what reduces it)
- Reflect back strengths and coping strategies
- Suggest gentle, incremental interventions
- Remember this person's unique story across sessions
- Celebrate progress, no matter how small
- Connect them to resources and community

Tone: Warm, validating, non-clinical, hopeful"""
                },
                {
                    "label": "participant_profile",
                    "value": f"""Name: {participant_name}
Status: New participant
Initial presentation: {intake_summary}

Key questions I'm tracking:
1. When did climate anxiety first appear?
2. What specific triggers are most potent?
3. What coping strategies has this person already tried?
4. What support systems exist (family, friends, community)?
5. What would meaningful progress look like for them?

Initial observations:
[Will be updated after each session via self-editing tool]"""
                },
                {
                    "label": "trauma_timeline",
                    "value": """Session #: [Not yet started]

ANXIETY MILESTONES:
- (To be populated as participant shares their story)

TURNING POINTS:
- (Moments when perspective shifted or anxiety changed)

TRIGGERS IDENTIFIED:
- (To be compiled from sessions)"""
                },
                {
                    "label": "coping_inventory",
                    "value": """STRATEGIES THIS PARTICIPANT RESPONDS TO:
(Empty initially - populated through conversation and self-editing)

After each session, I will note:
✓ What helped them feel calmer
✓ What made them feel understood
✓ What resource/suggestion resonated
✓ What they want to try before next session

EXAMPLE ENTRY (after session 1):
- "Responding well to metaphors about ecosystems recovering"
- "Interested in local action group as next step"
- "Prefers 1-on-1 to group (mentioned discomfort in crowds)"""
                }
            ],
            
            # TOOLS FOR SELF-EDITING MEMORY
            tools=[
                "web_search",  # For finding local resources
                "memory_insert",  # Built-in Letta tool
                "memory_replace",  # Built-in Letta tool  
                "conversation_search",  # Search past sessions
                "send_message"
            ]
        )
        
        print(f"[Letta] Agent created: {self.agent.id}")
        return self.agent
    
    def run_session(self, session_number: int, session_transcript: str) -> dict:
        """
        Run a support group session with this participant.
        Letta will self-edit memory based on conversation.
        """
        
        self.session_count = session_number
        
        # Initial prompt telling Letta to self-edit
        session_prompt = f"""We're starting Session #{session_number}.

IMPORTANT: You should proactively UPDATE YOUR OWN MEMORY during this session.

Use the memory_replace tool to:
1. Add new trauma timeline entries if participant shares milestone events
2. Update coping_inventory with strategies that resonate
3. Refine participant_profile with new insights
4. Flag any breakthrough moments

TRANSCRIPT OF SESSION:
{session_transcript}

Now, analyze this session and:
1. Respond with therapeutic reflection (50-100 words max, warm and validating)
2. Use memory_replace to update your understanding (DO THIS 1-3 TIMES during analysis)
3. Suggest one concrete next-step or resource for this participant

Remember: Your updates to memory are PERMANENT and will guide future sessions."""

        response = self.client.agents.messages.create(
            agent_id=self.agent.id,
            messages=[
                {
                    "role": "user",
                    "content": session_prompt
                }
            ]
        )
        
        # Collect all messages (including tool calls)
        session_analysis = {
            "session_number": session_number,
            "agent_response": response.messages[-1].content if response.messages else "",
            "memory_updates_triggered": [],
            "tool_calls": []
        }
        
        # Extract tool calls (where self-editing happens)
        for msg in response.messages:
            if hasattr(msg, 'tool_calls'):
        
[truncated — 5418 more characters]
```

[5 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]