loom-agent 0.0.1__py3-none-any.whl → 0.0.2__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 (38) 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 +5 -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 +505 -320
  13. loom/core/compression_manager.py +17 -10
  14. loom/core/context_assembly.py +329 -0
  15. loom/core/events.py +414 -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/interfaces/event_producer.py +172 -0
  21. loom/interfaces/tool.py +22 -1
  22. loom/security/__init__.py +13 -0
  23. loom/security/models.py +85 -0
  24. loom/security/path_validator.py +128 -0
  25. loom/security/validator.py +346 -0
  26. loom/tasks/PHASE_1_FOUNDATION/task_1.1_agent_events.md +121 -0
  27. loom/tasks/PHASE_1_FOUNDATION/task_1.2_streaming_api.md +521 -0
  28. loom/tasks/PHASE_1_FOUNDATION/task_1.3_context_assembler.md +606 -0
  29. loom/tasks/PHASE_2_CORE_FEATURES/task_2.1_tool_orchestrator.md +743 -0
  30. loom/tasks/PHASE_2_CORE_FEATURES/task_2.2_security_validator.md +676 -0
  31. loom/tasks/README.md +109 -0
  32. loom/tasks/__init__.py +11 -0
  33. loom/tasks/sql_placeholder.py +100 -0
  34. loom_agent-0.0.2.dist-info/METADATA +295 -0
  35. {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/RECORD +37 -19
  36. loom_agent-0.0.1.dist-info/METADATA +0 -457
  37. {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/WHEEL +0 -0
  38. {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -18,6 +18,10 @@ class Calculator(BaseTool):
18
18
  description = "Evaluate simple arithmetic expressions"
19
19
  args_schema = CalcArgs
20
20
 
21
+ # 🆕 Loom 2.0 - Orchestration attributes
22
+ is_read_only = True # Pure computation, no side effects
23
+ category = "general"
24
+
21
25
  async def run(self, **kwargs) -> Any:
22
26
  expr = kwargs.get("expression", "")
23
27
  return str(_safe_eval(expr))
@@ -48,6 +48,11 @@ class DocumentSearchTool(BaseTool):
48
48
  """
49
49
 
50
50
  name = "search_documents"
51
+
52
+ # 🆕 Loom 2.0 - Orchestration attributes
53
+ is_read_only = True # Only searches documents, no modifications
54
+ category = "general"
55
+
51
56
  description = (
52
57
  "Search for relevant documents from the knowledge base. "
53
58
  "Use this when you need specific information that might be in the documents. "
@@ -19,6 +19,10 @@ class GlobTool(BaseTool):
19
19
  description = "按模式匹配文件路径"
20
20
  args_schema = GlobArgs
21
21
 
22
+ # 🆕 Loom 2.0 - Orchestration attributes
23
+ is_read_only = True
24
+ category = "general"
25
+
22
26
  async def run(self, **kwargs) -> Any:
23
27
  args = self.args_schema(**kwargs) # type: ignore
24
28
  cwd = Path(args.cwd).expanduser() if args.cwd else Path.cwd()
@@ -22,6 +22,10 @@ class GrepTool(BaseTool):
22
22
  description = "在文件或文件集内检索正则匹配"
23
23
  args_schema = GrepArgs
24
24
 
25
+ # 🆕 Loom 2.0 - Orchestration attributes
26
+ is_read_only = True
27
+ category = "general"
28
+
25
29
  async def run(self, **kwargs) -> Any:
26
30
  args = self.args_schema(**kwargs) # type: ignore
27
31
  flags = 0
@@ -35,6 +35,11 @@ class HTTPRequestTool(BaseTool):
35
35
  args_schema = HTTPRequestInput
36
36
  is_concurrency_safe = True
37
37
 
38
+ # 🆕 Loom 2.0 - Orchestration attributes
39
+ is_read_only = False # POST/PUT/DELETE may modify remote state
40
+ category = "network" # Network operation
41
+ requires_confirmation = False # Usually safe, but depends on usage
42
+
38
43
  def __init__(self, timeout: int = 10) -> None:
39
44
  if httpx is None:
40
45
  raise ImportError("Please install httpx: pip install httpx")
@@ -33,6 +33,11 @@ class PythonREPLTool(BaseTool):
33
33
  args_schema = PythonREPLInput
34
34
  is_concurrency_safe = False # 代码执行不并发安全
35
35
 
36
+ # 🆕 Loom 2.0 - Orchestration attributes
37
+ is_read_only = False # Code execution may have side effects
38
+ category = "destructive" # Potentially dangerous
39
+ requires_confirmation = True # Should require user confirmation
40
+
36
41
  async def run(self, code: str, **kwargs: Any) -> str:
37
42
  """执行 Python 代码"""
38
43
  # 安全性检查 - 禁止危险操作
@@ -19,6 +19,10 @@ class ReadFileTool(BaseTool):
19
19
  description = "读取文本文件内容"
20
20
  args_schema = ReadArgs
21
21
 
22
+ # 🆕 Loom 2.0 - Orchestration attributes
23
+ is_read_only = True
24
+ category = "general"
25
+
22
26
  async def run(self, **kwargs) -> Any:
23
27
  args = self.args_schema(**kwargs) # type: ignore
24
28
  p = Path(args.path).expanduser()
@@ -47,6 +47,11 @@ class TaskTool(BaseTool):
47
47
  args_schema = TaskInput
48
48
  is_concurrency_safe = True
49
49
 
50
+ # 🆕 Loom 2.0 - Orchestration attributes
51
+ is_read_only = False # Sub-agent may use write tools
52
+ category = "general" # Not inherently dangerous, but depends on sub-agent's tools
53
+ requires_confirmation = False
54
+
50
55
  def __init__(
51
56
  self,
52
57
  agent_factory: Optional[callable] = None,
@@ -33,6 +33,10 @@ class WebSearchTool(BaseTool):
33
33
  args_schema = WebSearchInput
34
34
  is_concurrency_safe = True
35
35
 
36
+ # 🆕 Loom 2.0 - Orchestration attributes
37
+ is_read_only = True # Only reads from web, no local side effects
38
+ category = "network"
39
+
36
40
  def __init__(self) -> None:
37
41
  if DDGS is None:
38
42
  raise ImportError(
@@ -20,6 +20,10 @@ class WriteFileTool(BaseTool):
20
20
  description = "写入文本到文件(可能覆盖)"
21
21
  args_schema = WriteArgs
22
22
 
23
+ # 🆕 Loom 2.0 - Orchestration attributes
24
+ is_read_only = False
25
+ category = "destructive"
26
+
23
27
  async def run(self, **kwargs) -> Any:
24
28
  args = self.args_schema(**kwargs) # type: ignore
25
29
  p = Path(args.path).expanduser()
loom/components/agent.py CHANGED
@@ -1,10 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import uuid
5
+ from pathlib import Path
4
6
  from typing import AsyncGenerator, Dict, List, Optional
5
7
 
6
8
  from loom.core.agent_executor import AgentExecutor
7
- from loom.core.types import StreamEvent
9
+ from loom.core.types import StreamEvent, Message
10
+ from loom.core.events import AgentEvent, AgentEventType # 🆕 Loom 2.0
11
+ from loom.core.turn_state import TurnState # 🆕 Loom 2.0 tt mode
12
+ from loom.core.execution_context import ExecutionContext # 🆕 Loom 2.0 tt mode
8
13
  from loom.interfaces.llm import BaseLLM
9
14
  from loom.interfaces.memory import BaseMemory
10
15
  from loom.interfaces.tool import BaseTool
@@ -35,6 +40,7 @@ class Agent:
35
40
  callbacks: Optional[List[BaseCallback]] = None,
36
41
  steering_control: Optional[SteeringControl] = None,
37
42
  metrics: Optional[MetricsCollector] = None,
43
+ enable_steering: bool = True, # v4.0.0: Enable steering by default
38
44
  ) -> None:
39
45
  # v4.0.0: Auto-instantiate CompressionManager (always enabled)
40
46
  if compressor is None:
@@ -61,7 +67,7 @@ class Agent:
61
67
  permission_manager=None,
62
68
  system_instructions=system_instructions,
63
69
  callbacks=callbacks,
64
- enable_steering=True, # v4.0.0: Always enabled
70
+ enable_steering=enable_steering,
65
71
  )
66
72
 
67
73
  # 始终构造 PermissionManager(以便支持 safe_mode/持久化);保持默认语义
@@ -83,11 +89,121 @@ class Agent:
83
89
  cancel_token: Optional[asyncio.Event] = None, # 🆕 US1
84
90
  correlation_id: Optional[str] = None, # 🆕 US1
85
91
  ) -> str:
86
- return await self.executor.execute(input, cancel_token=cancel_token, correlation_id=correlation_id)
92
+ """
93
+ Execute agent and return final response (backward compatible).
94
+
95
+ This method wraps the new execute() streaming API and extracts
96
+ the final response for backward compatibility.
97
+
98
+ Args:
99
+ input: User input
100
+ cancel_token: Optional cancellation event
101
+ correlation_id: Optional correlation ID for tracing
102
+
103
+ Returns:
104
+ Final response text
105
+ """
106
+ final_content = ""
107
+
108
+ async for event in self.execute(input):
109
+ # Accumulate LLM deltas
110
+ if event.type == AgentEventType.LLM_DELTA:
111
+ final_content += event.content or ""
112
+
113
+ # Return on finish
114
+ elif event.type == AgentEventType.AGENT_FINISH:
115
+ return event.content or final_content
116
+
117
+ # Raise on error
118
+ elif event.type == AgentEventType.ERROR:
119
+ if event.error:
120
+ raise event.error
121
+
122
+ return final_content
123
+
124
+ async def execute(
125
+ self,
126
+ input: str,
127
+ cancel_token: Optional[asyncio.Event] = None,
128
+ correlation_id: Optional[str] = None,
129
+ working_dir: Optional[Path] = None,
130
+ ) -> AsyncGenerator[AgentEvent, None]:
131
+ """
132
+ Execute agent with streaming events using tt recursive mode (Loom 2.0).
133
+
134
+ This is the new unified streaming interface that produces AgentEvent
135
+ instances for all execution phases. It uses tt (tail-recursive) control
136
+ loop as the ONLY core execution method.
137
+
138
+ Args:
139
+ input: User input
140
+ cancel_token: Optional cancellation event
141
+ correlation_id: Optional correlation ID for tracing
142
+ working_dir: Optional working directory
143
+
144
+ Yields:
145
+ AgentEvent: Events representing execution progress
146
+
147
+ Example:
148
+ ```python
149
+ async for event in agent.execute("Your prompt"):
150
+ if event.type == AgentEventType.LLM_DELTA:
151
+ print(event.content, end="", flush=True)
152
+ elif event.type == AgentEventType.TOOL_PROGRESS:
153
+ print(f"\\n[Tool] {event.metadata['status']}")
154
+ elif event.type == AgentEventType.AGENT_FINISH:
155
+ print(f"\\n✓ {event.content}")
156
+ ```
157
+ """
158
+ # Initialize immutable turn state
159
+ turn_state = TurnState.initial(max_iterations=self.executor.max_iterations)
160
+
161
+ # Create execution context
162
+ context = ExecutionContext.create(
163
+ working_dir=working_dir,
164
+ correlation_id=correlation_id,
165
+ cancel_token=cancel_token,
166
+ )
167
+
168
+ # Create initial message
169
+ messages = [Message(role="user", content=input)]
170
+
171
+ # Delegate to executor's tt recursive control loop
172
+ async for event in self.executor.tt(messages, turn_state, context):
173
+ yield event
87
174
 
88
175
  async def stream(self, input: str) -> AsyncGenerator[StreamEvent, None]:
89
- async for ev in self.executor.stream(input):
90
- yield ev
176
+ """
177
+ Legacy streaming API (backward compatible).
178
+
179
+ NOTE: This uses the old StreamEvent type. For new code, use execute()
180
+ which returns AgentEvent instances.
181
+
182
+ This method now converts AgentEvent to StreamEvent for backward compatibility.
183
+ """
184
+ # Use tt mode under the hood
185
+ async for agent_event in self.execute(input):
186
+ # Convert AgentEvent to legacy StreamEvent
187
+ if agent_event.type == AgentEventType.LLM_DELTA:
188
+ yield StreamEvent(
189
+ type="llm_delta",
190
+ content=agent_event.content or "",
191
+ )
192
+ elif agent_event.type == AgentEventType.AGENT_FINISH:
193
+ yield StreamEvent(
194
+ type="agent_finish",
195
+ content=agent_event.content or "",
196
+ )
197
+ elif agent_event.type == AgentEventType.TOOL_RESULT:
198
+ yield StreamEvent(
199
+ type="tool_result",
200
+ content=agent_event.tool_result.content if agent_event.tool_result else "",
201
+ )
202
+ elif agent_event.type == AgentEventType.ERROR:
203
+ yield StreamEvent(
204
+ type="error",
205
+ content=str(agent_event.error) if agent_event.error else "Unknown error",
206
+ )
91
207
 
92
208
  # LangChain 风格的别名,便于迁移/调用
93
209
  async def ainvoke(