crow-cli 0.1.28__tar.gz → 0.1.30__tar.gz

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.
Files changed (57) hide show
  1. {crow_cli-0.1.28 → crow_cli-0.1.30}/PKG-INFO +2 -1
  2. {crow_cli-0.1.28 → crow_cli-0.1.30}/pyproject.toml +12 -1
  3. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/configure.py +4 -4
  4. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/default/defaults.py +2 -28
  5. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/main.py +6 -13
  6. crow_cli-0.1.30/src/crow_cli/agent/memory.py +164 -0
  7. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/session.py +23 -23
  8. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/cli/init_cmd.py +29 -39
  9. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/cli/main.py +7 -7
  10. crow_cli-0.1.30/uv.lock +2930 -0
  11. crow_cli-0.1.28/src/crow_cli/agent/memory.py +0 -267
  12. crow_cli-0.1.28/uv.lock +0 -1548
  13. {crow_cli-0.1.28 → crow_cli-0.1.30}/.gitignore +0 -0
  14. {crow_cli-0.1.28 → crow_cli-0.1.30}/.python-version +0 -0
  15. {crow_cli-0.1.28 → crow_cli-0.1.30}/README.md +0 -0
  16. {crow_cli-0.1.28 → crow_cli-0.1.30}/agent.json +0 -0
  17. {crow_cli-0.1.28 → crow_cli-0.1.30}/crow-cli.spec +0 -0
  18. {crow_cli-0.1.28 → crow_cli-0.1.30}/examples/mc_escher_loop.py +0 -0
  19. {crow_cli-0.1.28 → crow_cli-0.1.30}/examples/quick_test.py +0 -0
  20. {crow_cli-0.1.28 → crow_cli-0.1.30}/run_tests.sh +0 -0
  21. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/__init__.py +0 -0
  22. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/__init__.py +0 -0
  23. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/compact.py +0 -0
  24. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/context.py +0 -0
  25. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/default/__init__.py +0 -0
  26. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/default/searxng_settings.py +0 -0
  27. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/hooks.py +0 -0
  28. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/llm.py +0 -0
  29. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/logger.py +0 -0
  30. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/mcp_client.py +0 -0
  31. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/prompt.py +0 -0
  32. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/react.py +0 -0
  33. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/slash.py +0 -0
  34. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent/tools.py +0 -0
  35. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/agent_runner.py +0 -0
  36. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/cli/__init__.py +0 -0
  37. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/cli/install.py +0 -0
  38. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/client/__init__.py +0 -0
  39. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/client/main.py +0 -0
  40. {crow_cli-0.1.28 → crow_cli-0.1.30}/src/crow_cli/client/terminal.py +0 -0
  41. {crow_cli-0.1.28 → crow_cli-0.1.30}/test_send_request_error_handling.py +0 -0
  42. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/__init__.py +0 -0
  43. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/analyze_payload_cache_invalidation.py +0 -0
  44. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/analyze_payload_deep.py +0 -0
  45. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/conftest.py +0 -0
  46. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/e2e/test_session_update_transmission.py +0 -0
  47. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/integration/test_agent_init.py +0 -0
  48. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/integration/test_auth_validation.py +0 -0
  49. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_client_terminal.py +0 -0
  50. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_compact.py +0 -0
  51. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_compact_last_messages_list_content.json +0 -0
  52. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_compact_last_messages_list_content.py +0 -0
  53. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_json_repair.py +0 -0
  54. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_request_logging.py +0 -0
  55. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_session.py +0 -0
  56. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_stream_processing.py +0 -0
  57. {crow_cli-0.1.28 → crow_cli-0.1.30}/tests/unit/test_uv_project_enforcement.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crow-cli
3
- Version: 0.1.28
3
+ Version: 0.1.30
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/crow-cli/crow-cli
6
6
  Project-URL: Repository, https://github.com/crow-cli/crow-cli
@@ -9,6 +9,7 @@ Project-URL: Documentation, https://github.com/crow-cli/crow-cli#readme
9
9
  Requires-Python: >=3.14
10
10
  Requires-Dist: agent-client-protocol>=0.9.0
11
11
  Requires-Dist: coolname>=2.2.0
12
+ Requires-Dist: crow-memory>=0.1.30
12
13
  Requires-Dist: directory-tree>=1.0.0
13
14
  Requires-Dist: fastmcp>=2.14.5
14
15
  Requires-Dist: httpx>=0.28.1
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "crow-cli"
3
- version = "0.1.28"
3
+ version = "0.1.30"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
7
7
  dependencies = [
8
8
  "agent-client-protocol>=0.9.0",
9
9
  "coolname>=2.2.0",
10
+ "crow-memory>=0.1.30",
10
11
  "directory-tree>=1.0.0",
11
12
  "fastmcp>=2.14.5",
12
13
  "httpx>=0.28.1",
@@ -19,6 +20,16 @@ dependencies = [
19
20
  "typer>=0.24.1",
20
21
  ]
21
22
 
23
+ [tool.uv.sources]
24
+ crow-memory = { path = "../crow-memory", editable = true }
25
+ torch = [{ index = "pytorch-cpu" }]
26
+ torchvision = [{ index = "pytorch-cpu" }]
27
+
28
+ [[tool.uv.index]]
29
+ name = "pytorch-cpu"
30
+ url = "https://download.pytorch.org/whl/cpu"
31
+ explicit = true
32
+
22
33
 
23
34
  [project.urls]
24
35
  Homepage = "https://github.com/crow-cli/crow-cli"
@@ -91,7 +91,7 @@ class LLMConfig:
91
91
  class Config:
92
92
  config_dir: Path
93
93
  llm: LLMConfig = field(default_factory=LLMConfig)
94
- memory_url: str = ""
94
+ memory_path: str = ""
95
95
  mcp_servers: dict[str, Any] = field(default_factory=dict)
96
96
  max_retries_per_step: int = 3
97
97
  MAX_COMPACT_TOKENS: int = 190000
@@ -134,7 +134,7 @@ class Config:
134
134
  _logger.info("No config.yaml found, returning bare Config")
135
135
  return cls(
136
136
  config_dir=target_dir,
137
- memory_url="http://localhost:8901",
137
+ memory_path=str(Path.home() / ".crow" / "memory.lance"),
138
138
  )
139
139
 
140
140
  with open(config_file) as f:
@@ -162,7 +162,7 @@ class Config:
162
162
  )
163
163
 
164
164
  # Parse overrides
165
- memory_url = parsed.get("memory_url") or "http://localhost:8901"
165
+ memory_path = os.path.expanduser(parsed.get("memory_path") or "~/.crow/memory.lance")
166
166
  overrides = {}
167
167
  for key, typ in (
168
168
  ("max_retries_per_step", int),
@@ -184,7 +184,7 @@ class Config:
184
184
  return cls(
185
185
  config_dir=target_dir,
186
186
  llm=llm,
187
- memory_url=memory_url,
187
+ memory_path=memory_path,
188
188
  mcp_servers=mcp_servers,
189
189
  system_prompt_path=system_prompt_path,
190
190
  **overrides,
@@ -160,32 +160,6 @@ COMPOSE_YAML = """services:
160
160
  volumes:
161
161
  - ./searxng/:/etc/searxng
162
162
 
163
- crow-memory:
164
- image: ghcr.io/crow-cli/crow-memory:latest
165
- # For local development, replace image with build:
166
- # build:
167
- # context: /path/to/crow-cli/crow-memory
168
- # dockerfile: Dockerfile
169
- restart: always
170
- ports:
171
- - "8901:8901"
172
- environment:
173
- - CROW_MEMORY_PATH=/data/memory.lance
174
- - CROW_MEMORY_HOST=0.0.0.0
175
- - CROW_MEMORY_PORT=8901
176
- - CUDA_VISIBLE_DEVICES=
177
- volumes:
178
- - ./memory.lance:/data/memory.lance
179
- # GPU acceleration — uncomment when you want hardware-accelerated embeddings.
180
- # CPU mode is ~100ms text / ~2-3s image, which is fine for background agents.
181
- # deploy:
182
- # resources:
183
- # reservations:
184
- # devices:
185
- # - driver: nvidia
186
- # count: 1
187
- # capabilities: [gpu]
188
-
189
163
  litellm:
190
164
  image: ghcr.io/berriai/litellm:main-v1.82.6-nightly
191
165
  restart: always
@@ -214,8 +188,8 @@ mcpServers:
214
188
  args:
215
189
  - crow-mcp
216
190
 
217
- # DEFAULT crow-memory service URL
218
- memory_url: http://localhost:8901
191
+ # Memory store path (LanceDB, in-process)
192
+ memory_path: ~/.crow/memory.lance
219
193
 
220
194
  # EXAMPLE PROVIDER
221
195
  # providers:
@@ -36,13 +36,6 @@ Now you're guaranteed to have an API key and a base url to access an LLM, run th
36
36
  uvx crow-cli init
37
37
  ```
38
38
 
39
- # Start the memory service
40
- crow-cli persists every session to a local [crow-memory](https://github.com/crow-cli/crow-cli/tree/main/crow-memory) service (LanceDB + ColBERT embeddings) running in Docker. The init wizard writes a `compose.yaml` for you:
41
- ```bash
42
- cd ~/.crow && docker compose up -d
43
- ```
44
- This starts crow-memory (port 8901) and optionally SearXNG for web search. Without it, sessions won't persist and the memory tools (`list_sessions`, `query_memory`, `query_session`) won't work.
45
-
46
39
  Then restart your agent.
47
40
  """
48
41
 
@@ -172,7 +165,7 @@ class AcpAgent(Agent):
172
165
  hooks if hooks is not None else [uv_project_hook]
173
166
  )
174
167
  self._logger = setup_logger(self._config.config_dir / "logs" / "crow-cli.log")
175
- self._memory_url = self._config.memory_url
168
+ self._memory_path = self._config.memory_path
176
169
  self._exit_stack = AsyncExitStack()
177
170
  self._agent_id: str | None = None
178
171
  self._session_id: str | None = None # stripped version for ACP upstream
@@ -422,14 +415,14 @@ class AcpAgent(Agent):
422
415
 
423
416
  try:
424
417
  # Find the highest-indexed agent for this session
425
- max_idx = AgentSession.get_max_agent_idx(session_id, memory_url=self._memory_url)
418
+ max_idx = AgentSession.get_max_agent_idx(session_id, memory_path=self._memory_path)
426
419
  agent_id = f"{session_id}-{max_idx}"
427
420
  self._logger.info(
428
421
  "LOAD_SESSION: Step 1: Loading agent %s from DB", agent_id
429
422
  )
430
423
  session = AgentSession.load(
431
424
  agent_id,
432
- memory_url=self._memory_url,
425
+ memory_path=self._memory_path,
433
426
  )
434
427
  self._logger.info("LOAD_SESSION: Step 1 complete: Agent loaded from DB")
435
428
 
@@ -513,7 +506,7 @@ class AcpAgent(Agent):
513
506
  if self._session_id == session_id and self._agent_id:
514
507
  agent_id = self._agent_id
515
508
  else:
516
- max_idx = AgentSession.get_max_agent_idx(session_id, memory_url=self._memory_url)
509
+ max_idx = AgentSession.get_max_agent_idx(session_id, memory_path=self._memory_path)
517
510
  agent_id = f"{session_id}-{max_idx}"
518
511
 
519
512
  # Initialize if not set
@@ -565,7 +558,7 @@ class AcpAgent(Agent):
565
558
  if self._session_id == session_id and self._agent_id:
566
559
  agent_id = self._agent_id
567
560
  else:
568
- max_idx = AgentSession.get_max_agent_idx(session_id, memory_url=self._memory_url)
561
+ max_idx = AgentSession.get_max_agent_idx(session_id, memory_path=self._memory_path)
569
562
  agent_id = f"{session_id}-{max_idx}"
570
563
 
571
564
  async def _execute_turn() -> PromptResponse:
@@ -786,7 +779,7 @@ class AcpAgent(Agent):
786
779
  self._logger.info("Listing sessions for working directory: %s", cwd)
787
780
  if cwd is None:
788
781
  return ListSessionsResponse(sessions=[], next_cursor=None)
789
- sessions_info = get_session_by_cwd(cwd, self._memory_url)
782
+ sessions_info = get_session_by_cwd(cwd, self._memory_path)
790
783
  sessions = [SessionInfo(**session) for session in sessions_info]
791
784
  return ListSessionsResponse(sessions=sessions, next_cursor=None)
792
785
 
@@ -0,0 +1,164 @@
1
+ """In-process memory layer for crow-cli.
2
+
3
+ Wraps crow-memory's MemoryStore (LanceDB + ColBERT/ColQwen2) directly — no
4
+ HTTP, no service, no retry logic. The embedders are loaded once per process
5
+ via a singleton; the model weights are mmap'd and shared with crow-mcp by the
6
+ kernel's page cache.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ DEFAULT_MEMORY_PATH = str(Path.home() / ".crow" / "memory.lance")
15
+
16
+
17
+ class MemoryServiceError(Exception):
18
+ """Raised when a memory operation fails (e.g. agent not found)."""
19
+
20
+ def __init__(self, status: int, detail: str):
21
+ self.status = status
22
+ self.detail = detail
23
+ super().__init__(f"memory error {status}: {detail}")
24
+
25
+
26
+ class MemoryClient:
27
+ """In-process adapter over MemoryStore.
28
+
29
+ Same interface as the old HTTP client so session.py / main.py don't change
30
+ shape — but backed by a direct LanceDB connection. The `base_url` param is
31
+ now a filesystem path (kept as positional for compat during transition).
32
+ """
33
+
34
+ def __init__(self, path: str = DEFAULT_MEMORY_PATH, **_kwargs):
35
+ from crow_memory import get_store
36
+ self._store = get_store(path)
37
+
38
+ def close(self):
39
+ pass # nothing to close; store is process-global
40
+
41
+ def __enter__(self):
42
+ return self
43
+
44
+ def __exit__(self, *exc):
45
+ pass
46
+
47
+ # ---- agents ----
48
+
49
+ def create_agent(
50
+ self,
51
+ *,
52
+ agent_id: str,
53
+ session_id: str,
54
+ agent_idx: int = 1,
55
+ cwd: str = "/tmp",
56
+ prompt_id: str | None = None,
57
+ prompt_args: dict | None = None,
58
+ system_prompt: str = "",
59
+ tool_definitions: list[dict] | None = None,
60
+ request_params: dict | None = None,
61
+ model_identifier: str = "",
62
+ initial_messages: list[dict] | None = None,
63
+ ) -> dict:
64
+ agent = self._store.add_agent({
65
+ "agent_id": agent_id,
66
+ "session_id": session_id,
67
+ "agent_idx": agent_idx,
68
+ "cwd": cwd,
69
+ "prompt_id": prompt_id,
70
+ "prompt_args": prompt_args or {},
71
+ "system_prompt": system_prompt,
72
+ "tool_definitions": tool_definitions or [],
73
+ "request_params": request_params or {},
74
+ "model_identifier": model_identifier,
75
+ })
76
+ for m in (initial_messages or []):
77
+ if m.get("role") != "system":
78
+ self._store.add_message(agent_id, m)
79
+ return agent
80
+
81
+ def load(self, agent_id: str, hydrate: bool = False) -> tuple[dict, list[dict]]:
82
+ agent = self._store.get_agent(agent_id)
83
+ if agent is None:
84
+ raise MemoryServiceError(404, f"agent '{agent_id}' not found")
85
+ messages = self._store.load_messages(agent_id, hydrate_images=hydrate)
86
+ return agent, messages
87
+
88
+ def list_agents(self, session_id: str | None = None) -> list[dict]:
89
+ return self._store.list_agents(session_id=session_id)
90
+
91
+ # ---- messages ----
92
+
93
+ def add_message(self, agent_id: str, message: dict, usage: dict | None = None) -> dict:
94
+ return self._store.add_message(agent_id, message, usage)
95
+
96
+ def save_messages(self, agent_id: str, messages: list[dict]) -> dict:
97
+ ids = [self._store.add_message(agent_id, m)["id"] for m in messages]
98
+ return {"agent_id": agent_id, "ids": ids, "count": len(ids)}
99
+
100
+ def query_messages(
101
+ self,
102
+ *,
103
+ session_id: str | None = None,
104
+ agent_id: str | None = None,
105
+ agent_idx: int | None = None,
106
+ roles: list[str] | None = None,
107
+ after: str | None = None,
108
+ before: str | None = None,
109
+ order: str = "asc",
110
+ limit: int = 1_000_000,
111
+ offset: int = 0,
112
+ ) -> list[dict]:
113
+ return self._store.query_messages(
114
+ session_id=session_id, agent_id=agent_id, agent_idx=agent_idx,
115
+ roles=roles, after=after, before=before, order=order,
116
+ limit=limit, offset=offset,
117
+ )
118
+
119
+ # ---- sessions ----
120
+
121
+ def get_max_agent_idx(self, session_id: str) -> int:
122
+ return self._store.get_max_agent_idx(session_id)
123
+
124
+ def list_sessions(self, limit: int = 50, offset: int = 0) -> list[dict]:
125
+ return self._store.list_sessions(limit=limit, offset=offset)
126
+
127
+ # ---- prompts ----
128
+
129
+ def lookup_or_create_prompt(self, template: str, name: str = "crow-default") -> str:
130
+ result = self._store.lookup_or_create_prompt(template, name)
131
+ return result["id"]
132
+
133
+ def get_prompt(self, prompt_id: str) -> dict:
134
+ p = self._store.get_prompt(prompt_id)
135
+ if p is None:
136
+ raise MemoryServiceError(404, f"prompt '{prompt_id}' not found")
137
+ return p
138
+
139
+ # ---- images ----
140
+
141
+ def get_image(self, image_id: str) -> tuple[bytes, str]:
142
+ img = self._store.get_image(image_id)
143
+ if img is None:
144
+ raise MemoryServiceError(404, f"image '{image_id}' not found")
145
+ return img["data"], img["mime"]
146
+
147
+ # ---- search ----
148
+
149
+ def search(
150
+ self,
151
+ query: str | None = None,
152
+ modality: str = "text",
153
+ filters: dict | None = None,
154
+ limit: int = 10,
155
+ query_image: bytes | None = None,
156
+ ) -> dict:
157
+ out = {"messages": [], "images": []}
158
+ if modality in ("text", "both") and query:
159
+ out["messages"] = self._store.search_messages(query, filters, limit)
160
+ if modality in ("image", "both"):
161
+ out["images"] = self._store.search_images(
162
+ query=query, query_image=query_image, limit=limit
163
+ )
164
+ return out
@@ -16,19 +16,19 @@ from typing import Any
16
16
  import yaml
17
17
  from coolname import generate_slug
18
18
 
19
- from crow_cli.agent.memory import DEFAULT_MEMORY_URL, MemoryClient, MemoryServiceError
19
+ from crow_cli.agent.memory import DEFAULT_MEMORY_PATH, MemoryClient, MemoryServiceError
20
20
  from crow_cli.agent.prompt import render_template
21
21
  from crow_cli.agent.context import get_directory_tree
22
22
 
23
23
  from crow_cli.agent.configure import Config
24
24
 
25
- def get_session_by_cwd(cwd, memory_url=DEFAULT_MEMORY_URL):
25
+ def get_session_by_cwd(cwd, memory_path=DEFAULT_MEMORY_PATH):
26
26
  """
27
- Lookup agents by working directory via the crow-memory service.
27
+ Lookup agents by working directory via the in-process memory store.
28
28
 
29
29
  Returns list of session info dicts with session_id, title, updated_at.
30
30
  """
31
- client = MemoryClient(memory_url)
31
+ client = MemoryClient(memory_path)
32
32
  try:
33
33
  result = []
34
34
  for agent in client.list_agents():
@@ -80,12 +80,12 @@ def get_coolname() -> str:
80
80
  def lookup_or_create_prompt(
81
81
  template: str,
82
82
  name: str,
83
- memory_url: str = DEFAULT_MEMORY_URL,
83
+ memory_path: str = DEFAULT_MEMORY_PATH,
84
84
  ) -> str:
85
85
  """
86
86
  Lookup existing prompt by template content, or create new one if not found.
87
87
  """
88
- client = MemoryClient(memory_url)
88
+ client = MemoryClient(memory_path)
89
89
  try:
90
90
  return client.lookup_or_create_prompt(template, name)
91
91
  finally:
@@ -94,7 +94,7 @@ def lookup_or_create_prompt(
94
94
 
95
95
  class AgentSession:
96
96
  """
97
- Manages conversation state and persistence via the crow-memory service.
97
+ Manages conversation state and persistence via the in-process memory store (LanceDB).
98
98
 
99
99
  agent_id = "{session_id}-{agent_idx}" is the key.
100
100
  session_id is derived for ACP upstream routing only.
@@ -105,13 +105,13 @@ class AgentSession:
105
105
  agent_id: str,
106
106
  session_id: str,
107
107
  agent_idx: int = 0,
108
- memory_url: str = DEFAULT_MEMORY_URL,
108
+ memory_path: str = DEFAULT_MEMORY_PATH,
109
109
  cwd: str = "/tmp",
110
110
  ):
111
111
  self.agent_id = agent_id
112
112
  self.session_id = session_id
113
113
  self.agent_idx = agent_idx
114
- self.memory_url = memory_url
114
+ self.memory_path = memory_path
115
115
  self.cwd = cwd
116
116
  self.messages: list[dict] = []
117
117
  self._client = None
@@ -119,9 +119,9 @@ class AgentSession:
119
119
 
120
120
  @property
121
121
  def client(self) -> MemoryClient:
122
- """Lazy-load the HTTP client to the crow-memory service."""
122
+ """Lazy-load the in-process memory client."""
123
123
  if self._client is None:
124
- self._client = MemoryClient(self.memory_url)
124
+ self._client = MemoryClient(self.memory_path)
125
125
  return self._client
126
126
 
127
127
  def add_message(self, msg: dict, usage: dict | None = None):
@@ -190,14 +190,14 @@ class AgentSession:
190
190
  tool_definitions: list[dict],
191
191
  request_params: dict[str, Any],
192
192
  model_identifier: str,
193
- memory_url: str = DEFAULT_MEMORY_URL,
193
+ memory_path: str = DEFAULT_MEMORY_PATH,
194
194
  cwd: str = "/tmp",
195
195
  agent_idx: int = 1,
196
196
  session_id: str | None = None,
197
197
  initial_messages: list[dict[str, Any]] | None = None,
198
198
  ) -> "AgentSession":
199
199
  """Factory method to create a new agent session."""
200
- client = MemoryClient(memory_url)
200
+ client = MemoryClient(memory_path)
201
201
 
202
202
  # Load and render prompt
203
203
  try:
@@ -228,7 +228,7 @@ class AgentSession:
228
228
  client.close()
229
229
 
230
230
  # Build session instance
231
- session = cls(agent_id, session_id, agent_idx, memory_url, cwd=cwd)
231
+ session = cls(agent_id, session_id, agent_idx, memory_path, cwd=cwd)
232
232
  session.model_identifier = model_identifier
233
233
  session.tools = tool_definitions
234
234
  session.request_params = request_params
@@ -251,10 +251,10 @@ class AgentSession:
251
251
  def get_max_agent_idx(
252
252
  cls,
253
253
  session_id: str,
254
- memory_url: str = DEFAULT_MEMORY_URL,
254
+ memory_path: str = DEFAULT_MEMORY_PATH,
255
255
  ) -> int:
256
256
  """Return the highest agent_idx for a given session_id."""
257
- client = MemoryClient(memory_url)
257
+ client = MemoryClient(memory_path)
258
258
  try:
259
259
  return client.get_max_agent_idx(session_id)
260
260
  finally:
@@ -265,10 +265,10 @@ class AgentSession:
265
265
  cls,
266
266
  limit: int = 50,
267
267
  offset: int = 0,
268
- memory_url: str = DEFAULT_MEMORY_URL,
268
+ memory_path: str = DEFAULT_MEMORY_PATH,
269
269
  ) -> list[dict]:
270
270
  """List sessions ordered by most-recent message activity (desc)."""
271
- client = MemoryClient(memory_url)
271
+ client = MemoryClient(memory_path)
272
272
  try:
273
273
  return client.list_sessions(limit=limit, offset=offset)
274
274
  finally:
@@ -278,10 +278,10 @@ class AgentSession:
278
278
  def load(
279
279
  cls,
280
280
  agent_id: str,
281
- memory_url: str = DEFAULT_MEMORY_URL,
281
+ memory_path: str = DEFAULT_MEMORY_PATH,
282
282
  ) -> "AgentSession":
283
283
  """Factory method to load existing agent session from the service."""
284
- client = MemoryClient(memory_url)
284
+ client = MemoryClient(memory_path)
285
285
  try:
286
286
  agent, messages = client.load(agent_id, hydrate=True)
287
287
  except MemoryServiceError as e:
@@ -295,7 +295,7 @@ class AgentSession:
295
295
  agent_id=agent["agent_id"],
296
296
  session_id=agent["session_id"],
297
297
  agent_idx=agent["agent_idx"],
298
- memory_url=memory_url,
298
+ memory_path=memory_path,
299
299
  cwd=agent["cwd"],
300
300
  )
301
301
  session.model_identifier = agent["model_identifier"]
@@ -446,7 +446,7 @@ def make_agent_session(
446
446
  else:
447
447
  template = config.system_prompt
448
448
  prompt_id = lookup_or_create_prompt(
449
- template, name="crow-default", memory_url=config.memory_url
449
+ template, name="crow-default", memory_path=config.memory_path
450
450
  )
451
451
  skills = get_skills(config.config_dir / "skills")
452
452
 
@@ -471,7 +471,7 @@ def make_agent_session(
471
471
  tool_definitions=tools,
472
472
  request_params={"temperature": 0.2},
473
473
  model_identifier=model_id,
474
- memory_url=config.memory_url,
474
+ memory_path=config.memory_path,
475
475
  cwd=cwd,
476
476
  agent_idx=agent_idx,
477
477
  session_id=session_id,
@@ -285,8 +285,8 @@ def run_init(config_dir: Path, yes: bool = False):
285
285
  # =========================================================================
286
286
  console.print("\n[bold cyan]═══ Step 3: Review ═══[/bold cyan]\n")
287
287
 
288
- memory_url = "http://localhost:8901"
289
- console.print(f"[dim]Using crow-memory service at {memory_url}[/dim]")
288
+ memory_path = str(Path.home() / ".crow" / "memory.lance")
289
+ console.print(f"[dim]Memory store: {memory_path}[/dim]")
290
290
 
291
291
  if providers:
292
292
  p_table = Table(title="Providers", show_header=True)
@@ -309,11 +309,11 @@ def run_init(config_dir: Path, yes: bool = False):
309
309
  s_table.add_column("Service", style="cyan")
310
310
  s_table.add_column("Status", style="green")
311
311
  s_table.add_row("SearXNG", "✓ Docker" if setup_searxng else "✗ Skip")
312
- s_table.add_row("Memory", "crow-memory service")
312
+ s_table.add_row("Memory", "in-process LanceDB")
313
313
  console.print(s_table)
314
314
 
315
315
  console.print(f"\n[dim]Config directory: {config_dir}[/dim]")
316
- console.print(f"[dim]Memory service: {memory_url}[/dim]")
316
+ console.print(f"[dim]Memory store: {memory_path}[/dim]")
317
317
 
318
318
  if not yes and not Confirm.ask("\nLooks good?", default=True):
319
319
  console.print("[red]Aborted. No files were written.[/red]")
@@ -345,7 +345,7 @@ def run_init(config_dir: Path, yes: bool = False):
345
345
  "args": ["crow-mcp"],
346
346
  }
347
347
  },
348
- "memory_url": memory_url,
348
+ "memory_path": memory_path,
349
349
  "providers": providers,
350
350
  "models": models,
351
351
  "MAX_COMPACT_TOKENS": 190000,
@@ -384,19 +384,15 @@ def run_init(config_dir: Path, yes: bool = False):
384
384
  available_services = compose_template.get("services", {})
385
385
  active_services: dict[str, Any] = {}
386
386
 
387
- # crow-memory is always included — it's the persistence layer
388
- if "crow-memory" in available_services:
389
- active_services["crow-memory"] = available_services["crow-memory"]
390
-
391
387
  if setup_searxng and "searxng" in available_services:
392
388
  active_services["searxng"] = available_services["searxng"]
393
389
 
390
+ compose_file = config_dir / "compose.yaml"
394
391
  if active_services:
395
392
  compose_data: dict[str, Any] = {
396
393
  "services": active_services,
397
394
  "volumes": compose_template.get("volumes", {}),
398
395
  }
399
- compose_file = config_dir / "compose.yaml"
400
396
  with open(compose_file, "w") as f:
401
397
  yaml.dump(compose_data, f, default_flow_style=False, sort_keys=False)
402
398
  console.print(f"[green]✓[/green] Written {compose_file}")
@@ -407,20 +403,17 @@ def run_init(config_dir: Path, yes: bool = False):
407
403
  # =========================================================================
408
404
  # STEP 5: Start services (just instructions)
409
405
  # =========================================================================
410
- console.print("\n[bold cyan]═══ Step 5: Start Services ═══[/bold cyan]\n")
411
-
412
- services_list = ["crow-memory"]
413
- if setup_searxng:
414
- services_list.append("SearXNG")
415
-
416
- console.print(
417
- Panel(
418
- f"[bold white]cd {config_dir} && docker compose up -d[/bold white]\n\n"
419
- f"[dim]Starts: {', '.join(services_list)}[/dim]",
420
- title="[yellow]Start " + " + ".join(services_list) + "[/yellow]",
421
- border_style="yellow",
406
+ if active_services:
407
+ console.print("\n[bold cyan]═══ Step 5: Start Services ═══[/bold cyan]\n")
408
+ services_list = list(active_services.keys())
409
+ console.print(
410
+ Panel(
411
+ f"[bold white]cd {config_dir} && docker compose up -d[/bold white]\n\n"
412
+ f"[dim]Starts: {', '.join(services_list)}[/dim]",
413
+ title="[yellow]Start " + " + ".join(services_list) + "[/yellow]",
414
+ border_style="yellow",
415
+ )
422
416
  )
423
- )
424
417
 
425
418
  # =========================================================================
426
419
  # Done
@@ -429,22 +422,19 @@ def run_init(config_dir: Path, yes: bool = False):
429
422
  system_prompt_dir = config_dir / "prompts"
430
423
 
431
424
  console.print()
432
- console.print(
433
- Panel.fit(
434
- "[bold green]✓ Configuration complete![/bold green]\n\n"
435
- f"Config: [cyan]{config_file}[/cyan]\n"
436
- f"Memory: [cyan]{config_dir / 'memory.lance'}[/cyan]\n"
437
- f"Logs: [cyan]{config_logs}[/cyan]\n"
438
- f"Prompt: [cyan]{system_prompt_dir}/system_prompt.jinja2[/cyan]\n"
439
- f"Secrets: [cyan]{env_file}[/cyan]\n"
440
- f"Compose: [cyan]{compose_file}[/cyan]\n\n"
441
- "[dim]Start services with:[/dim]\n"
442
- f" [bold red]cd {config_dir} && docker compose up -d[/bold red]\n"
443
- f"[dim]Then to test:\n"
444
- f' [bold white]crow-cli run "hey"[/bold white]',
445
- border_style="green",
446
- )
447
- )
425
+ done_lines = [
426
+ "[bold green]✓ Configuration complete![/bold green]\n",
427
+ f"Config: [cyan]{config_file}[/cyan]",
428
+ f"Memory: [cyan]{config_dir / 'memory.lance'}[/cyan] [dim](in-process, no service)[/dim]",
429
+ f"Logs: [cyan]{config_logs}[/cyan]",
430
+ f"Prompt: [cyan]{system_prompt_dir}/system_prompt.jinja2[/cyan]",
431
+ f"Secrets: [cyan]{env_file}[/cyan]",
432
+ ]
433
+ if active_services:
434
+ done_lines.append(f"Compose: [cyan]{compose_file}[/cyan]")
435
+ done_lines.append(f"\n[dim]Start services:[/dim] [bold red]cd {config_dir} && docker compose up -d[/bold red]")
436
+ done_lines.append(f'\n[dim]Test:[/dim] [bold white]crow-cli run "hey"[/bold white]')
437
+ console.print(Panel.fit("\n".join(done_lines), border_style="green"))
448
438
 
449
439
 
450
440
  # For typer integration