Spaces:
Sleeping
Sleeping
Merge pull request #55 from NielsRogge/feature/update_neurips_dates
Browse files- agents/MODAL_DEBUGGING.md +82 -0
- agents/agent.py +3 -18
- agents/modal_agent.py +14 -16
- src/data/conferences/neurips.yml +2 -0
agents/MODAL_DEBUGGING.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modal Agent Debugging - SOLVED
|
| 2 |
+
|
| 3 |
+
## Problem Summary
|
| 4 |
+
|
| 5 |
+
When running the conference deadline agent on Modal, the `claude-agent-sdk` query only received 1 message (the `SystemMessage` init) and then completed immediately, without any actual Claude response or tool calls.
|
| 6 |
+
|
| 7 |
+
## Root Causes Found
|
| 8 |
+
|
| 9 |
+
### 1. ✅ Python Version (Minor Factor)
|
| 10 |
+
Changed from Python 3.12 to 3.11 to match the working `github_issues_reply` example.
|
| 11 |
+
|
| 12 |
+
### 2. ✅ sys.path Order Bug (MAJOR)
|
| 13 |
+
The `sys.path.insert()` calls were in the wrong order, causing Python to import the agent code from the **cloned repo** (which had old code) instead of the **mounted local code**.
|
| 14 |
+
|
| 15 |
+
**Bug:**
|
| 16 |
+
```python
|
| 17 |
+
sys.path.insert(0, "/home/agent/app") # Position 0
|
| 18 |
+
sys.path.insert(0, REPO_DIR) # This pushes /home/agent/app to position 1!
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
**Fix:**
|
| 22 |
+
```python
|
| 23 |
+
sys.path.insert(0, REPO_DIR) # Position 0
|
| 24 |
+
sys.path.insert(0, "/home/agent/app") # This is now at position 0!
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### 3. ✅ Missing USE_CWD_AS_PROJECT_ROOT
|
| 28 |
+
The `agent.py` uses `PROJECT_ROOT` to find conference YAML files. Without the environment variable, it pointed to `/home/agent/app` instead of the cloned repo where the files actually are.
|
| 29 |
+
|
| 30 |
+
**Fix in modal_agent.py:**
|
| 31 |
+
```python
|
| 32 |
+
os.environ["USE_CWD_AS_PROJECT_ROOT"] = "1"
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
**Fix in agent.py:**
|
| 36 |
+
```python
|
| 37 |
+
PROJECT_ROOT = Path(os.getcwd()) if os.environ.get("USE_CWD_AS_PROJECT_ROOT") else SCRIPT_DIR.parent
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 4. ✅ Overly Complex ClaudeAgentOptions
|
| 41 |
+
Removed unnecessary options that might cause issues:
|
| 42 |
+
- Removed `mcp_servers` (Exa MCP server)
|
| 43 |
+
- Removed `cwd` parameter
|
| 44 |
+
- Removed `stderr` callback
|
| 45 |
+
- Removed `max_turns`
|
| 46 |
+
- Removed `extra_args={"debug-to-stderr": None}`
|
| 47 |
+
|
| 48 |
+
**Simplified to:**
|
| 49 |
+
```python
|
| 50 |
+
options = ClaudeAgentOptions(
|
| 51 |
+
system_prompt=system_prompt,
|
| 52 |
+
permission_mode="bypassPermissions",
|
| 53 |
+
settings=settings_path,
|
| 54 |
+
)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
## Working Configuration
|
| 58 |
+
|
| 59 |
+
### modal_agent.py Key Points:
|
| 60 |
+
- Python 3.11 (matching working example)
|
| 61 |
+
- Correct sys.path order (mounted code takes priority)
|
| 62 |
+
- Set `USE_CWD_AS_PROJECT_ROOT=1` before importing agent
|
| 63 |
+
- Minimal secrets: just `anthropic` and `github-token`
|
| 64 |
+
|
| 65 |
+
### agent.py Key Points:
|
| 66 |
+
- Simple `ClaudeAgentOptions` with just system_prompt, permission_mode, settings
|
| 67 |
+
- Dynamic `PROJECT_ROOT` based on environment variable
|
| 68 |
+
|
| 69 |
+
## Test Command
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
uv run modal run agents/modal_agent.py --conference-name neurips
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Expected: Multiple messages (50+), web searches, file edits, git operations.
|
| 76 |
+
|
| 77 |
+
## Modal Secrets Required
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
uv run modal secret create anthropic ANTHROPIC_API_KEY=<your-key>
|
| 81 |
+
uv run modal secret create github-token GH_TOKEN=<token-with-repo-scope>
|
| 82 |
+
```
|
agents/agent.py
CHANGED
|
@@ -25,13 +25,13 @@ from claude_agent_sdk import (
|
|
| 25 |
UserMessage,
|
| 26 |
query,
|
| 27 |
)
|
| 28 |
-
from claude_agent_sdk.types import McpHttpServerConfig
|
| 29 |
|
| 30 |
# Script directory for resolving relative paths
|
| 31 |
SCRIPT_DIR = Path(__file__).parent
|
| 32 |
|
| 33 |
-
# Project root directory
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
async def read_prompt(filename: str) -> str:
|
|
@@ -129,24 +129,10 @@ async def find_conference_deadlines(conference_name: str) -> None:
|
|
| 129 |
settings_path = Path.home() / ".claude" / "settings.local.json"
|
| 130 |
settings_path = str(settings_path)
|
| 131 |
|
| 132 |
-
# Configure Exa MCP server for web search capabilities
|
| 133 |
-
# See: https://docs.exa.ai/reference/exa-mcp
|
| 134 |
-
exa_api_key = os.environ.get("EXA_API_KEY", "")
|
| 135 |
-
# ?exaApiKey={exa_api_key}
|
| 136 |
-
exa_mcp_url = f"https://mcp.exa.ai/mcp"
|
| 137 |
-
|
| 138 |
-
mcp_servers: dict[str, McpHttpServerConfig] = {
|
| 139 |
-
"exa": McpHttpServerConfig(
|
| 140 |
-
type="http",
|
| 141 |
-
url=exa_mcp_url,
|
| 142 |
-
)
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
options = ClaudeAgentOptions(
|
| 146 |
system_prompt=system_prompt,
|
| 147 |
permission_mode="bypassPermissions",
|
| 148 |
settings=settings_path,
|
| 149 |
-
mcp_servers=mcp_servers,
|
| 150 |
)
|
| 151 |
|
| 152 |
# Run the agent query
|
|
@@ -160,7 +146,6 @@ async def find_conference_deadlines(conference_name: str) -> None:
|
|
| 160 |
print(f"Settings path exists: {Path(settings_path).exists()}")
|
| 161 |
print(f"System prompt length: {len(system_prompt)}")
|
| 162 |
print(f"Conference data loaded: {len(conference_data)} characters")
|
| 163 |
-
print(f"Exa MCP server configured: {'Yes (API key set)' if exa_api_key else 'Yes (no API key)'}")
|
| 164 |
|
| 165 |
message_count = 0
|
| 166 |
try:
|
|
|
|
| 25 |
UserMessage,
|
| 26 |
query,
|
| 27 |
)
|
|
|
|
| 28 |
|
| 29 |
# Script directory for resolving relative paths
|
| 30 |
SCRIPT_DIR = Path(__file__).parent
|
| 31 |
|
| 32 |
+
# Project root directory - use current working directory if set (for Modal),
|
| 33 |
+
# otherwise use parent of agents/ directory (for local development)
|
| 34 |
+
PROJECT_ROOT = Path(os.getcwd()) if os.environ.get("USE_CWD_AS_PROJECT_ROOT") else SCRIPT_DIR.parent
|
| 35 |
|
| 36 |
|
| 37 |
async def read_prompt(filename: str) -> str:
|
|
|
|
| 129 |
settings_path = Path.home() / ".claude" / "settings.local.json"
|
| 130 |
settings_path = str(settings_path)
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
options = ClaudeAgentOptions(
|
| 133 |
system_prompt=system_prompt,
|
| 134 |
permission_mode="bypassPermissions",
|
| 135 |
settings=settings_path,
|
|
|
|
| 136 |
)
|
| 137 |
|
| 138 |
# Run the agent query
|
|
|
|
| 146 |
print(f"Settings path exists: {Path(settings_path).exists()}")
|
| 147 |
print(f"System prompt length: {len(system_prompt)}")
|
| 148 |
print(f"Conference data loaded: {len(conference_data)} characters")
|
|
|
|
| 149 |
|
| 150 |
message_count = 0
|
| 151 |
try:
|
agents/modal_agent.py
CHANGED
|
@@ -21,10 +21,8 @@ Setup:
|
|
| 21 |
1. Install Modal: uv add modal
|
| 22 |
2. Authenticate: uv run modal setup
|
| 23 |
3. Create secrets:
|
| 24 |
-
uv run modal secret create anthropic-base-url ANTHROPIC_BASE_URL=<your-base-url>
|
| 25 |
uv run modal secret create anthropic ANTHROPIC_API_KEY=<your-key>
|
| 26 |
-
uv run modal secret create github-
|
| 27 |
-
uv run modal secret create exa EXA_API_KEY=<your-key> # optional
|
| 28 |
|
| 29 |
Note: The GITHUB_PAT token needs the following scopes:
|
| 30 |
- `repo` - for cloning and pushing to the repository
|
|
@@ -62,7 +60,7 @@ def get_conferences(base_dir: str = REPO_DIR) -> list[str]:
|
|
| 62 |
|
| 63 |
# Define the Modal image with all required dependencies
|
| 64 |
image = (
|
| 65 |
-
modal.Image.debian_slim(python_version="3.
|
| 66 |
.apt_install("git", "curl")
|
| 67 |
.run_commands(
|
| 68 |
# Install GitHub CLI
|
|
@@ -105,10 +103,8 @@ app = modal.App(
|
|
| 105 |
name="conference-deadlines-agent",
|
| 106 |
image=image,
|
| 107 |
secrets=[
|
| 108 |
-
modal.Secret.from_name("anthropic-base-url"),
|
| 109 |
modal.Secret.from_name("anthropic"),
|
| 110 |
-
modal.Secret.from_name("github-
|
| 111 |
-
modal.Secret.from_name("exa", required=False),
|
| 112 |
],
|
| 113 |
)
|
| 114 |
|
|
@@ -118,12 +114,9 @@ def setup_git_and_clone():
|
|
| 118 |
import os
|
| 119 |
import subprocess
|
| 120 |
|
| 121 |
-
|
| 122 |
-
if not
|
| 123 |
-
raise ValueError("
|
| 124 |
-
|
| 125 |
-
# Set GH_TOKEN for GitHub CLI authentication
|
| 126 |
-
os.environ["GH_TOKEN"] = github_pat
|
| 127 |
|
| 128 |
# Configure git user
|
| 129 |
subprocess.run(
|
|
@@ -144,7 +137,7 @@ def setup_git_and_clone():
|
|
| 144 |
# Store credentials
|
| 145 |
credentials_file = os.path.expanduser("~/.git-credentials")
|
| 146 |
with open(credentials_file, "w") as f:
|
| 147 |
-
f.write(f"https://x-access-token:{
|
| 148 |
os.chmod(credentials_file, 0o600)
|
| 149 |
|
| 150 |
# Clone the repository if it doesn't exist
|
|
@@ -189,13 +182,18 @@ def process_single_conference(conference_name: str) -> dict:
|
|
| 189 |
setup_git_and_clone()
|
| 190 |
|
| 191 |
# Add the app directory to the path for imports
|
| 192 |
-
|
|
|
|
| 193 |
sys.path.insert(0, REPO_DIR)
|
|
|
|
| 194 |
|
| 195 |
# Change to repo directory so relative paths work
|
| 196 |
os.chdir(REPO_DIR)
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
-
# Import and run the agent
|
| 199 |
from agents.agent import find_conference_deadlines
|
| 200 |
|
| 201 |
async def _process():
|
|
|
|
| 21 |
1. Install Modal: uv add modal
|
| 22 |
2. Authenticate: uv run modal setup
|
| 23 |
3. Create secrets:
|
|
|
|
| 24 |
uv run modal secret create anthropic ANTHROPIC_API_KEY=<your-key>
|
| 25 |
+
uv run modal secret create github-token GH_TOKEN=<token-with-repo-and-pr-scope>
|
|
|
|
| 26 |
|
| 27 |
Note: The GITHUB_PAT token needs the following scopes:
|
| 28 |
- `repo` - for cloning and pushing to the repository
|
|
|
|
| 60 |
|
| 61 |
# Define the Modal image with all required dependencies
|
| 62 |
image = (
|
| 63 |
+
modal.Image.debian_slim(python_version="3.11")
|
| 64 |
.apt_install("git", "curl")
|
| 65 |
.run_commands(
|
| 66 |
# Install GitHub CLI
|
|
|
|
| 103 |
name="conference-deadlines-agent",
|
| 104 |
image=image,
|
| 105 |
secrets=[
|
|
|
|
| 106 |
modal.Secret.from_name("anthropic"),
|
| 107 |
+
modal.Secret.from_name("github-token"),
|
|
|
|
| 108 |
],
|
| 109 |
)
|
| 110 |
|
|
|
|
| 114 |
import os
|
| 115 |
import subprocess
|
| 116 |
|
| 117 |
+
github_token = os.environ.get("GH_TOKEN", "")
|
| 118 |
+
if not github_token:
|
| 119 |
+
raise ValueError("GH_TOKEN environment variable is required")
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
# Configure git user
|
| 122 |
subprocess.run(
|
|
|
|
| 137 |
# Store credentials
|
| 138 |
credentials_file = os.path.expanduser("~/.git-credentials")
|
| 139 |
with open(credentials_file, "w") as f:
|
| 140 |
+
f.write(f"https://x-access-token:{github_token}@github.com\n")
|
| 141 |
os.chmod(credentials_file, 0o600)
|
| 142 |
|
| 143 |
# Clone the repository if it doesn't exist
|
|
|
|
| 182 |
setup_git_and_clone()
|
| 183 |
|
| 184 |
# Add the app directory to the path for imports
|
| 185 |
+
# IMPORTANT: /home/agent/app must be first so the mounted code is used,
|
| 186 |
+
# not the cloned repo code
|
| 187 |
sys.path.insert(0, REPO_DIR)
|
| 188 |
+
sys.path.insert(0, "/home/agent/app")
|
| 189 |
|
| 190 |
# Change to repo directory so relative paths work
|
| 191 |
os.chdir(REPO_DIR)
|
| 192 |
+
|
| 193 |
+
# Tell agent.py to use current working directory as PROJECT_ROOT
|
| 194 |
+
os.environ["USE_CWD_AS_PROJECT_ROOT"] = "1"
|
| 195 |
|
| 196 |
+
# Import and run the agent (uses mounted code from /home/agent/app/agents/)
|
| 197 |
from agents.agent import find_conference_deadlines
|
| 198 |
|
| 199 |
async def _process():
|
src/data/conferences/neurips.yml
CHANGED
|
@@ -24,6 +24,8 @@
|
|
| 24 |
full_name: Conference on Neural Information Processing Systems
|
| 25 |
link: https://neurips.cc/
|
| 26 |
date: December 6-12, 2026
|
|
|
|
|
|
|
| 27 |
city: Sydney
|
| 28 |
country: Australia
|
| 29 |
era_rating: a
|
|
|
|
| 24 |
full_name: Conference on Neural Information Processing Systems
|
| 25 |
link: https://neurips.cc/
|
| 26 |
date: December 6-12, 2026
|
| 27 |
+
start: '2026-12-06'
|
| 28 |
+
end: '2026-12-12'
|
| 29 |
city: Sydney
|
| 30 |
country: Australia
|
| 31 |
era_rating: a
|