maestro-bundle 1.7.0 → 1.9.0
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/README.md +120 -150
- package/package.json +1 -1
- package/src/cli.mjs +6 -2
- package/templates/bundle-ai-agents-deep/.spec/constitution.md +24 -0
- package/templates/bundle-ai-agents-deep/AGENTS.md +105 -0
- package/templates/bundle-ai-agents-deep/PRD_TEMPLATE.md +161 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-backends/SKILL.md +165 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-cli/SKILL.md +158 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-creation/SKILL.md +165 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-deployment/SKILL.md +196 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-hitl/SKILL.md +152 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-memory/SKILL.md +158 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-middleware/SKILL.md +131 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-skills-system/SKILL.md +155 -0
- package/templates/bundle-ai-agents-deep/skills/deep-agent-subagents/SKILL.md +141 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-agent-deployment
|
|
3
|
+
description: Deploy Deep Agents as APIs or CLI tools using FastAPI, LangGraph Platform, or Docker. Use when serving the agent as an API, deploying to production, or creating a CLI interface.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Maestro
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Deep Agent Deployment
|
|
9
|
+
|
|
10
|
+
Deploy your Deep Agent as a REST API, WebSocket server, or CLI tool for production use.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
- When serving the agent as an API endpoint
|
|
14
|
+
- When deploying to LangGraph Platform
|
|
15
|
+
- When creating a Docker container for the agent
|
|
16
|
+
- When building a CLI interface
|
|
17
|
+
|
|
18
|
+
## Available Operations
|
|
19
|
+
1. Serve as FastAPI REST API
|
|
20
|
+
2. Add WebSocket streaming
|
|
21
|
+
3. Deploy with Docker
|
|
22
|
+
4. Deploy to LangGraph Platform
|
|
23
|
+
5. Create CLI interface
|
|
24
|
+
|
|
25
|
+
## Multi-Step Workflow
|
|
26
|
+
|
|
27
|
+
### Step 1: FastAPI REST API
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# api/server.py
|
|
31
|
+
from fastapi import FastAPI, HTTPException
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
from deepagents import create_deep_agent
|
|
34
|
+
from langgraph.checkpoint.postgres import PostgresSaver
|
|
35
|
+
|
|
36
|
+
app = FastAPI(title="Deep Agent API")
|
|
37
|
+
|
|
38
|
+
agent = create_deep_agent(
|
|
39
|
+
model="anthropic:claude-sonnet-4-6",
|
|
40
|
+
checkpointer=PostgresSaver(conn_string=os.environ["DATABASE_URL"])
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
class ChatRequest(BaseModel):
|
|
44
|
+
message: str
|
|
45
|
+
thread_id: str
|
|
46
|
+
|
|
47
|
+
class ChatResponse(BaseModel):
|
|
48
|
+
response: str
|
|
49
|
+
thread_id: str
|
|
50
|
+
|
|
51
|
+
@app.post("/api/chat", response_model=ChatResponse)
|
|
52
|
+
async def chat(req: ChatRequest):
|
|
53
|
+
config = {"configurable": {"thread_id": req.thread_id}}
|
|
54
|
+
result = agent.invoke(
|
|
55
|
+
{"messages": [{"role": "user", "content": req.message}]},
|
|
56
|
+
config=config
|
|
57
|
+
)
|
|
58
|
+
return ChatResponse(
|
|
59
|
+
response=result["messages"][-1].content,
|
|
60
|
+
thread_id=req.thread_id
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Step 2: WebSocket Streaming
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# api/websocket.py
|
|
68
|
+
from fastapi import WebSocket
|
|
69
|
+
|
|
70
|
+
@app.websocket("/ws/chat/{thread_id}")
|
|
71
|
+
async def ws_chat(websocket: WebSocket, thread_id: str):
|
|
72
|
+
await websocket.accept()
|
|
73
|
+
config = {"configurable": {"thread_id": thread_id}}
|
|
74
|
+
|
|
75
|
+
while True:
|
|
76
|
+
message = await websocket.receive_text()
|
|
77
|
+
|
|
78
|
+
async for event in agent.astream_events(
|
|
79
|
+
{"messages": [{"role": "user", "content": message}]},
|
|
80
|
+
config=config,
|
|
81
|
+
version="v2"
|
|
82
|
+
):
|
|
83
|
+
if event["event"] == "on_chat_model_stream":
|
|
84
|
+
chunk = event["data"]["chunk"].content
|
|
85
|
+
if chunk:
|
|
86
|
+
await websocket.send_text(chunk)
|
|
87
|
+
|
|
88
|
+
await websocket.send_text("[DONE]")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 3: Docker
|
|
92
|
+
|
|
93
|
+
```dockerfile
|
|
94
|
+
FROM python:3.11-slim
|
|
95
|
+
WORKDIR /app
|
|
96
|
+
COPY requirements.txt .
|
|
97
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
98
|
+
COPY src/ ./src/
|
|
99
|
+
COPY skills/ ./skills/
|
|
100
|
+
USER 1000
|
|
101
|
+
EXPOSE 8000
|
|
102
|
+
CMD ["uvicorn", "src.api.server:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
docker build -t deep-agent:latest .
|
|
107
|
+
docker run -p 8000:8000 -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY deep-agent:latest
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Step 4: LangGraph Platform
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# langgraph.json
|
|
114
|
+
{
|
|
115
|
+
"graphs": {
|
|
116
|
+
"agent": {
|
|
117
|
+
"path": "src/agent/main.py:agent"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"dependencies": ["deepagents", "langchain"]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Deploy to LangGraph Platform
|
|
126
|
+
langgraph deploy
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Step 5: CLI Interface
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# cli.py
|
|
133
|
+
import asyncio
|
|
134
|
+
from deepagents import create_deep_agent
|
|
135
|
+
|
|
136
|
+
async def main():
|
|
137
|
+
agent = create_deep_agent(model="anthropic:claude-sonnet-4-6")
|
|
138
|
+
config = {"configurable": {"thread_id": "cli-session"}}
|
|
139
|
+
|
|
140
|
+
print("Deep Agent CLI (type 'exit' to quit)")
|
|
141
|
+
while True:
|
|
142
|
+
user_input = input("\n> ")
|
|
143
|
+
if user_input.lower() == "exit":
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
async for event in agent.astream_events(
|
|
147
|
+
{"messages": [{"role": "user", "content": user_input}]},
|
|
148
|
+
config=config,
|
|
149
|
+
version="v2"
|
|
150
|
+
):
|
|
151
|
+
if event["event"] == "on_chat_model_stream":
|
|
152
|
+
print(event["data"]["chunk"].content, end="", flush=True)
|
|
153
|
+
|
|
154
|
+
asyncio.run(main())
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Step 6: Verify
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Test API
|
|
161
|
+
uvicorn src.api.server:app --reload --port 8000
|
|
162
|
+
curl -X POST http://localhost:8000/api/chat \
|
|
163
|
+
-H "Content-Type: application/json" \
|
|
164
|
+
-d '{"message": "Hello", "thread_id": "test-1"}'
|
|
165
|
+
|
|
166
|
+
# Test CLI
|
|
167
|
+
python cli.py
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Resources
|
|
171
|
+
- `references/deployment-checklist.md` — Production deployment checklist
|
|
172
|
+
|
|
173
|
+
## Examples
|
|
174
|
+
|
|
175
|
+
### Example 1: REST API
|
|
176
|
+
User asks: "Serve the agent as an API"
|
|
177
|
+
Response approach:
|
|
178
|
+
1. Create FastAPI app with `/api/chat` endpoint
|
|
179
|
+
2. Add PostgresSaver for persistence
|
|
180
|
+
3. Run with `uvicorn`
|
|
181
|
+
4. Test with curl
|
|
182
|
+
|
|
183
|
+
### Example 2: Streaming UI
|
|
184
|
+
User asks: "Real-time streaming like ChatGPT"
|
|
185
|
+
Response approach:
|
|
186
|
+
1. Add WebSocket endpoint
|
|
187
|
+
2. Use `astream_events` for token-by-token streaming
|
|
188
|
+
3. Send chunks via WebSocket
|
|
189
|
+
4. Frontend connects and renders
|
|
190
|
+
|
|
191
|
+
## Notes
|
|
192
|
+
- Always use PostgresSaver in production (not MemorySaver)
|
|
193
|
+
- Set resource limits in Docker (memory, CPU)
|
|
194
|
+
- Add health check endpoint: `GET /health`
|
|
195
|
+
- Rate limit the API endpoints
|
|
196
|
+
- Use environment variables for API keys, never hardcode
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-agent-hitl
|
|
3
|
+
description: Configure human-in-the-loop approval workflows for Deep Agents. Use when requiring human approval before destructive operations like file deletion, deployment, or email sending.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Maestro
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Deep Agent Human-in-the-Loop
|
|
9
|
+
|
|
10
|
+
Configure approval gates that pause the agent and wait for human approval before executing sensitive operations.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
- When the agent should ask before deleting files
|
|
14
|
+
- When deploys require human approval
|
|
15
|
+
- When sending emails/messages needs confirmation
|
|
16
|
+
- When any destructive operation needs a gate
|
|
17
|
+
|
|
18
|
+
## Available Operations
|
|
19
|
+
1. Configure `interrupt_on` per tool
|
|
20
|
+
2. Handle approval/rejection flow
|
|
21
|
+
3. Resume after approval
|
|
22
|
+
4. Custom approval decisions
|
|
23
|
+
|
|
24
|
+
## Multi-Step Workflow
|
|
25
|
+
|
|
26
|
+
### Step 1: Configure Interrupts
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from deepagents import create_deep_agent
|
|
30
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
31
|
+
|
|
32
|
+
agent = create_deep_agent(
|
|
33
|
+
model="anthropic:claude-sonnet-4-6",
|
|
34
|
+
tools=[delete_file, read_file, send_email, deploy],
|
|
35
|
+
interrupt_on={
|
|
36
|
+
"delete_file": True, # Always ask
|
|
37
|
+
"read_file": False, # Never ask
|
|
38
|
+
"send_email": True, # Always ask
|
|
39
|
+
"deploy": True, # Always ask
|
|
40
|
+
"write_file": { # Custom decisions
|
|
41
|
+
"allowed_decisions": ["approve", "reject", "modify"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
checkpointer=MemorySaver() # REQUIRED for interrupts
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Step 2: Run and Handle Interrupts
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
config = {"configurable": {"thread_id": "session-1"}}
|
|
52
|
+
|
|
53
|
+
# Agent runs until it hits an interrupt
|
|
54
|
+
result = agent.invoke(
|
|
55
|
+
{"messages": [{"role": "user", "content": "Delete the old log files"}]},
|
|
56
|
+
config=config
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Check if agent is waiting for approval
|
|
60
|
+
if result.get("__interrupt__"):
|
|
61
|
+
interrupt = result["__interrupt__"]
|
|
62
|
+
print(f"Agent wants to: {interrupt['tool']} with args: {interrupt['args']}")
|
|
63
|
+
|
|
64
|
+
# Approve
|
|
65
|
+
result = agent.invoke(
|
|
66
|
+
{"messages": [{"role": "user", "content": "approved"}]},
|
|
67
|
+
config=config
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Or reject
|
|
71
|
+
# result = agent.invoke(
|
|
72
|
+
# {"messages": [{"role": "user", "content": "rejected, don't delete those"}]},
|
|
73
|
+
# config=config
|
|
74
|
+
# )
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Step 3: Build Approval UI (FastAPI)
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from fastapi import FastAPI
|
|
81
|
+
from pydantic import BaseModel
|
|
82
|
+
|
|
83
|
+
app = FastAPI()
|
|
84
|
+
|
|
85
|
+
class ApprovalRequest(BaseModel):
|
|
86
|
+
session_id: str
|
|
87
|
+
decision: str # "approve" or "reject"
|
|
88
|
+
reason: str = ""
|
|
89
|
+
|
|
90
|
+
@app.post("/api/approve")
|
|
91
|
+
async def approve(req: ApprovalRequest):
|
|
92
|
+
config = {"configurable": {"thread_id": req.session_id}}
|
|
93
|
+
result = agent.invoke(
|
|
94
|
+
{"messages": [{"role": "user", "content": req.decision}]},
|
|
95
|
+
config=config
|
|
96
|
+
)
|
|
97
|
+
return {"status": "resumed", "result": result["messages"][-1].content}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 4: Verify
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -c "
|
|
104
|
+
from deepagents import create_deep_agent
|
|
105
|
+
from langchain.tools import tool
|
|
106
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
107
|
+
|
|
108
|
+
@tool
|
|
109
|
+
def dangerous_operation(action: str) -> str:
|
|
110
|
+
'''Execute a dangerous operation.'''
|
|
111
|
+
return f'Executed: {action}'
|
|
112
|
+
|
|
113
|
+
agent = create_deep_agent(
|
|
114
|
+
tools=[dangerous_operation],
|
|
115
|
+
interrupt_on={'dangerous_operation': True},
|
|
116
|
+
checkpointer=MemorySaver()
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
config = {'configurable': {'thread_id': 'test-hitl'}}
|
|
120
|
+
result = agent.invoke(
|
|
121
|
+
{'messages': [{'role': 'user', 'content': 'Run dangerous operation: cleanup'}]},
|
|
122
|
+
config=config
|
|
123
|
+
)
|
|
124
|
+
print('Interrupted:', bool(result.get('__interrupt__')))
|
|
125
|
+
"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Resources
|
|
129
|
+
- `references/hitl-patterns.md` — Human-in-the-loop patterns
|
|
130
|
+
|
|
131
|
+
## Examples
|
|
132
|
+
|
|
133
|
+
### Example 1: Approve Before Deploy
|
|
134
|
+
User asks: "Agent should ask before deploying"
|
|
135
|
+
Response approach:
|
|
136
|
+
1. Add `"deploy": True` to `interrupt_on`
|
|
137
|
+
2. Agent pauses before calling deploy tool
|
|
138
|
+
3. Show user what will be deployed
|
|
139
|
+
4. Resume on approval
|
|
140
|
+
|
|
141
|
+
### Example 2: Custom Approval Decisions
|
|
142
|
+
User asks: "For file writes, allow approve, reject, or modify"
|
|
143
|
+
Response approach:
|
|
144
|
+
1. Use `{"write_file": {"allowed_decisions": ["approve", "reject", "modify"]}}`
|
|
145
|
+
2. If "modify", user provides updated content
|
|
146
|
+
3. Agent retries with modified input
|
|
147
|
+
|
|
148
|
+
## Notes
|
|
149
|
+
- `checkpointer` is REQUIRED — without it, interrupts don't work
|
|
150
|
+
- Agent remembers state during interrupt — it resumes exactly where it paused
|
|
151
|
+
- Use for: delete, deploy, send email, run destructive commands
|
|
152
|
+
- Don't interrupt on read-only operations (wastes user's time)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-agent-memory
|
|
3
|
+
description: Configure persistent memory for Deep Agents using AGENTS.md and LangGraph Store. Use when the agent needs to remember context across sessions, persist learnings, or maintain long-term knowledge.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Maestro
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Deep Agent Memory
|
|
9
|
+
|
|
10
|
+
Give your Deep Agent persistent memory that survives across sessions using AGENTS.md files and LangGraph Store.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
- When the agent should remember context between sessions
|
|
14
|
+
- When loading project-specific knowledge at startup
|
|
15
|
+
- When persisting learnings across threads
|
|
16
|
+
- When configuring AGENTS.md for persistent context
|
|
17
|
+
|
|
18
|
+
## Available Operations
|
|
19
|
+
1. Load AGENTS.md as persistent context
|
|
20
|
+
2. Configure LangGraph Store for cross-thread memory
|
|
21
|
+
3. Use CompositeBackend for memory routing
|
|
22
|
+
4. Persist agent learnings
|
|
23
|
+
|
|
24
|
+
## Multi-Step Workflow
|
|
25
|
+
|
|
26
|
+
### Step 1: AGENTS.md as Memory
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from deepagents import create_deep_agent
|
|
30
|
+
from deepagents.backends.utils import create_file_data
|
|
31
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
32
|
+
|
|
33
|
+
agents_md = """
|
|
34
|
+
# Project Context
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
This project uses Clean Architecture with Python FastAPI.
|
|
38
|
+
|
|
39
|
+
## Conventions
|
|
40
|
+
- Use Pydantic for all DTOs
|
|
41
|
+
- Pytest for testing
|
|
42
|
+
- Ruff for linting
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
agent = create_deep_agent(
|
|
46
|
+
model="anthropic:claude-sonnet-4-6",
|
|
47
|
+
memory=["/AGENTS.md"],
|
|
48
|
+
checkpointer=MemorySaver()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
result = agent.invoke(
|
|
52
|
+
{
|
|
53
|
+
"messages": [{"role": "user", "content": "What architecture do we use?"}],
|
|
54
|
+
"files": {"/AGENTS.md": create_file_data(agents_md)}
|
|
55
|
+
},
|
|
56
|
+
config={"configurable": {"thread_id": "session-1"}}
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 2: Persistent Store Memory
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from deepagents import create_deep_agent
|
|
64
|
+
from deepagents.backends import StoreBackend
|
|
65
|
+
from langgraph.store.postgres import PostgresStore
|
|
66
|
+
from langgraph.checkpoint.postgres import PostgresSaver
|
|
67
|
+
|
|
68
|
+
# Production: PostgreSQL for both
|
|
69
|
+
store = PostgresStore(conn_string="postgresql://user:pass@localhost/agents")
|
|
70
|
+
checkpointer = PostgresSaver(conn_string="postgresql://user:pass@localhost/agents")
|
|
71
|
+
|
|
72
|
+
agent = create_deep_agent(
|
|
73
|
+
model="anthropic:claude-sonnet-4-6",
|
|
74
|
+
backend=lambda rt: StoreBackend(rt),
|
|
75
|
+
store=store,
|
|
76
|
+
checkpointer=checkpointer,
|
|
77
|
+
memory=["/AGENTS.md"]
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 3: CompositeBackend with Memory
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
|
|
85
|
+
|
|
86
|
+
composite = lambda rt: CompositeBackend(
|
|
87
|
+
default=StateBackend(rt),
|
|
88
|
+
routes={
|
|
89
|
+
"/memories/": StoreBackend(rt), # Long-term memory: persistent
|
|
90
|
+
"/workspace/": StateBackend(rt), # Working files: ephemeral
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
agent = create_deep_agent(
|
|
95
|
+
backend=composite,
|
|
96
|
+
store=InMemoryStore()
|
|
97
|
+
)
|
|
98
|
+
# Agent writes to /memories/ for things it wants to remember
|
|
99
|
+
# Agent writes to /workspace/ for temporary work
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Step 4: Consistent Thread IDs
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# Same thread_id = same conversation context
|
|
106
|
+
config = {"configurable": {"thread_id": f"project-{project_id}"}}
|
|
107
|
+
|
|
108
|
+
# First invocation
|
|
109
|
+
agent.invoke({"messages": [msg1]}, config=config)
|
|
110
|
+
|
|
111
|
+
# Later invocation — agent remembers the first one
|
|
112
|
+
agent.invoke({"messages": [msg2]}, config=config)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Step 5: Verify
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python -c "
|
|
119
|
+
from deepagents import create_deep_agent
|
|
120
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
121
|
+
|
|
122
|
+
agent = create_deep_agent(checkpointer=MemorySaver())
|
|
123
|
+
config = {'configurable': {'thread_id': 'test-memory'}}
|
|
124
|
+
|
|
125
|
+
# First message
|
|
126
|
+
agent.invoke({'messages': [{'role': 'user', 'content': 'My name is João'}]}, config=config)
|
|
127
|
+
|
|
128
|
+
# Second message — should remember
|
|
129
|
+
result = agent.invoke({'messages': [{'role': 'user', 'content': 'What is my name?'}]}, config=config)
|
|
130
|
+
print(result['messages'][-1].content)
|
|
131
|
+
"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Resources
|
|
135
|
+
- `references/memory-patterns.md` — Memory architecture patterns
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
### Example 1: Project Context Memory
|
|
140
|
+
User asks: "Agent should know our project conventions"
|
|
141
|
+
Response approach:
|
|
142
|
+
1. Write AGENTS.md with conventions
|
|
143
|
+
2. Pass via `memory=["/AGENTS.md"]`
|
|
144
|
+
3. Agent loads it every session
|
|
145
|
+
|
|
146
|
+
### Example 2: Learning Memory
|
|
147
|
+
User asks: "Agent should remember what worked in previous sessions"
|
|
148
|
+
Response approach:
|
|
149
|
+
1. Use CompositeBackend with StoreBackend for `/memories/`
|
|
150
|
+
2. Agent writes learnings to `/memories/`
|
|
151
|
+
3. Next session, agent reads from `/memories/`
|
|
152
|
+
|
|
153
|
+
## Notes
|
|
154
|
+
- `checkpointer` is REQUIRED for memory to persist
|
|
155
|
+
- AGENTS.md follows the agents.md standard (https://agents.md/)
|
|
156
|
+
- Use PostgresStore for production, InMemoryStore for dev
|
|
157
|
+
- Thread ID determines conversation scope
|
|
158
|
+
- Memory files are loaded at session start
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-agent-middleware
|
|
3
|
+
description: Configure and create custom middleware for Deep Agents including logging, validation, and tool interception. Use when adding cross-cutting concerns, logging tool calls, or modifying agent behavior.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Maestro
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Deep Agent Middleware
|
|
9
|
+
|
|
10
|
+
Add cross-cutting concerns to Deep Agents with built-in and custom middleware for logging, validation, rate limiting, and tool interception.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
- When you need to log all tool calls
|
|
14
|
+
- When you need to validate inputs before tools execute
|
|
15
|
+
- When you need rate limiting on API calls
|
|
16
|
+
- When you need custom behavior around tool execution
|
|
17
|
+
|
|
18
|
+
## Available Operations
|
|
19
|
+
1. Use built-in middleware (TodoList, Filesystem, SubAgent, Summarization)
|
|
20
|
+
2. Create custom middleware with `@wrap_tool_call`
|
|
21
|
+
3. Chain multiple middleware
|
|
22
|
+
4. Add conditional middleware
|
|
23
|
+
|
|
24
|
+
## Multi-Step Workflow
|
|
25
|
+
|
|
26
|
+
### Step 1: Understand Built-in Middleware
|
|
27
|
+
|
|
28
|
+
These are automatically active:
|
|
29
|
+
|
|
30
|
+
| Middleware | What it does | Can disable? |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| TodoListMiddleware | `write_todos` tool for planning | No |
|
|
33
|
+
| FilesystemMiddleware | `ls`, `read_file`, `write_file`, etc. | No |
|
|
34
|
+
| SubAgentMiddleware | `task` tool for delegation | No |
|
|
35
|
+
| SummarizationMiddleware | Compresses long conversations | No |
|
|
36
|
+
| AnthropicPromptCachingMiddleware | Token optimization for Anthropic | Auto |
|
|
37
|
+
| PatchToolCallsMiddleware | Repairs broken tool calls | Auto |
|
|
38
|
+
|
|
39
|
+
### Step 2: Create Custom Middleware
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# agent/middleware.py
|
|
43
|
+
from langchain.agents.middleware import wrap_tool_call
|
|
44
|
+
import time
|
|
45
|
+
import logging
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
@wrap_tool_call
|
|
50
|
+
def log_tool_calls(request, handler):
|
|
51
|
+
"""Log every tool call with timing."""
|
|
52
|
+
start = time.time()
|
|
53
|
+
logger.info(f"[TOOL] {request.name} called with: {request.args}")
|
|
54
|
+
|
|
55
|
+
result = handler(request)
|
|
56
|
+
|
|
57
|
+
duration = time.time() - start
|
|
58
|
+
logger.info(f"[TOOL] {request.name} completed in {duration:.2f}s")
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
@wrap_tool_call
|
|
62
|
+
def validate_file_paths(request, handler):
|
|
63
|
+
"""Prevent access to sensitive files."""
|
|
64
|
+
if request.name in ("read_file", "write_file", "edit_file"):
|
|
65
|
+
path = request.args.get("path", "")
|
|
66
|
+
blocked = [".env", "secrets", "credentials", ".git/config"]
|
|
67
|
+
if any(b in path for b in blocked):
|
|
68
|
+
return f"Access denied: {path} is a protected file"
|
|
69
|
+
|
|
70
|
+
return handler(request)
|
|
71
|
+
|
|
72
|
+
@wrap_tool_call
|
|
73
|
+
def rate_limit_api_calls(request, handler):
|
|
74
|
+
"""Rate limit external API calls."""
|
|
75
|
+
api_tools = ["internet_search", "call_api"]
|
|
76
|
+
if request.name in api_tools:
|
|
77
|
+
time.sleep(1) # Simple rate limit
|
|
78
|
+
|
|
79
|
+
return handler(request)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 3: Register Middleware
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from deepagents import create_deep_agent
|
|
86
|
+
from agent.middleware import log_tool_calls, validate_file_paths, rate_limit_api_calls
|
|
87
|
+
|
|
88
|
+
agent = create_deep_agent(
|
|
89
|
+
model="anthropic:claude-sonnet-4-6",
|
|
90
|
+
middleware=[
|
|
91
|
+
log_tool_calls,
|
|
92
|
+
validate_file_paths,
|
|
93
|
+
rate_limit_api_calls,
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step 4: Verify
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Run agent and check logs
|
|
102
|
+
python agent/main.py 2>&1 | grep "[TOOL]"
|
|
103
|
+
|
|
104
|
+
# Run tests
|
|
105
|
+
pytest tests/test_middleware.py -v
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Resources
|
|
109
|
+
- `references/middleware-guide.md` — Middleware patterns and best practices
|
|
110
|
+
|
|
111
|
+
## Examples
|
|
112
|
+
|
|
113
|
+
### Example 1: Add Logging
|
|
114
|
+
User asks: "Log all tool calls with timing"
|
|
115
|
+
Response approach:
|
|
116
|
+
1. Create `@wrap_tool_call` that logs name, args, duration
|
|
117
|
+
2. Register in `create_deep_agent(middleware=[...])`
|
|
118
|
+
3. Run agent, check logs
|
|
119
|
+
|
|
120
|
+
### Example 2: Block Sensitive Files
|
|
121
|
+
User asks: "Agent should not read .env files"
|
|
122
|
+
Response approach:
|
|
123
|
+
1. Create middleware that checks file paths
|
|
124
|
+
2. Return "Access denied" for blocked paths
|
|
125
|
+
3. Test with agent trying to read .env
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
- Do NOT mutate agent attributes after initialization — use graph state
|
|
129
|
+
- Middleware executes in order: first registered = outermost wrapper
|
|
130
|
+
- Built-in middleware cannot be removed
|
|
131
|
+
- Keep middleware lightweight — it runs on every tool call
|