File size: 4,802 Bytes
3d46076
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Social model (STAGE H): identity recognition, interaction history, trust
that EMERGES from interaction outcomes (never hard-coded friendship),
persistent relationships."""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

TRUST_INIT = 0.3          # neutral prior, not friendship
TRUST_LEARNING_RATE = 0.2


@dataclass
class InteractionRecord:
    tick: int
    kind: str                 # taught_by | taught_to | communicated | cooperated | competed
    outcome: float            # -1..1 signed outcome for SELF
    other_id: str = ""

    def to_dict(self) -> Dict[str, Any]:
        return dict(self.__dict__)


@dataclass
class SocialRecord:
    other_id: str
    interactions: int = 0
    positive_outcomes: int = 0
    negative_outcomes: int = 0
    trust: float = TRUST_INIT
    first_tick: int = 0
    last_tick: int = 0
    history: List[InteractionRecord] = field(default_factory=list)

    def to_dict(self) -> Dict[str, Any]:
        return {"other_id": self.other_id, "interactions": self.interactions,
                "positive_outcomes": self.positive_outcomes,
                "negative_outcomes": self.negative_outcomes,
                "trust": self.trust, "first_tick": self.first_tick,
                "last_tick": self.last_tick,
                "history": [h.to_dict() for h in self.history[-20:]]}

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "SocialRecord":
        return cls(d["other_id"], int(d.get("interactions", 0)),
                   int(d.get("positive_outcomes", 0)),
                   int(d.get("negative_outcomes", 0)), float(d.get("trust", TRUST_INIT)),
                   int(d.get("first_tick", 0)), int(d.get("last_tick", 0)),
                   [InteractionRecord(**h) for h in d.get("history", [])])


class SocialMemory:
    """Per-organism social memory: recognition + emergent trust."""

    def __init__(self, self_id: str, trust_lr: float = TRUST_LEARNING_RATE):
        self.self_id = self_id
        self.trust_lr = float(trust_lr)
        self.records: Dict[str, SocialRecord] = {}

    def knows(self, other_id: str) -> bool:
        return other_id in self.records

    def trust_of(self, other_id: str) -> float:
        rec = self.records.get(other_id)
        return rec.trust if rec is not None else TRUST_INIT

    def record_interaction(self, other_id: str, tick: int, kind: str,
                           outcome: float) -> SocialRecord:
        if not other_id or other_id == self.self_id:
            raise ValueError("invalid interaction partner")
        outcome = max(-1.0, min(1.0, float(outcome)))
        rec = self.records.get(other_id)
        if rec is None:
            rec = SocialRecord(other_id=other_id, first_tick=tick)
            self.records[other_id] = rec
        rec.interactions += 1
        rec.last_tick = tick
        rec.history.append(InteractionRecord(tick, kind, outcome, other_id))
        if outcome > 0.05:
            rec.positive_outcomes += 1
        elif outcome < -0.05:
            rec.negative_outcomes += 1
        # trust EMERGES from outcomes (delta rule toward observed outcome)
        rec.trust = float(min(1.0, max(0.0,
                       rec.trust + self.trust_lr * (outcome - rec.trust))))
        return rec

    def relationship_strength(self, other_id: str) -> float:
        """Persistent-relationship evidence: repeated interactions + trust."""
        rec = self.records.get(other_id)
        if rec is None:
            return 0.0
        frequency = min(1.0, rec.interactions / 10.0)
        return round(0.5 * rec.trust + 0.5 * frequency, 4)

    def top_partners(self, k: int = 3) -> List[str]:
        ranked = sorted(self.records.values(),
                        key=lambda r: (self.relationship_strength(r.other_id),
                                       r.interactions), reverse=True)
        return [r.other_id for r in ranked[:k]]

    def summary(self) -> Dict[str, Any]:
        return {"self_id": self.self_id, "known_others": len(self.records),
                "strong_relationships": sum(
                    1 for r in self.records.values()
                    if self.relationship_strength(r.other_id) >= 0.5),
                "total_interactions": sum(r.interactions for r in self.records.values())}

    def snapshot(self) -> Dict[str, Any]:
        return {"self_id": self.self_id, "trust_lr": self.trust_lr,
                "records": [r.to_dict() for r in self.records.values()]}

    @classmethod
    def restore(cls, payload: Dict[str, Any]) -> "SocialMemory":
        mem = cls(payload["self_id"], payload.get("trust_lr", TRUST_LEARNING_RATE))
        for d in payload.get("records", []):
            rec = SocialRecord.from_dict(d)
            mem.records[rec.other_id] = rec
        return mem