kader 0.1.6__py3-none-any.whl → 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,70 @@
1
+ You are Kader, an Executor Agent specialized in deep and complex tasks.
2
+
3
+ You excel at:
4
+ - Writing high-quality code and implementing features
5
+ - Deep research and comprehensive analysis
6
+ - Complex problem-solving and debugging
7
+ - Thorough documentation and testing
8
+
9
+ You have access to the following tools:
10
+
11
+ {% for tool in tools %}
12
+ {{ tool.name }}: {{ tool.description }}
13
+ {% endfor %}
14
+
15
+ ## Your Objective
16
+
17
+ Complete the assigned task safely and thoroughly. Think carefully before each action.
18
+
19
+ ## Execution Process
20
+
21
+ 1. **Analyze the Task**: Before taking any action, carefully think about:
22
+ - What exactly needs to be done
23
+ - What could go wrong and how to prevent it
24
+ - The safest approach to complete the task
25
+
26
+ 2. **Plan Your Steps**: Create a mental action plan before executing
27
+
28
+ 3. **Execute Safely**: For each action:
29
+ - Think about potential side effects
30
+ - Verify inputs before executing
31
+ - Handle errors gracefully
32
+
33
+ 4. **Track Your Work**: Keep track of:
34
+ - Files created or modified
35
+ - Commands executed
36
+ - Any issues encountered
37
+
38
+ ## Response Format
39
+
40
+ Use the following format for reasoning:
41
+
42
+ Thought: Analyze what needs to be done and plan the approach
43
+ Action: the action to take, should be one of [{{ tool_names }}]
44
+ Action Input: the input to the action
45
+ Observation: the result of the action
46
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
47
+ Thought: I have completed the task
48
+ Final Answer: [Your detailed execution report]
49
+
50
+ ## CRITICAL: Final Answer Requirements
51
+
52
+ Your Final Answer MUST include a structured execution report with:
53
+
54
+ ### What Has Been Done
55
+ - List each action completed with details
56
+
57
+ ### Files Created/Modified
58
+ - Full path and purpose of each file
59
+ - If no files: state "No files were created or modified"
60
+
61
+ ### Execution Summary
62
+ - Brief summary of the overall execution
63
+ - Whether the task was completed successfully
64
+
65
+ ### Issues/Notes
66
+ - Any errors encountered and how they were resolved
67
+ - Any warnings or important notes for the caller
68
+ - If no issues: state "No issues encountered"
69
+
70
+ Begin!
@@ -0,0 +1,71 @@
1
+ You are Kader, an intelligent planning agent. You have access to the following tools:
2
+
3
+ {% for tool in tools %}
4
+ {{ tool.name }}: {{ tool.description }}
5
+ {% endfor %}
6
+
7
+ {% if context %}
8
+ ## Previous Context (What Has Been Done)
9
+
10
+ The following summarizes what has been accomplished in previous rounds. Consider this context when planning your next steps to avoid repeating completed work:
11
+
12
+ {{ context }}
13
+
14
+ {% endif %}
15
+ Your goal is to complete the user's request by creating a plan and executing it step-by-step.
16
+
17
+ ## Core Instructions
18
+
19
+ 1. Break down the user request into logical steps.
20
+ 2. For each step, determine if a tool is needed.
21
+ 3. Execute necessary tools.
22
+ 4. CRITICAL: Always create a TODO using the TODO Tool for the plan.
23
+ 5. CRITICAL: Follow the instructions of the TODO list strictly. The current item in the TODO list is your PRIMARY instruction.
24
+ 6. CRITICAL: After completing a step (e.g., creating a file, running a test), you MUST immediately use the TODO Tool to update the status of that item to 'completed'. Do not proceed to the next item without updating the TODO list.
25
+ 7. If you have enough information, provide the final answer.
26
+
27
+ ## Using Agent as a Tool
28
+
29
+ When delegating tasks to sub-agents using the Agent Tool, follow these guidelines:
30
+
31
+ ### Task Parameter
32
+ - **Primary Instruction**: This MUST come directly from the current item in your TODO list (e.g., "Implement unit tests", "Create database schema").
33
+ - **Secondary Instructions (Objectives)**: These are the supporting details or objectives for the sub-agent (e.g., "View the code to understand the context", "Ensure 100% coverage").
34
+ - **Structure**:
35
+ - Start with the Primary Instruction.
36
+ - Follow with "Objectives:" listing the Secondary Instructions.
37
+ - **Example**:
38
+ - If the TODO item is "Implement feature X", and you need the agent to also read the docs:
39
+ - Task: "Implement feature X. Objectives: 1. Read documentation at doc/feature.md. 2. Verify implementation with tests."
40
+ - **REQUIRED**: Include instruction that the agent MUST return:
41
+ - What has been done (actions completed)
42
+ - What files have been written or modified (if any)
43
+ - A summary of the execution
44
+ - Any issues or errors that occurred during execution
45
+
46
+ ### Context Parameter (REQUIRED)
47
+ The context parameter MUST include:
48
+ 1. **Brief about latest actions**: Summarize what has been done so far and the current state
49
+ 2. **Completed actions to avoid repetition**: List actions that were successfully completed so the sub-agent does NOT repeat them
50
+
51
+ Example Agent Tool usage:
52
+ ```
53
+ task: "Create unit tests for the user authentication module"
54
+ context: "We are building a user management system. So far, the User model and AuthService have been implemented in src/auth/. Completed actions: 1) Created User model with email/password fields, 2) Implemented AuthService with login/register methods, 3) Set up pytest configuration. Do NOT recreate these files."
55
+ ```
56
+
57
+ ## Example Planning Flow
58
+
59
+ Task: Implement a ML Inference Endpoint
60
+
61
+ Plan:
62
+ 1. Analyze the requirements and design the system architecture
63
+ 2. Implement the endpoint using a web framework
64
+ 3. Test the endpoint to ensure it works as expected
65
+ 4. Deploy the endpoint to a production environment
66
+
67
+ When executing this plan:
68
+ - Create the TODO list first
69
+ - For each step, consider if delegation to an Agent Tool is appropriate
70
+ - Update TODO status after completing each step
71
+ - Provide context to sub-agents about completed work
kader/providers/ollama.py CHANGED
@@ -433,11 +433,11 @@ class OllamaProvider(BaseLLMProvider):
433
433
  models_config = {}
434
434
  for model in models:
435
435
  models_config[model] = client.show(model)
436
+ accepted_capabilities = ["completion", "tools"]
436
437
  return [
437
438
  model
438
439
  for model, config in models_config.items()
439
- if config.capabilities
440
- in [["completion", "tools", "thinking"], ["completion", "tools"]]
440
+ if set(accepted_capabilities).issubset(set(config.capabilities))
441
441
  ]
442
442
  except Exception:
443
443
  return []
kader/tools/__init__.py CHANGED
@@ -9,6 +9,7 @@ from kader.tools.exec_commands import (
9
9
  CommandExecutorTool,
10
10
  )
11
11
 
12
+ from .agent import AgentTool
12
13
  from .base import (
13
14
  # Core classes
14
15
  BaseTool,
@@ -84,6 +85,28 @@ def get_default_registry() -> ToolRegistry:
84
85
  return registry
85
86
 
86
87
 
88
+ # Module-level cached registry singleton
89
+ _cached_default_registry: ToolRegistry | None = None
90
+
91
+
92
+ def get_cached_default_registry() -> ToolRegistry:
93
+ """
94
+ Get a cached registry populated with all standard tools.
95
+
96
+ This is more efficient than get_default_registry() when called multiple times,
97
+ as it avoids repeated tool instantiation and registration.
98
+
99
+ The cached registry is created once and reused for all subsequent calls.
100
+
101
+ Returns:
102
+ Cached ToolRegistry with all standard tools registered.
103
+ """
104
+ global _cached_default_registry
105
+ if _cached_default_registry is None:
106
+ _cached_default_registry = get_default_registry()
107
+ return _cached_default_registry
108
+
109
+
87
110
  __all__ = [
88
111
  # Core classes
89
112
  "BaseTool",
@@ -125,6 +148,9 @@ __all__ = [
125
148
  "CommandExecutorTool",
126
149
  # Todo Tool
127
150
  "TodoTool",
151
+ # Agent Tool
152
+ "AgentTool",
128
153
  # Helpers
129
154
  "get_default_registry",
155
+ "get_cached_default_registry",
130
156
  ]
kader/tools/agent.py ADDED
@@ -0,0 +1,452 @@
1
+ """
2
+ Agent Tool - Use a ReActAgent as a callable tool.
3
+
4
+ Allows spawning sub-agents to execute specific tasks with isolated memory contexts.
5
+ """
6
+
7
+ import uuid
8
+ from pathlib import Path
9
+ from typing import Any, Callable, Optional, Tuple
10
+
11
+ from kader.memory import SlidingWindowConversationManager
12
+ from kader.memory.types import aread_text, save_json
13
+ from kader.prompts import ExecutorAgentPrompt
14
+ from kader.providers.base import BaseLLMProvider, Message
15
+ from kader.utils import Checkpointer, ContextAggregator
16
+
17
+ from .base import BaseTool, ParameterSchema, ToolCategory
18
+
19
+
20
+ class PersistentSlidingWindowConversationManager(SlidingWindowConversationManager):
21
+ """
22
+ SlidingWindowConversationManager with JSON persistence.
23
+
24
+ Saves the entire message history (dict format) to a JSON file
25
+ after every add_message(s) call.
26
+ """
27
+
28
+ def __init__(self, file_path: Path, window_size: int = 20) -> None:
29
+ """
30
+ Initialize with a file path for persistence.
31
+ """
32
+ super().__init__(window_size=window_size)
33
+ self.file_path = file_path
34
+
35
+ def _save(self) -> None:
36
+ """Save entire history to JSON."""
37
+ try:
38
+ # We want to save plain dicts
39
+ messages_dicts = [msg.message for msg in self._messages]
40
+ data = {"messages": messages_dicts}
41
+ # Ensure parent temp-directory exists is done by caller usually,
42
+ # but best effort here:
43
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
44
+ save_json(self.file_path, data)
45
+ except Exception:
46
+ # Best effort save
47
+ pass
48
+
49
+ def add_message(self, message: Any) -> Any:
50
+ # Call super
51
+ result = super().add_message(message)
52
+ # Save
53
+ self._save()
54
+ return result
55
+
56
+ def add_messages(self, messages: list[Any]) -> list[Any]:
57
+ # Call super
58
+ result = super().add_messages(messages)
59
+ # Save
60
+ self._save()
61
+ return result
62
+
63
+
64
+ class AgentTool(BaseTool[str]):
65
+ """
66
+ Tool that spawns a ReActAgent to execute a specific task.
67
+
68
+ Creates an agent with its own memory context and default tools
69
+ (filesystem, web, command executor) to complete the given task.
70
+
71
+ When `interrupt_before_tool=True`, the agent will pause before each tool
72
+ execution and use the `tool_confirmation_callback` to get user confirmation.
73
+
74
+ Example:
75
+ # Autonomous execution (no interrupts)
76
+ tool = AgentTool(name="research_agent", interrupt_before_tool=False)
77
+ result = tool.execute(task="Find the current stock price of AAPL")
78
+
79
+ # Interactive execution with tool confirmation
80
+ def my_callback(tool_info: str) -> Tuple[bool, Optional[str]]:
81
+ user_input = input(f"Execute {tool_info}? [y/n]: ")
82
+ return (user_input.lower() == 'y', None)
83
+
84
+ tool = AgentTool(
85
+ name="research_agent",
86
+ interrupt_before_tool=True,
87
+ tool_confirmation_callback=my_callback
88
+ )
89
+ result = tool.execute(task="Find info about topic X")
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ name: str,
95
+ description: str = "Execute a specific task using an AI agent",
96
+ provider: Optional[BaseLLMProvider] = None,
97
+ model_name: str = "qwen3-coder:480b-cloud",
98
+ interrupt_before_tool: bool = True,
99
+ tool_confirmation_callback: Optional[
100
+ Callable[..., Tuple[bool, Optional[str]]]
101
+ ] = None,
102
+ direct_execution_callback: Optional[Callable[..., None]] = None,
103
+ tool_execution_result_callback: Optional[Callable[..., None]] = None,
104
+ ) -> None:
105
+ """
106
+ Initialize the AgentTool.
107
+
108
+ Args:
109
+ name: Name of the agent tool (used as identifier).
110
+ description: Description of what this agent does.
111
+ provider: Optional LLM provider (uses OllamaProvider by default).
112
+ model_name: Model to use for the agent.
113
+ interrupt_before_tool: If True, pause before tool execution for user
114
+ confirmation. The task will only complete when the agent returns
115
+ its final response.
116
+ tool_confirmation_callback: Callback function for tool confirmation.
117
+ Should return (should_execute: bool, additional_context: Optional[str]).
118
+ If not provided and interrupt_before_tool=True, uses stdin prompts.
119
+ """
120
+ super().__init__(
121
+ name=name,
122
+ description=description,
123
+ parameters=[
124
+ ParameterSchema(
125
+ name="task",
126
+ type="string",
127
+ description="The specific task for the agent to execute",
128
+ required=True,
129
+ ),
130
+ ParameterSchema(
131
+ name="context",
132
+ type="string",
133
+ description="Context to provide to the agent before executing the task",
134
+ required=True,
135
+ ),
136
+ ],
137
+ category=ToolCategory.UTILITY,
138
+ )
139
+ self._provider = provider
140
+ self._model_name = model_name
141
+ self._interrupt_before_tool = interrupt_before_tool
142
+ self._tool_confirmation_callback = tool_confirmation_callback
143
+ self._direct_execution_callback = direct_execution_callback
144
+ self._tool_execution_result_callback = tool_execution_result_callback
145
+
146
+ def _load_aggregated_context(self, main_session_id: str) -> str | None:
147
+ """
148
+ Load the aggregated checkpoint from executors directory if it exists.
149
+
150
+ Args:
151
+ main_session_id: The main session ID
152
+
153
+ Returns:
154
+ Content of the aggregated checkpoint, or None if not found
155
+ """
156
+ if main_session_id == "standalone":
157
+ return None
158
+
159
+ home = Path.home()
160
+ aggregated_path = (
161
+ home
162
+ / ".kader"
163
+ / "memory"
164
+ / "sessions"
165
+ / main_session_id
166
+ / "executors"
167
+ / "checkpoint.md"
168
+ )
169
+
170
+ if aggregated_path.exists():
171
+ try:
172
+ return aggregated_path.read_text(encoding="utf-8")
173
+ except Exception:
174
+ return None
175
+ return None
176
+
177
+ async def _aload_aggregated_context(self, main_session_id: str) -> str | None:
178
+ """
179
+ Asynchronously load the aggregated checkpoint from executors directory.
180
+
181
+ Args:
182
+ main_session_id: The main session ID
183
+
184
+ Returns:
185
+ Content of the aggregated checkpoint, or None if not found
186
+ """
187
+ if main_session_id == "standalone":
188
+ return None
189
+
190
+ home = Path.home()
191
+ aggregated_path = (
192
+ home
193
+ / ".kader"
194
+ / "memory"
195
+ / "sessions"
196
+ / main_session_id
197
+ / "executors"
198
+ / "checkpoint.md"
199
+ )
200
+
201
+ if aggregated_path.exists():
202
+ try:
203
+ return await aread_text(aggregated_path)
204
+ except Exception:
205
+ return None
206
+ return None
207
+
208
+ def execute(self, task: str, context: str) -> str:
209
+ """
210
+ Execute a task using a ReActAgent with isolated memory.
211
+
212
+ When interrupt_before_tool is True, the agent will pause before each tool
213
+ execution for user confirmation. The task only ends when the agent returns
214
+ its final response.
215
+
216
+ Args:
217
+ task: The task to execute.
218
+ context: Context to add to memory before the task.
219
+
220
+ Returns:
221
+ A summary of what the agent accomplished.
222
+ """
223
+ # Import here to avoid circular imports
224
+ from kader.agent.agents import ReActAgent
225
+
226
+ # Create a fresh memory manager for isolated context
227
+ # Persistence: ~/.kader/memory/sessions/<main-session-id>/executors/<agent-name>-<id>.json
228
+ execution_id = str(uuid.uuid4())
229
+ # Use propagated session ID or 'standalone' if not set
230
+ main_session_id = self._session_id if self._session_id else "standalone"
231
+
232
+ home = Path.home()
233
+ memory_dir = (
234
+ home
235
+ / ".kader"
236
+ / "memory"
237
+ / "sessions"
238
+ / main_session_id
239
+ / "executors"
240
+ / f"{self.name}-{execution_id}"
241
+ )
242
+ memory_file = memory_dir / "conversation.json"
243
+
244
+ memory = PersistentSlidingWindowConversationManager(
245
+ file_path=memory_file, window_size=20
246
+ )
247
+
248
+ # Load aggregated context from previous executors
249
+ aggregated_context = self._load_aggregated_context(main_session_id)
250
+ if aggregated_context:
251
+ full_context = f"## Previous Executor Context\n{aggregated_context}\n\n## Current Task Context\n{context}"
252
+ else:
253
+ full_context = context
254
+
255
+ # Add context to memory as user message
256
+ memory.add_message(Message.user(full_context))
257
+
258
+ # Get default tools (filesystem, web, command executor) - use cached version
259
+ from kader.tools import get_cached_default_registry
260
+
261
+ tools = get_cached_default_registry()
262
+
263
+ # Create ExecutorAgentPrompt with tool descriptions
264
+ system_prompt = ExecutorAgentPrompt(tools=tools.tools)
265
+
266
+ # Create the ReActAgent with separate memory and executor prompt
267
+ agent = ReActAgent(
268
+ name=f"{self.name}_worker",
269
+ tools=tools,
270
+ system_prompt=system_prompt,
271
+ provider=self._provider,
272
+ memory=memory,
273
+ model_name=self._model_name,
274
+ interrupt_before_tool=self._interrupt_before_tool,
275
+ tool_confirmation_callback=self._tool_confirmation_callback,
276
+ direct_execution_callback=self._direct_execution_callback,
277
+ tool_execution_result_callback=self._tool_execution_result_callback,
278
+ )
279
+
280
+ try:
281
+ # Invoke the agent with the task
282
+ # The agent will handle tool interruptions internally
283
+ response = agent.invoke(task)
284
+
285
+ # Generate checkpoint and aggregate it
286
+ try:
287
+ checkpointer = Checkpointer()
288
+ checkpoint_path = checkpointer.generate_checkpoint(str(memory_file))
289
+ checkpoint_content = Path(checkpoint_path).read_text(encoding="utf-8")
290
+
291
+ # Aggregate the checkpoint into the main executors checkpoint
292
+ if main_session_id != "standalone":
293
+ aggregator = ContextAggregator(session_id=main_session_id)
294
+ # Use relative path from executors directory
295
+ relative_path = f"{self.name}-{execution_id}/checkpoint.md"
296
+ aggregator.aggregate(relative_path, subagent_name=self.name)
297
+
298
+ # Append the agent's response to the checkpoint content if it exists
299
+ response_content = None
300
+ if hasattr(response, "content"):
301
+ response_content = str(response.content)
302
+ elif isinstance(response, dict):
303
+ response_content = str(response.get("content", str(response)))
304
+ else:
305
+ response_content = str(response)
306
+
307
+ if response_content and response_content != "None":
308
+ checkpoint_content += f"\n\nResponse:\n{response_content}"
309
+
310
+ return checkpoint_content
311
+ except Exception:
312
+ # Fallback to raw response if checkpointing fails
313
+ if hasattr(response, "content"):
314
+ return str(response.content)
315
+ elif isinstance(response, dict):
316
+ return str(response.get("content", str(response)))
317
+ else:
318
+ return str(response)
319
+
320
+ except Exception as e:
321
+ return f"Agent execution failed: {str(e)}"
322
+
323
+ async def aexecute(self, task: str, context: str) -> str:
324
+ """
325
+ Asynchronously execute a task using a ReActAgent.
326
+
327
+ When interrupt_before_tool is True, the agent will pause before each tool
328
+ execution for user confirmation. The task only ends when the agent returns
329
+ its final response.
330
+
331
+ Args:
332
+ task: The task to execute.
333
+ context: Context to add to memory before the task.
334
+
335
+ Returns:
336
+ A summary of what the agent accomplished.
337
+ """
338
+ # Import here to avoid circular imports
339
+ from kader.agent.agents import ReActAgent
340
+
341
+ # Create a fresh memory manager for isolated context
342
+ # Persistence: ~/.kader/memory/sessions/<main-session-id>/executors/<agent-name>-<id>.json
343
+ execution_id = str(uuid.uuid4())
344
+ # Use propagated session ID or 'standalone' if not set
345
+ main_session_id = self._session_id if self._session_id else "standalone"
346
+
347
+ home = Path.home()
348
+ memory_dir = (
349
+ home
350
+ / ".kader"
351
+ / "memory"
352
+ / "sessions"
353
+ / main_session_id
354
+ / "executors"
355
+ / f"{self.name}-{execution_id}"
356
+ )
357
+ memory_file = memory_dir / "conversation.json"
358
+
359
+ memory = PersistentSlidingWindowConversationManager(
360
+ file_path=memory_file, window_size=20
361
+ )
362
+
363
+ # Load aggregated context from previous executors (async)
364
+ aggregated_context = await self._aload_aggregated_context(main_session_id)
365
+ if aggregated_context:
366
+ full_context = f"## Previous Executor Context\n{aggregated_context}\n\n## Current Task Context\n{context}"
367
+ else:
368
+ full_context = context
369
+
370
+ # Add context to memory as user message
371
+ memory.add_message(Message.user(full_context))
372
+
373
+ # Get default tools (filesystem, web, command executor) - use cached version
374
+ from kader.tools import get_cached_default_registry
375
+
376
+ tools = get_cached_default_registry()
377
+
378
+ # Create ExecutorAgentPrompt with tool descriptions
379
+ system_prompt = ExecutorAgentPrompt(tools=tools.tools)
380
+
381
+ # Create the ReActAgent with separate memory and executor prompt
382
+ agent = ReActAgent(
383
+ name=f"{self.name}_worker",
384
+ tools=tools,
385
+ system_prompt=system_prompt,
386
+ provider=self._provider,
387
+ memory=memory,
388
+ model_name=self._model_name,
389
+ interrupt_before_tool=self._interrupt_before_tool,
390
+ tool_confirmation_callback=self._tool_confirmation_callback,
391
+ direct_execution_callback=self._direct_execution_callback,
392
+ tool_execution_result_callback=self._tool_execution_result_callback,
393
+ )
394
+
395
+ try:
396
+ # Invoke the agent asynchronously
397
+ # The agent will handle tool interruptions internally
398
+ response = await agent.ainvoke(task)
399
+
400
+ # Generate checkpoint and aggregate it
401
+ try:
402
+ checkpointer = Checkpointer()
403
+ checkpoint_path = await checkpointer.agenerate_checkpoint(
404
+ str(memory_file)
405
+ )
406
+ checkpoint_content = await aread_text(Path(checkpoint_path))
407
+
408
+ # Aggregate the checkpoint into the main executors checkpoint
409
+ if main_session_id != "standalone":
410
+ aggregator = ContextAggregator(session_id=main_session_id)
411
+ # Use relative path from executors directory
412
+ relative_path = f"{self.name}-{execution_id}/checkpoint.md"
413
+ await aggregator.aaggregate(relative_path, subagent_name=self.name)
414
+
415
+ # Append the agent's response to the checkpoint content if it exists
416
+ response_content = None
417
+ if hasattr(response, "content"):
418
+ response_content = str(response.content)
419
+ elif isinstance(response, dict):
420
+ response_content = str(response.get("content", str(response)))
421
+ else:
422
+ response_content = str(response)
423
+
424
+ if response_content and response_content != "None":
425
+ checkpoint_content += f"\n\nResponse:\n{response_content}"
426
+
427
+ return checkpoint_content
428
+ except Exception:
429
+ # Fallback to raw response if checkpointing fails
430
+ if hasattr(response, "content"):
431
+ return str(response.content)
432
+ elif isinstance(response, dict):
433
+ return str(response.get("content", str(response)))
434
+ else:
435
+ return str(response)
436
+
437
+ except Exception as e:
438
+ return f"Agent execution failed: {str(e)}"
439
+
440
+ def get_interruption_message(self, task: str, **kwargs: Any) -> str:
441
+ """
442
+ Get a message describing the agent action for user confirmation.
443
+
444
+ Args:
445
+ task: The task the agent will execute.
446
+
447
+ Returns:
448
+ A formatted string describing the action.
449
+ """
450
+ # Truncate long tasks for readability
451
+ task_preview = task[:100] + "..." if len(task) > 100 else task
452
+ return f"execute {self.name}: {task_preview}"
kader/tools/filesys.py CHANGED
@@ -646,5 +646,5 @@ def get_filesystem_tools(
646
646
  EditFileTool(bp, virtual_mode),
647
647
  GrepTool(bp, virtual_mode),
648
648
  GlobTool(bp, virtual_mode),
649
- SearchInDirectoryTool(bp),
649
+ # SearchInDirectoryTool(bp), #TODO: remove search in directory from file system tools
650
650
  ]