agentram 0.1.37 → 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 +19 -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()
|
|
@@ -1416,6 +1427,9 @@ def build_main_loop_prompt(
|
|
|
1416
1427
|
"No hard step limit. Continue until user goal is complete or blocked.",
|
|
1417
1428
|
"Do not stop early if unfinished work remains. Do not invent completion.",
|
|
1418
1429
|
"If complete, include DONE. If blocked, include BLOCKER and reason.",
|
|
1430
|
+
"Main agent may use normal coding-agent tools, including git read operations such as status/diff when useful.",
|
|
1431
|
+
"Before destructive git actions or commits, ask user approval explicitly.",
|
|
1432
|
+
"If file changes require user approval, request approval before editing.",
|
|
1419
1433
|
"User request:",
|
|
1420
1434
|
prompt,
|
|
1421
1435
|
"Supervisor / execution plan:",
|
|
@@ -1720,6 +1734,10 @@ def model_error_hint(message: str) -> str:
|
|
|
1720
1734
|
return message + " | Fix: start your OpenAI-compatible server at base_url, or change --base-url."
|
|
1721
1735
|
if "http 403" in lower or "403" in lower and "groq" in lower:
|
|
1722
1736
|
return message + " | Fix: Groq rejected auth/model. Rotate/check API key, ensure provider=openai-compatible, base_url=https://api.groq.com/openai/v1, and use a Groq-supported model name."
|
|
1737
|
+
if "http 500" in lower and "fetch failed" in lower:
|
|
1738
|
+
return message + " | Fix: local OpenAI-compatible proxy failed to fetch upstream model. Check proxy logs, upstream API key, provider route, model name, and network. AgentRAM request reached your local server."
|
|
1739
|
+
if "http 500" in lower:
|
|
1740
|
+
return message + " | Fix: local/provider server crashed or failed upstream. Check server logs, model route, API key, and provider health."
|
|
1723
1741
|
if "http 404" in lower or "404: not found" in lower:
|
|
1724
1742
|
return message + " | Fix: verify base_url includes /v1, provider supports /chat/completions, and model name exists."
|
|
1725
1743
|
if "http 400" in lower and "not supported" in lower:
|
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