-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path05_multi_agent_shared_memory.py
More file actions
153 lines (125 loc) · 5.34 KB
/
05_multi_agent_shared_memory.py
File metadata and controls
153 lines (125 loc) · 5.34 KB
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
"""Three concurrent research agents share memory via AIngram.
This example demonstrates the multi-agent shared-memory pattern that
AIngram is designed to support. Three logical agents run in one process
(asyncio tasks), share a single MemoryStore, and `recall()` each other's
findings to avoid duplicating exploration.
The toy research task is hyperparameter search for a 2-layer MLP on
synthetic data. Each agent proposes a (hidden_size, activation) combo,
"trains" it via a deterministic mock scoring function, and records the
result. On subsequent iterations, agents recall sibling findings and
bias toward combos that scored well.
No ONNX download (inline stub embedder). No autoresearch dependency.
For the full autoresearch integration, see https://github.com/bozbuilds/aingram-AR
"""
from __future__ import annotations
import asyncio
import hashlib
import math
import struct
import tempfile
from pathlib import Path
from aingram import MemoryStore
class _StubEmbedder:
"""Hash-derived unit-vector embedder. No model download, no network."""
dim = 768
def embed(self, text: str) -> list[float]:
seed = hashlib.sha256(text.encode('utf-8')).digest()
vec: list[float] = []
chunk = seed
while len(vec) < self.dim:
chunk = hashlib.sha256(chunk).digest()
for i in range(0, 32, 4):
if len(vec) >= self.dim:
break
(x,) = struct.unpack('<f', chunk[i : i + 4])
if not math.isfinite(x):
x = 0.0
vec.append(x)
norm = math.sqrt(sum(x * x for x in vec)) or 1.0
return [x / norm for x in vec]
OPTIMAL = {'hidden_size': 64, 'activation': 'relu'}
BASE_LOSS = 1.0
def mock_train(hidden_size: int, activation: str) -> float:
"""Deterministic loss: best when hidden_size=64, activation='relu'."""
loss = BASE_LOSS
if hidden_size != OPTIMAL['hidden_size']:
loss += 0.1 * abs(math.log2(hidden_size / OPTIMAL['hidden_size']))
if activation != OPTIMAL['activation']:
loss += 0.15
return round(loss, 4)
async def run_agent(
agent_idx: int,
store: MemoryStore,
iterations: int,
) -> list[dict]:
"""Run one agent for N iterations: recall → propose → train → remember."""
candidates = {
'hidden_size': [16, 32, 64, 128, 256],
'activation': ['relu', 'gelu', 'tanh'],
}
hypothesis = {
'hidden_size': candidates['hidden_size'][agent_idx % 5],
'activation': candidates['activation'][agent_idx % 3],
}
results = []
for i in range(iterations):
recall_hits = list(store.recall('MLP hyperparameter', limit=5))
sibling_hit = None
for r in recall_hits:
entry = getattr(r, 'entry', None)
if entry is None:
continue
content = entry.content
if 'loss=' in content and 'status=keep' in content:
sibling_hit = content
break
if sibling_hit:
for line in sibling_hit.splitlines():
if line.startswith('activation='):
sibling_act = line.split('=', 1)[1].strip()
if sibling_act in candidates['activation']:
hypothesis['activation'] = sibling_act
break
loss = mock_train(hypothesis['hidden_size'], hypothesis['activation'])
status = 'keep' if loss < BASE_LOSS + 0.1 else 'discard'
content = (
f'[mlp_experiment]\n'
f'agent={agent_idx}\n'
f'iteration={i}\n'
f'hidden_size={hypothesis["hidden_size"]}\n'
f'activation={hypothesis["activation"]}\n'
f'loss={loss}\n'
f'status={status}'
)
entry_id = store.remember(
content,
metadata={
'agent_idx': agent_idx,
'iteration': i,
'hypothesis': dict(hypothesis),
},
)
results.append({'entry_id': entry_id, 'loss': loss, 'status': status})
# If this config didn't work, explore the next hidden_size. On 'keep'
# we stay put — this is the config we want sibling agents to piggyback on.
if status == 'discard':
idx = candidates['hidden_size'].index(hypothesis['hidden_size'])
hypothesis['hidden_size'] = candidates['hidden_size'][
min(idx + 1, len(candidates['hidden_size']) - 1)
]
return results
async def main() -> None:
"""Run 3 agents for 3 iterations each against a single shared MemoryStore."""
with tempfile.TemporaryDirectory() as tmp_root:
db_path = Path(tmp_root) / 'shared_memory_demo.db'
with MemoryStore(str(db_path), embedder=_StubEmbedder()) as store:
tasks = [run_agent(i, store, iterations=3) for i in range(3)]
all_results = await asyncio.gather(*tasks)
print(f'Ran 3 agents × 3 iterations = {sum(len(r) for r in all_results)} total findings')
for i, results in enumerate(all_results):
kept = sum(1 for r in results if r['status'] == 'keep')
best = min(r['loss'] for r in results)
print(f' Agent {i}: {len(results)} findings, {kept} kept, best loss = {best}')
print('\nFor the full autoresearch integration: https://github.com/bozbuilds/aingram-AR')
if __name__ == '__main__':
asyncio.run(main())