55
66import json
77import re
8+ import os
9+ import subprocess
10+ import tempfile
11+ from pathlib import Path
812from typing import Dict , Optional
9- from anthropic import Anthropic
1013from ml_agent .core .context_manager import ContextManager
1114from ml_agent .core .tool_router import ToolRouter
1215
1316
17+ class ClaudeClient :
18+ """Client using Claude CLI with --continue for multi-turn."""
19+
20+ def __init__ (self ):
21+ # Check claude CLI is available
22+ try :
23+ subprocess .run (
24+ ["claude" , "--version" ],
25+ capture_output = True ,
26+ timeout = 2 ,
27+ check = True
28+ )
29+ except (FileNotFoundError , subprocess .CalledProcessError ):
30+ raise RuntimeError ("Claude CLI not found. Install it: https://claude.ai/download" )
31+ print ("✓ Using Claude CLI" )
32+ self .session_id = None
33+
34+ def messages_create (self , model : str , max_tokens : int , system : str , messages : list ) -> str :
35+ """Call Claude via CLI, using --continue for multi-turn."""
36+ # Build the prompt from messages
37+ prompt_parts = []
38+ for msg in messages :
39+ prompt_parts .append (f"[{ msg ['role' ].upper ()} ]\n { msg ['content' ]} " )
40+
41+ prompt = "\n \n " .join (prompt_parts )
42+
43+ cmd = ["claude" , "-p" , prompt ]
44+
45+ # Add system prompt as append flag
46+ if system :
47+ cmd .extend (["--append-system-prompt" , system ])
48+
49+ # Use --continue if we have a session
50+ if self .session_id :
51+ cmd .extend (["--continue" , "--resume" , self .session_id ])
52+
53+ try :
54+ result = subprocess .run (
55+ cmd ,
56+ capture_output = True ,
57+ text = True ,
58+ timeout = 120
59+ )
60+
61+ if result .returncode == 0 :
62+ output = result .stdout .strip ()
63+ # Extract session ID from output if present
64+ if "session:" in result .stderr .lower ():
65+ lines = result .stderr .split ('\n ' )
66+ for line in lines :
67+ if "session" in line .lower ():
68+ self .session_id = line .split (":" )[- 1 ].strip ()
69+ return output
70+ else :
71+ raise RuntimeError (f"Claude error: { result .stderr [:200 ]} " )
72+ except subprocess .TimeoutExpired :
73+ raise RuntimeError ("Claude call timed out" )
74+
75+
1476class DoomLoopDetector :
1577 """Detects repeated patterns that indicate doom loops."""
1678
@@ -40,7 +102,7 @@ class AgenticLoop:
40102 """Main agent loop - iterates until task completion."""
41103
42104 def __init__ (self , provider : str = "claude" ):
43- self .client = Anthropic ()
105+ self .client = ClaudeClient ()
44106 self .context = ContextManager ()
45107 self .router = ToolRouter ()
46108 self .doom_detector = DoomLoopDetector ()
@@ -49,30 +111,34 @@ def __init__(self, provider: str = "claude"):
49111
50112 def get_system_prompt (self ) -> str :
51113 """System prompt for the agent."""
52- return f"""You are an autonomous ML research agent.
114+ return f"""You are an autonomous ML research agent working on LaTeX dataset collection and model training .
53115
54- Your task: Collect LaTeX datasets, train models, and deploy them to Hugging Face Hub .
116+ IMPORTANT: You MUST call tools to complete tasks. Do not just describe what you would do .
55117
56118Available tools:
57119{ self .router .get_tool_specs ()}
58120
59- For each turn:
60- 1. Analyze the current state
61- 2. Decide the next action
62- 3. Call a tool with: <tool_call>
63- {{
64- "tool": "tool_name",
65- "params": {{"key": "value"}}
66- }}
121+ CRITICAL TOOL CALLING FORMAT:
122+ When you need to call a tool, YOU MUST respond with EXACTLY this format:
123+
124+ <tool_call>
125+ {{"tool": "tool_name", "params": {{"param1": "value1", "param2": "value2"}}}}
67126</tool_call>
68- 4. Interpret results and continue
127+
128+ Then wait for the result and continue.
129+
130+ MANDATORY WORKFLOW:
131+ 1. Start by calling collect_arxiv to fetch papers
132+ 2. After collection, call train_model with the dataset
133+ 3. After training, call deploy_model to save the result
134+ 4. Report progress at each step
135+ 5. Stop only when all steps are complete
69136
70137Guidelines:
71- - Be autonomous - make decisions without asking
72- - Iterate until completion
73- - Report progress after each step
74- - Handle failures gracefully
75- - Stop when task is complete"""
138+ - Always use tool_call blocks for actions
139+ - Be autonomous - make decisions without asking for permission
140+ - Never skip steps
141+ - Report what you did after each tool call"""
76142
77143 async def run (self , task : str , max_iterations : int = 300 ) -> Dict :
78144 """Run the agentic loop."""
@@ -90,14 +156,12 @@ async def run(self, task: str, max_iterations: int = 300) -> Dict:
90156 print (f"[Iteration { self .iteration } /{ self .max_iterations } ]" )
91157
92158 # Get response from Claude
93- response = self .client .messages . create (
159+ assistant_message = self .client .messages_create (
94160 model = "claude-opus-4-8" ,
95161 max_tokens = 2048 ,
96162 system = self .get_system_prompt (),
97163 messages = self .context .get_messages (),
98164 )
99-
100- assistant_message = response .content [0 ].text
101165 self .context .add_message ("assistant" , assistant_message )
102166
103167 print (f"Agent: { assistant_message [:200 ]} ...\n " )
0 commit comments