agentram 0.1.0 → 0.1.2
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/.claude/agents/agentram-docs.md +18 -0
- package/.claude/agents/agentram-memory.md +20 -0
- package/.claude/agents/agentram-planner.md +18 -0
- package/.claude/agents/agentram-reviewer.md +18 -0
- package/.claude/commands/ram-drift.md +11 -0
- package/.claude/commands/ram-note.md +17 -0
- package/.claude/commands/ram-orchestrate.md +15 -0
- package/.claude/commands/ram-status.md +10 -0
- package/.claude/commands/ram.md +14 -0
- package/README.md +117 -0
- package/agentram/__init__.py +4 -0
- package/agentram/cli.py +547 -240
- package/agentram/mcp_server.py +107 -1
- package/agentram/orchestration.py +145 -0
- package/agentram/storage.py +40 -0
- package/package.json +3 -1
- package/pyproject.toml +1 -1
package/agentram/mcp_server.py
CHANGED
|
@@ -11,9 +11,10 @@ from typing import Any
|
|
|
11
11
|
from .adapter import HeuristicMemoryAdapter
|
|
12
12
|
from .config import default_ram_root
|
|
13
13
|
from .merger import MemoryMerger
|
|
14
|
+
from .orchestration import AgentModelEndpoint, CodingOrchestrationRequest, MultiModelCodingOrchestrator
|
|
14
15
|
from .retriever import MemoryRetriever
|
|
15
16
|
from .schema import Event, EventType
|
|
16
|
-
from .storage import JsonGoalStore, JsonMemoryStore, JsonlEventLog, ensure_ram_storage
|
|
17
|
+
from .storage import JsonAgentProfileStore, JsonGoalStore, JsonMemoryStore, JsonlEventLog, ensure_ram_storage
|
|
17
18
|
|
|
18
19
|
JSONRPC_VERSION = "2.0"
|
|
19
20
|
SERVER_NAME = "agentram"
|
|
@@ -43,6 +44,10 @@ class McpContext:
|
|
|
43
44
|
def goal_store(self) -> JsonGoalStore:
|
|
44
45
|
return JsonGoalStore(self.ram_root / "goal_state.json")
|
|
45
46
|
|
|
47
|
+
@property
|
|
48
|
+
def profile_store(self) -> JsonAgentProfileStore:
|
|
49
|
+
return JsonAgentProfileStore(self.ram_root / "agent_profile.json")
|
|
50
|
+
|
|
46
51
|
@property
|
|
47
52
|
def merger(self) -> MemoryMerger:
|
|
48
53
|
return MemoryMerger(self.memory_store)
|
|
@@ -220,6 +225,60 @@ def tools_list() -> dict[str, Any]:
|
|
|
220
225
|
},
|
|
221
226
|
},
|
|
222
227
|
},
|
|
228
|
+
|
|
229
|
+
{
|
|
230
|
+
"name": "agentram_bind_coding_model",
|
|
231
|
+
"description": "Bind a coding-agent model endpoint that Claude or other agents can orchestrate later.",
|
|
232
|
+
"inputSchema": {
|
|
233
|
+
"type": "object",
|
|
234
|
+
"properties": {
|
|
235
|
+
"id": {"type": "string"},
|
|
236
|
+
"provider": {"type": "string", "default": "openai-compatible"},
|
|
237
|
+
"model": {"type": "string"},
|
|
238
|
+
"role": {"type": "string", "default": "coder"},
|
|
239
|
+
"base_url": {"type": "string"},
|
|
240
|
+
"api_key_env": {"type": "string"},
|
|
241
|
+
"modalities": {"type": "array", "items": {"type": "string"}},
|
|
242
|
+
"enabled": {"type": "boolean", "default": True},
|
|
243
|
+
},
|
|
244
|
+
"required": ["provider", "model"],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "agentram_read_coding_models",
|
|
249
|
+
"description": "Read coding-agent model endpoints bound to this project RAM.",
|
|
250
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"name": "agentram_build_coding_orchestration",
|
|
254
|
+
"description": "Build prompts for all enabled coding model endpoints without calling models.",
|
|
255
|
+
"inputSchema": {
|
|
256
|
+
"type": "object",
|
|
257
|
+
"properties": {
|
|
258
|
+
"task": {"type": "string"},
|
|
259
|
+
"query": {"type": "string"},
|
|
260
|
+
"files": {"type": "array", "items": {"type": "string"}},
|
|
261
|
+
"modalities": {"type": "array", "items": {"type": "string"}},
|
|
262
|
+
},
|
|
263
|
+
"required": ["task"],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"name": "agentram_run_coding_orchestration",
|
|
268
|
+
"description": "Run enabled coding model endpoints concurrently when execute=true, otherwise return dry-run plan.",
|
|
269
|
+
"inputSchema": {
|
|
270
|
+
"type": "object",
|
|
271
|
+
"properties": {
|
|
272
|
+
"task": {"type": "string"},
|
|
273
|
+
"query": {"type": "string"},
|
|
274
|
+
"files": {"type": "array", "items": {"type": "string"}},
|
|
275
|
+
"modalities": {"type": "array", "items": {"type": "string"}},
|
|
276
|
+
"execute": {"type": "boolean", "default": False},
|
|
277
|
+
"max_workers": {"type": "integer", "default": 4},
|
|
278
|
+
},
|
|
279
|
+
"required": ["task"],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
223
282
|
{
|
|
224
283
|
"name": "agentram_check_goal_drift",
|
|
225
284
|
"description": "Check whether a proposed task aligns with the current Goal Stack. Returns no suggestions when aligned.",
|
|
@@ -284,6 +343,32 @@ def call_tool(context: McpContext, name: str, arguments: dict[str, Any]) -> dict
|
|
|
284
343
|
state = context.goal_store.update(arguments)
|
|
285
344
|
return tool_result({"ok": True, "goal_state": state.to_dict(), "context": state.context_block()})
|
|
286
345
|
|
|
346
|
+
|
|
347
|
+
if name == "agentram_bind_coding_model":
|
|
348
|
+
endpoint = AgentModelEndpoint.from_dict(arguments)
|
|
349
|
+
data = context.profile_store.upsert(endpoint)
|
|
350
|
+
return tool_result({"ok": True, "endpoint": endpoint.to_dict(), "profile": data})
|
|
351
|
+
|
|
352
|
+
if name == "agentram_read_coding_models":
|
|
353
|
+
endpoints = context.profile_store.list_endpoints()
|
|
354
|
+
return tool_result({"endpoints": [endpoint.to_dict() for endpoint in endpoints]})
|
|
355
|
+
|
|
356
|
+
if name == "agentram_build_coding_orchestration":
|
|
357
|
+
request = orchestration_request_from_args(context, arguments)
|
|
358
|
+
endpoints = context.profile_store.list_endpoints()
|
|
359
|
+
plan = MultiModelCodingOrchestrator(endpoints).build_plan(request)
|
|
360
|
+
return tool_result({"ok": True, "execute": False, **plan})
|
|
361
|
+
|
|
362
|
+
if name == "agentram_run_coding_orchestration":
|
|
363
|
+
request = orchestration_request_from_args(context, arguments)
|
|
364
|
+
endpoints = context.profile_store.list_endpoints()
|
|
365
|
+
orchestrator = MultiModelCodingOrchestrator(endpoints)
|
|
366
|
+
if not bool(arguments.get("execute", False)):
|
|
367
|
+
plan = orchestrator.build_plan(request)
|
|
368
|
+
return tool_result({"ok": True, "execute": False, **plan})
|
|
369
|
+
result = orchestrator.run(request, max_workers=int(arguments.get("max_workers", 4)))
|
|
370
|
+
return tool_result({"ok": True, "execute": True, **result})
|
|
371
|
+
|
|
287
372
|
if name == "agentram_check_goal_drift":
|
|
288
373
|
task = str(arguments.get("task", ""))
|
|
289
374
|
if not task.strip():
|
|
@@ -293,6 +378,27 @@ def call_tool(context: McpContext, name: str, arguments: dict[str, Any]) -> dict
|
|
|
293
378
|
raise ValueError(f"unknown tool: {name}")
|
|
294
379
|
|
|
295
380
|
|
|
381
|
+
|
|
382
|
+
def orchestration_request_from_args(context: McpContext, arguments: dict[str, Any]) -> CodingOrchestrationRequest:
|
|
383
|
+
task = str(arguments.get("task", ""))
|
|
384
|
+
if not task.strip():
|
|
385
|
+
raise ValueError("task is required")
|
|
386
|
+
query = str(arguments.get("query") or task)
|
|
387
|
+
files = arguments.get("files", [])
|
|
388
|
+
modalities = arguments.get("modalities", ["text"])
|
|
389
|
+
if not isinstance(files, list):
|
|
390
|
+
raise ValueError("files must be an array")
|
|
391
|
+
if not isinstance(modalities, list):
|
|
392
|
+
raise ValueError("modalities must be an array")
|
|
393
|
+
retriever = MemoryRetriever(context.memory_store, context.goal_store)
|
|
394
|
+
context_block = retriever.context_block(query=query, task_id="codex", limit=8)
|
|
395
|
+
return CodingOrchestrationRequest(
|
|
396
|
+
task=task,
|
|
397
|
+
context=context_block,
|
|
398
|
+
files=[str(item) for item in files],
|
|
399
|
+
modalities=[str(item) for item in modalities],
|
|
400
|
+
)
|
|
401
|
+
|
|
296
402
|
def handle_request(context: McpContext, request: dict[str, Any]) -> dict[str, Any] | None:
|
|
297
403
|
request_id = request.get("id")
|
|
298
404
|
method = request.get("method")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import urllib.request
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
from uuid import uuid4
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class AgentModelEndpoint:
|
|
14
|
+
provider: str
|
|
15
|
+
model: str
|
|
16
|
+
role: str = "coder"
|
|
17
|
+
base_url: str = ""
|
|
18
|
+
api_key_env: str = ""
|
|
19
|
+
modalities: list[str] = field(default_factory=lambda: ["text"])
|
|
20
|
+
enabled: bool = True
|
|
21
|
+
id: str = field(default_factory=lambda: f"model_{uuid4().hex[:10]}")
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict[str, Any]:
|
|
24
|
+
return {
|
|
25
|
+
"id": self.id,
|
|
26
|
+
"provider": self.provider,
|
|
27
|
+
"model": self.model,
|
|
28
|
+
"role": self.role,
|
|
29
|
+
"base_url": self.base_url,
|
|
30
|
+
"api_key_env": self.api_key_env,
|
|
31
|
+
"modalities": self.modalities,
|
|
32
|
+
"enabled": self.enabled,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_dict(cls, data: dict[str, Any]) -> "AgentModelEndpoint":
|
|
37
|
+
return cls(
|
|
38
|
+
id=str(data.get("id") or f"model_{uuid4().hex[:10]}"),
|
|
39
|
+
provider=str(data.get("provider", "openai-compatible")),
|
|
40
|
+
model=str(data.get("model", "")),
|
|
41
|
+
role=str(data.get("role", "coder")),
|
|
42
|
+
base_url=str(data.get("base_url", "")),
|
|
43
|
+
api_key_env=str(data.get("api_key_env", "")),
|
|
44
|
+
modalities=[str(item) for item in data.get("modalities", ["text"])],
|
|
45
|
+
enabled=bool(data.get("enabled", True)),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class CodingOrchestrationRequest:
|
|
51
|
+
task: str
|
|
52
|
+
context: str = ""
|
|
53
|
+
files: list[str] = field(default_factory=list)
|
|
54
|
+
modalities: list[str] = field(default_factory=lambda: ["text"])
|
|
55
|
+
|
|
56
|
+
def to_prompt(self, endpoint: AgentModelEndpoint) -> str:
|
|
57
|
+
return "\n".join(
|
|
58
|
+
[
|
|
59
|
+
f"You are AgentRAM {endpoint.role} agent.",
|
|
60
|
+
f"Model: {endpoint.model}",
|
|
61
|
+
f"Provider: {endpoint.provider}",
|
|
62
|
+
f"Modalities: {', '.join(endpoint.modalities)}",
|
|
63
|
+
"Task:",
|
|
64
|
+
self.task,
|
|
65
|
+
"Files:",
|
|
66
|
+
"\n".join(f"- {item}" for item in self.files) or "- none provided",
|
|
67
|
+
"AgentRAM Context:",
|
|
68
|
+
self.context or "No context provided.",
|
|
69
|
+
"Output contract:",
|
|
70
|
+
"- Return concise coding findings or patch plan.",
|
|
71
|
+
"- Mention files to inspect or change.",
|
|
72
|
+
"- Do not invent repo facts.",
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MultiModelCodingOrchestrator:
|
|
78
|
+
def __init__(self, endpoints: list[AgentModelEndpoint], client: Callable[[AgentModelEndpoint, str], str] | None = None) -> None:
|
|
79
|
+
self.endpoints = [endpoint for endpoint in endpoints if endpoint.enabled]
|
|
80
|
+
self.client = client or self._call_endpoint
|
|
81
|
+
|
|
82
|
+
def build_plan(self, request: CodingOrchestrationRequest) -> dict[str, Any]:
|
|
83
|
+
agents = []
|
|
84
|
+
for endpoint in self.endpoints:
|
|
85
|
+
agents.append({"endpoint": endpoint.to_dict(), "prompt": request.to_prompt(endpoint)})
|
|
86
|
+
return {"task": request.task, "agent_count": len(agents), "agents": agents}
|
|
87
|
+
|
|
88
|
+
def run(self, request: CodingOrchestrationRequest, max_workers: int = 4) -> dict[str, Any]:
|
|
89
|
+
results: list[dict[str, Any]] = []
|
|
90
|
+
if not self.endpoints:
|
|
91
|
+
return {"task": request.task, "agent_count": 0, "results": []}
|
|
92
|
+
with ThreadPoolExecutor(max_workers=max(1, min(max_workers, len(self.endpoints)))) as executor:
|
|
93
|
+
future_map = {executor.submit(self.client, endpoint, request.to_prompt(endpoint)): endpoint for endpoint in self.endpoints}
|
|
94
|
+
for future in as_completed(future_map):
|
|
95
|
+
endpoint = future_map[future]
|
|
96
|
+
try:
|
|
97
|
+
output = future.result()
|
|
98
|
+
results.append({"endpoint": endpoint.to_dict(), "ok": True, "output": output})
|
|
99
|
+
except Exception as error: # noqa: BLE001 - agent boundary
|
|
100
|
+
results.append({"endpoint": endpoint.to_dict(), "ok": False, "error": str(error)})
|
|
101
|
+
return {"task": request.task, "agent_count": len(self.endpoints), "results": results}
|
|
102
|
+
|
|
103
|
+
def _call_endpoint(self, endpoint: AgentModelEndpoint, prompt: str) -> str:
|
|
104
|
+
provider = endpoint.provider.lower()
|
|
105
|
+
if provider in {"openai", "openai-compatible", "groq", "vllm", "lmstudio", "ollama-v1"}:
|
|
106
|
+
return self._call_openai_compatible(endpoint, prompt)
|
|
107
|
+
if provider in {"anthropic", "claude"}:
|
|
108
|
+
return self._call_anthropic(endpoint, prompt)
|
|
109
|
+
raise ValueError(f"Unsupported provider for execution: {endpoint.provider}")
|
|
110
|
+
|
|
111
|
+
def _call_openai_compatible(self, endpoint: AgentModelEndpoint, prompt: str) -> str:
|
|
112
|
+
api_key = os.getenv(endpoint.api_key_env) if endpoint.api_key_env else ""
|
|
113
|
+
if not endpoint.base_url:
|
|
114
|
+
raise ValueError("base_url is required")
|
|
115
|
+
if endpoint.api_key_env and not api_key:
|
|
116
|
+
raise ValueError(f"missing API key env: {endpoint.api_key_env}")
|
|
117
|
+
payload = {"model": endpoint.model, "messages": [{"role": "user", "content": prompt}], "temperature": 0}
|
|
118
|
+
request = urllib.request.Request(
|
|
119
|
+
endpoint.base_url.rstrip("/") + "/chat/completions",
|
|
120
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
121
|
+
headers={"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"} if api_key else {"Content-Type": "application/json"},
|
|
122
|
+
method="POST",
|
|
123
|
+
)
|
|
124
|
+
with urllib.request.urlopen(request, timeout=120) as response:
|
|
125
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
126
|
+
return str(data.get("choices", [{}])[0].get("message", {}).get("content", ""))
|
|
127
|
+
|
|
128
|
+
def _call_anthropic(self, endpoint: AgentModelEndpoint, prompt: str) -> str:
|
|
129
|
+
api_key = os.getenv(endpoint.api_key_env) if endpoint.api_key_env else ""
|
|
130
|
+
if endpoint.api_key_env and not api_key:
|
|
131
|
+
raise ValueError(f"missing API key env: {endpoint.api_key_env}")
|
|
132
|
+
base_url = endpoint.base_url.rstrip("/") if endpoint.base_url else "https://api.anthropic.com/v1"
|
|
133
|
+
payload = {"model": endpoint.model, "max_tokens": 1024, "messages": [{"role": "user", "content": prompt}]}
|
|
134
|
+
request = urllib.request.Request(
|
|
135
|
+
base_url + "/messages",
|
|
136
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
137
|
+
headers={"Content-Type": "application/json", "x-api-key": api_key, "anthropic-version": "2023-06-01"},
|
|
138
|
+
method="POST",
|
|
139
|
+
)
|
|
140
|
+
with urllib.request.urlopen(request, timeout=120) as response:
|
|
141
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
142
|
+
content = data.get("content", [])
|
|
143
|
+
if content and isinstance(content, list):
|
|
144
|
+
return "\n".join(str(item.get("text", "")) for item in content if isinstance(item, dict))
|
|
145
|
+
return str(data)
|
package/agentram/storage.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
+
from .orchestration import AgentModelEndpoint
|
|
6
7
|
from .schema import Event, GoalState, MemoryItem, utc_now
|
|
7
8
|
|
|
8
9
|
|
|
@@ -74,6 +75,40 @@ class JsonGoalStore:
|
|
|
74
75
|
return self.save(GoalState.from_dict(current))
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
class JsonAgentProfileStore:
|
|
79
|
+
def __init__(self, path: str | Path) -> None:
|
|
80
|
+
self.path = Path(path)
|
|
81
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
def load(self) -> dict[str, object]:
|
|
84
|
+
if not self.path.exists():
|
|
85
|
+
return {"updated_at": utc_now(), "endpoints": []}
|
|
86
|
+
return json.loads(self.path.read_text(encoding="utf-8"))
|
|
87
|
+
|
|
88
|
+
def save(self, endpoints: list[AgentModelEndpoint]) -> dict[str, object]:
|
|
89
|
+
payload = {"updated_at": utc_now(), "endpoints": [endpoint.to_dict() for endpoint in endpoints]}
|
|
90
|
+
self.path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
91
|
+
return payload
|
|
92
|
+
|
|
93
|
+
def list_endpoints(self) -> list[AgentModelEndpoint]:
|
|
94
|
+
data = self.load()
|
|
95
|
+
return [AgentModelEndpoint.from_dict(item) for item in data.get("endpoints", []) if isinstance(item, dict)]
|
|
96
|
+
|
|
97
|
+
def upsert(self, endpoint: AgentModelEndpoint) -> dict[str, object]:
|
|
98
|
+
endpoints = self.list_endpoints()
|
|
99
|
+
replaced = False
|
|
100
|
+
updated: list[AgentModelEndpoint] = []
|
|
101
|
+
for current in endpoints:
|
|
102
|
+
if current.id == endpoint.id:
|
|
103
|
+
updated.append(endpoint)
|
|
104
|
+
replaced = True
|
|
105
|
+
else:
|
|
106
|
+
updated.append(current)
|
|
107
|
+
if not replaced:
|
|
108
|
+
updated.append(endpoint)
|
|
109
|
+
return self.save(updated)
|
|
110
|
+
|
|
111
|
+
|
|
77
112
|
def ensure_ram_storage(root: str | Path) -> dict[str, object]:
|
|
78
113
|
ram_root = Path(root)
|
|
79
114
|
ram_root.mkdir(parents=True, exist_ok=True)
|
|
@@ -81,6 +116,7 @@ def ensure_ram_storage(root: str | Path) -> dict[str, object]:
|
|
|
81
116
|
events_path = ram_root / "events.jsonl"
|
|
82
117
|
memory_path = ram_root / "memory.json"
|
|
83
118
|
goal_path = ram_root / "goal_state.json"
|
|
119
|
+
profile_path = ram_root / "agent_profile.json"
|
|
84
120
|
|
|
85
121
|
created: list[str] = []
|
|
86
122
|
if not events_path.exists():
|
|
@@ -92,6 +128,9 @@ def ensure_ram_storage(root: str | Path) -> dict[str, object]:
|
|
|
92
128
|
if not goal_path.exists():
|
|
93
129
|
JsonGoalStore(goal_path).save(GoalState())
|
|
94
130
|
created.append("goal_state.json")
|
|
131
|
+
if not profile_path.exists():
|
|
132
|
+
JsonAgentProfileStore(profile_path).save([])
|
|
133
|
+
created.append("agent_profile.json")
|
|
95
134
|
|
|
96
135
|
return {
|
|
97
136
|
"ram_root": str(ram_root),
|
|
@@ -100,5 +139,6 @@ def ensure_ram_storage(root: str | Path) -> dict[str, object]:
|
|
|
100
139
|
"events": str(events_path),
|
|
101
140
|
"memory": str(memory_path),
|
|
102
141
|
"goal_state": str(goal_path),
|
|
142
|
+
"agent_profile": str(profile_path),
|
|
103
143
|
},
|
|
104
144
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentram",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Async, model-agnostic Working RAM for coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/trancongnghia/AGENT_RAM#readme",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"agentram/*.py",
|
|
35
35
|
"bin/*.js",
|
|
36
|
+
".claude/agents/*.md",
|
|
37
|
+
".claude/commands/*.md",
|
|
36
38
|
"agentram_mcp_server.py",
|
|
37
39
|
"agentram_daemon.py",
|
|
38
40
|
"codex_ram.py",
|