agentram 0.1.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/LICENSE +21 -0
- package/README.md +729 -0
- package/agentram/__init__.py +26 -0
- package/agentram/adapter.py +368 -0
- package/agentram/cli.py +242 -0
- package/agentram/codex_wrapper.py +163 -0
- package/agentram/config.py +87 -0
- package/agentram/daemon.py +106 -0
- package/agentram/demo.py +29 -0
- package/agentram/mcp_server.py +369 -0
- package/agentram/merger.py +66 -0
- package/agentram/orchestrator.py +45 -0
- package/agentram/retriever.py +51 -0
- package/agentram/schema.py +258 -0
- package/agentram/storage.py +104 -0
- package/agentram/worker.py +40 -0
- package/agentram_daemon.py +4 -0
- package/agentram_mcp_server.py +4 -0
- package/bin/agentram-mcp.js +24 -0
- package/bin/agentram.js +24 -0
- package/codex_ram.py +4 -0
- package/package.json +49 -0
- package/pyproject.toml +38 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def utc_now() -> str:
|
|
11
|
+
return datetime.now(timezone.utc).isoformat()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EventType(str, Enum):
|
|
15
|
+
READ_FILE = "READ_FILE"
|
|
16
|
+
WRITE_FILE = "WRITE_FILE"
|
|
17
|
+
TOOL_CALL = "TOOL_CALL"
|
|
18
|
+
DECISION = "DECISION"
|
|
19
|
+
USER_MESSAGE = "USER_MESSAGE"
|
|
20
|
+
AGENT_MESSAGE = "AGENT_MESSAGE"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MemoryType(str, Enum):
|
|
24
|
+
DECISION = "decision"
|
|
25
|
+
FACT = "fact"
|
|
26
|
+
TASK_STATE = "task_state"
|
|
27
|
+
FILE_SUMMARY = "file_summary"
|
|
28
|
+
OPEN_QUESTION = "open_question"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MemoryScope(str, Enum):
|
|
32
|
+
SESSION = "session"
|
|
33
|
+
TASK = "task"
|
|
34
|
+
PROJECT = "project"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class Event:
|
|
39
|
+
type: EventType
|
|
40
|
+
payload: dict[str, Any]
|
|
41
|
+
task_id: str = "default"
|
|
42
|
+
id: str = field(default_factory=lambda: f"evt_{uuid4().hex}")
|
|
43
|
+
created_at: str = field(default_factory=utc_now)
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict[str, Any]:
|
|
46
|
+
return {
|
|
47
|
+
"id": self.id,
|
|
48
|
+
"task_id": self.task_id,
|
|
49
|
+
"type": self.type.value,
|
|
50
|
+
"payload": self.payload,
|
|
51
|
+
"created_at": self.created_at,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def from_dict(cls, data: dict[str, Any]) -> "Event":
|
|
56
|
+
return cls(
|
|
57
|
+
id=data["id"],
|
|
58
|
+
task_id=data.get("task_id", "default"),
|
|
59
|
+
type=EventType(data["type"]),
|
|
60
|
+
payload=dict(data.get("payload", {})),
|
|
61
|
+
created_at=data["created_at"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class MemoryItem:
|
|
67
|
+
type: MemoryType
|
|
68
|
+
content: str
|
|
69
|
+
source_events: list[str]
|
|
70
|
+
task_id: str = "default"
|
|
71
|
+
related_files: list[str] = field(default_factory=list)
|
|
72
|
+
confidence: float = 0.8
|
|
73
|
+
scope: MemoryScope = MemoryScope.TASK
|
|
74
|
+
id: str = field(default_factory=lambda: f"mem_{uuid4().hex}")
|
|
75
|
+
created_at: str = field(default_factory=utc_now)
|
|
76
|
+
updated_at: str = field(default_factory=utc_now)
|
|
77
|
+
expires_at: str | None = None
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> dict[str, Any]:
|
|
80
|
+
return {
|
|
81
|
+
"id": self.id,
|
|
82
|
+
"task_id": self.task_id,
|
|
83
|
+
"type": self.type.value,
|
|
84
|
+
"content": self.content,
|
|
85
|
+
"source_events": self.source_events,
|
|
86
|
+
"related_files": self.related_files,
|
|
87
|
+
"confidence": self.confidence,
|
|
88
|
+
"scope": self.scope.value,
|
|
89
|
+
"created_at": self.created_at,
|
|
90
|
+
"updated_at": self.updated_at,
|
|
91
|
+
"expires_at": self.expires_at,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_dict(cls, data: dict[str, Any]) -> "MemoryItem":
|
|
96
|
+
return cls(
|
|
97
|
+
id=data["id"],
|
|
98
|
+
task_id=data.get("task_id", "default"),
|
|
99
|
+
type=MemoryType(data["type"]),
|
|
100
|
+
content=data["content"],
|
|
101
|
+
source_events=list(data.get("source_events", [])),
|
|
102
|
+
related_files=list(data.get("related_files", [])),
|
|
103
|
+
confidence=float(data.get("confidence", 0.8)),
|
|
104
|
+
scope=MemoryScope(data.get("scope", "task")),
|
|
105
|
+
created_at=data["created_at"],
|
|
106
|
+
updated_at=data.get("updated_at", data["created_at"]),
|
|
107
|
+
expires_at=data.get("expires_at"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class MemoryPatch:
|
|
113
|
+
op: Literal["upsert", "delete"]
|
|
114
|
+
item: MemoryItem | None = None
|
|
115
|
+
target_id: str | None = None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class GoalTask:
|
|
120
|
+
title: str
|
|
121
|
+
status: str = "pending"
|
|
122
|
+
id: str = field(default_factory=lambda: f"task_{uuid4().hex}")
|
|
123
|
+
|
|
124
|
+
def to_dict(self) -> dict[str, Any]:
|
|
125
|
+
return {"id": self.id, "title": self.title, "status": self.status}
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_dict(cls, data: dict[str, Any]) -> "GoalTask":
|
|
129
|
+
return cls(id=str(data.get("id") or f"task_{uuid4().hex}"), title=str(data.get("title", "")), status=str(data.get("status", "pending")))
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class GoalNode:
|
|
133
|
+
title: str
|
|
134
|
+
status: str = "pending"
|
|
135
|
+
tasks: list[GoalTask] = field(default_factory=list)
|
|
136
|
+
id: str = field(default_factory=lambda: f"goal_{uuid4().hex}")
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> dict[str, Any]:
|
|
139
|
+
return {"id": self.id, "title": self.title, "status": self.status, "tasks": [task.to_dict() for task in self.tasks]}
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_dict(cls, data: dict[str, Any]) -> "GoalNode":
|
|
143
|
+
return cls(
|
|
144
|
+
id=str(data.get("id") or f"goal_{uuid4().hex}"),
|
|
145
|
+
title=str(data.get("title", "")),
|
|
146
|
+
status=str(data.get("status", "pending")),
|
|
147
|
+
tasks=[GoalTask.from_dict(item) for item in data.get("tasks", []) if isinstance(item, dict)],
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class Milestone:
|
|
152
|
+
title: str
|
|
153
|
+
status: str = "pending"
|
|
154
|
+
goals: list[GoalNode] = field(default_factory=list)
|
|
155
|
+
id: str = field(default_factory=lambda: f"mile_{uuid4().hex}")
|
|
156
|
+
|
|
157
|
+
def to_dict(self) -> dict[str, Any]:
|
|
158
|
+
return {"id": self.id, "title": self.title, "status": self.status, "goals": [goal.to_dict() for goal in self.goals]}
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_dict(cls, data: dict[str, Any]) -> "Milestone":
|
|
162
|
+
return cls(
|
|
163
|
+
id=str(data.get("id") or f"mile_{uuid4().hex}"),
|
|
164
|
+
title=str(data.get("title", "")),
|
|
165
|
+
status=str(data.get("status", "pending")),
|
|
166
|
+
goals=[GoalNode.from_dict(item) for item in data.get("goals", []) if isinstance(item, dict)],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class DecisionRecord:
|
|
171
|
+
content: str
|
|
172
|
+
source_events: list[str] = field(default_factory=list)
|
|
173
|
+
confidence: float = 0.8
|
|
174
|
+
id: str = field(default_factory=lambda: f"dec_{uuid4().hex}")
|
|
175
|
+
created_at: str = field(default_factory=utc_now)
|
|
176
|
+
|
|
177
|
+
def to_dict(self) -> dict[str, Any]:
|
|
178
|
+
return {"id": self.id, "content": self.content, "source_events": self.source_events, "confidence": self.confidence, "created_at": self.created_at}
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def from_dict(cls, data: dict[str, Any]) -> "DecisionRecord":
|
|
182
|
+
return cls(
|
|
183
|
+
id=str(data.get("id") or f"dec_{uuid4().hex}"),
|
|
184
|
+
content=str(data.get("content", "")),
|
|
185
|
+
source_events=list(data.get("source_events", [])),
|
|
186
|
+
confidence=float(data.get("confidence", 0.8)),
|
|
187
|
+
created_at=str(data.get("created_at") or utc_now()),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class GoalState:
|
|
192
|
+
mission: str = ""
|
|
193
|
+
project_goal: str = ""
|
|
194
|
+
milestones: list[Milestone] = field(default_factory=list)
|
|
195
|
+
completed_milestones: list[str] = field(default_factory=list)
|
|
196
|
+
current_milestone: str = ""
|
|
197
|
+
current_goal: str = ""
|
|
198
|
+
current_task: str = ""
|
|
199
|
+
next_tasks: list[str] = field(default_factory=list)
|
|
200
|
+
goal_history: list[str] = field(default_factory=list)
|
|
201
|
+
decision_history: list[DecisionRecord] = field(default_factory=list)
|
|
202
|
+
updated_at: str = field(default_factory=utc_now)
|
|
203
|
+
|
|
204
|
+
def to_dict(self) -> dict[str, Any]:
|
|
205
|
+
return {
|
|
206
|
+
"mission": self.mission,
|
|
207
|
+
"project_goal": self.project_goal,
|
|
208
|
+
"milestones": [milestone.to_dict() for milestone in self.milestones],
|
|
209
|
+
"completed_milestones": self.completed_milestones,
|
|
210
|
+
"current_milestone": self.current_milestone,
|
|
211
|
+
"current_goal": self.current_goal,
|
|
212
|
+
"current_task": self.current_task,
|
|
213
|
+
"next_tasks": self.next_tasks,
|
|
214
|
+
"goal_history": self.goal_history,
|
|
215
|
+
"decision_history": [decision.to_dict() for decision in self.decision_history],
|
|
216
|
+
"updated_at": self.updated_at,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def from_dict(cls, data: dict[str, Any]) -> "GoalState":
|
|
221
|
+
return cls(
|
|
222
|
+
mission=str(data.get("mission", "")),
|
|
223
|
+
project_goal=str(data.get("project_goal", "")),
|
|
224
|
+
milestones=[Milestone.from_dict(item) for item in data.get("milestones", []) if isinstance(item, dict)],
|
|
225
|
+
completed_milestones=[str(item) for item in data.get("completed_milestones", [])],
|
|
226
|
+
current_milestone=str(data.get("current_milestone", "")),
|
|
227
|
+
current_goal=str(data.get("current_goal", "")),
|
|
228
|
+
current_task=str(data.get("current_task", "")),
|
|
229
|
+
next_tasks=[str(item) for item in data.get("next_tasks", [])],
|
|
230
|
+
goal_history=[str(item) for item in data.get("goal_history", [])],
|
|
231
|
+
decision_history=[DecisionRecord.from_dict(item) for item in data.get("decision_history", []) if isinstance(item, dict)],
|
|
232
|
+
updated_at=str(data.get("updated_at") or utc_now()),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def context_block(self) -> str:
|
|
236
|
+
lines: list[str] = []
|
|
237
|
+
if self.mission:
|
|
238
|
+
lines.append(f"Mission: {self.mission}")
|
|
239
|
+
if self.project_goal:
|
|
240
|
+
lines.append(f"Project Goal: {self.project_goal}")
|
|
241
|
+
if self.current_milestone:
|
|
242
|
+
lines.append(f"Current Milestone: {self.current_milestone}")
|
|
243
|
+
if self.current_goal:
|
|
244
|
+
lines.append(f"Current Goal: {self.current_goal}")
|
|
245
|
+
if self.current_task:
|
|
246
|
+
lines.append(f"Current Task: {self.current_task}")
|
|
247
|
+
if self.next_tasks:
|
|
248
|
+
lines.append("Next Tasks: " + "; ".join(self.next_tasks[:8]))
|
|
249
|
+
if self.completed_milestones:
|
|
250
|
+
lines.append("Completed Milestones: " + "; ".join(self.completed_milestones[:8]))
|
|
251
|
+
if self.goal_history:
|
|
252
|
+
lines.append("Goal History: " + " -> ".join(self.goal_history[-8:]))
|
|
253
|
+
if self.decision_history:
|
|
254
|
+
lines.append("Decision History:")
|
|
255
|
+
lines.extend(f"- {decision.content}" for decision in self.decision_history[-8:])
|
|
256
|
+
if not lines:
|
|
257
|
+
return ""
|
|
258
|
+
return "AgentRAM Goal Stack:\n" + "\n".join(lines)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .schema import Event, GoalState, MemoryItem, utc_now
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JsonlEventLog:
|
|
10
|
+
def __init__(self, path: str | Path) -> None:
|
|
11
|
+
self.path = Path(path)
|
|
12
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
|
|
14
|
+
def append(self, event: Event) -> None:
|
|
15
|
+
with self.path.open("a", encoding="utf-8") as file:
|
|
16
|
+
file.write(json.dumps(event.to_dict(), ensure_ascii=False) + "\n")
|
|
17
|
+
|
|
18
|
+
def read_all(self) -> list[Event]:
|
|
19
|
+
if not self.path.exists():
|
|
20
|
+
return []
|
|
21
|
+
events: list[Event] = []
|
|
22
|
+
with self.path.open("r", encoding="utf-8") as file:
|
|
23
|
+
for line in file:
|
|
24
|
+
if line.strip():
|
|
25
|
+
events.append(Event.from_dict(json.loads(line)))
|
|
26
|
+
return events
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JsonMemoryStore:
|
|
30
|
+
def __init__(self, path: str | Path) -> None:
|
|
31
|
+
self.path = Path(path)
|
|
32
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
def list_items(self) -> list[MemoryItem]:
|
|
35
|
+
if not self.path.exists():
|
|
36
|
+
return []
|
|
37
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
38
|
+
return [MemoryItem.from_dict(item) for item in data.get("items", [])]
|
|
39
|
+
|
|
40
|
+
def save_items(self, items: list[MemoryItem]) -> None:
|
|
41
|
+
payload = {
|
|
42
|
+
"updated_at": utc_now(),
|
|
43
|
+
"items": [item.to_dict() for item in items],
|
|
44
|
+
}
|
|
45
|
+
self.path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class JsonGoalStore:
|
|
49
|
+
def __init__(self, path: str | Path) -> None:
|
|
50
|
+
self.path = Path(path)
|
|
51
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
def load(self) -> GoalState:
|
|
54
|
+
if not self.path.exists():
|
|
55
|
+
return GoalState()
|
|
56
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
57
|
+
return GoalState.from_dict(data)
|
|
58
|
+
|
|
59
|
+
def save(self, state: GoalState) -> GoalState:
|
|
60
|
+
payload = state.to_dict()
|
|
61
|
+
payload["updated_at"] = utc_now()
|
|
62
|
+
self.path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
63
|
+
return GoalState.from_dict(payload)
|
|
64
|
+
|
|
65
|
+
def update(self, patch: dict[str, object]) -> GoalState:
|
|
66
|
+
current = self.load().to_dict()
|
|
67
|
+
for key, value in patch.items():
|
|
68
|
+
if key == "decision_history" and isinstance(value, list):
|
|
69
|
+
current[key] = current.get(key, []) + value
|
|
70
|
+
elif key == "goal_history" and isinstance(value, list):
|
|
71
|
+
current[key] = current.get(key, []) + value
|
|
72
|
+
elif key in current:
|
|
73
|
+
current[key] = value
|
|
74
|
+
return self.save(GoalState.from_dict(current))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def ensure_ram_storage(root: str | Path) -> dict[str, object]:
|
|
78
|
+
ram_root = Path(root)
|
|
79
|
+
ram_root.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
events_path = ram_root / "events.jsonl"
|
|
82
|
+
memory_path = ram_root / "memory.json"
|
|
83
|
+
goal_path = ram_root / "goal_state.json"
|
|
84
|
+
|
|
85
|
+
created: list[str] = []
|
|
86
|
+
if not events_path.exists():
|
|
87
|
+
events_path.write_text("", encoding="utf-8")
|
|
88
|
+
created.append("events.jsonl")
|
|
89
|
+
if not memory_path.exists():
|
|
90
|
+
JsonMemoryStore(memory_path).save_items([])
|
|
91
|
+
created.append("memory.json")
|
|
92
|
+
if not goal_path.exists():
|
|
93
|
+
JsonGoalStore(goal_path).save(GoalState())
|
|
94
|
+
created.append("goal_state.json")
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"ram_root": str(ram_root),
|
|
98
|
+
"created": created,
|
|
99
|
+
"files": {
|
|
100
|
+
"events": str(events_path),
|
|
101
|
+
"memory": str(memory_path),
|
|
102
|
+
"goal_state": str(goal_path),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from .adapter import MemoryModelAdapter
|
|
6
|
+
from .merger import MemoryMerger
|
|
7
|
+
from .schema import Event
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MemoryWorker:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
queue: asyncio.Queue[Event],
|
|
14
|
+
adapter: MemoryModelAdapter,
|
|
15
|
+
merger: MemoryMerger,
|
|
16
|
+
batch_size: int = 8,
|
|
17
|
+
) -> None:
|
|
18
|
+
self.queue = queue
|
|
19
|
+
self.adapter = adapter
|
|
20
|
+
self.merger = merger
|
|
21
|
+
self.batch_size = batch_size
|
|
22
|
+
self._running = False
|
|
23
|
+
|
|
24
|
+
async def run(self) -> None:
|
|
25
|
+
self._running = True
|
|
26
|
+
while self._running:
|
|
27
|
+
batch = [await self.queue.get()]
|
|
28
|
+
while len(batch) < self.batch_size:
|
|
29
|
+
try:
|
|
30
|
+
batch.append(self.queue.get_nowait())
|
|
31
|
+
except asyncio.QueueEmpty:
|
|
32
|
+
break
|
|
33
|
+
current = self.merger.store.list_items()
|
|
34
|
+
patches = self.adapter.create_patches(batch, current)
|
|
35
|
+
self.merger.apply(patches)
|
|
36
|
+
for _ in batch:
|
|
37
|
+
self.queue.task_done()
|
|
38
|
+
|
|
39
|
+
def stop(self) -> None:
|
|
40
|
+
self._running = False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
6
|
+
const existingPythonPath = process.env.PYTHONPATH || "";
|
|
7
|
+
const env = {
|
|
8
|
+
...process.env,
|
|
9
|
+
PYTHONPATH: existingPythonPath ? `${packageRoot}${path.delimiter}${existingPythonPath}` : packageRoot,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function run(command, args) {
|
|
13
|
+
return spawnSync(command, args, { stdio: "inherit", env });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let result = run(process.env.PYTHON || "python", ["-m", "agentram.mcp_server", ...process.argv.slice(2)]);
|
|
17
|
+
if (result.error && process.platform === "win32") {
|
|
18
|
+
result = run("py", ["-3", "-m", "agentram.mcp_server", ...process.argv.slice(2)]);
|
|
19
|
+
}
|
|
20
|
+
if (result.error) {
|
|
21
|
+
console.error(`AgentRAM MCP failed to start Python: ${result.error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
process.exit(result.status ?? 0);
|
package/bin/agentram.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
6
|
+
const existingPythonPath = process.env.PYTHONPATH || "";
|
|
7
|
+
const env = {
|
|
8
|
+
...process.env,
|
|
9
|
+
PYTHONPATH: existingPythonPath ? `${packageRoot}${path.delimiter}${existingPythonPath}` : packageRoot,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function run(command, args) {
|
|
13
|
+
return spawnSync(command, args, { stdio: "inherit", env });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let result = run(process.env.PYTHON || "python", ["-m", "agentram.cli", ...process.argv.slice(2)]);
|
|
17
|
+
if (result.error && process.platform === "win32") {
|
|
18
|
+
result = run("py", ["-3", "-m", "agentram.cli", ...process.argv.slice(2)]);
|
|
19
|
+
}
|
|
20
|
+
if (result.error) {
|
|
21
|
+
console.error(`AgentRAM failed to start Python: ${result.error.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
process.exit(result.status ?? 0);
|
package/codex_ram.py
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentram",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Async, model-agnostic Working RAM for coding agents.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/trancongnghia/AGENT_RAM#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/trancongnghia/AGENT_RAM.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/trancongnghia/AGENT_RAM/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"agent",
|
|
16
|
+
"memory",
|
|
17
|
+
"codex",
|
|
18
|
+
"claude",
|
|
19
|
+
"mcp",
|
|
20
|
+
"llm"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"agentram": "bin/agentram.js",
|
|
24
|
+
"agentram-mcp": "bin/agentram-mcp.js"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"agentram": "node bin/agentram.js",
|
|
28
|
+
"agentram:mcp": "node bin/agentram-mcp.js",
|
|
29
|
+
"test": "python -m pytest -q",
|
|
30
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
31
|
+
"prepublishOnly": "npm test"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"agentram/*.py",
|
|
35
|
+
"bin/*.js",
|
|
36
|
+
"agentram_mcp_server.py",
|
|
37
|
+
"agentram_daemon.py",
|
|
38
|
+
"codex_ram.py",
|
|
39
|
+
"pyproject.toml",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=16"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentram"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Async, model-agnostic RAM layer for agentic coding tools."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{name = "AgentRAM contributors"}]
|
|
12
|
+
keywords = ["agent", "memory", "codex", "mcp", "llm"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Topic :: Software Development :: Libraries",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
requires-python = ">=3.10"
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/your-org/agentram"
|
|
26
|
+
Repository = "https://github.com/your-org/agentram"
|
|
27
|
+
Issues = "https://github.com/your-org/agentram/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
agentram = "agentram.cli:main"
|
|
31
|
+
agentram-demo = "agentram.demo:main"
|
|
32
|
+
agentram-codex = "agentram.codex_wrapper:main"
|
|
33
|
+
agentram-mcp = "agentram.mcp_server:main"
|
|
34
|
+
agentram-daemon = "agentram.daemon:main"
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
pythonpath = ["."]
|