new-agent-memory 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. adapters/__init__.py +13 -0
  2. adapters/autonomous_bridge.py +149 -0
  3. adapters/base_adapter.py +394 -0
  4. adapters/claude_adapter.py +287 -0
  5. adapters/claude_code_bridge.py +138 -0
  6. adapters/cli.py +159 -0
  7. adapters/cli_bridge.py +102 -0
  8. adapters/client_bridge.py +238 -0
  9. adapters/codex_adapter.py +287 -0
  10. adapters/hermes_connector.py +208 -0
  11. adapters/hub_sub_agent.py +204 -0
  12. adapters/polling_adapter.py +140 -0
  13. adapters/webhook_adapter.py +84 -0
  14. core/__init__.py +81 -0
  15. core/agent_system.py +542 -0
  16. core/attention_system.py +620 -0
  17. core/audit_logger.py +262 -0
  18. core/bm25_retriever.py +192 -0
  19. core/cognitive_runtime.py +512 -0
  20. core/cognitive_state.py +711 -0
  21. core/consolidation_engine.py +343 -0
  22. core/context_compressor.py +254 -0
  23. core/data_manager.py +205 -0
  24. core/dense_retriever.py +211 -0
  25. core/emotion_engine.py +288 -0
  26. core/entity_extractor.py +302 -0
  27. core/json_store.py +146 -0
  28. core/llm_planner.py +886 -0
  29. core/local_llm.py +172 -0
  30. core/memory_extractor.py +421 -0
  31. core/memory_spec.py +143 -0
  32. core/persona_layer.py +416 -0
  33. core/pii_handler.py +156 -0
  34. core/query_planner.py +233 -0
  35. core/rrf_fusion.py +98 -0
  36. core/sqlite_store.py +376 -0
  37. core/store.py +168 -0
  38. core/weight_system.py +578 -0
  39. hub/__init__.py +6 -0
  40. hub/cli.py +117 -0
  41. hub/memory_bridge.py +46 -0
  42. hub/models.py +292 -0
  43. hub/server.py +371 -0
  44. hub/sse.py +70 -0
  45. hub/web_ui.py +617 -0
  46. new_agent_memory/__init__.py +83 -0
  47. new_agent_memory/cli.py +215 -0
  48. new_agent_memory-0.1.0.dist-info/METADATA +552 -0
  49. new_agent_memory-0.1.0.dist-info/RECORD +52 -0
  50. new_agent_memory-0.1.0.dist-info/WHEEL +4 -0
  51. new_agent_memory-0.1.0.dist-info/entry_points.txt +4 -0
  52. new_agent_memory-0.1.0.dist-info/licenses/LICENSE +661 -0
adapters/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """Agent adapters for connecting to the Communication Hub."""
2
+
3
+ from .base_adapter import BaseAdapter
4
+ from .claude_adapter import ClaudeAdapter
5
+ from .codex_adapter import CodexAdapter
6
+ from .hub_sub_agent import main as run_hub_sub_agent
7
+
8
+ __all__ = [
9
+ "BaseAdapter",
10
+ "ClaudeAdapter",
11
+ "CodexAdapter",
12
+ "run_hub_sub_agent",
13
+ ]
@@ -0,0 +1,149 @@
1
+ """自主运行的 Hub 桥接器
2
+
3
+ 完整流程:
4
+ 1. 轮询 Hub 获取新消息
5
+ 2. 收集未读消息
6
+ 3. 输出到 stdout(供子 agent 读取)
7
+ 4. 读取 stdin 的回复(子 agent 写入)
8
+ 5. 发送回复到 Hub
9
+
10
+ 子 agent 负责理解消息和生成回复,这个脚本只负责 IO。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ import time
19
+ import urllib.request
20
+ from typing import Any, Dict, List
21
+
22
+ HUB_URL = os.environ.get("HUB_URL", "http://localhost:8420").rstrip("/")
23
+ AGENT_ID = "agent_059026b8ab"
24
+ INBOX_FILE = "/tmp/claude_hub_inbox.json"
25
+ OUTBOX_FILE = "/tmp/claude_hub_outbox.json"
26
+
27
+ _proxy_handler = urllib.request.ProxyHandler({})
28
+ _opener = urllib.request.build_opener(_proxy_handler)
29
+
30
+
31
+ def api_get(path: str) -> Dict[str, Any]:
32
+ with _opener.open(HUB_URL + path, timeout=10) as resp:
33
+ return json.loads(resp.read().decode("utf-8"))
34
+
35
+
36
+ def api_post(path: str, body: dict) -> Dict[str, Any]:
37
+ data = json.dumps(body).encode("utf-8")
38
+ req = urllib.request.Request(HUB_URL + path, data=data, headers={"Content-Type": "application/json"})
39
+ with _opener.open(req, timeout=10) as resp:
40
+ return json.loads(resp.read().decode("utf-8"))
41
+
42
+
43
+ def send_message(content: str, channel: str = "general"):
44
+ api_post(f"/api/channels/{channel}/messages", {
45
+ "sender_id": AGENT_ID,
46
+ "content": content,
47
+ "message_type": "text",
48
+ })
49
+
50
+
51
+ def poll_new_messages(seen_ids: set, channel: str = "general") -> List[Dict]:
52
+ data = api_get(f"/api/channels/{channel}/messages?limit=50")
53
+ new = [m for m in data["messages"] if m["id"] not in seen_ids and m["sender_id"] != AGENT_ID]
54
+ for m in new:
55
+ seen_ids.add(m["id"])
56
+ return new
57
+
58
+
59
+ def load_seen_ids() -> set:
60
+ """Load existing message IDs to skip old messages."""
61
+ try:
62
+ data = api_get("/api/channels/general/messages?limit=50")
63
+ return {m["id"] for m in data["messages"]}
64
+ except Exception:
65
+ return set()
66
+
67
+
68
+ def heartbeat():
69
+ try:
70
+ api_post(f"/api/agents/{AGENT_ID}/heartbeat", {"status": "online"})
71
+ except Exception:
72
+ pass
73
+
74
+
75
+ def write_outbox(content: str):
76
+ with open(OUTBOX_FILE, "w", encoding="utf-8") as f:
77
+ json.dump({"content": content}, f, ensure_ascii=False)
78
+
79
+
80
+ def read_inbox() -> List[Dict]:
81
+ if not os.path.exists(INBOX_FILE):
82
+ return []
83
+ try:
84
+ with open(INBOX_FILE, "r", encoding="utf-8") as f:
85
+ msgs = json.load(f)
86
+ os.remove(INBOX_FILE)
87
+ return msgs
88
+ except Exception:
89
+ return []
90
+
91
+
92
+ def run_once(seen_ids: set) -> List[Dict]:
93
+ """One poll cycle. Returns new messages if any."""
94
+ heartbeat()
95
+ new_msgs = poll_new_messages(seen_ids)
96
+ if new_msgs:
97
+ # Write to inbox for sub-agent
98
+ with open(INBOX_FILE, "w", encoding="utf-8") as f:
99
+ json.dump(new_msgs, f, ensure_ascii=False, indent=2)
100
+ return new_msgs
101
+
102
+
103
+ def main():
104
+ seen_ids = load_seen_ids()
105
+ print(json.dumps({
106
+ "type": "ready",
107
+ "agent_id": AGENT_ID,
108
+ "hub": HUB_URL,
109
+ "seen_count": len(seen_ids),
110
+ }))
111
+ sys.stdout.flush()
112
+
113
+ while True:
114
+ try:
115
+ new_msgs = run_once(seen_ids)
116
+ if new_msgs:
117
+ print(json.dumps({
118
+ "type": "new_messages",
119
+ "count": len(new_msgs),
120
+ "messages": [{"sender": m["sender_name"], "content": m["content"]} for m in new_msgs],
121
+ }))
122
+ sys.stdout.flush()
123
+
124
+ # Check for outbox (sub-agent reply)
125
+ if os.path.exists(OUTBOX_FILE):
126
+ try:
127
+ with open(OUTBOX_FILE, "r", encoding="utf-8") as f:
128
+ reply = json.load(f)
129
+ os.remove(OUTBOX_FILE)
130
+ if reply.get("content"):
131
+ send_message(reply["content"])
132
+ print(json.dumps({
133
+ "type": "sent",
134
+ "content": reply["content"][:80],
135
+ }))
136
+ sys.stdout.flush()
137
+ except Exception as exc:
138
+ print(json.dumps({"type": "error", "msg": str(exc)}))
139
+ sys.stdout.flush()
140
+
141
+ except Exception as exc:
142
+ print(json.dumps({"type": "error", "msg": str(exc)}))
143
+ sys.stdout.flush()
144
+
145
+ time.sleep(3)
146
+
147
+
148
+ if __name__ == "__main__":
149
+ main()
@@ -0,0 +1,394 @@
1
+ """统一适配器基类
2
+
3
+ 所有 agent 适配器的基类,提供:
4
+ - 自动注册到 hub
5
+ - 心跳机制
6
+ - SSE 实时监听
7
+ - 发送消息
8
+ - 抽象方法:on_message, on_task
9
+
10
+ 使用方式:
11
+ class MyAgent(BaseAdapter):
12
+ def on_message(self, msg):
13
+ print(f"收到消息: {msg['content']}")
14
+
15
+ def on_task(self, task):
16
+ print(f"收到任务: {task['task']}")
17
+
18
+ agent = MyAgent("MyAgent", "my_type")
19
+ agent.connect()
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import os
26
+ import sys
27
+ import threading
28
+ import time
29
+ import urllib.request
30
+ from abc import ABC, abstractmethod
31
+ from typing import Any, Callable, Dict, List, Optional
32
+
33
+ # Bypass proxy for localhost
34
+ _proxy_handler = urllib.request.ProxyHandler({})
35
+ _opener = urllib.request.build_opener(_proxy_handler)
36
+
37
+
38
+ class BaseAdapter(ABC):
39
+ """所有 agent 适配器的基类"""
40
+
41
+ def __init__(self, name: str, agent_type: str, hub_url: str = None):
42
+ """
43
+ 初始化适配器
44
+
45
+ Args:
46
+ name: agent 名称
47
+ agent_type: agent 类型 (claude, codex, hermes, human, etc.)
48
+ hub_url: hub 服务器地址,默认从环境变量读取
49
+ """
50
+ self.name = name
51
+ self.agent_type = agent_type
52
+ self.hub_url = (hub_url or os.environ.get("HUB_URL", "http://localhost:8420")).rstrip("/")
53
+ self.agent_id: Optional[str] = None
54
+ self._running = False
55
+ self._heartbeat_thread: Optional[threading.Thread] = None
56
+ self._sse_thread: Optional[threading.Thread] = None
57
+ self._seen_ids: set = set()
58
+
59
+ # -------------------------------------------------------------------------
60
+ # 公共方法
61
+ # -------------------------------------------------------------------------
62
+
63
+ def connect(self):
64
+ """连接到 hub(自动注册 + 启动监听)"""
65
+ self.agent_id = self._register()
66
+ self._running = True
67
+ self._start_heartbeat()
68
+ self._start_sse_listener()
69
+ self._load_existing_messages()
70
+ print(f"[{self.name}] 已连接到 hub,ID: {self.agent_id}")
71
+ print(f"[{self.name}] Hub 地址: {self.hub_url}")
72
+ print()
73
+
74
+ def disconnect(self):
75
+ """断开连接"""
76
+ self._running = False
77
+ self._update_status("offline")
78
+ print(f"[{self.name}] 已断开连接")
79
+
80
+ def send(self, content: str, channel: str = "general", reply_to: str = None,
81
+ message_type: str = "text", metadata: dict = None):
82
+ """
83
+ 发送消息
84
+
85
+ Args:
86
+ content: 消息内容
87
+ channel: 频道 ID,默认 general
88
+ reply_to: 回复的消息 ID(可选)
89
+ message_type: 消息类型 (text, task, task_result, error, correction, collaboration)
90
+ metadata: 额外元数据
91
+ """
92
+ body = {
93
+ "sender_id": self.agent_id,
94
+ "content": content,
95
+ "message_type": message_type,
96
+ }
97
+ if reply_to:
98
+ body["reply_to"] = reply_to
99
+ if metadata:
100
+ body["metadata"] = metadata
101
+
102
+ try:
103
+ self._api_post(f"/api/channels/{channel}/messages", body)
104
+ except Exception as e:
105
+ print(f"[{self.name}] 发送消息失败: {e}")
106
+
107
+ def send_task(self, task: str, assignee: str, channel: str = "general",
108
+ priority: str = "normal", context: dict = None):
109
+ """
110
+ 发送任务给其他 agent
111
+
112
+ Args:
113
+ task: 任务描述
114
+ assignee: 目标 agent ID
115
+ channel: 频道 ID
116
+ priority: 优先级 (low, normal, high, urgent)
117
+ context: 任务上下文
118
+ """
119
+ metadata = {
120
+ "assignee": assignee,
121
+ "priority": priority,
122
+ "context": context or {},
123
+ }
124
+ self.send(task, channel=channel, message_type="task", metadata=metadata)
125
+
126
+ def send_correction(self, target_message_id: str, target_agent: str,
127
+ issue: str, suggestion: str, code_fix: str = None,
128
+ channel: str = "general"):
129
+ """
130
+ 发送纠错消息
131
+
132
+ Args:
133
+ target_message_id: 目标消息 ID
134
+ target_agent: 目标 agent ID
135
+ issue: 问题描述
136
+ suggestion: 建议
137
+ code_fix: 代码修复(可选)
138
+ channel: 频道 ID
139
+ """
140
+ metadata = {
141
+ "target_message_id": target_message_id,
142
+ "target_agent": target_agent,
143
+ "issue": issue,
144
+ "suggestion": suggestion,
145
+ "code_fix": code_fix,
146
+ }
147
+ content = f"@{target_agent} {issue}\n建议: {suggestion}"
148
+ if code_fix:
149
+ content += f"\n修复:\n{code_fix}"
150
+ self.send(content, channel=channel, message_type="correction", metadata=metadata)
151
+
152
+ def get_messages(self, channel: str = "general", limit: int = 50) -> List[Dict]:
153
+ """获取频道消息"""
154
+ try:
155
+ data = self._api_get(f"/api/channels/{channel}/messages?limit={limit}")
156
+ return data.get("messages", [])
157
+ except Exception:
158
+ return []
159
+
160
+ def list_channels(self) -> List[Dict]:
161
+ """列出所有频道"""
162
+ try:
163
+ data = self._api_get("/api/channels")
164
+ return data.get("channels", [])
165
+ except Exception:
166
+ return []
167
+
168
+ def list_agents(self) -> List[Dict]:
169
+ """列出所有 agent"""
170
+ try:
171
+ data = self._api_get("/api/agents")
172
+ return data.get("agents", [])
173
+ except Exception:
174
+ return []
175
+
176
+ # -------------------------------------------------------------------------
177
+ # 抽象方法(子类必须实现)
178
+ # -------------------------------------------------------------------------
179
+
180
+ @abstractmethod
181
+ def on_message(self, msg: Dict[str, Any]):
182
+ """
183
+ 收到消息时的回调
184
+
185
+ Args:
186
+ msg: 消息字典,包含:
187
+ - id: 消息 ID
188
+ - channel_id: 频道 ID
189
+ - sender_id: 发送者 ID
190
+ - sender_name: 发送者名称
191
+ - content: 消息内容
192
+ - message_type: 消息类型
193
+ - metadata: 元数据
194
+ - created_at: 创建时间
195
+ """
196
+ pass
197
+
198
+ @abstractmethod
199
+ def on_task(self, task: Dict[str, Any]):
200
+ """
201
+ 收到任务时的回调
202
+
203
+ Args:
204
+ task: 任务字典,包含:
205
+ - id: 消息 ID
206
+ - task: 任务描述
207
+ - assignee: 目标 agent ID
208
+ - priority: 优先级
209
+ - context: 上下文
210
+ - sender_name: 发送者名称
211
+ """
212
+ pass
213
+
214
+ def on_correction(self, correction: Dict[str, Any]):
215
+ """
216
+ 收到纠错时的回调(可选重写)
217
+
218
+ Args:
219
+ correction: 纠错字典,包含:
220
+ - issue: 问题描述
221
+ - suggestion: 建议
222
+ - code_fix: 代码修复
223
+ - sender_name: 发送者名称
224
+ """
225
+ print(f"[{self.name}] 收到纠错: {correction.get('issue')}")
226
+
227
+ def on_agent_status(self, agent: Dict[str, Any]):
228
+ """
229
+ 收到 agent 状态变化时的回调(可选重写)
230
+
231
+ Args:
232
+ agent: agent 信息,包含:
233
+ - id: agent ID
234
+ - name: agent 名称
235
+ - status: 状态 (online, offline, idle, busy)
236
+ """
237
+ pass
238
+
239
+ # -------------------------------------------------------------------------
240
+ # 内部方法
241
+ # -------------------------------------------------------------------------
242
+
243
+ def _register(self) -> str:
244
+ """注册到 hub,返回 agent_id"""
245
+ body = {
246
+ "name": self.name,
247
+ "agent_type": self.agent_type,
248
+ }
249
+ try:
250
+ data = self._api_post("/api/agents/register", body)
251
+ return data["id"]
252
+ except Exception as e:
253
+ print(f"[{self.name}] 注册失败: {e}")
254
+ sys.exit(1)
255
+
256
+ def _update_status(self, status: str):
257
+ """更新 agent 状态"""
258
+ if not self.agent_id:
259
+ return
260
+ try:
261
+ self._api_post(f"/api/agents/{self.agent_id}/heartbeat", {"status": status})
262
+ except Exception:
263
+ pass
264
+
265
+ def _start_heartbeat(self):
266
+ """启动心跳线程"""
267
+ def _heartbeat_loop():
268
+ while self._running:
269
+ try:
270
+ self._update_status("online")
271
+ except Exception:
272
+ pass
273
+ time.sleep(30)
274
+
275
+ self._heartbeat_thread = threading.Thread(target=_heartbeat_loop, daemon=True)
276
+ self._heartbeat_thread.start()
277
+
278
+ def _start_sse_listener(self):
279
+ """启动 SSE 监听线程"""
280
+ def _sse_loop():
281
+ while self._running:
282
+ try:
283
+ self._connect_sse()
284
+ except Exception as e:
285
+ if self._running:
286
+ print(f"[{self.name}] SSE 连接断开,5秒后重连: {e}")
287
+ time.sleep(5)
288
+ except KeyboardInterrupt:
289
+ break
290
+
291
+ self._sse_thread = threading.Thread(target=_sse_loop, daemon=True)
292
+ self._sse_thread.start()
293
+
294
+ def _connect_sse(self):
295
+ """连接 SSE 流"""
296
+ url = f"{self.hub_url}/api/events"
297
+ req = urllib.request.Request(url)
298
+ req.add_header("Accept", "text/event-stream")
299
+ req.add_header("Cache-Control", "no-cache")
300
+
301
+ with _opener.open(req, timeout=60) as response:
302
+ buffer = ""
303
+ while self._running:
304
+ line = response.readline().decode("utf-8")
305
+ if not line:
306
+ break
307
+
308
+ buffer += line
309
+ if line.strip() == "":
310
+ # 空行表示事件结束
311
+ self._process_sse_event(buffer.strip())
312
+ buffer = ""
313
+
314
+ def _process_sse_event(self, event_str: str):
315
+ """处理 SSE 事件"""
316
+ if not event_str:
317
+ return
318
+
319
+ # 解析 SSE 格式
320
+ event_type = None
321
+ data = None
322
+ for line in event_str.split("\n"):
323
+ if line.startswith("event:"):
324
+ event_type = line[6:].strip()
325
+ elif line.startswith("data:"):
326
+ data = line[5:].strip()
327
+
328
+ if not event_type or not data:
329
+ return
330
+
331
+ try:
332
+ payload = json.loads(data)
333
+ except json.JSONDecodeError:
334
+ return
335
+
336
+ # 分发事件
337
+ if event_type == "message":
338
+ self._handle_message(payload)
339
+ elif event_type == "agent_status":
340
+ self._handle_agent_status(payload)
341
+ elif event_type == "channel_created":
342
+ pass # 忽略频道创建事件
343
+
344
+ def _handle_message(self, msg: Dict[str, Any]):
345
+ """处理消息事件"""
346
+ # 忽略自己发送的消息
347
+ if msg.get("sender_id") == self.agent_id:
348
+ return
349
+
350
+ # 忽略已经处理过的消息
351
+ msg_id = msg.get("id")
352
+ if msg_id in self._seen_ids:
353
+ return
354
+ self._seen_ids.add(msg_id)
355
+
356
+ # 根据消息类型分发
357
+ message_type = msg.get("message_type", "text")
358
+ if message_type == "task":
359
+ self.on_task(msg)
360
+ elif message_type == "correction":
361
+ self.on_correction(msg)
362
+ else:
363
+ self.on_message(msg)
364
+
365
+ def _handle_agent_status(self, agent: Dict[str, Any]):
366
+ """处理 agent 状态事件"""
367
+ self.on_agent_status(agent)
368
+
369
+ def _load_existing_messages(self):
370
+ """加载现有消息 ID,避免重复处理"""
371
+ try:
372
+ messages = self.get_messages(limit=100)
373
+ for msg in messages:
374
+ self._seen_ids.add(msg["id"])
375
+ except Exception:
376
+ pass
377
+
378
+ # -------------------------------------------------------------------------
379
+ # HTTP 工具方法
380
+ # -------------------------------------------------------------------------
381
+
382
+ def _api_get(self, path: str) -> Dict[str, Any]:
383
+ """GET 请求"""
384
+ url = self.hub_url + path
385
+ with _opener.open(url, timeout=10) as resp:
386
+ return json.loads(resp.read().decode("utf-8"))
387
+
388
+ def _api_post(self, path: str, body: dict) -> Dict[str, Any]:
389
+ """POST 请求"""
390
+ url = self.hub_url + path
391
+ data = json.dumps(body).encode("utf-8")
392
+ req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
393
+ with _opener.open(req, timeout=10) as resp:
394
+ return json.loads(resp.read().decode("utf-8"))