deepagents-cli 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

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.

Potentially problematic release.


This version of deepagents-cli might be problematic. Click here for more details.

deepagents/cli.py CHANGED
@@ -11,16 +11,16 @@ from pathlib import Path
11
11
  from tavily import TavilyClient
12
12
  from deepagents import create_deep_agent
13
13
  from langgraph.checkpoint.memory import InMemorySaver
14
- from langgraph.types import Command, Interrupt
14
+ from langchain.agents.middleware import ShellToolMiddleware, HostExecutionPolicy
15
15
 
16
16
  from rich.console import Console
17
17
  from rich.markdown import Markdown
18
18
  from rich.panel import Panel
19
- from rich.syntax import Syntax
20
- from rich.text import Text
21
- from rich.live import Live
22
19
  from rich.spinner import Spinner
23
- from rich.prompt import Prompt
20
+ from deepagents.memory.backends.filesystem import FilesystemBackend
21
+ from deepagents.memory.backends import CompositeBackend
22
+ from deepagents.middleware.agent_memory import AgentMemoryMiddleware
23
+ from pathlib import Path
24
24
  import shutil
25
25
  from rich import box
26
26
 
@@ -28,6 +28,31 @@ import dotenv
28
28
 
29
29
  dotenv.load_dotenv()
30
30
 
31
+ COLORS = {
32
+ "primary": "#10b981",
33
+ "dim": "#6b7280",
34
+ "user": "#ffffff",
35
+ "agent": "#10b981",
36
+ "thinking": "#34d399",
37
+ "tool": "#fbbf24",
38
+ }
39
+
40
+ DEEP_AGENTS_ASCII = """
41
+ ██████╗ ███████╗ ███████╗ ██████╗
42
+ ██╔══██╗ ██╔════╝ ██╔════╝ ██╔══██╗
43
+ ██║ ██║ █████╗ █████╗ ██████╔╝
44
+ ██║ ██║ ██╔══╝ ██╔══╝ ██╔═══╝
45
+ ██████╔╝ ███████╗ ███████╗ ██║
46
+ ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝
47
+
48
+ █████╗ ██████╗ ███████╗ ███╗ ██╗ ████████╗ ███████╗
49
+ ██╔══██╗ ██╔════╝ ██╔════╝ ████╗ ██║ ╚══██╔══╝ ██╔════╝
50
+ ███████║ ██║ ███╗ █████╗ ██╔██╗ ██║ ██║ ███████╗
51
+ ██╔══██║ ██║ ██║ ██╔══╝ ██║╚██╗██║ ██║ ╚════██║
52
+ ██║ ██║ ╚██████╔╝ ███████╗ ██║ ╚████║ ██║ ███████║
53
+ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═══╝ ╚═╝ ╚══════╝
54
+ """
55
+
31
56
  console = Console()
32
57
 
33
58
  tavily_client = TavilyClient(api_key=os.environ.get("TAVILY_API_KEY")) if os.environ.get("TAVILY_API_KEY") else None
@@ -137,62 +162,19 @@ def web_search(
137
162
  }
138
163
 
139
164
 
140
- def get_default_coding_instructions(include_memory: bool = True) -> str:
165
+ def get_default_coding_instructions() -> str:
141
166
  """Get the default coding agent instructions.
142
167
 
143
- Args:
144
- include_memory: If False, removes the Long-term Memory section from the prompt.
168
+ These are the immutable base instructions that cannot be modified by the agent.
169
+ Long-term memory (agent.md) is handled separately by the middleware.
145
170
  """
146
171
  default_prompt_path = Path(__file__).parent / "default_agent_prompt.md"
147
- prompt = default_prompt_path.read_text()
148
-
149
- if not include_memory:
150
- # Remove the Long-term Memory section
151
- lines = prompt.split('\n')
152
- result_lines = []
153
- skip = False
154
-
155
- for line in lines:
156
- if line.strip() == "## Long-term Memory":
157
- skip = True
158
- continue
159
- if skip and line.startswith('##'):
160
- skip = False
161
- if not skip:
162
- result_lines.append(line)
163
-
164
- prompt = '\n'.join(result_lines).rstrip() + '\n'
165
-
166
- return prompt
167
-
168
-
169
- def get_coding_instructions(agent_name: str | None, long_term_memory: bool = False) -> str:
170
- """Get the coding agent instructions from file or create default.
171
-
172
- If agent_name is None or long_term_memory is False, returns default instructions without memory features.
173
- """
174
- if agent_name is None or not long_term_memory:
175
- return get_default_coding_instructions(include_memory=False)
176
-
177
- agent_dir = Path.home() / ".deepagents" / agent_name
178
- agent_prompt_file = agent_dir / "agent.md"
179
-
180
- if agent_prompt_file.exists():
181
- agent_memory_content = agent_prompt_file.read_text()
182
- else:
183
- agent_dir.mkdir(parents=True, exist_ok=True)
184
- default_prompt = get_default_coding_instructions(include_memory=True)
185
- agent_prompt_file.write_text(default_prompt)
186
- agent_memory_content = default_prompt
187
-
188
- return f"<agent_memory>\n{agent_memory_content}\n</agent_memory>"
172
+ return default_prompt_path.read_text()
189
173
 
190
174
 
191
175
  config = {"recursion_limit": 1000}
192
176
 
193
- # Constants for display truncation
194
- MAX_ARG_LENGTH = 200
195
- MAX_RESULT_LENGTH = 200
177
+ MAX_ARG_LENGTH = 150
196
178
 
197
179
 
198
180
  def truncate_value(value: str, max_length: int = MAX_ARG_LENGTH) -> str:
@@ -202,22 +184,22 @@ def truncate_value(value: str, max_length: int = MAX_ARG_LENGTH) -> str:
202
184
  return value
203
185
 
204
186
 
205
- def format_tool_args(tool_input: dict) -> str:
206
- """Format tool arguments for display, truncating long values."""
207
- if not tool_input:
208
- return ""
187
+ def execute_task(user_input: str, agent, assistant_id: str | None):
188
+ """Execute any task by passing it directly to the AI agent."""
189
+ console.print()
209
190
 
210
- args_parts = []
211
- for key, value in tool_input.items():
212
- value_str = str(value)
213
- value_str = truncate_value(value_str)
214
- args_parts.append(f"{key}={value_str}")
191
+ config = {
192
+ "configurable": {"thread_id": "main"},
193
+ "metadata": {"assistant_id": assistant_id} if assistant_id else {}
194
+ }
215
195
 
216
- return ", ".join(args_parts)
217
-
218
-
219
- def display_tool_call(tool_name: str, tool_input: dict):
220
- """Display a tool call with arguments, truncating long values."""
196
+ has_responded = False
197
+ current_text = ""
198
+ printed_tool_calls_after_text = False
199
+
200
+ status = console.status(f"[bold {COLORS['thinking']}]Agent is thinking...", spinner="dots")
201
+ status.start()
202
+ spinner_active = True
221
203
 
222
204
  tool_icons = {
223
205
  "read_file": "📖",
@@ -233,52 +215,6 @@ def display_tool_call(tool_name: str, tool_input: dict):
233
215
  "write_todos": "📋",
234
216
  }
235
217
 
236
- icon = tool_icons.get(tool_name, "🔧")
237
- args_str = format_tool_args(tool_input)
238
-
239
- # Display: icon tool_name(args)
240
- if args_str:
241
- console.print(f"[dim]{icon} {tool_name}({args_str})[/dim]")
242
- else:
243
- console.print(f"[dim]{icon} {tool_name}()[/dim]")
244
-
245
-
246
- def display_text_content(text: str):
247
- """Display text content with markdown rendering."""
248
- if text.strip():
249
- if "```" in text or "#" in text or "**" in text:
250
- md = Markdown(text)
251
- console.print(md)
252
- else:
253
- console.print(text, style="white")
254
-
255
-
256
- def extract_and_display_content(message_content):
257
- """Extract content from agent messages and display with rich formatting."""
258
- if isinstance(message_content, str):
259
- display_text_content(message_content)
260
- return
261
-
262
- if isinstance(message_content, list):
263
- for block in message_content:
264
- if isinstance(block, dict):
265
- if block.get("type") == "text" and "text" in block:
266
- display_text_content(block["text"])
267
- elif block.get("type") == "tool_use":
268
- tool_name = block.get("name", "unknown_tool")
269
- tool_input = block.get("input", {})
270
- display_tool_call(tool_name, tool_input)
271
- # Skip tool_result blocks - they're just noise
272
- elif block.get("type") == "tool_result":
273
- pass # Don't display tool results
274
-
275
-
276
- def execute_task(user_input: str, agent, agent_name: str | None):
277
- """Execute any task by passing it directly to the AI agent."""
278
- console.print()
279
-
280
- config = {"configurable": {"thread_id": "main", "agent_name": agent_name}} if agent_name else {"configurable": {"thread_id": "main"}}
281
-
282
218
  for _, chunk in agent.stream(
283
219
  {"messages": [{"role": "user", "content": user_input}]},
284
220
  stream_mode="updates",
@@ -286,112 +222,144 @@ def execute_task(user_input: str, agent, agent_name: str | None):
286
222
  config=config,
287
223
  durability="exit",
288
224
  ):
289
- chunk = list(chunk.values())[0]
290
- if chunk is not None:
291
- # Check for interrupts
292
- if "__interrupt__" in chunk:
293
- result = chunk
294
- break
225
+ chunk_data = list(chunk.values())[0]
226
+ if not chunk_data or "messages" not in chunk_data:
227
+ continue
228
+
229
+ last_message = chunk_data["messages"][-1]
230
+ message_role = getattr(last_message, "type", None)
231
+ message_content = getattr(last_message, "content", None)
232
+
233
+ # Skip tool results
234
+ if message_role == "tool":
235
+ continue
236
+
237
+ # Handle AI messages
238
+ if message_role == "ai":
239
+ # First, extract and display text content
240
+ text_content = ""
295
241
 
296
- # Normal message processing
297
- if "messages" in chunk and chunk["messages"]:
298
- last_message = chunk["messages"][-1]
299
-
300
- message_content = None
301
- message_role = getattr(last_message, "type", None)
302
- if isinstance(message_role, dict):
303
- message_role = last_message.get("role", "unknown")
304
-
305
- if hasattr(last_message, "content"):
306
- message_content = last_message.content
307
- elif isinstance(last_message, dict) and "content" in last_message:
308
- message_content = last_message["content"]
309
-
310
- if message_content:
311
- # Show tool calls with truncated args
312
- if message_role != "tool":
313
- if isinstance(message_content, list):
314
- for block in message_content:
315
- if isinstance(block, dict) and block.get("type") == "tool_use":
316
- tool_name = block.get("name", "unknown_tool")
317
- tool_input = block.get("input", {})
318
-
319
- tool_icons = {
320
- "read_file": "📖",
321
- "write_file": "✏️",
322
- "edit_file": "✂️",
323
- "ls": "📁",
324
- "glob": "🔍",
325
- "grep": "🔎",
326
- "shell": "⚡",
327
- "web_search": "🌐",
328
- "http_request": "🌍",
329
- "task": "🤖",
330
- "write_todos": "📋",
331
- }
332
-
333
- icon = tool_icons.get(tool_name, "🔧")
334
- args_str = format_tool_args(tool_input)
335
-
336
- if args_str:
337
- console.print(f"[dim]{icon} {tool_name}({args_str})[/dim]")
338
- else:
339
- console.print(f"[dim]{icon} {tool_name}()[/dim]")
242
+ if isinstance(message_content, str):
243
+ text_content = message_content
244
+ elif isinstance(message_content, list):
245
+ for block in message_content:
246
+ if isinstance(block, dict) and block.get("type") == "text":
247
+ text_content = block.get("text", "")
248
+ break
249
+
250
+ if text_content.strip():
251
+ if spinner_active:
252
+ status.stop()
253
+ spinner_active = False
254
+
255
+ if not has_responded:
256
+ console.print("... ", style=COLORS["agent"], end="", markup=False)
257
+ has_responded = True
258
+ printed_tool_calls_after_text = False
259
+
260
+ if text_content != current_text:
261
+ new_text = text_content[len(current_text):]
262
+ console.print(new_text, style=COLORS["agent"], end="", markup=False)
263
+ current_text = text_content
264
+ printed_tool_calls_after_text = False
265
+
266
+ # Then, handle tool calls from tool_calls attribute
267
+ tool_calls = getattr(last_message, "tool_calls", None)
268
+ if tool_calls:
269
+ # If we've printed text, ensure tool calls go on new line
270
+ if has_responded and current_text and not printed_tool_calls_after_text:
271
+ console.print()
272
+ printed_tool_calls_after_text = True
273
+
274
+ for tool_call in tool_calls:
275
+ tool_name = tool_call.get("name", "unknown")
276
+ tool_args = tool_call.get("args", {})
340
277
 
341
- # Show tool results with truncation
342
- if message_role == "tool":
343
- result_str = str(message_content)
344
- result_str = truncate_value(result_str, MAX_RESULT_LENGTH)
345
- console.print(f"[dim] → {result_str}[/dim]")
278
+ icon = tool_icons.get(tool_name, "🔧")
279
+ args_str = ", ".join(
280
+ f"{k}={truncate_value(str(v), 50)}" for k, v in tool_args.items()
281
+ )
346
282
 
347
- # Show text content
348
- if message_role != "tool":
349
- has_text_content = False
350
- if isinstance(message_content, str):
351
- has_text_content = True
352
- elif isinstance(message_content, list):
353
- for block in message_content:
354
- if isinstance(block, dict):
355
- if block.get("type") == "text" and block.get("text", "").strip():
356
- has_text_content = True
357
- break
358
-
359
- if has_text_content:
360
- extract_and_display_content(message_content)
361
- console.print()
362
-
363
- console.print()
364
-
365
-
366
- async def simple_cli(agent, agent_name: str | None):
283
+ if spinner_active:
284
+ status.stop()
285
+ console.print(f" {icon} {tool_name}({args_str})", style=f"dim {COLORS['tool']}")
286
+ if spinner_active:
287
+ status.start()
288
+
289
+ # Handle tool calls from content blocks (alternative format) - only if not already handled
290
+ elif isinstance(message_content, list):
291
+ has_tool_use = False
292
+ for block in message_content:
293
+ if isinstance(block, dict) and block.get("type") == "tool_use":
294
+ has_tool_use = True
295
+ break
296
+
297
+ if has_tool_use:
298
+ # If we've printed text, ensure tool calls go on new line
299
+ if has_responded and current_text and not printed_tool_calls_after_text:
300
+ console.print()
301
+ printed_tool_calls_after_text = True
302
+
303
+ for block in message_content:
304
+ if isinstance(block, dict) and block.get("type") == "tool_use":
305
+ tool_name = block.get("name", "unknown")
306
+ tool_input = block.get("input", {})
307
+
308
+ icon = tool_icons.get(tool_name, "🔧")
309
+ args = ", ".join(
310
+ f"{k}={truncate_value(str(v), 50)}" for k, v in tool_input.items()
311
+ )
312
+
313
+ if spinner_active:
314
+ status.stop()
315
+ console.print(f" {icon} {tool_name}({args})", style=f"dim {COLORS['tool']}")
316
+ if spinner_active:
317
+ status.start()
318
+
319
+ if spinner_active:
320
+ status.stop()
321
+
322
+ if has_responded:
323
+ console.print()
324
+ console.print()
325
+
326
+
327
+ async def simple_cli(agent, assistant_id: str | None):
367
328
  """Main CLI loop."""
329
+ console.clear()
330
+ console.print(DEEP_AGENTS_ASCII, style=f"bold {COLORS['primary']}")
368
331
  console.print()
369
- console.print(Panel.fit(
370
- "[bold cyan]DeepAgents[/bold cyan] [dim]|[/dim] AI Coding Assistant",
371
- border_style="cyan",
372
- box=box.DOUBLE
373
- ))
374
- console.print("[dim]Type 'quit' to exit[/dim]")
332
+
333
+ if tavily_client is None:
334
+ console.print(f"[yellow]⚠ Web search disabled:[/yellow] TAVILY_API_KEY not found.", style=COLORS["dim"])
335
+ console.print(f" To enable web search, set your Tavily API key:", style=COLORS["dim"])
336
+ console.print(f" export TAVILY_API_KEY=your_api_key_here", style=COLORS["dim"])
337
+ console.print(f" Or add it to your .env file. Get your key at: https://tavily.com", style=COLORS["dim"])
338
+ console.print()
339
+
340
+ console.print("... Ready to code! What would you like to build?", style=COLORS["agent"])
341
+ console.print()
342
+ console.print(f" Tip: Type 'quit' to exit", style=f"dim {COLORS['dim']}")
375
343
  console.print()
376
344
 
377
345
  while True:
378
346
  try:
379
- console.print("[bold green]❯[/bold green] ", end="")
347
+ console.print(f"> ", style=COLORS["user"], end="")
380
348
  user_input = input().strip()
381
349
  except EOFError:
382
350
  break
351
+ except KeyboardInterrupt:
352
+ console.print()
353
+ break
383
354
 
384
355
  if not user_input:
385
356
  continue
386
357
 
387
358
  if user_input.lower() in ["quit", "exit", "q"]:
388
- console.print("\n[bold cyan]👋 Goodbye![/bold cyan]\n")
359
+ console.print(f"\nGoodbye!", style=COLORS["primary"])
389
360
  break
390
361
 
391
-
392
-
393
- else:
394
- execute_task(user_input, agent, agent_name)
362
+ execute_task(user_input, agent, assistant_id)
395
363
 
396
364
 
397
365
  def list_agents():
@@ -400,10 +368,10 @@ def list_agents():
400
368
 
401
369
  if not agents_dir.exists() or not any(agents_dir.iterdir()):
402
370
  console.print("[yellow]No agents found.[/yellow]")
403
- console.print("[dim]Agents will be created in ~/.deepagents/ when you first use them.[/dim]")
371
+ console.print(f"[dim]Agents will be created in ~/.deepagents/ when you first use them.[/dim]", style=COLORS["dim"])
404
372
  return
405
373
 
406
- console.print("\n[bold cyan]Available Agents:[/bold cyan]\n")
374
+ console.print(f"\n[bold]Available Agents:[/bold]\n", style=COLORS["primary"])
407
375
 
408
376
  for agent_path in sorted(agents_dir.iterdir()):
409
377
  if agent_path.is_dir():
@@ -411,11 +379,11 @@ def list_agents():
411
379
  agent_md = agent_path / "agent.md"
412
380
 
413
381
  if agent_md.exists():
414
- console.print(f" [green][/green] [bold]{agent_name}[/bold]")
415
- console.print(f" [dim]{agent_path}[/dim]")
382
+ console.print(f" • [bold]{agent_name}[/bold]", style=COLORS["primary"])
383
+ console.print(f" {agent_path}", style=COLORS["dim"])
416
384
  else:
417
- console.print(f" [yellow][/yellow] [bold]{agent_name}[/bold] [dim](incomplete)[/dim]")
418
- console.print(f" [dim]{agent_path}[/dim]")
385
+ console.print(f" • [bold]{agent_name}[/bold] [dim](incomplete)[/dim]", style=COLORS["tool"])
386
+ console.print(f" {agent_path}", style=COLORS["dim"])
419
387
 
420
388
  console.print()
421
389
 
@@ -437,58 +405,57 @@ def reset_agent(agent_name: str, source_agent: str = None):
437
405
  action_desc = f"contents of agent '{source_agent}'"
438
406
  else:
439
407
  source_content = get_default_coding_instructions()
440
- action_desc = "default prompt"
408
+ action_desc = "default"
441
409
 
442
410
  if agent_dir.exists():
443
411
  shutil.rmtree(agent_dir)
444
- console.print(f"[yellow]Removed existing agent directory:[/yellow] {agent_dir}")
412
+ console.print(f"Removed existing agent directory: {agent_dir}", style=COLORS["tool"])
445
413
 
446
414
  agent_dir.mkdir(parents=True, exist_ok=True)
447
415
  agent_md = agent_dir / "agent.md"
448
416
  agent_md.write_text(source_content)
449
417
 
450
- console.print(f"[bold green][/bold green] Agent '{agent_name}' reset to {action_desc}")
451
- console.print(f"[dim]Location: {agent_dir}[/dim]\n")
418
+ console.print(f"✓ Agent '{agent_name}' reset to {action_desc}", style=COLORS["primary"])
419
+ console.print(f"Location: {agent_dir}\n", style=COLORS["dim"])
452
420
 
453
421
 
454
422
  def show_help():
455
423
  """Show help information."""
456
- help_text = """
457
- [bold cyan]DeepAgents - AI Coding Assistant[/bold cyan]
458
-
459
- [bold]Usage:[/bold]
460
- deepagents [--agent NAME] [--no-memory] Start interactive session
461
- deepagents list List all available agents
462
- deepagents reset --agent AGENT Reset agent to default prompt
463
- deepagents reset --agent AGENT --target SOURCE Reset agent to copy of another agent
464
- deepagents help Show this help message
465
-
466
- [bold]Examples:[/bold]
467
- deepagents # Start with default agent (long-term memory enabled)
468
- deepagents --agent mybot # Start with agent named 'mybot'
469
- deepagents --no-memory # Start without long-term memory
470
- deepagents list # List all agents
471
- deepagents reset --agent mybot # Reset mybot to default
472
- deepagents reset --agent mybot --target other # Reset mybot to copy of 'other' agent
473
-
474
- [bold]Long-term Memory:[/bold]
475
- By default, long-term memory is ENABLED using agent name 'agent'.
476
- Memory includes:
477
- - Persistent agent.md file with your instructions
478
- - /memories/ folder for storing context across sessions
479
-
480
- Use --no-memory to disable these features.
481
- Note: --agent and --no-memory cannot be used together.
482
-
483
- [bold]Agent Storage:[/bold]
484
- Agents are stored in: ~/.deepagents/AGENT_NAME/
485
- Each agent has an agent.md file containing its prompt
486
-
487
- [bold]Interactive Commands:[/bold]
488
- quit, exit, q Exit the session
489
- help Show usage examples
490
- """
491
- console.print(help_text)
424
+ console.print()
425
+ console.print(DEEP_AGENTS_ASCII, style=f"bold {COLORS['primary']}")
426
+ console.print()
427
+
428
+ console.print("[bold]Usage:[/bold]", style=COLORS["primary"])
429
+ console.print(" deepagents [--agent NAME] Start interactive session")
430
+ console.print(" deepagents list List all available agents")
431
+ console.print(" deepagents reset --agent AGENT Reset agent to default prompt")
432
+ console.print(" deepagents reset --agent AGENT --target SOURCE Reset agent to copy of another agent")
433
+ console.print(" deepagents help Show this help message")
434
+ console.print()
435
+
436
+ console.print("[bold]Examples:[/bold]", style=COLORS["primary"])
437
+ console.print(" deepagents # Start with default agent", style=COLORS["dim"])
438
+ console.print(" deepagents --agent mybot # Start with agent named 'mybot'", style=COLORS["dim"])
439
+ console.print(" deepagents list # List all agents", style=COLORS["dim"])
440
+ console.print(" deepagents reset --agent mybot # Reset mybot to default", style=COLORS["dim"])
441
+ console.print(" deepagents reset --agent mybot --target other # Reset mybot to copy of 'other' agent", style=COLORS["dim"])
442
+ console.print()
443
+
444
+ console.print("[bold]Long-term Memory:[/bold]", style=COLORS["primary"])
445
+ console.print(" By default, long-term memory is ENABLED using agent name 'agent'.", style=COLORS["dim"])
446
+ console.print(" Memory includes:", style=COLORS["dim"])
447
+ console.print(" - Persistent agent.md file with your instructions", style=COLORS["dim"])
448
+ console.print(" - /memories/ folder for storing context across sessions", style=COLORS["dim"])
449
+ console.print()
450
+
451
+ console.print("[bold]Agent Storage:[/bold]", style=COLORS["primary"])
452
+ console.print(" Agents are stored in: ~/.deepagents/AGENT_NAME/", style=COLORS["dim"])
453
+ console.print(" Each agent has an agent.md file containing its prompt", style=COLORS["dim"])
454
+ console.print()
455
+
456
+ console.print("[bold]Interactive Commands:[/bold]", style=COLORS["primary"])
457
+ console.print(" quit, exit, q Exit the session", style=COLORS["dim"])
458
+ console.print()
492
459
 
493
460
 
494
461
  def parse_args():
@@ -519,47 +486,59 @@ def parse_args():
519
486
  help="Agent identifier for separate memory stores (default: agent).",
520
487
  )
521
488
 
522
- parser.add_argument(
523
- "--no-memory",
524
- action="store_true",
525
- help="Disable long-term memory features (no agent.md or /memories/ access).",
526
- )
527
-
528
489
  return parser.parse_args()
529
490
 
530
491
 
531
- async def main(agent_name: str, no_memory: bool):
492
+ async def main(assistant_id: str):
532
493
  """Main entry point."""
533
- # Error if both --agent and --no-memory are specified
534
- if agent_name != "agent" and no_memory:
535
- console.print("[bold red]Error:[/bold red] Cannot use --agent with --no-memory flag.")
536
- console.print("Either specify --agent for memory-enabled mode, or use --no-memory for memory-disabled mode.")
537
- return
538
-
539
- # Disable long-term memory if --no-memory flag is set
540
- long_term_memory = not no_memory
541
-
542
- # If memory is disabled, set agent_name to None
543
- effective_agent_name = None if no_memory else agent_name
544
494
 
545
495
  # Create agent with conditional tools
546
496
  tools = [http_request]
547
497
  if tavily_client is not None:
548
498
  tools.append(web_search)
549
-
499
+
500
+ shell_middleware = ShellToolMiddleware(
501
+ workspace_root=os.getcwd(),
502
+ execution_policy=HostExecutionPolicy()
503
+ )
504
+
505
+ # For long-term memory, point to ~/.deepagents/AGENT_NAME/ with /memories/ prefix
506
+ agent_dir = Path.home() / ".deepagents" / assistant_id
507
+ agent_dir.mkdir(parents=True, exist_ok=True)
508
+ agent_md = agent_dir / "agent.md"
509
+ if not agent_md.exists():
510
+ source_content = get_default_coding_instructions()
511
+ agent_md.write_text(source_content)
512
+
513
+ # Long-term backend - rooted at agent directory
514
+ # This handles both /memories/ files and /agent.md
515
+ long_term_backend = FilesystemBackend(root_dir=agent_dir, virtual_mode=True)
516
+
517
+ # Composite backend: current working directory for default, agent directory for /memories/
518
+ backend = CompositeBackend(
519
+ default=FilesystemBackend(),
520
+ routes={"/memories/": long_term_backend}
521
+ )
522
+
523
+ # Use the same backend for agent memory middleware
524
+ agent_middleware = [AgentMemoryMiddleware(backend=long_term_backend, memory_path="/memories/"), shell_middleware]
525
+ system_prompt = f"""### Current Working Directory
526
+
527
+ The filesystem backend is currently operating in: `{Path.cwd()}`"""
528
+
550
529
  agent = create_deep_agent(
530
+ system_prompt=system_prompt,
551
531
  tools=tools,
552
- system_prompt=get_coding_instructions(effective_agent_name, long_term_memory=long_term_memory),
553
- use_local_filesystem=True,
554
- long_term_memory=long_term_memory,
532
+ memory_backend=backend,
533
+ middleware=agent_middleware,
555
534
  ).with_config(config)
556
535
 
557
536
  agent.checkpointer = InMemorySaver()
558
537
 
559
538
  try:
560
- await simple_cli(agent, effective_agent_name)
539
+ await simple_cli(agent, assistant_id)
561
540
  except KeyboardInterrupt:
562
- console.print("\n\n[bold cyan]👋 Goodbye![/bold cyan]\n")
541
+ console.print(f"\n\nGoodbye!", style=COLORS["primary"])
563
542
  except Exception as e:
564
543
  console.print(f"\n[bold red]❌ Error:[/bold red] {e}\n")
565
544
 
@@ -575,8 +554,14 @@ def cli_main():
575
554
  elif args.command == "reset":
576
555
  reset_agent(args.agent, args.source_agent)
577
556
  else:
578
- asyncio.run(main(args.agent, args.no_memory))
557
+ if not os.environ.get("ANTHROPIC_API_KEY"):
558
+ console.print("[bold red]Error:[/bold red] ANTHROPIC_API_KEY environment variable is not set.")
559
+ console.print("Please set your Anthropic API key:")
560
+ console.print(" export ANTHROPIC_API_KEY=your_api_key_here")
561
+ console.print("\nOr add it to your .env file.")
562
+ return
563
+ asyncio.run(main(args.agent))
579
564
 
580
565
 
581
566
  if __name__ == "__main__":
582
- cli_main()
567
+ cli_main()