loom-agent 0.0.1__py3-none-any.whl → 0.0.3__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.

Potentially problematic release.


This version of loom-agent might be problematic. Click here for more details.

Files changed (39) hide show
  1. loom/builtin/tools/calculator.py +4 -0
  2. loom/builtin/tools/document_search.py +5 -0
  3. loom/builtin/tools/glob.py +4 -0
  4. loom/builtin/tools/grep.py +4 -0
  5. loom/builtin/tools/http_request.py +5 -0
  6. loom/builtin/tools/python_repl.py +5 -0
  7. loom/builtin/tools/read_file.py +4 -0
  8. loom/builtin/tools/task.py +105 -0
  9. loom/builtin/tools/web_search.py +4 -0
  10. loom/builtin/tools/write_file.py +4 -0
  11. loom/components/agent.py +121 -5
  12. loom/core/agent_executor.py +777 -321
  13. loom/core/compression_manager.py +17 -10
  14. loom/core/context_assembly.py +437 -0
  15. loom/core/events.py +660 -0
  16. loom/core/execution_context.py +119 -0
  17. loom/core/tool_orchestrator.py +383 -0
  18. loom/core/turn_state.py +188 -0
  19. loom/core/types.py +15 -4
  20. loom/core/unified_coordination.py +389 -0
  21. loom/interfaces/event_producer.py +172 -0
  22. loom/interfaces/tool.py +22 -1
  23. loom/security/__init__.py +13 -0
  24. loom/security/models.py +85 -0
  25. loom/security/path_validator.py +128 -0
  26. loom/security/validator.py +346 -0
  27. loom/tasks/PHASE_1_FOUNDATION/task_1.1_agent_events.md +121 -0
  28. loom/tasks/PHASE_1_FOUNDATION/task_1.2_streaming_api.md +521 -0
  29. loom/tasks/PHASE_1_FOUNDATION/task_1.3_context_assembler.md +606 -0
  30. loom/tasks/PHASE_2_CORE_FEATURES/task_2.1_tool_orchestrator.md +743 -0
  31. loom/tasks/PHASE_2_CORE_FEATURES/task_2.2_security_validator.md +676 -0
  32. loom/tasks/README.md +109 -0
  33. loom/tasks/__init__.py +11 -0
  34. loom/tasks/sql_placeholder.py +100 -0
  35. loom_agent-0.0.3.dist-info/METADATA +292 -0
  36. {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/RECORD +38 -19
  37. loom_agent-0.0.1.dist-info/METADATA +0 -457
  38. {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/WHEEL +0 -0
  39. {loom_agent-0.0.1.dist-info → loom_agent-0.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,743 @@
1
+ # Task 2.1: Implement ToolOrchestrator
2
+
3
+ **Status**: 🔄 In Progress
4
+ **Priority**: P0 (Critical)
5
+ **Estimated Time**: 2-3 days
6
+ **Started**: 2025-10-25
7
+ **Dependencies**: Task 1.1, 1.2, 1.3
8
+
9
+ ---
10
+
11
+ ## 📋 Overview
12
+
13
+ ### Objective
14
+
15
+ Implement intelligent tool orchestration that distinguishes between read-only and write tools, executing them in parallel or sequentially to prevent race conditions and improve safety.
16
+
17
+ ### Current Problem
18
+
19
+ ```python
20
+ # Loom 1.0 - All tools execute in parallel
21
+ await asyncio.gather(*[tool.execute() for tool in tool_calls])
22
+
23
+ # Problem scenarios:
24
+ # 1. ReadTool and EditTool on same file → race condition
25
+ # 2. Multiple EditTools on same file → data corruption
26
+ # 3. No consideration for tool side effects
27
+ ```
28
+
29
+ **Real Bug Example**:
30
+ ```python
31
+ # User asks: "Read config.json and update the version field"
32
+ # LLM generates:
33
+ tool_calls = [
34
+ ToolCall(name="Read", arguments={"path": "config.json"}),
35
+ ToolCall(name="Edit", arguments={"path": "config.json", ...})
36
+ ]
37
+
38
+ # Current behavior: Both execute in parallel
39
+ # Result: Edit might start before Read completes → race condition!
40
+ ```
41
+
42
+ ### Solution
43
+
44
+ ```python
45
+ # Loom 2.0 - Intelligent orchestration
46
+ class ToolOrchestrator:
47
+ async def execute_batch(self, tool_calls: List[ToolCall]):
48
+ # 1. Categorize tools
49
+ read_only = [tc for tc in tool_calls if tools[tc.name].is_read_only]
50
+ write_tools = [tc for tc in tool_calls if not tools[tc.name].is_read_only]
51
+
52
+ # 2. Execute read-only tools in parallel (safe)
53
+ if read_only:
54
+ async for result in self.execute_parallel(read_only):
55
+ yield result
56
+
57
+ # 3. Execute write tools sequentially (safe)
58
+ for tc in write_tools:
59
+ result = await self.execute_one(tc)
60
+ yield result
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 🎯 Goals
66
+
67
+ 1. **Safety**: Prevent race conditions between tools
68
+ 2. **Performance**: Parallel execution for safe operations
69
+ 3. **Flexibility**: Easy to classify new tools
70
+ 4. **Observability**: Yield AgentEvent for each execution phase
71
+
72
+ ---
73
+
74
+ ## 🏗️ Architecture
75
+
76
+ ### Component Diagram
77
+
78
+ ```
79
+ ┌─────────────────────────────────────────────────────┐
80
+ │ AgentExecutor │
81
+ │ │
82
+ │ ┌───────────────────────────────────────────────┐ │
83
+ │ │ ToolOrchestrator │ │
84
+ │ │ │ │
85
+ │ │ 1. Categorize tools (read-only vs write) │ │
86
+ │ │ 2. Execute parallel/sequential │ │
87
+ │ │ 3. Yield AgentEvent for each phase │ │
88
+ │ │ │ │
89
+ │ │ ┌─────────────┐ ┌──────────────┐ │ │
90
+ │ │ │ Read-Only │ │ Write Tools │ │ │
91
+ │ │ │ (Parallel) │ │ (Sequential) │ │ │
92
+ │ │ │ │ │ │ │ │
93
+ │ │ │ • ReadTool │ │ • EditTool │ │ │
94
+ │ │ │ • GrepTool │ │ • WriteTool │ │ │
95
+ │ │ │ • GlobTool │ │ • BashTool │ │ │
96
+ │ │ └─────────────┘ └──────────────┘ │ │
97
+ │ └───────────────────────────────────────────────┘ │
98
+ └─────────────────────────────────────────────────────┘
99
+ ```
100
+
101
+ ### Class Design
102
+
103
+ ```python
104
+ # loom/core/tool_orchestrator.py
105
+
106
+ from enum import Enum
107
+ from typing import AsyncGenerator, Dict, List
108
+ from loom.core.types import ToolCall, ToolResult
109
+ from loom.core.events import AgentEvent, AgentEventType
110
+ from loom.interfaces.tool import BaseTool
111
+
112
+
113
+ class ToolCategory(str, Enum):
114
+ """Tool execution categories for safety classification."""
115
+ READ_ONLY = "read_only" # Safe to parallelize
116
+ WRITE = "write" # Must execute sequentially
117
+ NETWORK = "network" # May need rate limiting (future)
118
+ DESTRUCTIVE = "destructive" # Requires extra validation (future)
119
+
120
+
121
+ class ToolOrchestrator:
122
+ """
123
+ Intelligent tool execution orchestrator.
124
+
125
+ Features:
126
+ - Categorize tools by safety (read-only vs write)
127
+ - Execute read-only tools in parallel
128
+ - Execute write tools sequentially
129
+ - Yield AgentEvent for observability
130
+ - Integration with permission system
131
+
132
+ Example:
133
+ ```python
134
+ orchestrator = ToolOrchestrator(
135
+ tools={"Read": ReadTool(), "Edit": EditTool()},
136
+ permission_manager=pm
137
+ )
138
+
139
+ tool_calls = [
140
+ ToolCall(name="Read", arguments={"path": "a.txt"}),
141
+ ToolCall(name="Read", arguments={"path": "b.txt"}),
142
+ ToolCall(name="Edit", arguments={"path": "c.txt"})
143
+ ]
144
+
145
+ async for event in orchestrator.execute_batch(tool_calls):
146
+ if event.type == AgentEventType.TOOL_RESULT:
147
+ print(event.tool_result.content)
148
+ ```
149
+ """
150
+
151
+ def __init__(
152
+ self,
153
+ tools: Dict[str, BaseTool],
154
+ permission_manager: Optional[PermissionManager] = None,
155
+ max_parallel: int = 5
156
+ ):
157
+ self.tools = tools
158
+ self.permission_manager = permission_manager
159
+ self.max_parallel = max_parallel
160
+
161
+ async def execute_batch(
162
+ self,
163
+ tool_calls: List[ToolCall]
164
+ ) -> AsyncGenerator[AgentEvent, None]:
165
+ """
166
+ Execute a batch of tool calls with intelligent orchestration.
167
+
168
+ Strategy:
169
+ 1. Categorize tools (read-only vs write)
170
+ 2. Execute read-only in parallel (up to max_parallel)
171
+ 3. Execute write tools sequentially
172
+ 4. Yield AgentEvent for each execution phase
173
+
174
+ Args:
175
+ tool_calls: List of tool calls to execute
176
+
177
+ Yields:
178
+ AgentEvent: Execution progress events
179
+ """
180
+ ...
181
+
182
+ def categorize_tools(
183
+ self,
184
+ tool_calls: List[ToolCall]
185
+ ) -> tuple[List[ToolCall], List[ToolCall]]:
186
+ """
187
+ Categorize tool calls into read-only and write.
188
+
189
+ Returns:
190
+ (read_only_calls, write_calls)
191
+ """
192
+ ...
193
+
194
+ async def execute_parallel(
195
+ self,
196
+ tool_calls: List[ToolCall]
197
+ ) -> AsyncGenerator[AgentEvent, None]:
198
+ """Execute read-only tools in parallel."""
199
+ ...
200
+
201
+ async def execute_sequential(
202
+ self,
203
+ tool_calls: List[ToolCall]
204
+ ) -> AsyncGenerator[AgentEvent, None]:
205
+ """Execute write tools sequentially."""
206
+ ...
207
+
208
+ async def execute_one(
209
+ self,
210
+ tool_call: ToolCall
211
+ ) -> AgentEvent:
212
+ """Execute a single tool call."""
213
+ ...
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 📝 Implementation Steps
219
+
220
+ ### Step 1: Modify BaseTool Interface
221
+
222
+ **File**: `loom/interfaces/tool.py`
223
+
224
+ ```python
225
+ # Add to BaseTool
226
+ class BaseTool(ABC):
227
+ name: str
228
+ description: str
229
+ args_schema: Type[BaseModel]
230
+
231
+ # 🆕 New attributes for orchestration
232
+ is_read_only: bool = False
233
+ """Whether this tool only reads data (safe to parallelize)."""
234
+
235
+ category: str = "general"
236
+ """Tool category: general, destructive, network."""
237
+
238
+ requires_confirmation: bool = False
239
+ """Whether this tool requires user confirmation (future)."""
240
+ ```
241
+
242
+ **Changes**:
243
+ - Add `is_read_only` boolean attribute
244
+ - Add `category` string attribute
245
+ - Add `requires_confirmation` for future use
246
+ - Update docstrings
247
+
248
+ ### Step 2: Classify Built-in Tools
249
+
250
+ **Files to modify**:
251
+ - `loom/builtin/tools/read.py`
252
+ - `loom/builtin/tools/edit.py`
253
+ - `loom/builtin/tools/write.py`
254
+ - `loom/builtin/tools/grep.py`
255
+ - `loom/builtin/tools/glob.py`
256
+ - `loom/builtin/tools/bash.py`
257
+ - Any other tools in `loom/builtin/tools/`
258
+
259
+ **Classification**:
260
+
261
+ | Tool | is_read_only | category | Rationale |
262
+ |------|--------------|----------|-----------|
263
+ | ReadTool | ✅ True | general | Only reads files |
264
+ | GrepTool | ✅ True | general | Only searches content |
265
+ | GlobTool | ✅ True | general | Only lists files |
266
+ | EditTool | ❌ False | destructive | Modifies files |
267
+ | WriteTool | ❌ False | destructive | Creates/overwrites files |
268
+ | BashTool | ❌ False | general | May have side effects |
269
+
270
+ **Example**:
271
+ ```python
272
+ # loom/builtin/tools/read.py
273
+ class ReadTool(BaseTool):
274
+ name = "Read"
275
+ description = "Read file contents"
276
+ is_read_only = True # 🆕
277
+ category = "general" # 🆕
278
+
279
+ async def run(self, path: str) -> str:
280
+ ...
281
+ ```
282
+
283
+ ### Step 3: Implement ToolOrchestrator
284
+
285
+ **File**: `loom/core/tool_orchestrator.py` (new file)
286
+
287
+ **Implementation outline**:
288
+
289
+ ```python
290
+ class ToolOrchestrator:
291
+ def __init__(self, tools, permission_manager, max_parallel=5):
292
+ self.tools = tools
293
+ self.permission_manager = permission_manager
294
+ self.max_parallel = max_parallel
295
+
296
+ async def execute_batch(self, tool_calls):
297
+ """Main orchestration logic."""
298
+ # Emit start event
299
+ yield AgentEvent(
300
+ type=AgentEventType.TOOL_CALLS_START,
301
+ metadata={"total_tools": len(tool_calls)}
302
+ )
303
+
304
+ # Categorize
305
+ read_only, write_tools = self.categorize_tools(tool_calls)
306
+
307
+ # Execute read-only in parallel
308
+ if read_only:
309
+ yield AgentEvent(
310
+ type=AgentEventType.TOOL_ORCHESTRATION,
311
+ metadata={
312
+ "phase": "parallel",
313
+ "count": len(read_only)
314
+ }
315
+ )
316
+ async for event in self.execute_parallel(read_only):
317
+ yield event
318
+
319
+ # Execute write sequentially
320
+ if write_tools:
321
+ yield AgentEvent(
322
+ type=AgentEventType.TOOL_ORCHESTRATION,
323
+ metadata={
324
+ "phase": "sequential",
325
+ "count": len(write_tools)
326
+ }
327
+ )
328
+ async for event in self.execute_sequential(write_tools):
329
+ yield event
330
+
331
+ def categorize_tools(self, tool_calls):
332
+ """Split into read-only and write."""
333
+ read_only = []
334
+ write_tools = []
335
+
336
+ for tc in tool_calls:
337
+ tool = self.tools.get(tc.name)
338
+ if tool and tool.is_read_only:
339
+ read_only.append(tc)
340
+ else:
341
+ write_tools.append(tc)
342
+
343
+ return read_only, write_tools
344
+
345
+ async def execute_parallel(self, tool_calls):
346
+ """Execute read-only tools in parallel."""
347
+ import asyncio
348
+
349
+ # Use semaphore to limit concurrency
350
+ semaphore = asyncio.Semaphore(self.max_parallel)
351
+
352
+ async def execute_with_semaphore(tc):
353
+ async with semaphore:
354
+ return await self.execute_one(tc)
355
+
356
+ # Execute all in parallel
357
+ tasks = [execute_with_semaphore(tc) for tc in tool_calls]
358
+
359
+ for coro in asyncio.as_completed(tasks):
360
+ event = await coro
361
+ yield event
362
+
363
+ async def execute_sequential(self, tool_calls):
364
+ """Execute write tools sequentially."""
365
+ for tc in tool_calls:
366
+ event = await self.execute_one(tc)
367
+ yield event
368
+
369
+ async def execute_one(self, tool_call):
370
+ """Execute a single tool."""
371
+ # Emit start event
372
+ yield AgentEvent(
373
+ type=AgentEventType.TOOL_EXECUTION_START,
374
+ tool_call=EventToolCall(
375
+ id=tool_call.id,
376
+ name=tool_call.name,
377
+ arguments=tool_call.arguments
378
+ )
379
+ )
380
+
381
+ try:
382
+ # Check permissions
383
+ if self.permission_manager:
384
+ allowed = await self.permission_manager.check_tool(
385
+ tool_call.name,
386
+ tool_call.arguments
387
+ )
388
+ if not allowed:
389
+ raise PermissionError(f"Tool {tool_call.name} not allowed")
390
+
391
+ # Execute tool
392
+ tool = self.tools[tool_call.name]
393
+ result_content = await tool.run(**tool_call.arguments)
394
+
395
+ # Create result
396
+ result = ToolResult(
397
+ tool_call_id=tool_call.id,
398
+ tool_name=tool_call.name,
399
+ content=result_content,
400
+ is_error=False
401
+ )
402
+
403
+ # Emit result event
404
+ yield AgentEvent.tool_result(result)
405
+
406
+ except Exception as e:
407
+ # Handle error
408
+ result = ToolResult(
409
+ tool_call_id=tool_call.id,
410
+ tool_name=tool_call.name,
411
+ content=str(e),
412
+ is_error=True
413
+ )
414
+
415
+ yield AgentEvent(
416
+ type=AgentEventType.TOOL_ERROR,
417
+ tool_result=result,
418
+ error=e
419
+ )
420
+ ```
421
+
422
+ ### Step 4: Integrate into AgentExecutor
423
+
424
+ **File**: `loom/core/agent_executor.py`
425
+
426
+ **Changes**:
427
+ 1. Add ToolOrchestrator import
428
+ 2. Initialize in `__init__`
429
+ 3. Replace existing tool execution with orchestrator
430
+
431
+ ```python
432
+ # In __init__
433
+ self.tool_orchestrator = ToolOrchestrator(
434
+ tools=self.tools,
435
+ permission_manager=self.permission_manager,
436
+ max_parallel=5
437
+ )
438
+
439
+ # In execute_stream (replace tool execution section)
440
+ if tool_calls:
441
+ # Use orchestrator instead of direct execution
442
+ async for event in self.tool_orchestrator.execute_batch(tc_models):
443
+ yield event
444
+
445
+ # Update history with results
446
+ if event.type == AgentEventType.TOOL_RESULT:
447
+ tool_msg = Message(
448
+ role="tool",
449
+ content=event.tool_result.content,
450
+ tool_call_id=event.tool_result.tool_call_id
451
+ )
452
+ history.append(tool_msg)
453
+ ```
454
+
455
+ ### Step 5: Add New Event Types (if needed)
456
+
457
+ **File**: `loom/core/events.py`
458
+
459
+ ```python
460
+ # Add to AgentEventType
461
+ class AgentEventType(str, Enum):
462
+ # ... existing events ...
463
+
464
+ # 🆕 Orchestration events
465
+ TOOL_ORCHESTRATION = "tool_orchestration"
466
+ """Tool orchestration phase (parallel/sequential)."""
467
+ ```
468
+
469
+ ---
470
+
471
+ ## 🧪 Testing Requirements
472
+
473
+ ### Unit Tests
474
+
475
+ **File**: `tests/unit/test_tool_orchestrator.py`
476
+
477
+ **Test cases** (target 25-30 tests):
478
+
479
+ ```python
480
+ class TestToolOrchestrator:
481
+ """Test ToolOrchestrator class."""
482
+
483
+ def test_init(self):
484
+ """Test initialization."""
485
+ ...
486
+
487
+ def test_categorize_read_only_tools(self):
488
+ """Test categorization of read-only tools."""
489
+ ...
490
+
491
+ def test_categorize_write_tools(self):
492
+ """Test categorization of write tools."""
493
+ ...
494
+
495
+ def test_categorize_mixed_tools(self):
496
+ """Test categorization of mixed tool calls."""
497
+ ...
498
+
499
+ async def test_execute_parallel_read_only(self):
500
+ """Test parallel execution of read-only tools."""
501
+ ...
502
+
503
+ async def test_execute_sequential_write(self):
504
+ """Test sequential execution of write tools."""
505
+ ...
506
+
507
+ async def test_execute_batch_mixed(self):
508
+ """Test batch execution with mixed tools."""
509
+ ...
510
+
511
+ async def test_parallel_respects_max_parallel(self):
512
+ """Test max_parallel limit is respected."""
513
+ ...
514
+
515
+ async def test_execute_one_success(self):
516
+ """Test single tool execution success."""
517
+ ...
518
+
519
+ async def test_execute_one_error(self):
520
+ """Test single tool execution error handling."""
521
+ ...
522
+
523
+ async def test_permission_check_integration(self):
524
+ """Test integration with permission manager."""
525
+ ...
526
+
527
+ async def test_event_emission(self):
528
+ """Test AgentEvent emission during execution."""
529
+ ...
530
+
531
+ async def test_parallel_execution_order_independent(self):
532
+ """Test parallel tools can complete in any order."""
533
+ ...
534
+
535
+ async def test_sequential_execution_order(self):
536
+ """Test sequential tools execute in order."""
537
+ ...
538
+
539
+
540
+ class TestToolCategorization:
541
+ """Test tool categorization logic."""
542
+
543
+ def test_read_tool_is_read_only(self):
544
+ """Verify ReadTool is classified as read-only."""
545
+ ...
546
+
547
+ def test_grep_tool_is_read_only(self):
548
+ """Verify GrepTool is classified as read-only."""
549
+ ...
550
+
551
+ def test_edit_tool_is_write(self):
552
+ """Verify EditTool is classified as write."""
553
+ ...
554
+
555
+ def test_bash_tool_is_write(self):
556
+ """Verify BashTool is classified as write."""
557
+ ...
558
+
559
+
560
+ class TestRaceConditionPrevention:
561
+ """Test race condition prevention."""
562
+
563
+ async def test_read_and_edit_same_file(self):
564
+ """Test Read and Edit on same file execute safely."""
565
+ # Read should complete before Edit starts
566
+ ...
567
+
568
+ async def test_multiple_edits_same_file(self):
569
+ """Test multiple Edits on same file are sequential."""
570
+ ...
571
+
572
+ async def test_multiple_reads_same_file(self):
573
+ """Test multiple Reads on same file can be parallel."""
574
+ ...
575
+
576
+
577
+ class TestPerformance:
578
+ """Test performance improvements."""
579
+
580
+ async def test_parallel_faster_than_sequential(self):
581
+ """Test parallel execution is faster for read-only tools."""
582
+ ...
583
+
584
+ async def test_max_parallel_limiting(self):
585
+ """Test max_parallel limits concurrent execution."""
586
+ ...
587
+ ```
588
+
589
+ ### Integration Tests
590
+
591
+ **File**: `tests/integration/test_tool_orchestration.py`
592
+
593
+ ```python
594
+ class TestOrchestrationIntegration:
595
+ """Test orchestration in full agent context."""
596
+
597
+ async def test_agent_with_mixed_tool_calls(self):
598
+ """Test agent executing mixed read/write tools."""
599
+ ...
600
+
601
+ async def test_orchestration_with_rag(self):
602
+ """Test orchestration with RAG context."""
603
+ ...
604
+
605
+ async def test_orchestration_events(self):
606
+ """Test AgentEvent emission during orchestration."""
607
+ ...
608
+ ```
609
+
610
+ ---
611
+
612
+ ## ✅ Acceptance Criteria
613
+
614
+ - [ ] `BaseTool` interface has `is_read_only` and `category` attributes
615
+ - [ ] All built-in tools are classified (read-only vs write)
616
+ - [ ] `ToolOrchestrator` class implemented
617
+ - [ ] `categorize_tools()` correctly splits tools
618
+ - [ ] `execute_parallel()` executes read-only tools concurrently
619
+ - [ ] `execute_sequential()` executes write tools in order
620
+ - [ ] `max_parallel` limit is respected
621
+ - [ ] Integration with `AgentExecutor`
622
+ - [ ] `execute_stream()` uses orchestrator
623
+ - [ ] `execute()` uses orchestrator
624
+ - [ ] `stream()` uses orchestrator
625
+ - [ ] Test coverage ≥ 80%
626
+ - [ ] 25-30 unit tests
627
+ - [ ] All tests pass
628
+ - [ ] No race conditions in concurrent execution
629
+ - [ ] Performance: Parallel execution faster than sequential for read-only
630
+ - [ ] Events emitted correctly
631
+ - [ ] Backward compatible (existing tests still pass)
632
+
633
+ ---
634
+
635
+ ## 📦 Deliverables
636
+
637
+ 1. **Core Implementation**
638
+ - [ ] `loom/core/tool_orchestrator.py` (~300-400 lines)
639
+ - [ ] Modified `loom/interfaces/tool.py`
640
+ - [ ] Modified `loom/core/agent_executor.py`
641
+
642
+ 2. **Tool Classification**
643
+ - [ ] All tools in `loom/builtin/tools/` classified
644
+ - [ ] Documentation for each classification
645
+
646
+ 3. **Tests**
647
+ - [ ] `tests/unit/test_tool_orchestrator.py` (25-30 tests)
648
+ - [ ] `tests/integration/test_tool_orchestration.py` (3-5 tests)
649
+ - [ ] All tests passing
650
+
651
+ 4. **Documentation**
652
+ - [ ] Code docstrings complete
653
+ - [ ] Example usage in docstrings
654
+ - [ ] `examples/tool_orchestration_demo.py`
655
+ - [ ] `docs/TASK_2.1_COMPLETION_SUMMARY.md`
656
+
657
+ 5. **Validation**
658
+ - [ ] Run full test suite: `pytest tests/ -v`
659
+ - [ ] Coverage report: `pytest --cov=loom --cov-report=html`
660
+ - [ ] Performance benchmark (parallel vs sequential)
661
+
662
+ ---
663
+
664
+ ## 📚 References
665
+
666
+ ### Claude Code Inspiration
667
+
668
+ From `cc分析/Tools.md`:
669
+ - Intelligent tool parallelization
670
+ - Safety-first execution model
671
+ - Read-only vs write distinction
672
+
673
+ ### Design Principles
674
+
675
+ 1. **Safety First**: Prevent race conditions by default
676
+ 2. **Performance**: Parallelize when safe
677
+ 3. **Observability**: Emit events for all phases
678
+ 4. **Flexibility**: Easy to add new tools
679
+ 5. **Backward Compatibility**: Don't break existing code
680
+
681
+ ---
682
+
683
+ ## 🔍 Testing Checklist
684
+
685
+ Before marking as complete:
686
+
687
+ - [ ] All unit tests pass
688
+ - [ ] All integration tests pass
689
+ - [ ] All existing tests still pass (no regressions)
690
+ - [ ] Code coverage ≥ 80%
691
+ - [ ] Type checking passes: `mypy loom/`
692
+ - [ ] Linting passes: `ruff check loom/`
693
+ - [ ] Performance test shows parallel is faster
694
+ - [ ] Manual testing with real agent
695
+ - [ ] Example code runs successfully
696
+ - [ ] Documentation reviewed
697
+
698
+ ---
699
+
700
+ ## 🎓 Learning Notes
701
+
702
+ ### Key Concepts
703
+
704
+ 1. **Read-only Tools**: Can execute in parallel safely
705
+ - ReadTool, GrepTool, GlobTool
706
+ - No side effects
707
+ - No file modifications
708
+
709
+ 2. **Write Tools**: Must execute sequentially
710
+ - EditTool, WriteTool, BashTool
711
+ - Have side effects
712
+ - May modify files/system state
713
+
714
+ 3. **Race Condition Example**:
715
+ ```python
716
+ # Dangerous: Read and Write same file in parallel
717
+ await asyncio.gather(
718
+ read_tool.run("config.json"),
719
+ edit_tool.run("config.json", ...)
720
+ )
721
+ # Result: undefined behavior!
722
+ ```
723
+
724
+ 4. **Safe Orchestration**:
725
+ ```python
726
+ # Safe: Execute sequentially
727
+ content = await read_tool.run("config.json")
728
+ await edit_tool.run("config.json", ...)
729
+ ```
730
+
731
+ ### Implementation Tips
732
+
733
+ 1. Use `asyncio.Semaphore` for parallel execution limiting
734
+ 2. Use `asyncio.as_completed` for result streaming
735
+ 3. Always emit events for observability
736
+ 4. Handle tool errors gracefully
737
+ 5. Test with actual file I/O for race conditions
738
+
739
+ ---
740
+
741
+ **Created**: 2025-10-25
742
+ **Last Updated**: 2025-10-25
743
+ **Status**: 🔄 In Progress