ultra-memory 3.0.0
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.
- package/CLAWHUB.md +190 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/SKILL.md +383 -0
- package/package.json +107 -0
- package/platform/SYSTEM_PROMPT.md +184 -0
- package/platform/__pycache__/server.cpython-313.pyc +0 -0
- package/platform/openapi.yaml +305 -0
- package/platform/server.py +454 -0
- package/platform/tools_gemini.json +176 -0
- package/platform/tools_openai.json +207 -0
- package/scripts/__pycache__/cleanup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/export.cpython-313.pyc +0 -0
- package/scripts/__pycache__/extract_entities.cpython-313.pyc +0 -0
- package/scripts/__pycache__/init.cpython-313.pyc +0 -0
- package/scripts/__pycache__/log_op.cpython-313.pyc +0 -0
- package/scripts/__pycache__/recall.cpython-313.pyc +0 -0
- package/scripts/__pycache__/restore.cpython-313.pyc +0 -0
- package/scripts/__pycache__/summarize.cpython-313.pyc +0 -0
- package/scripts/cleanup.py +156 -0
- package/scripts/export.py +158 -0
- package/scripts/extract_entities.py +289 -0
- package/scripts/init.py +243 -0
- package/scripts/log_op.py +328 -0
- package/scripts/mcp-server.js +341 -0
- package/scripts/recall.py +683 -0
- package/scripts/restore.py +267 -0
- package/scripts/summarize.py +389 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ultra-memory HTTP REST Server
|
|
4
|
+
将 9 个记忆工具暴露为标准 HTTP 端点,供任意 LLM 平台通过 function calling 调用。
|
|
5
|
+
|
|
6
|
+
零外部依赖,纯 Python stdlib。
|
|
7
|
+
|
|
8
|
+
启动:
|
|
9
|
+
python3 platform/server.py # 默认 localhost:3200
|
|
10
|
+
python3 platform/server.py --port 8080
|
|
11
|
+
python3 platform/server.py --host 0.0.0.0 # 局域网访问(谨慎使用)
|
|
12
|
+
|
|
13
|
+
端点一览:
|
|
14
|
+
GET /health 服务健康检查
|
|
15
|
+
GET /tools 列出所有工具
|
|
16
|
+
POST /tools/{tool_name} 调用工具
|
|
17
|
+
GET /session/current 获取当前活跃会话
|
|
18
|
+
|
|
19
|
+
支持的工具(POST /tools/xxx):
|
|
20
|
+
memory_init 初始化会话
|
|
21
|
+
memory_status 查询会话状态与 context 压力
|
|
22
|
+
memory_log 记录操作(自动提取实体)
|
|
23
|
+
memory_recall 四层统一检索
|
|
24
|
+
memory_summarize 触发摘要压缩(含元压缩)
|
|
25
|
+
memory_restore 恢复上次会话
|
|
26
|
+
memory_profile 读写用户画像
|
|
27
|
+
memory_entities 查询实体索引
|
|
28
|
+
memory_extract_entities 全量重提取实体
|
|
29
|
+
|
|
30
|
+
请求格式(JSON body):
|
|
31
|
+
{"project": "my-project", "resume": false}
|
|
32
|
+
|
|
33
|
+
响应格式:
|
|
34
|
+
{"success": true, "output": "...", "tool": "memory_init"}
|
|
35
|
+
{"success": false, "error": "...", "tool": "memory_init"}
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import os
|
|
39
|
+
import sys
|
|
40
|
+
import json
|
|
41
|
+
import subprocess
|
|
42
|
+
import argparse
|
|
43
|
+
import logging
|
|
44
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from urllib.parse import urlparse
|
|
47
|
+
|
|
48
|
+
if sys.stdout.encoding != "utf-8":
|
|
49
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
50
|
+
if sys.stderr.encoding != "utf-8":
|
|
51
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
52
|
+
|
|
53
|
+
# ── 路径配置 ──────────────────────────────────────────────────────────────
|
|
54
|
+
PLATFORM_DIR = Path(__file__).parent
|
|
55
|
+
SCRIPTS_DIR = PLATFORM_DIR.parent / "scripts"
|
|
56
|
+
PYTHON = sys.executable
|
|
57
|
+
ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
|
|
58
|
+
|
|
59
|
+
VERSION = "3.0.0"
|
|
60
|
+
|
|
61
|
+
logging.basicConfig(
|
|
62
|
+
level=logging.INFO,
|
|
63
|
+
format="[ultra-memory] %(asctime)s %(levelname)s %(message)s",
|
|
64
|
+
datefmt="%H:%M:%S",
|
|
65
|
+
)
|
|
66
|
+
log = logging.getLogger("ultra-memory")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── 工具路由表 ────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
def _run_script(script: str, args: list[str], timeout: int = 20) -> tuple[bool, str]:
|
|
72
|
+
"""运行 Python 脚本,返回 (success, output)"""
|
|
73
|
+
cmd = [PYTHON, str(SCRIPTS_DIR / script)] + args
|
|
74
|
+
try:
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
cmd, capture_output=True, text=True,
|
|
77
|
+
encoding="utf-8", errors="replace",
|
|
78
|
+
env={**os.environ, "ULTRA_MEMORY_HOME": str(ULTRA_MEMORY_HOME)},
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
)
|
|
81
|
+
output = (result.stdout + result.stderr).strip()
|
|
82
|
+
return result.returncode == 0, output
|
|
83
|
+
except subprocess.TimeoutExpired:
|
|
84
|
+
return False, f"脚本执行超时(>{timeout}s)"
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return False, str(e)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def tool_memory_init(body: dict) -> tuple[bool, str]:
|
|
90
|
+
args = ["--project", body.get("project", "default")]
|
|
91
|
+
if body.get("resume"):
|
|
92
|
+
args.append("--resume")
|
|
93
|
+
return _run_script("init.py", args)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def tool_memory_status(body: dict) -> tuple[bool, str]:
|
|
97
|
+
session_id = body.get("session_id", "")
|
|
98
|
+
if not session_id:
|
|
99
|
+
return False, "缺少 session_id 参数"
|
|
100
|
+
|
|
101
|
+
meta_file = ULTRA_MEMORY_HOME / "sessions" / session_id / "meta.json"
|
|
102
|
+
if not meta_file.exists():
|
|
103
|
+
return False, f"会话不存在: {session_id}"
|
|
104
|
+
|
|
105
|
+
with open(meta_file, encoding="utf-8") as f:
|
|
106
|
+
meta = json.load(f)
|
|
107
|
+
|
|
108
|
+
ok, pressure_out = _run_script("init.py", ["--check-pressure", session_id])
|
|
109
|
+
lines = [
|
|
110
|
+
f"会话 ID: {session_id}",
|
|
111
|
+
f"项目: {meta.get('project', 'default')}",
|
|
112
|
+
f"操作数: {meta.get('op_count', 0)}",
|
|
113
|
+
f"最后里程碑: {meta.get('last_milestone', '(无)')}",
|
|
114
|
+
f"上次压缩: {meta.get('last_summary_at', '(未压缩)')}",
|
|
115
|
+
pressure_out,
|
|
116
|
+
]
|
|
117
|
+
return True, "\n".join(lines)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def tool_memory_log(body: dict) -> tuple[bool, str]:
|
|
121
|
+
session_id = body.get("session_id", "")
|
|
122
|
+
op_type = body.get("op_type", "")
|
|
123
|
+
summary = body.get("summary", "")
|
|
124
|
+
if not all([session_id, op_type, summary]):
|
|
125
|
+
return False, "缺少必填参数: session_id / op_type / summary"
|
|
126
|
+
|
|
127
|
+
args = [
|
|
128
|
+
"--session", session_id,
|
|
129
|
+
"--type", op_type,
|
|
130
|
+
"--summary", summary,
|
|
131
|
+
"--detail", json.dumps(body.get("detail", {}), ensure_ascii=False),
|
|
132
|
+
"--tags", ",".join(body.get("tags", [])),
|
|
133
|
+
]
|
|
134
|
+
return _run_script("log_op.py", args)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def tool_memory_recall(body: dict) -> tuple[bool, str]:
|
|
138
|
+
session_id = body.get("session_id", "")
|
|
139
|
+
query = body.get("query", "")
|
|
140
|
+
if not all([session_id, query]):
|
|
141
|
+
return False, "缺少必填参数: session_id / query"
|
|
142
|
+
|
|
143
|
+
args = [
|
|
144
|
+
"--session", session_id,
|
|
145
|
+
"--query", query,
|
|
146
|
+
"--top-k", str(body.get("top_k", 5)),
|
|
147
|
+
]
|
|
148
|
+
return _run_script("recall.py", args)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def tool_memory_summarize(body: dict) -> tuple[bool, str]:
|
|
152
|
+
session_id = body.get("session_id", "")
|
|
153
|
+
if not session_id:
|
|
154
|
+
return False, "缺少 session_id 参数"
|
|
155
|
+
|
|
156
|
+
args = ["--session", session_id]
|
|
157
|
+
if body.get("force"):
|
|
158
|
+
args.append("--force")
|
|
159
|
+
return _run_script("summarize.py", args)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def tool_memory_restore(body: dict) -> tuple[bool, str]:
|
|
163
|
+
args = ["--project", body.get("project", "default")]
|
|
164
|
+
if body.get("verbose"):
|
|
165
|
+
args.append("--verbose")
|
|
166
|
+
return _run_script("restore.py", args)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def tool_memory_profile(body: dict) -> tuple[bool, str]:
|
|
170
|
+
action = body.get("action", "read")
|
|
171
|
+
profile_file = ULTRA_MEMORY_HOME / "semantic" / "user_profile.json"
|
|
172
|
+
|
|
173
|
+
if action == "read":
|
|
174
|
+
try:
|
|
175
|
+
content = profile_file.read_text(encoding="utf-8")
|
|
176
|
+
return True, content
|
|
177
|
+
except FileNotFoundError:
|
|
178
|
+
return True, "{}"
|
|
179
|
+
|
|
180
|
+
elif action == "update":
|
|
181
|
+
profile = {}
|
|
182
|
+
if profile_file.exists():
|
|
183
|
+
try:
|
|
184
|
+
profile = json.loads(profile_file.read_text(encoding="utf-8"))
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
profile.update(body.get("updates", {}))
|
|
188
|
+
from datetime import date
|
|
189
|
+
profile["last_updated"] = str(date.today())
|
|
190
|
+
profile_file.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
profile_file.write_text(
|
|
192
|
+
json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
193
|
+
)
|
|
194
|
+
return True, "用户画像已更新"
|
|
195
|
+
|
|
196
|
+
return False, f"未知 action: {action},支持 read / update"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def tool_memory_entities(body: dict) -> tuple[bool, str]:
|
|
200
|
+
entities_file = ULTRA_MEMORY_HOME / "semantic" / "entities.jsonl"
|
|
201
|
+
target_type = body.get("entity_type", "all").lower()
|
|
202
|
+
query = body.get("query", "").lower()
|
|
203
|
+
top_k = int(body.get("top_k", 10))
|
|
204
|
+
|
|
205
|
+
if not entities_file.exists():
|
|
206
|
+
return True, "实体索引尚未建立,请先记录操作(memory_log)"
|
|
207
|
+
|
|
208
|
+
all_entities = []
|
|
209
|
+
for line in entities_file.read_text(encoding="utf-8").splitlines():
|
|
210
|
+
line = line.strip()
|
|
211
|
+
if not line:
|
|
212
|
+
continue
|
|
213
|
+
try:
|
|
214
|
+
all_entities.append(json.loads(line))
|
|
215
|
+
except json.JSONDecodeError:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
# 类型过滤
|
|
219
|
+
filtered = [e for e in all_entities
|
|
220
|
+
if target_type == "all" or e.get("entity_type") == target_type]
|
|
221
|
+
|
|
222
|
+
# 关键词过滤
|
|
223
|
+
if query:
|
|
224
|
+
filtered = [e for e in filtered
|
|
225
|
+
if query in e.get("name", "").lower()
|
|
226
|
+
or query in e.get("context", "").lower()]
|
|
227
|
+
|
|
228
|
+
# 去重(同类型同名保留最新)
|
|
229
|
+
seen: set[str] = set()
|
|
230
|
+
deduped = []
|
|
231
|
+
for e in reversed(filtered): # 倒序 → 最新优先
|
|
232
|
+
key = f"{e.get('entity_type')}:{e.get('name')}"
|
|
233
|
+
if key not in seen:
|
|
234
|
+
seen.add(key)
|
|
235
|
+
deduped.append(e)
|
|
236
|
+
deduped = deduped[:top_k]
|
|
237
|
+
|
|
238
|
+
if not deduped:
|
|
239
|
+
return True, "未找到匹配实体"
|
|
240
|
+
|
|
241
|
+
lines = [f"找到 {len(deduped)} 个实体:\n"]
|
|
242
|
+
for e in deduped:
|
|
243
|
+
et = e.get("entity_type", "?")
|
|
244
|
+
name = e.get("name", "?")
|
|
245
|
+
ctx = e.get("context", "")
|
|
246
|
+
extra = ""
|
|
247
|
+
if et == "dependency" and e.get("manager"):
|
|
248
|
+
extra = f" [via {e['manager']}]"
|
|
249
|
+
elif et == "decision" and e.get("rationale"):
|
|
250
|
+
extra = f"\n 依据: {e['rationale']}"
|
|
251
|
+
elif et == "error" and e.get("message"):
|
|
252
|
+
extra = f" ← {e['message']}"
|
|
253
|
+
lines.append(f"[{et}] {name}{extra}")
|
|
254
|
+
if ctx:
|
|
255
|
+
lines.append(f" 来源: {ctx}")
|
|
256
|
+
return True, "\n".join(lines)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def tool_memory_extract_entities(body: dict) -> tuple[bool, str]:
|
|
260
|
+
session_id = body.get("session_id", "")
|
|
261
|
+
if not session_id:
|
|
262
|
+
return False, "缺少 session_id 参数"
|
|
263
|
+
return _run_script("extract_entities.py", ["--session", session_id, "--all"])
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ── 工具注册表 ────────────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
TOOL_HANDLERS = {
|
|
269
|
+
"memory_init": tool_memory_init,
|
|
270
|
+
"memory_status": tool_memory_status,
|
|
271
|
+
"memory_log": tool_memory_log,
|
|
272
|
+
"memory_recall": tool_memory_recall,
|
|
273
|
+
"memory_summarize": tool_memory_summarize,
|
|
274
|
+
"memory_restore": tool_memory_restore,
|
|
275
|
+
"memory_profile": tool_memory_profile,
|
|
276
|
+
"memory_entities": tool_memory_entities,
|
|
277
|
+
"memory_extract_entities": tool_memory_extract_entities,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
TOOL_DESCRIPTIONS = {
|
|
281
|
+
"memory_init": "初始化会话,返回 session_id",
|
|
282
|
+
"memory_status": "查询会话状态与 context 压力级别",
|
|
283
|
+
"memory_log": "记录一条操作到日志(自动提取实体)",
|
|
284
|
+
"memory_recall": "四层统一检索:ops / summary / semantic / entity",
|
|
285
|
+
"memory_summarize": "触发摘要压缩(含分层元压缩)",
|
|
286
|
+
"memory_restore": "恢复上次会话,输出自然语言总结",
|
|
287
|
+
"memory_profile": "读写用户画像(技术栈、偏好)",
|
|
288
|
+
"memory_entities": "查询结构化实体索引(函数/文件/依赖/决策/错误)",
|
|
289
|
+
"memory_extract_entities": "对整个 ops.jsonl 全量重提取实体",
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ── HTTP 请求处理 ─────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
class MemoryHandler(BaseHTTPRequestHandler):
|
|
296
|
+
|
|
297
|
+
def log_message(self, fmt, *args):
|
|
298
|
+
log.info(f"{self.address_string()} {fmt % args}")
|
|
299
|
+
|
|
300
|
+
def _send_json(self, status: int, data: dict):
|
|
301
|
+
body = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")
|
|
302
|
+
self.send_response(status)
|
|
303
|
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
304
|
+
self.send_header("Content-Length", str(len(body)))
|
|
305
|
+
# CORS:允许本地 web 客户端调用
|
|
306
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
307
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
308
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
309
|
+
self.end_headers()
|
|
310
|
+
self.wfile.write(body)
|
|
311
|
+
|
|
312
|
+
def _read_body(self) -> dict:
|
|
313
|
+
length = int(self.headers.get("Content-Length", 0))
|
|
314
|
+
if length == 0:
|
|
315
|
+
return {}
|
|
316
|
+
raw = self.rfile.read(length)
|
|
317
|
+
try:
|
|
318
|
+
return json.loads(raw.decode("utf-8"))
|
|
319
|
+
except Exception:
|
|
320
|
+
return {}
|
|
321
|
+
|
|
322
|
+
def do_OPTIONS(self):
|
|
323
|
+
"""CORS 预检"""
|
|
324
|
+
self.send_response(204)
|
|
325
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
326
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
327
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
328
|
+
self.end_headers()
|
|
329
|
+
|
|
330
|
+
def do_GET(self):
|
|
331
|
+
parsed = urlparse(self.path)
|
|
332
|
+
path = parsed.path.rstrip("/")
|
|
333
|
+
|
|
334
|
+
if path == "/health":
|
|
335
|
+
self._send_json(200, {
|
|
336
|
+
"status": "ok",
|
|
337
|
+
"version": VERSION,
|
|
338
|
+
"scripts_dir": str(SCRIPTS_DIR),
|
|
339
|
+
"storage": str(ULTRA_MEMORY_HOME),
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
elif path == "/tools":
|
|
343
|
+
self._send_json(200, {
|
|
344
|
+
"tools": [
|
|
345
|
+
{"name": name, "description": desc,
|
|
346
|
+
"endpoint": f"POST /tools/{name}"}
|
|
347
|
+
for name, desc in TOOL_DESCRIPTIONS.items()
|
|
348
|
+
]
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
elif path == "/session/current":
|
|
352
|
+
# 返回最近活跃会话(跨项目)
|
|
353
|
+
sessions_dir = ULTRA_MEMORY_HOME / "sessions"
|
|
354
|
+
if not sessions_dir.exists():
|
|
355
|
+
self._send_json(200, {"session": None, "message": "尚无会话记录"})
|
|
356
|
+
return
|
|
357
|
+
latest = None
|
|
358
|
+
latest_ts = ""
|
|
359
|
+
for d in sessions_dir.iterdir():
|
|
360
|
+
if not d.is_dir():
|
|
361
|
+
continue
|
|
362
|
+
meta_f = d / "meta.json"
|
|
363
|
+
if not meta_f.exists():
|
|
364
|
+
continue
|
|
365
|
+
try:
|
|
366
|
+
m = json.loads(meta_f.read_text(encoding="utf-8"))
|
|
367
|
+
ts = m.get("last_op_at") or m.get("started_at", "")
|
|
368
|
+
if ts > latest_ts:
|
|
369
|
+
latest_ts = ts
|
|
370
|
+
latest = m
|
|
371
|
+
except Exception:
|
|
372
|
+
continue
|
|
373
|
+
self._send_json(200, {"session": latest})
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
self._send_json(404, {"error": f"路径不存在: {path}"})
|
|
377
|
+
|
|
378
|
+
def do_POST(self):
|
|
379
|
+
parsed = urlparse(self.path)
|
|
380
|
+
path = parsed.path.rstrip("/")
|
|
381
|
+
|
|
382
|
+
# POST /tools/{tool_name}
|
|
383
|
+
if path.startswith("/tools/"):
|
|
384
|
+
tool_name = path[len("/tools/"):]
|
|
385
|
+
|
|
386
|
+
if tool_name not in TOOL_HANDLERS:
|
|
387
|
+
self._send_json(404, {
|
|
388
|
+
"error": f"未知工具: {tool_name}",
|
|
389
|
+
"available": list(TOOL_HANDLERS.keys()),
|
|
390
|
+
})
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
body = self._read_body()
|
|
394
|
+
handler = TOOL_HANDLERS[tool_name]
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
success, output = handler(body)
|
|
398
|
+
self._send_json(200 if success else 500, {
|
|
399
|
+
"success": success,
|
|
400
|
+
"tool": tool_name,
|
|
401
|
+
"output": output,
|
|
402
|
+
})
|
|
403
|
+
except Exception as e:
|
|
404
|
+
log.exception(f"工具 {tool_name} 执行异常")
|
|
405
|
+
self._send_json(500, {
|
|
406
|
+
"success": False,
|
|
407
|
+
"tool": tool_name,
|
|
408
|
+
"error": str(e),
|
|
409
|
+
})
|
|
410
|
+
else:
|
|
411
|
+
self._send_json(404, {"error": f"路径不存在: {path}"})
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# ── 主入口 ────────────────────────────────────────────────────────────────
|
|
415
|
+
|
|
416
|
+
def main():
|
|
417
|
+
parser = argparse.ArgumentParser(description="ultra-memory HTTP REST Server")
|
|
418
|
+
parser.add_argument("--host", default="127.0.0.1",
|
|
419
|
+
help="监听地址(默认 127.0.0.1,仅本机访问)")
|
|
420
|
+
parser.add_argument("--port", type=int, default=3200,
|
|
421
|
+
help="监听端口(默认 3200)")
|
|
422
|
+
parser.add_argument("--storage", default=None,
|
|
423
|
+
help="覆盖 ULTRA_MEMORY_HOME 路径")
|
|
424
|
+
args = parser.parse_args()
|
|
425
|
+
|
|
426
|
+
global ULTRA_MEMORY_HOME
|
|
427
|
+
if args.storage:
|
|
428
|
+
ULTRA_MEMORY_HOME = Path(args.storage)
|
|
429
|
+
os.environ["ULTRA_MEMORY_HOME"] = str(ULTRA_MEMORY_HOME)
|
|
430
|
+
|
|
431
|
+
server = HTTPServer((args.host, args.port), MemoryHandler)
|
|
432
|
+
|
|
433
|
+
log.info(f"ultra-memory REST Server v{VERSION} 已启动")
|
|
434
|
+
log.info(f"地址: http://{args.host}:{args.port}")
|
|
435
|
+
log.info(f"存储: {ULTRA_MEMORY_HOME}")
|
|
436
|
+
log.info(f"脚本: {SCRIPTS_DIR}")
|
|
437
|
+
log.info(f"工具: {list(TOOL_HANDLERS.keys())}")
|
|
438
|
+
log.info("按 Ctrl+C 停止服务")
|
|
439
|
+
log.info("")
|
|
440
|
+
log.info("快速测试:")
|
|
441
|
+
log.info(f" curl http://{args.host}:{args.port}/health")
|
|
442
|
+
log.info(f" curl http://{args.host}:{args.port}/tools")
|
|
443
|
+
log.info(f' curl -X POST http://{args.host}:{args.port}/tools/memory_init \\')
|
|
444
|
+
log.info(f' -H "Content-Type: application/json" \\')
|
|
445
|
+
log.info(f' -d \'{{"project": "my-project"}}\'')
|
|
446
|
+
|
|
447
|
+
try:
|
|
448
|
+
server.serve_forever()
|
|
449
|
+
except KeyboardInterrupt:
|
|
450
|
+
log.info("服务已停止")
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
if __name__ == "__main__":
|
|
454
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
{
|
|
2
|
+
"function_declarations": [
|
|
3
|
+
{
|
|
4
|
+
"name": "memory_init",
|
|
5
|
+
"description": "Initialize ultra-memory session. Call at conversation start to create three-layer memory structure and inject historical context.",
|
|
6
|
+
"parameters": {
|
|
7
|
+
"type": "OBJECT",
|
|
8
|
+
"properties": {
|
|
9
|
+
"project": {
|
|
10
|
+
"type": "STRING",
|
|
11
|
+
"description": "Project name (default: 'default')"
|
|
12
|
+
},
|
|
13
|
+
"resume": {
|
|
14
|
+
"type": "BOOLEAN",
|
|
15
|
+
"description": "Try to resume the most recent session for this project"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "memory_status",
|
|
22
|
+
"description": "Query current session status: operation count, last milestone, context pressure level (low/medium/high/critical). Use to decide when to compress.",
|
|
23
|
+
"parameters": {
|
|
24
|
+
"type": "OBJECT",
|
|
25
|
+
"properties": {
|
|
26
|
+
"session_id": {
|
|
27
|
+
"type": "STRING",
|
|
28
|
+
"description": "Session ID returned by memory_init"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": ["session_id"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "memory_log",
|
|
36
|
+
"description": "Record an operation to the current session log (Layer 1). Call after every significant action: file write, bash command, tool call, decision, error, or milestone.",
|
|
37
|
+
"parameters": {
|
|
38
|
+
"type": "OBJECT",
|
|
39
|
+
"properties": {
|
|
40
|
+
"session_id": {
|
|
41
|
+
"type": "STRING",
|
|
42
|
+
"description": "Current session ID"
|
|
43
|
+
},
|
|
44
|
+
"op_type": {
|
|
45
|
+
"type": "STRING",
|
|
46
|
+
"description": "Operation type: one of tool_call, file_write, file_read, bash_exec, reasoning, user_instruction, decision, error, milestone"
|
|
47
|
+
},
|
|
48
|
+
"summary": {
|
|
49
|
+
"type": "STRING",
|
|
50
|
+
"description": "Operation summary (under 50 words)"
|
|
51
|
+
},
|
|
52
|
+
"detail": {
|
|
53
|
+
"type": "OBJECT",
|
|
54
|
+
"description": "Additional detail fields (optional)"
|
|
55
|
+
},
|
|
56
|
+
"tags": {
|
|
57
|
+
"type": "ARRAY",
|
|
58
|
+
"items": { "type": "STRING" },
|
|
59
|
+
"description": "Tag list (optional, auto-generated if omitted)"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": ["session_id", "op_type", "summary"]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "memory_recall",
|
|
67
|
+
"description": "Retrieve relevant records from all memory layers (ops log, summary, semantic, entity index). Use when you need to recall past work, decisions, or errors.",
|
|
68
|
+
"parameters": {
|
|
69
|
+
"type": "OBJECT",
|
|
70
|
+
"properties": {
|
|
71
|
+
"session_id": {
|
|
72
|
+
"type": "STRING",
|
|
73
|
+
"description": "Current session ID"
|
|
74
|
+
},
|
|
75
|
+
"query": {
|
|
76
|
+
"type": "STRING",
|
|
77
|
+
"description": "Search keywords (Chinese or English)"
|
|
78
|
+
},
|
|
79
|
+
"top_k": {
|
|
80
|
+
"type": "NUMBER",
|
|
81
|
+
"description": "Number of results to return (default: 5)"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"required": ["session_id", "query"]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "memory_summarize",
|
|
89
|
+
"description": "Trigger summary compression of the current session (compresses ops log into summary.md). Call when context pressure is high or critical, or every ~50 operations.",
|
|
90
|
+
"parameters": {
|
|
91
|
+
"type": "OBJECT",
|
|
92
|
+
"properties": {
|
|
93
|
+
"session_id": {
|
|
94
|
+
"type": "STRING",
|
|
95
|
+
"description": "Current session ID"
|
|
96
|
+
},
|
|
97
|
+
"force": {
|
|
98
|
+
"type": "BOOLEAN",
|
|
99
|
+
"description": "Force compression even if operation count is low"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"required": ["session_id"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "memory_restore",
|
|
107
|
+
"description": "Restore the last session context for a project. Use at conversation start to continue a previous task across days.",
|
|
108
|
+
"parameters": {
|
|
109
|
+
"type": "OBJECT",
|
|
110
|
+
"properties": {
|
|
111
|
+
"project": {
|
|
112
|
+
"type": "STRING",
|
|
113
|
+
"description": "Project name (default: 'default')"
|
|
114
|
+
},
|
|
115
|
+
"verbose": {
|
|
116
|
+
"type": "BOOLEAN",
|
|
117
|
+
"description": "Show detailed operation records"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "memory_profile",
|
|
124
|
+
"description": "Read or update user profile (tech stack, preferences, project list). Persist user-level preferences across all sessions.",
|
|
125
|
+
"parameters": {
|
|
126
|
+
"type": "OBJECT",
|
|
127
|
+
"properties": {
|
|
128
|
+
"action": {
|
|
129
|
+
"type": "STRING",
|
|
130
|
+
"description": "Operation type: read or update"
|
|
131
|
+
},
|
|
132
|
+
"updates": {
|
|
133
|
+
"type": "OBJECT",
|
|
134
|
+
"description": "Fields to update (required when action=update)"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"required": ["action"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "memory_entities",
|
|
142
|
+
"description": "Query structured entity index (functions, files, dependencies, decisions, errors). Use for precise questions like 'which functions were used', 'which packages were installed', 'what decisions were made'.",
|
|
143
|
+
"parameters": {
|
|
144
|
+
"type": "OBJECT",
|
|
145
|
+
"properties": {
|
|
146
|
+
"entity_type": {
|
|
147
|
+
"type": "STRING",
|
|
148
|
+
"description": "Entity type filter: function, file, dependency, decision, error, class, or all (no filter)"
|
|
149
|
+
},
|
|
150
|
+
"query": {
|
|
151
|
+
"type": "STRING",
|
|
152
|
+
"description": "Search keyword (optional, empty returns all of that type)"
|
|
153
|
+
},
|
|
154
|
+
"top_k": {
|
|
155
|
+
"type": "NUMBER",
|
|
156
|
+
"description": "Number of results to return (default: 10)"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "memory_extract_entities",
|
|
163
|
+
"description": "Re-extract all structured entities from a session's ops log. Use to repair or initialize the entity index.",
|
|
164
|
+
"parameters": {
|
|
165
|
+
"type": "OBJECT",
|
|
166
|
+
"properties": {
|
|
167
|
+
"session_id": {
|
|
168
|
+
"type": "STRING",
|
|
169
|
+
"description": "Session ID"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"required": ["session_id"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|