agentram 0.1.39 → 0.1.41
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/agentram/cli.py +12 -1
- package/agentram/config.py +9 -0
- package/agentram/mcp_server.py +6 -2
- package/agentram/storage.py +20 -4
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/agentram/cli.py
CHANGED
|
@@ -612,6 +612,7 @@ def run_textual_tui(context: McpContext) -> bool:
|
|
|
612
612
|
modalities=["text"],
|
|
613
613
|
)
|
|
614
614
|
self.context.profile_store.set_agent(slot, endpoint)
|
|
615
|
+
set_global_agent_if_available(self.context, slot, endpoint)
|
|
615
616
|
if saved_env_name:
|
|
616
617
|
return f"{slot}={provider}:{model} key=.env:{saved_env_name}"
|
|
617
618
|
return f"{slot}={provider}:{model}"
|
|
@@ -1248,6 +1249,14 @@ def bind_model_text(context: McpContext, options: dict[str, object]) -> str:
|
|
|
1248
1249
|
return f"Bound model: {endpoint.get('id')} provider={endpoint.get('provider')} model={endpoint.get('model')} role={endpoint.get('role')}"
|
|
1249
1250
|
|
|
1250
1251
|
|
|
1252
|
+
def set_global_agent_if_available(context: McpContext, slot: str, endpoint: AgentModelEndpoint) -> bool:
|
|
1253
|
+
try:
|
|
1254
|
+
context.global_profile_store.set_agent(slot, endpoint)
|
|
1255
|
+
return True
|
|
1256
|
+
except OSError:
|
|
1257
|
+
return False
|
|
1258
|
+
|
|
1259
|
+
|
|
1251
1260
|
def bind_agent_text(context: McpContext, options: dict[str, object]) -> str:
|
|
1252
1261
|
slot = str(options.get("slot", "")).strip().lower()
|
|
1253
1262
|
if slot not in {"supervisor", "main", "small"}:
|
|
@@ -1265,7 +1274,9 @@ def bind_agent_text(context: McpContext, options: dict[str, object]) -> str:
|
|
|
1265
1274
|
modalities=["text"],
|
|
1266
1275
|
)
|
|
1267
1276
|
context.profile_store.set_agent(slot, endpoint)
|
|
1268
|
-
|
|
1277
|
+
global_saved = set_global_agent_if_available(context, slot, endpoint)
|
|
1278
|
+
scope = "project+global" if global_saved else "project"
|
|
1279
|
+
return f"Bound {slot} agent: provider={endpoint.provider} model={endpoint.model} role={endpoint.role} scope={scope}"
|
|
1269
1280
|
|
|
1270
1281
|
def workflow_text(context: McpContext) -> str:
|
|
1271
1282
|
agents = context.profile_store.list_agents()
|
package/agentram/config.py
CHANGED
|
@@ -20,6 +20,15 @@ def default_ram_root(cwd: str | Path | None = None) -> Path:
|
|
|
20
20
|
base = Path.cwd() if cwd is None else Path(cwd)
|
|
21
21
|
return base / DEFAULT_RAM_DIR_NAME
|
|
22
22
|
|
|
23
|
+
def global_agent_profile_path() -> Path:
|
|
24
|
+
override = os.getenv("AGENTRAM_GLOBAL_PROFILE_PATH")
|
|
25
|
+
if override:
|
|
26
|
+
return Path(override)
|
|
27
|
+
base = os.getenv("APPDATA")
|
|
28
|
+
if base:
|
|
29
|
+
return Path(base) / "AgentRAM" / "agent_profile.jsonl"
|
|
30
|
+
return Path.home() / ".agentram" / "agent_profile.jsonl"
|
|
31
|
+
|
|
23
32
|
def load_env_file(path: str | Path = ".env") -> dict[str, str]:
|
|
24
33
|
env_path = Path(path)
|
|
25
34
|
if not env_path.exists():
|
package/agentram/mcp_server.py
CHANGED
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .adapter import HeuristicMemoryAdapter
|
|
12
|
-
from .config import default_ram_root
|
|
12
|
+
from .config import default_ram_root, global_agent_profile_path
|
|
13
13
|
from .merger import MemoryMerger
|
|
14
14
|
from .orchestration import AgentModelEndpoint, CodingOrchestrationRequest, MultiModelCodingOrchestrator
|
|
15
15
|
from .retriever import MemoryRetriever
|
|
@@ -46,7 +46,11 @@ class McpContext:
|
|
|
46
46
|
|
|
47
47
|
@property
|
|
48
48
|
def profile_store(self) -> JsonAgentProfileStore:
|
|
49
|
-
return JsonAgentProfileStore(self.ram_root / "agent_profile.jsonl")
|
|
49
|
+
return JsonAgentProfileStore(self.ram_root / "agent_profile.jsonl", fallback_path=global_agent_profile_path())
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def global_profile_store(self) -> JsonAgentProfileStore:
|
|
53
|
+
return JsonAgentProfileStore(global_agent_profile_path())
|
|
50
54
|
|
|
51
55
|
@property
|
|
52
56
|
def merger(self) -> MemoryMerger:
|
package/agentram/storage.py
CHANGED
|
@@ -76,8 +76,9 @@ class JsonGoalStore:
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class JsonAgentProfileStore:
|
|
79
|
-
def __init__(self, path: str | Path) -> None:
|
|
79
|
+
def __init__(self, path: str | Path, fallback_path: str | Path | None = None) -> None:
|
|
80
80
|
self.path = Path(path)
|
|
81
|
+
self.fallback_path = Path(fallback_path) if fallback_path else None
|
|
81
82
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
83
|
|
|
83
84
|
def default_payload(self) -> dict[str, object]:
|
|
@@ -93,18 +94,33 @@ class JsonAgentProfileStore:
|
|
|
93
94
|
data.setdefault("agents", {})
|
|
94
95
|
return data
|
|
95
96
|
|
|
97
|
+
def is_empty_payload(self, data: dict[str, object]) -> bool:
|
|
98
|
+
return not data.get("endpoints") and not data.get("router") and not data.get("agents")
|
|
99
|
+
|
|
100
|
+
def load_fallback(self) -> dict[str, object] | None:
|
|
101
|
+
if not self.fallback_path or self.fallback_path == self.path or not self.fallback_path.exists():
|
|
102
|
+
return None
|
|
103
|
+
lines = [line.strip() for line in self.fallback_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
104
|
+
if not lines:
|
|
105
|
+
return None
|
|
106
|
+
data = self.normalize_payload(json.loads(lines[-1]))
|
|
107
|
+
return data if not self.is_empty_payload(data) else None
|
|
108
|
+
|
|
96
109
|
def load(self) -> dict[str, object]:
|
|
97
110
|
if self.path.exists():
|
|
98
111
|
lines = [line.strip() for line in self.path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
99
112
|
if not lines:
|
|
100
|
-
return self.default_payload()
|
|
101
|
-
|
|
113
|
+
return self.load_fallback() or self.default_payload()
|
|
114
|
+
data = self.normalize_payload(json.loads(lines[-1]))
|
|
115
|
+
if self.is_empty_payload(data):
|
|
116
|
+
return self.load_fallback() or data
|
|
117
|
+
return data
|
|
102
118
|
legacy = self.legacy_json_path()
|
|
103
119
|
if legacy.exists() and legacy != self.path:
|
|
104
120
|
data = self.normalize_payload(json.loads(legacy.read_text(encoding="utf-8")))
|
|
105
121
|
self.write_snapshot(data)
|
|
106
122
|
return data
|
|
107
|
-
return self.default_payload()
|
|
123
|
+
return self.load_fallback() or self.default_payload()
|
|
108
124
|
|
|
109
125
|
def write_snapshot(self, payload: dict[str, object]) -> None:
|
|
110
126
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
package/package.json
CHANGED