NielsRogge commited on
Commit
3f6c2d4
·
unverified ·
2 Parent(s): ec8340f081c0f3

Merge pull request #55 from NielsRogge/feature/update_neurips_dates

Browse files
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 (parent of agents/)
34
- PROJECT_ROOT = SCRIPT_DIR.parent
 
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-pat GITHUB_PAT=<token-with-repo-and-pr-scope>
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.12")
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-pat"),
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
- github_pat = os.environ.get("GITHUB_PAT", "")
122
- if not github_pat:
123
- raise ValueError("GITHUB_PAT environment variable is required")
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:{github_pat}@github.com\n")
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
- sys.path.insert(0, "/home/agent/app")
 
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