deepagents-cli 0.0.1__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/__init__.py +18 -0
- deepagents/cli.py +582 -0
- deepagents/default_agent_prompt.md +91 -0
- deepagents/graph.py +168 -0
- deepagents/middleware/__init__.py +13 -0
- deepagents/middleware/common.py +16 -0
- deepagents/middleware/filesystem.py +1159 -0
- deepagents/middleware/local_filesystem.py +741 -0
- deepagents/middleware/subagents.py +480 -0
- deepagents/prompts.py +327 -0
- deepagents/skills.py +85 -0
- deepagents_cli-0.0.1.dist-info/METADATA +555 -0
- deepagents_cli-0.0.1.dist-info/RECORD +17 -0
- deepagents_cli-0.0.1.dist-info/WHEEL +5 -0
- deepagents_cli-0.0.1.dist-info/entry_points.txt +2 -0
- deepagents_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
- deepagents_cli-0.0.1.dist-info/top_level.txt +1 -0
deepagents/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""DeepAgents package."""
|
|
2
|
+
|
|
3
|
+
from deepagents.graph import create_deep_agent
|
|
4
|
+
from deepagents.middleware.filesystem import FilesystemMiddleware
|
|
5
|
+
from deepagents.middleware.local_filesystem import LocalFilesystemMiddleware
|
|
6
|
+
from deepagents.middleware.subagents import CompiledSubAgent, SubAgent, SubAgentMiddleware
|
|
7
|
+
from deepagents.skills import load_skills, SkillDefinition
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"CompiledSubAgent",
|
|
11
|
+
"FilesystemMiddleware",
|
|
12
|
+
"LocalFilesystemMiddleware",
|
|
13
|
+
"SkillDefinition",
|
|
14
|
+
"SubAgent",
|
|
15
|
+
"SubAgentMiddleware",
|
|
16
|
+
"create_deep_agent",
|
|
17
|
+
"load_skills",
|
|
18
|
+
]
|
deepagents/cli.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import platform
|
|
7
|
+
import requests
|
|
8
|
+
from typing import Dict, Any, Union, Literal
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from tavily import TavilyClient
|
|
12
|
+
from deepagents import create_deep_agent
|
|
13
|
+
from langgraph.checkpoint.memory import InMemorySaver
|
|
14
|
+
from langgraph.types import Command, Interrupt
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.markdown import Markdown
|
|
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
|
+
from rich.spinner import Spinner
|
|
23
|
+
from rich.prompt import Prompt
|
|
24
|
+
import shutil
|
|
25
|
+
from rich import box
|
|
26
|
+
|
|
27
|
+
import dotenv
|
|
28
|
+
|
|
29
|
+
dotenv.load_dotenv()
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
tavily_client = TavilyClient(api_key=os.environ.get("TAVILY_API_KEY")) if os.environ.get("TAVILY_API_KEY") else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def http_request(
|
|
37
|
+
url: str,
|
|
38
|
+
method: str = "GET",
|
|
39
|
+
headers: Dict[str, str] = None,
|
|
40
|
+
data: Union[str, Dict] = None,
|
|
41
|
+
params: Dict[str, str] = None,
|
|
42
|
+
timeout: int = 30,
|
|
43
|
+
) -> Dict[str, Any]:
|
|
44
|
+
"""
|
|
45
|
+
Make HTTP requests to APIs and web services.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
url: Target URL
|
|
49
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
50
|
+
headers: HTTP headers to include
|
|
51
|
+
data: Request body data (string or dict)
|
|
52
|
+
params: URL query parameters
|
|
53
|
+
timeout: Request timeout in seconds
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dictionary with response data including status, headers, and content
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
kwargs = {"url": url, "method": method.upper(), "timeout": timeout}
|
|
60
|
+
|
|
61
|
+
if headers:
|
|
62
|
+
kwargs["headers"] = headers
|
|
63
|
+
if params:
|
|
64
|
+
kwargs["params"] = params
|
|
65
|
+
if data:
|
|
66
|
+
if isinstance(data, dict):
|
|
67
|
+
kwargs["json"] = data
|
|
68
|
+
else:
|
|
69
|
+
kwargs["data"] = data
|
|
70
|
+
|
|
71
|
+
response = requests.request(**kwargs)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
content = response.json()
|
|
75
|
+
except:
|
|
76
|
+
content = response.text
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
"success": response.status_code < 400,
|
|
80
|
+
"status_code": response.status_code,
|
|
81
|
+
"headers": dict(response.headers),
|
|
82
|
+
"content": content,
|
|
83
|
+
"url": response.url,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
except requests.exceptions.Timeout:
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"status_code": 0,
|
|
90
|
+
"headers": {},
|
|
91
|
+
"content": f"Request timed out after {timeout} seconds",
|
|
92
|
+
"url": url,
|
|
93
|
+
}
|
|
94
|
+
except requests.exceptions.RequestException as e:
|
|
95
|
+
return {
|
|
96
|
+
"success": False,
|
|
97
|
+
"status_code": 0,
|
|
98
|
+
"headers": {},
|
|
99
|
+
"content": f"Request error: {str(e)}",
|
|
100
|
+
"url": url,
|
|
101
|
+
}
|
|
102
|
+
except Exception as e:
|
|
103
|
+
return {
|
|
104
|
+
"success": False,
|
|
105
|
+
"status_code": 0,
|
|
106
|
+
"headers": {},
|
|
107
|
+
"content": f"Error making request: {str(e)}",
|
|
108
|
+
"url": url,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def web_search(
|
|
113
|
+
query: str,
|
|
114
|
+
max_results: int = 5,
|
|
115
|
+
topic: Literal["general", "news", "finance"] = "general",
|
|
116
|
+
include_raw_content: bool = False,
|
|
117
|
+
):
|
|
118
|
+
"""Search the web using Tavily for programming-related information."""
|
|
119
|
+
if tavily_client is None:
|
|
120
|
+
return {
|
|
121
|
+
"error": "Tavily API key not configured. Please set TAVILY_API_KEY environment variable.",
|
|
122
|
+
"query": query
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
search_docs = tavily_client.search(
|
|
127
|
+
query,
|
|
128
|
+
max_results=max_results,
|
|
129
|
+
include_raw_content=include_raw_content,
|
|
130
|
+
topic=topic,
|
|
131
|
+
)
|
|
132
|
+
return search_docs
|
|
133
|
+
except Exception as e:
|
|
134
|
+
return {
|
|
135
|
+
"error": f"Web search error: {str(e)}",
|
|
136
|
+
"query": query
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_default_coding_instructions(include_memory: bool = True) -> str:
|
|
141
|
+
"""Get the default coding agent instructions.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
include_memory: If False, removes the Long-term Memory section from the prompt.
|
|
145
|
+
"""
|
|
146
|
+
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>"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
config = {"recursion_limit": 1000}
|
|
192
|
+
|
|
193
|
+
# Constants for display truncation
|
|
194
|
+
MAX_ARG_LENGTH = 200
|
|
195
|
+
MAX_RESULT_LENGTH = 200
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def truncate_value(value: str, max_length: int = MAX_ARG_LENGTH) -> str:
|
|
199
|
+
"""Truncate a string value if it exceeds max_length."""
|
|
200
|
+
if len(value) > max_length:
|
|
201
|
+
return value[:max_length] + "..."
|
|
202
|
+
return value
|
|
203
|
+
|
|
204
|
+
|
|
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 ""
|
|
209
|
+
|
|
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}")
|
|
215
|
+
|
|
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."""
|
|
221
|
+
|
|
222
|
+
tool_icons = {
|
|
223
|
+
"read_file": "📖",
|
|
224
|
+
"write_file": "✏️",
|
|
225
|
+
"edit_file": "✂️",
|
|
226
|
+
"ls": "📁",
|
|
227
|
+
"glob": "🔍",
|
|
228
|
+
"grep": "🔎",
|
|
229
|
+
"shell": "⚡",
|
|
230
|
+
"web_search": "🌐",
|
|
231
|
+
"http_request": "🌍",
|
|
232
|
+
"task": "🤖",
|
|
233
|
+
"write_todos": "📋",
|
|
234
|
+
}
|
|
235
|
+
|
|
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
|
+
for _, chunk in agent.stream(
|
|
283
|
+
{"messages": [{"role": "user", "content": user_input}]},
|
|
284
|
+
stream_mode="updates",
|
|
285
|
+
subgraphs=True,
|
|
286
|
+
config=config,
|
|
287
|
+
durability="exit",
|
|
288
|
+
):
|
|
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
|
|
295
|
+
|
|
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]")
|
|
340
|
+
|
|
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]")
|
|
346
|
+
|
|
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):
|
|
367
|
+
"""Main CLI loop."""
|
|
368
|
+
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]")
|
|
375
|
+
console.print()
|
|
376
|
+
|
|
377
|
+
while True:
|
|
378
|
+
try:
|
|
379
|
+
console.print("[bold green]❯[/bold green] ", end="")
|
|
380
|
+
user_input = input().strip()
|
|
381
|
+
except EOFError:
|
|
382
|
+
break
|
|
383
|
+
|
|
384
|
+
if not user_input:
|
|
385
|
+
continue
|
|
386
|
+
|
|
387
|
+
if user_input.lower() in ["quit", "exit", "q"]:
|
|
388
|
+
console.print("\n[bold cyan]👋 Goodbye![/bold cyan]\n")
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
else:
|
|
394
|
+
execute_task(user_input, agent, agent_name)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def list_agents():
|
|
398
|
+
"""List all available agents."""
|
|
399
|
+
agents_dir = Path.home() / ".deepagents"
|
|
400
|
+
|
|
401
|
+
if not agents_dir.exists() or not any(agents_dir.iterdir()):
|
|
402
|
+
console.print("[yellow]No agents found.[/yellow]")
|
|
403
|
+
console.print("[dim]Agents will be created in ~/.deepagents/ when you first use them.[/dim]")
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
console.print("\n[bold cyan]Available Agents:[/bold cyan]\n")
|
|
407
|
+
|
|
408
|
+
for agent_path in sorted(agents_dir.iterdir()):
|
|
409
|
+
if agent_path.is_dir():
|
|
410
|
+
agent_name = agent_path.name
|
|
411
|
+
agent_md = agent_path / "agent.md"
|
|
412
|
+
|
|
413
|
+
if agent_md.exists():
|
|
414
|
+
console.print(f" [green]•[/green] [bold]{agent_name}[/bold]")
|
|
415
|
+
console.print(f" [dim]{agent_path}[/dim]")
|
|
416
|
+
else:
|
|
417
|
+
console.print(f" [yellow]•[/yellow] [bold]{agent_name}[/bold] [dim](incomplete)[/dim]")
|
|
418
|
+
console.print(f" [dim]{agent_path}[/dim]")
|
|
419
|
+
|
|
420
|
+
console.print()
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def reset_agent(agent_name: str, source_agent: str = None):
|
|
424
|
+
"""Reset an agent to default or copy from another agent."""
|
|
425
|
+
agents_dir = Path.home() / ".deepagents"
|
|
426
|
+
agent_dir = agents_dir / agent_name
|
|
427
|
+
|
|
428
|
+
if source_agent:
|
|
429
|
+
source_dir = agents_dir / source_agent
|
|
430
|
+
source_md = source_dir / "agent.md"
|
|
431
|
+
|
|
432
|
+
if not source_md.exists():
|
|
433
|
+
console.print(f"[bold red]Error:[/bold red] Source agent '{source_agent}' not found or has no agent.md")
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
source_content = source_md.read_text()
|
|
437
|
+
action_desc = f"contents of agent '{source_agent}'"
|
|
438
|
+
else:
|
|
439
|
+
source_content = get_default_coding_instructions()
|
|
440
|
+
action_desc = "default prompt"
|
|
441
|
+
|
|
442
|
+
if agent_dir.exists():
|
|
443
|
+
shutil.rmtree(agent_dir)
|
|
444
|
+
console.print(f"[yellow]Removed existing agent directory:[/yellow] {agent_dir}")
|
|
445
|
+
|
|
446
|
+
agent_dir.mkdir(parents=True, exist_ok=True)
|
|
447
|
+
agent_md = agent_dir / "agent.md"
|
|
448
|
+
agent_md.write_text(source_content)
|
|
449
|
+
|
|
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")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def show_help():
|
|
455
|
+
"""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)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def parse_args():
|
|
495
|
+
"""Parse command line arguments."""
|
|
496
|
+
parser = argparse.ArgumentParser(
|
|
497
|
+
description="DeepAgents - AI Coding Assistant",
|
|
498
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
499
|
+
add_help=False,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
503
|
+
|
|
504
|
+
# List command
|
|
505
|
+
subparsers.add_parser("list", help="List all available agents")
|
|
506
|
+
|
|
507
|
+
# Help command
|
|
508
|
+
subparsers.add_parser("help", help="Show help information")
|
|
509
|
+
|
|
510
|
+
# Reset command
|
|
511
|
+
reset_parser = subparsers.add_parser("reset", help="Reset an agent")
|
|
512
|
+
reset_parser.add_argument("--agent", required=True, help="Name of agent to reset")
|
|
513
|
+
reset_parser.add_argument("--target", dest="source_agent", help="Copy prompt from another agent")
|
|
514
|
+
|
|
515
|
+
# Default interactive mode
|
|
516
|
+
parser.add_argument(
|
|
517
|
+
"--agent",
|
|
518
|
+
default="agent",
|
|
519
|
+
help="Agent identifier for separate memory stores (default: agent).",
|
|
520
|
+
)
|
|
521
|
+
|
|
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
|
+
return parser.parse_args()
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
async def main(agent_name: str, no_memory: bool):
|
|
532
|
+
"""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
|
+
|
|
545
|
+
# Create agent with conditional tools
|
|
546
|
+
tools = [http_request]
|
|
547
|
+
if tavily_client is not None:
|
|
548
|
+
tools.append(web_search)
|
|
549
|
+
|
|
550
|
+
agent = create_deep_agent(
|
|
551
|
+
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,
|
|
555
|
+
).with_config(config)
|
|
556
|
+
|
|
557
|
+
agent.checkpointer = InMemorySaver()
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
await simple_cli(agent, effective_agent_name)
|
|
561
|
+
except KeyboardInterrupt:
|
|
562
|
+
console.print("\n\n[bold cyan]👋 Goodbye![/bold cyan]\n")
|
|
563
|
+
except Exception as e:
|
|
564
|
+
console.print(f"\n[bold red]❌ Error:[/bold red] {e}\n")
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def cli_main():
|
|
568
|
+
"""Entry point for console script."""
|
|
569
|
+
args = parse_args()
|
|
570
|
+
|
|
571
|
+
if args.command == "help":
|
|
572
|
+
show_help()
|
|
573
|
+
elif args.command == "list":
|
|
574
|
+
list_agents()
|
|
575
|
+
elif args.command == "reset":
|
|
576
|
+
reset_agent(args.agent, args.source_agent)
|
|
577
|
+
else:
|
|
578
|
+
asyncio.run(main(args.agent, args.no_memory))
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
if __name__ == "__main__":
|
|
582
|
+
cli_main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
You are an AI assistant that helps users with various tasks including coding, research, and analysis.
|
|
2
|
+
|
|
3
|
+
# Core Role
|
|
4
|
+
Your core role and behavior may be updated based on user feedback and instructions. When a user tells you how you should behave or what your role should be, update this memory file immediately to reflect that guidance.
|
|
5
|
+
|
|
6
|
+
# Tone and Style
|
|
7
|
+
Be concise and direct. Answer in fewer than 4 lines unless the user asks for detail.
|
|
8
|
+
After working on a file, just stop - don't explain what you did unless asked.
|
|
9
|
+
Avoid unnecessary introductions or conclusions.
|
|
10
|
+
|
|
11
|
+
When you run non-trivial bash commands, briefly explain what they do.
|
|
12
|
+
|
|
13
|
+
## Proactiveness
|
|
14
|
+
Take action when asked, but don't surprise users with unrequested actions.
|
|
15
|
+
If asked how to approach something, answer first before taking action.
|
|
16
|
+
|
|
17
|
+
## Following Conventions
|
|
18
|
+
- Check existing code for libraries and frameworks before assuming availability
|
|
19
|
+
- Mimic existing code style, naming conventions, and patterns
|
|
20
|
+
- Never add comments unless asked
|
|
21
|
+
|
|
22
|
+
## Task Management
|
|
23
|
+
Use write_todos for complex multi-step tasks (3+ steps). Mark tasks in_progress before starting, completed immediately after finishing.
|
|
24
|
+
For simple 1-2 step tasks, just do them without todos.
|
|
25
|
+
|
|
26
|
+
## Working with Subagents (task tool)
|
|
27
|
+
When delegating to subagents:
|
|
28
|
+
- **Use filesystem for large I/O**: If input instructions are large (>500 words) OR expected output is large, communicate via files
|
|
29
|
+
- Write input context/instructions to a file, tell subagent to read it
|
|
30
|
+
- Ask subagent to write their output to a file, then read it after they return
|
|
31
|
+
- This prevents token bloat and keeps context manageable in both directions
|
|
32
|
+
- **Parallelize independent work**: When tasks are independent, spawn parallel subagents to work simultaneously
|
|
33
|
+
- **Clear specifications**: Tell subagent exactly what format/structure you need in their response or output file
|
|
34
|
+
- **Main agent synthesizes**: Subagents gather/execute, main agent integrates results into final deliverable
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
### execute_bash
|
|
39
|
+
Execute shell commands. Always quote paths with spaces.
|
|
40
|
+
Examples: `pytest /foo/bar/tests` (good), `cd /foo/bar && pytest tests` (bad)
|
|
41
|
+
|
|
42
|
+
### File Tools
|
|
43
|
+
- read_file: Read file contents (use absolute paths)
|
|
44
|
+
- edit_file: Replace exact strings in files (must read first, provide unique old_string)
|
|
45
|
+
- write_file: Create or overwrite files
|
|
46
|
+
- ls: List directory contents
|
|
47
|
+
- glob: Find files by pattern (e.g., "**/*.py")
|
|
48
|
+
- grep: Search file contents
|
|
49
|
+
|
|
50
|
+
Always use absolute paths starting with /.
|
|
51
|
+
|
|
52
|
+
### web_search
|
|
53
|
+
Search for documentation, error solutions, and code examples.
|
|
54
|
+
|
|
55
|
+
### http_request
|
|
56
|
+
Make HTTP requests to APIs (GET, POST, etc.).
|
|
57
|
+
|
|
58
|
+
## Code References
|
|
59
|
+
When referencing code, use format: `file_path:line_number`
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
- Do NOT create excessive markdown summary/documentation files after completing work
|
|
63
|
+
- Focus on the work itself, not documenting what you did
|
|
64
|
+
- Only create documentation when explicitly requested
|
|
65
|
+
|
|
66
|
+
## Long-term Memory
|
|
67
|
+
You have access to a long-term memory system using the `/memories/` path prefix.
|
|
68
|
+
Files stored in `/memories/` persist across sessions and are stored in your agent directory.
|
|
69
|
+
|
|
70
|
+
Your system prompt is loaded from `/memories/agent.md` at startup. You can update your own instructions by editing this file.
|
|
71
|
+
|
|
72
|
+
**When to update memories:**
|
|
73
|
+
- **IMMEDIATELY when the user describes your role or how you should behave** (e.g., "you are a web researcher", "you are an expert in X")
|
|
74
|
+
- **IMMEDIATELY when the user gives feedback on your work** - Before continuing, update memories to capture what was wrong and how to do it better
|
|
75
|
+
- When the user explicitly asks you to remember something
|
|
76
|
+
- When patterns or preferences emerge (coding styles, conventions, workflows)
|
|
77
|
+
- After significant work where context would help in future sessions
|
|
78
|
+
|
|
79
|
+
**Learning from feedback:**
|
|
80
|
+
- When user says something is better/worse, capture WHY and encode it as a pattern
|
|
81
|
+
- Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions
|
|
82
|
+
|
|
83
|
+
**What to store where:**
|
|
84
|
+
- **`/memories/agent.md`**: Update this to modify your core instructions and behavioral patterns
|
|
85
|
+
- **Other `/memories/` files**: Use for project-specific context, reference information, or structured notes
|
|
86
|
+
- If you create additional memory files, add references to them in `/memories/agent.md` so you remember to consult them
|
|
87
|
+
|
|
88
|
+
The portion of your system prompt that comes from `/memories/agent.md` is marked with `<agent_memory>` tags so you can identify what instructions come from your persistent memory.
|
|
89
|
+
|
|
90
|
+
Example: `edit_file('/memories/agent.md', ...)` to update your instructions
|
|
91
|
+
Example: `write_file('/memories/project_context.md', ...)` for project-specific notes, then reference it in agent.md
|