/ add_session_tool_simple.py
add_session_tool_simple.py
1 #!/usr/bin/env python3 2 """ 3 Simple script to add session_consult tool to all ECHO agents. 4 """ 5 6 import re 7 import os 8 import subprocess 9 10 AGENTS = { 11 "cto": "cto", 12 "chro": "chro", 13 "operations_head": "operations_head", 14 "product_manager": "product_manager", 15 "senior_architect": "senior_architect", 16 "uiux_engineer": "uiux_engineer", 17 "senior_developer": "senior_developer", 18 "test_lead": "test_lead", 19 } 20 21 TOOL_DEF_TEMPLATE = ''' }, 22 %{ 23 name: "session_consult", 24 description: """ 25 Query the AI assistant with conversation memory (LocalCode-style). 26 27 Maintains multi-turn conversations with automatic context injection: 28 - Your role, responsibilities, and authority limits 29 - Recent decisions and messages (last 5 each) 30 - Current system status (PostgreSQL, Redis, Ollama) 31 - Git context (branch, last commit) 32 - Conversation history (last 5 turns) 33 34 Perfect for exploratory questions, decision analysis with iterative thinking, 35 and strategy planning with follow-up questions. 36 """, 37 inputSchema: %{ 38 type: "object", 39 properties: %{ 40 question: %{ 41 type: "string", 42 description: "The question to ask the AI assistant", 43 minLength: 1 44 }, 45 session_id: %{ 46 type: "string", 47 description: "Session ID to continue conversation (optional, omit for new session)" 48 }, 49 context: %{ 50 type: "string", 51 description: "Additional context for this specific query (optional)" 52 } 53 }, 54 required: ["question"] 55 } 56 }''' 57 58 def add_session_consult(agent_dir, role): 59 """Add session_consult tool to an agent.""" 60 file_path = f"apps/{agent_dir}/lib/{agent_dir}.ex" 61 62 if not os.path.exists(file_path): 63 print(f"⚠️ Skipping {agent_dir}: File not found") 64 return False 65 66 with open(file_path, 'r') as f: 67 content = f.read() 68 69 # Check if already has session_consult 70 if '"session_consult"' in content: 71 print(f" ✓ {agent_dir} already has session_consult") 72 return True 73 74 # Backup 75 with open(f"{file_path}.backup", 'w') as f: 76 f.write(content) 77 78 #Step 1: Add tool definition before closing ] 79 # Find the last tool in the list (look for pattern "}\n ]\n end" in tools function) 80 tool_pattern = r'(\s+})\s+(\]\s+end\s+@impl true\s+def execute_tool)' 81 82 if not re.search(tool_pattern, content): 83 print(f" ❌ {agent_dir}: Could not find tools list end") 84 return False 85 86 content = re.sub( 87 tool_pattern, 88 rf'\1,{TOOL_DEF_TEMPLATE}\n \2', 89 content 90 ) 91 92 # Step 2: Add execute_tool handler before catch-all handler 93 handler_code = f''' 94 def execute_tool("session_consult", args) do 95 question = Map.fetch!(args, "question") 96 session_id = Map.get(args, "session_id") 97 context = Map.get(args, "context") 98 99 opts = if context, do: [context: context], else: [] 100 101 case DecisionHelper.consult_session(:{role}, session_id, question, opts) do 102 {{:ok, result}} -> 103 response = format_session_response(result) 104 {{:ok, response}} 105 106 {{:error, :llm_disabled}} -> 107 {{:error, "LLM is disabled for {agent_dir}. Enable with LLM_ENABLED=true or {agent_dir.upper()}_LLM_ENABLED=true"}} 108 109 {{:error, :session_not_found}} -> 110 {{:error, "Session not found: #{{session_id}}. It may have expired after 1 hour of inactivity."}} 111 112 {{:error, reason}} -> 113 {{:error, "AI consultation failed: #{{inspect(reason)}}"}} 114 end 115 end 116 ''' 117 118 # Find catch-all handler: def execute_tool(name, _args) do 119 catchall_pattern = r'(\s+def execute_tool\(name, _args\) do)' 120 content = re.sub(catchall_pattern, rf'{handler_code}\1', content) 121 122 # Step 3: Add format_session_response helper before module end 123 helper_code = f''' 124 defp format_session_response(result) do 125 model = EchoShared.LLM.Config.get_model(:{role}) 126 127 base = %{{ 128 "response" => result.response, 129 "session_id" => result.session_id, 130 "turn_count" => result.turn_count, 131 "estimated_tokens" => result.total_tokens, 132 "model" => model, 133 "agent" => "{agent_dir}" 134 }} 135 136 if result.warnings != [] do 137 Map.put(base, "warnings", result.warnings) 138 else 139 base 140 end 141 end 142 ''' 143 144 # Add before final "end" 145 content = content.rstrip() + helper_code + "\nend\n" 146 147 # Write modified content 148 with open(file_path, 'w') as f: 149 f.write(content) 150 151 # Compile to check 152 result = subprocess.run( 153 ['mix', 'compile'], 154 cwd=f"apps/{agent_dir}", 155 capture_output=True, 156 text=True 157 ) 158 159 if result.returncode == 0 and 'Generated' in result.stdout: 160 print(f" ✅ {agent_dir}: Success!") 161 os.remove(f"{file_path}.backup") 162 return True 163 else: 164 print(f" ❌ {agent_dir}: Compilation failed!") 165 print(f" {result.stderr[:200]}") 166 # Restore backup 167 with open(f"{file_path}.backup", 'r') as f: 168 with open(file_path, 'w') as out: 169 out.write(f.read()) 170 return False 171 172 def main(): 173 print("=" * 50) 174 print(" Adding session_consult to all agents") 175 print("=" * 50) 176 print() 177 178 success_count = 0 179 for agent_dir, role in AGENTS.items(): 180 print(f"📝 Processing {agent_dir} (role: {role})...") 181 if add_session_consult(agent_dir, role): 182 success_count += 1 183 print() 184 185 print("=" * 50) 186 print(f" Completed: {success_count}/{len(AGENTS)} agents updated") 187 print("=" * 50) 188 189 if __name__ == "__main__": 190 main()