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 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
- return f"Bound {slot} agent: provider={endpoint.provider} model={endpoint.model} role={endpoint.role}"
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:
@@ -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():
@@ -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:
@@ -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
- return self.normalize_payload(json.loads(lines[-1]))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentram",
3
- "version": "0.1.37",
3
+ "version": "0.1.41",
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",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentram"
7
- version = "0.1.37"
7
+ version = "0.1.41"
8
8
  description = "Async, model-agnostic RAM layer for agentic coding tools."
9
9
  readme = "README.md"
10
10
  license = "MIT"