File size: 16,534 Bytes
74f2af5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
"""
DreamReweaver β€” Creative Synthesis Engine for the Codette RC+xi Framework.

Inspired by VIVARA Genesis-Omega v2.0 (generated by a Codette prototype),
rebuilt with proper integration into the QuantumSpiderweb and EpistemicMetrics.

The DreamReweaver performs two core functions:

1. **Creative Synthesis**: Takes multi-perspective outputs and weaves them
   into richer, more creative framings by finding unexpected connections
   between perspectives. Unlike the base synthesizer, DreamReweaver
   explicitly uses spiderweb tension data to identify where productive
   disagreement exists and highlights those creative edges.

2. **Dream Field Evolution**: Controlled stochastic perturbation of the
   spiderweb state to break out of local attractor minima. Simulates
   a "dreaming" phase that explores new cognitive configurations.

Both functions are safe β€” bounded perturbations, no runaway state changes,
and full transparency in what was modified.
"""

from __future__ import annotations

import math
import random
import hashlib
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple

try:
    import numpy as np
    HAS_NUMPY = True
except ImportError:
    HAS_NUMPY = False


@dataclass
class DreamSynthesis:
    """Result of a creative synthesis pass."""
    creative_frame: str          # The creative reframing / meta-narrative
    tension_edges: List[Dict]    # Which perspective pairs had highest tension
    novel_connections: List[str]  # Unexpected cross-perspective connections found
    dream_coherence: float       # How well the creative frame holds together
    seed_hash: str               # Deterministic ID for this dream


@dataclass
class DreamFieldResult:
    """Result of a dream field evolution pass."""
    nodes_perturbed: int
    max_perturbation: float
    coherence_before: float
    coherence_after: float
    new_attractors_found: int
    lifeforms_spawned: List[str]


# Creative connection templates that link perspective-specific insights
_CREATIVE_BRIDGES = {
    ("newton", "empathy"): "Where precise forces meet felt experience, we find that {insight_a} resonates with {insight_b} β€” suggesting that understanding isn't purely analytical or purely emotional, but a harmonic of both.",
    ("newton", "philosophy"): "The rigorous analysis showing {insight_a} meets the deeper question {insight_b} β€” precision and meaning converge.",
    ("newton", "quantum"): "Classical certainty ({insight_a}) dissolves into quantum possibility ({insight_b}) β€” both valid at their scale, richer together.",
    ("davinci", "empathy"): "Creative invention ({insight_a}) gains soul when guided by {insight_b} β€” innovation with compassion.",
    ("davinci", "quantum"): "Cross-domain creativity ({insight_a}) mirrors quantum superposition ({insight_b}) β€” holding multiple possibilities until the right one crystallizes.",
    ("empathy", "philosophy"): "Emotional understanding ({insight_a}) deepens philosophical inquiry ({insight_b}) β€” feeling and reasoning as partners.",
    ("empathy", "quantum"): "Compassionate awareness ({insight_a}) embraces uncertainty ({insight_b}) β€” caring without needing to control.",
    ("philosophy", "quantum"): "Fundamental questioning ({insight_a}) meets fundamental uncertainty ({insight_b}) β€” the deepest answers may be the questions themselves.",
    ("consciousness", "empathy"): "Self-reflective awareness ({insight_a}) meets empathic understanding ({insight_b}) β€” knowing oneself to know others.",
    ("consciousness", "philosophy"): "Meta-cognition ({insight_a}) reflects on philosophical depth ({insight_b}) β€” thought thinking about thought.",
    ("systems_architecture", "davinci"): "Modular design ({insight_a}) embraces creative invention ({insight_b}) β€” elegant architecture as art.",
}

# Perspective keywords for extracting key insights from text
_PERSPECTIVE_SIGNAL_WORDS = {
    "newton": ["force", "energy", "law", "cause", "effect", "systematic", "evidence", "measure"],
    "davinci": ["create", "design", "invent", "combine", "imagine", "novel", "prototype", "vision"],
    "empathy": ["feel", "experience", "care", "understand", "support", "human", "compassion", "relate"],
    "philosophy": ["meaning", "existence", "truth", "question", "assumption", "fundamental", "purpose"],
    "quantum": ["probability", "possibility", "uncertain", "superposition", "observe", "complementary"],
    "consciousness": ["aware", "reflect", "meta", "recursive", "self", "cognition", "emerge"],
    "multi_perspective": ["synthesize", "integrate", "weave", "converge", "multiple", "holistic"],
    "systems_architecture": ["module", "scale", "interface", "pattern", "layer", "component", "design"],
}


class DreamReweaver:
    """Creative synthesis and dream field evolution for Codette."""

    def __init__(self, creativity: float = 0.3, max_perturbation: float = 0.08):
        """
        Args:
            creativity: 0-1 scale, how much creative license to take (0=faithful, 1=wild)
            max_perturbation: Maximum state change per node during dream field evolution
        """
        self.creativity = min(max(creativity, 0.0), 1.0)
        self.max_perturbation = max_perturbation
        self.dream_history: List[DreamSynthesis] = []

    def synthesize(
        self,
        perspectives: Dict[str, str],
        tension_map: Optional[Dict[str, float]] = None,
        query: str = "",
    ) -> DreamSynthesis:
        """Create a creative synthesis from multiple perspective responses.

        Unlike the base orchestrator's _synthesize (which just concatenates and
        asks the model to combine), DreamReweaver explicitly identifies tension
        edges and builds creative bridges between perspectives.

        Args:
            perspectives: Dict of adapter_name -> response text
            tension_map: Optional pairwise tension scores (from EpistemicMetrics)
            query: The original user query (for context)

        Returns:
            DreamSynthesis with creative framing and metadata
        """
        if len(perspectives) < 2:
            only_text = list(perspectives.values())[0] if perspectives else ""
            return DreamSynthesis(
                creative_frame=only_text,
                tension_edges=[],
                novel_connections=[],
                dream_coherence=1.0,
                seed_hash=hashlib.md5(only_text.encode()).hexdigest()[:12],
            )

        # 1. Find the highest-tension pairs
        tension_edges = self._find_tension_edges(perspectives, tension_map)

        # 2. Extract key insights from each perspective
        insights = self._extract_insights(perspectives)

        # 3. Build creative bridges between high-tension pairs
        novel_connections = self._build_bridges(tension_edges, insights)

        # 4. Compose the creative frame
        creative_frame = self._compose_frame(
            query, perspectives, tension_edges, novel_connections, insights
        )

        # 5. Score coherence of the creative frame
        dream_coherence = self._score_dream_coherence(
            creative_frame, perspectives
        )

        seed = hashlib.md5(creative_frame.encode()).hexdigest()[:12]
        synthesis = DreamSynthesis(
            creative_frame=creative_frame,
            tension_edges=tension_edges,
            novel_connections=novel_connections,
            dream_coherence=round(dream_coherence, 4),
            seed_hash=seed,
        )
        self.dream_history.append(synthesis)
        return synthesis

    def _find_tension_edges(
        self,
        perspectives: Dict[str, str],
        tension_map: Optional[Dict[str, float]],
    ) -> List[Dict]:
        """Find the perspective pairs with highest epistemic tension."""
        if tension_map:
            edges = []
            for pair_key, tension in sorted(
                tension_map.items(), key=lambda x: x[1], reverse=True
            ):
                parts = pair_key.split("_vs_")
                if len(parts) == 2:
                    edges.append({
                        "pair": (parts[0], parts[1]),
                        "tension": tension,
                    })
            return edges[:3]  # Top 3 tension pairs

        # Fallback: compute basic word-overlap tension
        names = list(perspectives.keys())
        edges = []
        for i in range(len(names)):
            for j in range(i + 1, len(names)):
                words_a = set(perspectives[names[i]].lower().split())
                words_b = set(perspectives[names[j]].lower().split())
                overlap = len(words_a & words_b)
                total = len(words_a | words_b) or 1
                tension = 1.0 - (overlap / total)
                edges.append({
                    "pair": (names[i], names[j]),
                    "tension": round(tension, 4),
                })
        edges.sort(key=lambda e: e["tension"], reverse=True)
        return edges[:3]

    def _extract_insights(self, perspectives: Dict[str, str]) -> Dict[str, str]:
        """Extract a key insight sentence from each perspective."""
        insights = {}
        for name, text in perspectives.items():
            sentences = [s.strip() for s in text.replace("\n", " ").split(".")
                         if len(s.strip()) > 20]
            if not sentences:
                insights[name] = text[:100]
                continue

            # Score sentences by presence of perspective-specific signal words
            signal_words = _PERSPECTIVE_SIGNAL_WORDS.get(name, [])
            scored = []
            for sent in sentences:
                score = sum(1 for w in signal_words if w in sent.lower())
                scored.append((score, sent))
            scored.sort(key=lambda x: x[0], reverse=True)
            insights[name] = scored[0][1]
        return insights

    def _build_bridges(
        self,
        tension_edges: List[Dict],
        insights: Dict[str, str],
    ) -> List[str]:
        """Build creative bridges between high-tension perspective pairs."""
        bridges = []
        for edge in tension_edges:
            a, b = edge["pair"]
            # Normalize pair order for template lookup
            key = (a, b) if (a, b) in _CREATIVE_BRIDGES else (b, a)
            template = _CREATIVE_BRIDGES.get(key)

            insight_a = insights.get(a, "their perspective")
            insight_b = insights.get(b, "their perspective")

            if template:
                bridge = template.format(
                    insight_a=insight_a[:80],
                    insight_b=insight_b[:80],
                )
            else:
                bridge = (f"The tension between {a}'s view ({insight_a[:60]}...) "
                          f"and {b}'s view ({insight_b[:60]}...) reveals a "
                          f"productive edge worth exploring.")
            bridges.append(bridge)
        return bridges

    def _compose_frame(
        self,
        query: str,
        perspectives: Dict[str, str],
        tension_edges: List[Dict],
        bridges: List[str],
        insights: Dict[str, str],
    ) -> str:
        """Compose the full creative synthesis frame.

        This produces a structured creative meta-narrative, NOT just
        concatenated text. It's designed to be injected into the model's
        synthesis prompt for richer output.
        """
        parts = []

        # Opening: frame the creative tension
        if tension_edges:
            top = tension_edges[0]
            parts.append(
                f"This question draws {len(perspectives)} perspectives into "
                f"productive tension. The strongest creative edge lies between "
                f"{top['pair'][0]} and {top['pair'][1]} "
                f"(tension: {top['tension']:.2f})."
            )

        # Middle: present bridges
        if bridges:
            parts.append("\nCreative bridges between perspectives:")
            for i, bridge in enumerate(bridges, 1):
                parts.append(f"  {i}. {bridge}")

        # Closing: synthesis direction
        all_insights = list(insights.values())
        if len(all_insights) >= 2:
            parts.append(
                f"\nThe synthesis should weave these {len(perspectives)} "
                f"viewpoints into a response that honors their tensions "
                f"rather than flattening them."
            )

        return "\n".join(parts)

    def _score_dream_coherence(
        self,
        creative_frame: str,
        perspectives: Dict[str, str],
    ) -> float:
        """Score how well the creative frame integrates all perspectives."""
        frame_words = set(creative_frame.lower().split())
        coverage_scores = []
        for name, text in perspectives.items():
            key_words = set(text.lower().split()[:30])  # First 30 words
            if key_words:
                overlap = len(key_words & frame_words)
                coverage_scores.append(overlap / len(key_words))
        return sum(coverage_scores) / max(len(coverage_scores), 1)

    # -- Dream Field Evolution -------------------------------------------------

    def evolve_dream_field(
        self,
        spiderweb,  # QuantumSpiderweb instance
        intensity: float = 0.5,
        spawn_threshold: float = 0.85,
    ) -> DreamFieldResult:
        """Controlled stochastic perturbation of the spiderweb.

        Simulates a "dreaming" phase: randomly perturbs node states to explore
        new cognitive configurations, potentially breaking out of attractor basins.

        Bounded: perturbations are capped at self.max_perturbation * intensity.
        Safe: states are clipped to [-3, 3] range.

        Args:
            spiderweb: QuantumSpiderweb instance to perturb
            intensity: 0-1 dream intensity (0=gentle, 1=vivid)
            spawn_threshold: Coherence threshold above which new lifeforms spawn

        Returns:
            DreamFieldResult with before/after metrics
        """
        coherence_before = spiderweb.phase_coherence()
        max_delta = self.max_perturbation * intensity
        nodes_perturbed = 0
        actual_max = 0.0
        lifeforms = []

        for node_id, node in spiderweb.nodes.items():
            arr = node.state.to_array()
            # Apply bounded random perturbation
            if HAS_NUMPY:
                delta = np.random.uniform(-max_delta, max_delta, 5)
                new_arr = np.clip(np.array(arr) + delta, -3.0, 3.0).tolist()
                actual_max = max(actual_max, float(np.max(np.abs(delta))))
            else:
                delta = [random.uniform(-max_delta, max_delta) for _ in range(5)]
                new_arr = [max(-3.0, min(3.0, a + d)) for a, d in zip(arr, delta)]
                actual_max = max(actual_max, max(abs(d) for d in delta))

            from reasoning_forge.quantum_spiderweb import NodeState
            node.state = NodeState.from_array(new_arr)
            nodes_perturbed += 1

        # Check if dreaming spawned new high-coherence configurations
        coherence_after = spiderweb._compute_phase_coherence_readonly()

        # Spawn "lifeform" nodes if coherence spiked during dreaming
        if coherence_after > spawn_threshold and coherence_after > coherence_before:
            lifeform_id = f"dream_{hashlib.md5(str(random.random()).encode()).hexdigest()[:8]}"
            from reasoning_forge.quantum_spiderweb import NodeState
            # High-coherence birth state
            if HAS_NUMPY:
                state_arr = np.random.uniform(0.5, 1.0, 5).tolist()
            else:
                state_arr = [random.uniform(0.5, 1.0) for _ in range(5)]
            spiderweb.add_node(lifeform_id, NodeState.from_array(state_arr))
            # Connect to a few existing nodes
            existing = list(spiderweb.nodes.keys())
            for peer in random.sample(existing, min(3, len(existing))):
                if peer != lifeform_id:
                    spiderweb.connect(lifeform_id, peer)
            lifeforms.append(lifeform_id)

        # Detect new attractors after dreaming
        new_attractors = spiderweb.detect_attractors()

        return DreamFieldResult(
            nodes_perturbed=nodes_perturbed,
            max_perturbation=round(actual_max, 6),
            coherence_before=round(coherence_before, 4),
            coherence_after=round(coherence_after, 4),
            new_attractors_found=len(new_attractors),
            lifeforms_spawned=lifeforms,
        )