loom-agent 0.0.1__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 (89) hide show
  1. loom/__init__.py +77 -0
  2. loom/agent.py +217 -0
  3. loom/agents/__init__.py +10 -0
  4. loom/agents/refs.py +28 -0
  5. loom/agents/registry.py +50 -0
  6. loom/builtin/compression/__init__.py +4 -0
  7. loom/builtin/compression/structured.py +79 -0
  8. loom/builtin/embeddings/__init__.py +9 -0
  9. loom/builtin/embeddings/openai_embedding.py +135 -0
  10. loom/builtin/embeddings/sentence_transformers_embedding.py +145 -0
  11. loom/builtin/llms/__init__.py +8 -0
  12. loom/builtin/llms/mock.py +34 -0
  13. loom/builtin/llms/openai.py +168 -0
  14. loom/builtin/llms/rule.py +102 -0
  15. loom/builtin/memory/__init__.py +5 -0
  16. loom/builtin/memory/in_memory.py +21 -0
  17. loom/builtin/memory/persistent_memory.py +278 -0
  18. loom/builtin/retriever/__init__.py +9 -0
  19. loom/builtin/retriever/chroma_store.py +265 -0
  20. loom/builtin/retriever/in_memory.py +106 -0
  21. loom/builtin/retriever/milvus_store.py +307 -0
  22. loom/builtin/retriever/pinecone_store.py +237 -0
  23. loom/builtin/retriever/qdrant_store.py +274 -0
  24. loom/builtin/retriever/vector_store.py +128 -0
  25. loom/builtin/retriever/vector_store_config.py +217 -0
  26. loom/builtin/tools/__init__.py +32 -0
  27. loom/builtin/tools/calculator.py +49 -0
  28. loom/builtin/tools/document_search.py +111 -0
  29. loom/builtin/tools/glob.py +27 -0
  30. loom/builtin/tools/grep.py +56 -0
  31. loom/builtin/tools/http_request.py +86 -0
  32. loom/builtin/tools/python_repl.py +73 -0
  33. loom/builtin/tools/read_file.py +32 -0
  34. loom/builtin/tools/task.py +158 -0
  35. loom/builtin/tools/web_search.py +64 -0
  36. loom/builtin/tools/write_file.py +31 -0
  37. loom/callbacks/base.py +9 -0
  38. loom/callbacks/logging.py +12 -0
  39. loom/callbacks/metrics.py +27 -0
  40. loom/callbacks/observability.py +248 -0
  41. loom/components/agent.py +107 -0
  42. loom/core/agent_executor.py +450 -0
  43. loom/core/circuit_breaker.py +178 -0
  44. loom/core/compression_manager.py +329 -0
  45. loom/core/context_retriever.py +185 -0
  46. loom/core/error_classifier.py +193 -0
  47. loom/core/errors.py +66 -0
  48. loom/core/message_queue.py +167 -0
  49. loom/core/permission_store.py +62 -0
  50. loom/core/permissions.py +69 -0
  51. loom/core/scheduler.py +125 -0
  52. loom/core/steering_control.py +47 -0
  53. loom/core/structured_logger.py +279 -0
  54. loom/core/subagent_pool.py +232 -0
  55. loom/core/system_prompt.py +141 -0
  56. loom/core/system_reminders.py +283 -0
  57. loom/core/tool_pipeline.py +113 -0
  58. loom/core/types.py +269 -0
  59. loom/interfaces/compressor.py +59 -0
  60. loom/interfaces/embedding.py +51 -0
  61. loom/interfaces/llm.py +33 -0
  62. loom/interfaces/memory.py +29 -0
  63. loom/interfaces/retriever.py +179 -0
  64. loom/interfaces/tool.py +27 -0
  65. loom/interfaces/vector_store.py +80 -0
  66. loom/llm/__init__.py +14 -0
  67. loom/llm/config.py +228 -0
  68. loom/llm/factory.py +111 -0
  69. loom/llm/model_health.py +235 -0
  70. loom/llm/model_pool_advanced.py +305 -0
  71. loom/llm/pool.py +170 -0
  72. loom/llm/registry.py +201 -0
  73. loom/mcp/__init__.py +4 -0
  74. loom/mcp/client.py +86 -0
  75. loom/mcp/registry.py +58 -0
  76. loom/mcp/tool_adapter.py +48 -0
  77. loom/observability/__init__.py +5 -0
  78. loom/patterns/__init__.py +5 -0
  79. loom/patterns/multi_agent.py +123 -0
  80. loom/patterns/rag.py +262 -0
  81. loom/plugins/registry.py +55 -0
  82. loom/resilience/__init__.py +5 -0
  83. loom/tooling.py +72 -0
  84. loom/utils/agent_loader.py +218 -0
  85. loom/utils/token_counter.py +19 -0
  86. loom_agent-0.0.1.dist-info/METADATA +457 -0
  87. loom_agent-0.0.1.dist-info/RECORD +89 -0
  88. loom_agent-0.0.1.dist-info/WHEEL +4 -0
  89. loom_agent-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,283 @@
1
+ """US7: Dynamic System Reminders
2
+
3
+ Provides contextual hints and reminders to agents based on runtime state.
4
+
5
+ Examples:
6
+ - "Warning: Memory usage is high (15,234 tokens). Consider using more concise responses."
7
+ - "Note: 3 stale todos detected. Consider cleaning up completed items."
8
+ - "Tip: Multiple file writes detected. Use batch operations for efficiency."
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import List, Optional, Callable, Any
14
+ from dataclasses import dataclass
15
+ from datetime import datetime, timedelta
16
+
17
+
18
+ @dataclass
19
+ class Reminder:
20
+ """A system reminder."""
21
+ category: str # "memory", "performance", "todos", "errors"
22
+ severity: str # "info", "warning", "critical"
23
+ message: str
24
+ metadata: dict
25
+
26
+
27
+ class ReminderRule:
28
+ """Base class for reminder rules."""
29
+
30
+ def __init__(self, category: str, severity: str = "info"):
31
+ self.category = category
32
+ self.severity = severity
33
+
34
+ def check(self, context: dict) -> Optional[Reminder]:
35
+ """Check if reminder should be triggered.
36
+
37
+ Args:
38
+ context: Runtime context dict
39
+
40
+ Returns:
41
+ Reminder if triggered, None otherwise
42
+ """
43
+ raise NotImplementedError
44
+
45
+
46
+ class HighMemoryRule(ReminderRule):
47
+ """Remind when memory usage is high."""
48
+
49
+ def __init__(self, threshold_tokens: int = 14000):
50
+ super().__init__("memory", "warning")
51
+ self.threshold_tokens = threshold_tokens
52
+
53
+ def check(self, context: dict) -> Optional[Reminder]:
54
+ current_tokens = context.get("current_tokens", 0)
55
+ max_tokens = context.get("max_tokens", 16000)
56
+
57
+ if current_tokens > self.threshold_tokens:
58
+ percentage = (current_tokens / max_tokens) * 100
59
+ return Reminder(
60
+ category=self.category,
61
+ severity=self.severity,
62
+ message=f"Memory usage is high ({current_tokens:,} tokens, {percentage:.1f}%). Consider using more concise responses or compressing context.",
63
+ metadata={
64
+ "current_tokens": current_tokens,
65
+ "max_tokens": max_tokens,
66
+ "percentage": percentage,
67
+ }
68
+ )
69
+ return None
70
+
71
+
72
+ class StaleTodosRule(ReminderRule):
73
+ """Remind about stale todos."""
74
+
75
+ def __init__(self, stale_threshold_hours: int = 24):
76
+ super().__init__("todos", "info")
77
+ self.stale_threshold = timedelta(hours=stale_threshold_hours)
78
+
79
+ def check(self, context: dict) -> Optional[Reminder]:
80
+ todos = context.get("todos", [])
81
+ now = datetime.now()
82
+
83
+ # Count stale completed todos
84
+ stale_count = 0
85
+ for todo in todos:
86
+ if todo.get("status") == "completed":
87
+ completed_at = todo.get("completed_at")
88
+ if completed_at and isinstance(completed_at, datetime):
89
+ if now - completed_at > self.stale_threshold:
90
+ stale_count += 1
91
+
92
+ if stale_count > 0:
93
+ return Reminder(
94
+ category=self.category,
95
+ severity=self.severity,
96
+ message=f"{stale_count} stale todo(s) detected (completed >24h ago). Consider cleaning up.",
97
+ metadata={"stale_count": stale_count}
98
+ )
99
+ return None
100
+
101
+
102
+ class HighErrorRateRule(ReminderRule):
103
+ """Remind when error rate is high."""
104
+
105
+ def __init__(self, threshold_percentage: float = 20.0):
106
+ super().__init__("errors", "warning")
107
+ self.threshold_percentage = threshold_percentage
108
+
109
+ def check(self, context: dict) -> Optional[Reminder]:
110
+ metrics = context.get("metrics", {})
111
+ total_ops = metrics.get("total_operations", 0)
112
+ errors = metrics.get("total_errors", 0)
113
+
114
+ if total_ops > 0:
115
+ error_rate = (errors / total_ops) * 100
116
+ if error_rate > self.threshold_percentage:
117
+ return Reminder(
118
+ category=self.category,
119
+ severity="critical" if error_rate > 50 else "warning",
120
+ message=f"High error rate detected: {errors}/{total_ops} operations failed ({error_rate:.1f}%). Check logs for details.",
121
+ metadata={
122
+ "error_rate": error_rate,
123
+ "errors": errors,
124
+ "total_ops": total_ops,
125
+ }
126
+ )
127
+ return None
128
+
129
+
130
+ class FrequentCompressionRule(ReminderRule):
131
+ """Remind when compression happens too frequently."""
132
+
133
+ def __init__(self, threshold_count: int = 5):
134
+ super().__init__("performance", "info")
135
+ self.threshold_count = threshold_count
136
+
137
+ def check(self, context: dict) -> Optional[Reminder]:
138
+ metrics = context.get("metrics", {})
139
+ compressions = metrics.get("compressions", 0)
140
+
141
+ if compressions >= self.threshold_count:
142
+ return Reminder(
143
+ category=self.category,
144
+ severity=self.severity,
145
+ message=f"Context compressed {compressions} times. Consider increasing max_context_tokens or using shorter prompts.",
146
+ metadata={"compression_count": compressions}
147
+ )
148
+ return None
149
+
150
+
151
+ class SystemReminderManager:
152
+ """Manages system reminders and dynamic hint injection.
153
+
154
+ Example:
155
+ manager = SystemReminderManager()
156
+ manager.add_rule(HighMemoryRule())
157
+ manager.add_rule(HighErrorRateRule())
158
+
159
+ # Check for reminders
160
+ context = {
161
+ "current_tokens": 15000,
162
+ "max_tokens": 16000,
163
+ "metrics": {"total_errors": 5, "total_operations": 10}
164
+ }
165
+
166
+ reminders = manager.check_all(context)
167
+ if reminders:
168
+ print(manager.format_reminders(reminders))
169
+ """
170
+
171
+ def __init__(self):
172
+ """Initialize reminder manager."""
173
+ self.rules: List[ReminderRule] = []
174
+ self._default_rules()
175
+
176
+ def _default_rules(self):
177
+ """Add default reminder rules."""
178
+ self.add_rule(HighMemoryRule(threshold_tokens=14000))
179
+ self.add_rule(HighErrorRateRule(threshold_percentage=20.0))
180
+ self.add_rule(FrequentCompressionRule(threshold_count=5))
181
+ # StaleTodosRule disabled by default (requires todo tracking)
182
+
183
+ def add_rule(self, rule: ReminderRule) -> None:
184
+ """Add a reminder rule.
185
+
186
+ Args:
187
+ rule: ReminderRule instance
188
+ """
189
+ self.rules.append(rule)
190
+
191
+ def remove_rule(self, category: str) -> None:
192
+ """Remove all rules for a category.
193
+
194
+ Args:
195
+ category: Category to remove
196
+ """
197
+ self.rules = [r for r in self.rules if r.category != category]
198
+
199
+ def check_all(self, context: dict) -> List[Reminder]:
200
+ """Check all rules and return triggered reminders.
201
+
202
+ Args:
203
+ context: Runtime context
204
+
205
+ Returns:
206
+ List of triggered reminders
207
+ """
208
+ reminders = []
209
+ for rule in self.rules:
210
+ try:
211
+ reminder = rule.check(context)
212
+ if reminder:
213
+ reminders.append(reminder)
214
+ except Exception:
215
+ # Don't fail if a rule raises
216
+ pass
217
+
218
+ return reminders
219
+
220
+ def format_reminders(self, reminders: List[Reminder]) -> str:
221
+ """Format reminders as human-readable text.
222
+
223
+ Args:
224
+ reminders: List of reminders
225
+
226
+ Returns:
227
+ Formatted reminder text
228
+ """
229
+ if not reminders:
230
+ return ""
231
+
232
+ lines = ["=== System Reminders ==="]
233
+
234
+ # Group by severity
235
+ critical = [r for r in reminders if r.severity == "critical"]
236
+ warnings = [r for r in reminders if r.severity == "warning"]
237
+ info = [r for r in reminders if r.severity == "info"]
238
+
239
+ for severity_list, prefix in [
240
+ (critical, "🚨 CRITICAL"),
241
+ (warnings, "⚠️ WARNING"),
242
+ (info, "ℹ️ INFO"),
243
+ ]:
244
+ for reminder in severity_list:
245
+ lines.append(f"{prefix}: {reminder.message}")
246
+
247
+ return "\n".join(lines)
248
+
249
+ def inject_into_context(
250
+ self,
251
+ context: dict,
252
+ existing_system_prompt: str = ""
253
+ ) -> str:
254
+ """Check reminders and inject into system prompt.
255
+
256
+ Args:
257
+ context: Runtime context
258
+ existing_system_prompt: Existing system prompt
259
+
260
+ Returns:
261
+ System prompt with reminders injected
262
+ """
263
+ reminders = self.check_all(context)
264
+
265
+ if not reminders:
266
+ return existing_system_prompt
267
+
268
+ reminder_text = self.format_reminders(reminders)
269
+
270
+ # Inject at end of system prompt
271
+ if existing_system_prompt:
272
+ return f"{existing_system_prompt}\n\n{reminder_text}"
273
+ else:
274
+ return reminder_text
275
+
276
+
277
+ # Global instance
278
+ _default_manager = SystemReminderManager()
279
+
280
+
281
+ def get_reminder_manager() -> SystemReminderManager:
282
+ """Get the global reminder manager."""
283
+ return _default_manager
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from enum import Enum
5
+ from typing import Any, AsyncGenerator, Dict, Iterable, List
6
+
7
+ from loom.core.errors import PermissionDeniedError, ToolNotFoundError, ToolValidationError
8
+ from loom.core.types import ToolCall, ToolResult
9
+ from loom.interfaces.tool import BaseTool
10
+ from loom.core.scheduler import Scheduler
11
+ from loom.core.permissions import PermissionManager, PermissionAction
12
+ from loom.callbacks.metrics import MetricsCollector
13
+
14
+
15
+ class ExecutionStage(str, Enum):
16
+ DISCOVER = "discover"
17
+ VALIDATE = "validate"
18
+ AUTHORIZE = "authorize"
19
+ CHECK_CANCEL = "check_cancel"
20
+ EXECUTE = "execute"
21
+ FORMAT = "format"
22
+
23
+
24
+ class ToolExecutionPipeline:
25
+ """最小可用的六阶段工具执行流水线(Discover→...→Format)。"""
26
+
27
+ def __init__(
28
+ self,
29
+ tools: Dict[str, BaseTool],
30
+ scheduler: Scheduler | None = None,
31
+ permission_manager: PermissionManager | None = None,
32
+ metrics: MetricsCollector | None = None,
33
+ ) -> None:
34
+ self.tools = tools
35
+ self.scheduler = scheduler or Scheduler()
36
+ self._stage_metrics: Dict[str, float] = {}
37
+ self.permission_manager = permission_manager or PermissionManager(policy={"default": "allow"})
38
+ self.metrics = metrics or MetricsCollector()
39
+
40
+ async def execute_calls(self, tool_calls: Iterable[ToolCall]) -> AsyncGenerator[ToolResult, None]:
41
+ for call in tool_calls:
42
+ try:
43
+ tool = await self._stage_discover(call)
44
+ args = await self._stage_validate(tool, call)
45
+ await self._stage_authorize(tool, call, args)
46
+ # check_cancel: 由上层 EventBus 注入,此处留空
47
+ result = await self._stage_execute(tool, args, call)
48
+ tr = await self._stage_format(result, call)
49
+ self.metrics.metrics.tool_calls += 1
50
+ yield tr
51
+ except Exception as e: # 简化:归一化错误
52
+ self.metrics.metrics.tool_calls += 1
53
+ self.metrics.metrics.total_errors += 1
54
+ yield ToolResult(
55
+ tool_call_id=call.id,
56
+ status="error",
57
+ content=str(e),
58
+ metadata={"stage_breakdown": self._stage_metrics.copy()},
59
+ )
60
+ finally:
61
+ self._stage_metrics.clear()
62
+
63
+ async def _stage_discover(self, tool_call: ToolCall) -> BaseTool:
64
+ start = time.time()
65
+ if tool_call.name not in self.tools:
66
+ raise ToolNotFoundError(f"Tool '{tool_call.name}' not registered")
67
+ tool = self.tools[tool_call.name]
68
+ self._stage_metrics[ExecutionStage.DISCOVER.value] = time.time() - start
69
+ return tool
70
+
71
+ async def _stage_validate(self, tool: BaseTool, tool_call: ToolCall) -> Dict[str, Any]:
72
+ start = time.time()
73
+ if not getattr(tool, "args_schema", None):
74
+ args = tool_call.arguments
75
+ else:
76
+ try:
77
+ args_model = tool.args_schema(**tool_call.arguments)
78
+ args = args_model.model_dump()
79
+ except Exception as e: # pydantic 验证错误
80
+ raise ToolValidationError(str(e))
81
+ self._stage_metrics[ExecutionStage.VALIDATE.value] = time.time() - start
82
+ return args
83
+
84
+ async def _stage_authorize(self, tool: BaseTool, tool_call: ToolCall, args: Dict[str, Any]) -> None:
85
+ start = time.time()
86
+ action = self.permission_manager.check(tool.name, tool_call.arguments)
87
+ if action == PermissionAction.DENY:
88
+ raise PermissionDeniedError(f"Permission denied for tool '{tool.name}'")
89
+ if action == PermissionAction.ASK:
90
+ confirmed = self.permission_manager.confirm(tool.name, args)
91
+ if not confirmed:
92
+ raise PermissionDeniedError(f"User denied permission for tool '{tool.name}'")
93
+ self._stage_metrics[ExecutionStage.AUTHORIZE.value] = time.time() - start
94
+
95
+ async def _stage_execute(self, tool: BaseTool, args: Dict[str, Any], call: ToolCall) -> Any:
96
+ start = time.time()
97
+ # 使用调度器包装执行
98
+ result = None
99
+ async for r in self.scheduler.schedule_batch([(tool, args)]):
100
+ result = r
101
+ self._stage_metrics[ExecutionStage.EXECUTE.value] = time.time() - start
102
+ return result
103
+
104
+ async def _stage_format(self, result: Any, call: ToolCall) -> ToolResult:
105
+ start = time.time()
106
+ tr = ToolResult(
107
+ tool_call_id=call.id,
108
+ status="success",
109
+ content=str(result) if result is not None else "",
110
+ metadata={"stage_breakdown": self._stage_metrics.copy()},
111
+ )
112
+ self._stage_metrics[ExecutionStage.FORMAT.value] = time.time() - start
113
+ return tr
loom/core/types.py ADDED
@@ -0,0 +1,269 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from typing import Any, Dict, List, Optional
7
+ from uuid import UUID, uuid4
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ @dataclass
13
+ class Message:
14
+ role: str # user | assistant | system | tool
15
+ content: str
16
+ metadata: Dict[str, Any] = field(default_factory=dict)
17
+ tool_call_id: Optional[str] = None
18
+
19
+
20
+ @dataclass
21
+ class ToolCall:
22
+ id: str
23
+ name: str
24
+ arguments: Dict[str, Any]
25
+
26
+
27
+ @dataclass
28
+ class ToolResult:
29
+ tool_call_id: str
30
+ status: str # success | error | warning
31
+ content: str
32
+ metadata: Dict[str, Any] = field(default_factory=dict)
33
+
34
+
35
+ @dataclass
36
+ class StreamEvent:
37
+ type: str # text_delta | tool_calls_start | tool_result | agent_finish | error | aborted
38
+ content: Optional[str] = None
39
+ tool_calls: Optional[List[ToolCall]] = None
40
+ result: Optional[ToolResult] = None
41
+ metadata: Dict[str, Any] = field(default_factory=dict)
42
+
43
+
44
+ # =============================================================================
45
+ # Phase 2: Foundation Types for Claude Code Enhancements (v4.0.0)
46
+ # =============================================================================
47
+
48
+
49
+ # -----------------------------------------------------------------------------
50
+ # T011: MessageQueueItem - US1 Real-Time Steering
51
+ # -----------------------------------------------------------------------------
52
+ class MessageQueueItem(BaseModel):
53
+ """Message queue item for h2A async message queue (US1)."""
54
+
55
+ id: UUID = Field(default_factory=uuid4, description="Unique message ID")
56
+ role: str = Field(..., description="Message role: user | assistant | system | tool")
57
+ content: str = Field(..., description="Message content")
58
+ priority: int = Field(default=5, ge=0, le=10, description="Priority 0-10 (10=highest)")
59
+ timestamp: datetime = Field(default_factory=datetime.utcnow, description="Creation timestamp")
60
+ cancellable: bool = Field(default=True, description="Can this message be cancelled?")
61
+ correlation_id: Optional[str] = Field(default=None, description="Request correlation ID")
62
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
63
+
64
+
65
+ # -----------------------------------------------------------------------------
66
+ # T012: CompressionMetadata - US2 Context Compression
67
+ # -----------------------------------------------------------------------------
68
+ class CompressionMetadata(BaseModel):
69
+ """Metadata for AU2 8-segment compression (US2)."""
70
+
71
+ original_message_count: int = Field(..., ge=1, description="Messages before compression")
72
+ compressed_message_count: int = Field(..., ge=1, description="Messages after compression")
73
+ original_token_count: int = Field(..., ge=1, description="Tokens before compression")
74
+ compressed_token_count: int = Field(..., ge=1, description="Tokens after compression")
75
+ compression_ratio: float = Field(..., ge=0.0, le=1.0, description="Reduction ratio (0.75 = 75%)")
76
+ key_topics: List[str] = Field(default_factory=list, description="Extracted key topics")
77
+ compression_method: str = Field(default="au2_8segment", description="Algorithm used")
78
+ timestamp: datetime = Field(default_factory=datetime.utcnow, description="Compression time")
79
+
80
+
81
+ # -----------------------------------------------------------------------------
82
+ # T013: SubAgentContext - US3 Sub-Agent Isolation
83
+ # -----------------------------------------------------------------------------
84
+ class SubAgentStatus(str, Enum):
85
+ """Sub-agent execution status."""
86
+
87
+ PENDING = "pending"
88
+ RUNNING = "running"
89
+ COMPLETED = "completed"
90
+ FAILED = "failed"
91
+ TIMEOUT = "timeout"
92
+ CANCELLED = "cancelled"
93
+
94
+
95
+ class SubAgentContext(BaseModel):
96
+ """Context for I2A isolated sub-agent (US3)."""
97
+
98
+ agent_id: UUID = Field(default_factory=uuid4, description="Sub-agent unique ID")
99
+ parent_agent_id: Optional[UUID] = Field(default=None, description="Parent agent ID")
100
+ agent_type: str = Field(default="general", description="AgentSpec type reference")
101
+ tool_whitelist: Optional[List[str]] = Field(default=None, description="Allowed tools (None = all)")
102
+ max_iterations: int = Field(default=50, ge=1, le=100, description="Max loop iterations")
103
+ timeout_seconds: int = Field(default=300, ge=1, le=600, description="Max execution time")
104
+ status: SubAgentStatus = Field(default=SubAgentStatus.PENDING, description="Current status")
105
+ execution_depth: int = Field(default=0, ge=0, le=3, description="Nesting level")
106
+ parent_correlation_id: Optional[str] = Field(default=None, description="Parent's correlation ID")
107
+ created_at: datetime = Field(default_factory=datetime.utcnow)
108
+ metadata: Dict[str, Any] = Field(default_factory=dict)
109
+
110
+
111
+ # -----------------------------------------------------------------------------
112
+ # T014: ToolExecutionStage - US4 Tool Pipeline
113
+ # -----------------------------------------------------------------------------
114
+ class ExecutionStage(str, Enum):
115
+ """6-stage tool execution pipeline (MH1)."""
116
+
117
+ DISCOVER = "discover"
118
+ VALIDATE = "validate"
119
+ AUTHORIZE = "authorize"
120
+ CHECK_CANCEL = "check_cancel"
121
+ EXECUTE = "execute"
122
+ FORMAT = "format"
123
+
124
+
125
+ class StageStatus(str, Enum):
126
+ """Status of pipeline stage."""
127
+
128
+ PENDING = "pending"
129
+ SUCCESS = "success"
130
+ FAILED = "failed"
131
+ SKIPPED = "skipped"
132
+
133
+
134
+ class ToolExecutionStageInfo(BaseModel):
135
+ """Information about single pipeline stage."""
136
+
137
+ stage_name: ExecutionStage = Field(..., description="Stage identifier")
138
+ stage_status: StageStatus = Field(..., description="Stage result")
139
+ start_time: Optional[datetime] = Field(default=None, description="Stage start")
140
+ end_time: Optional[datetime] = Field(default=None, description="Stage end")
141
+ duration_ms: Optional[float] = Field(default=None, description="Duration in milliseconds")
142
+ error: Optional[str] = Field(default=None, description="Error message if failed")
143
+ metadata: Dict[str, Any] = Field(default_factory=dict)
144
+
145
+
146
+ # -----------------------------------------------------------------------------
147
+ # T015: CircuitBreakerState - US5 Error Handling & Resilience
148
+ # -----------------------------------------------------------------------------
149
+ class CircuitState(str, Enum):
150
+ """Circuit breaker state machine."""
151
+
152
+ CLOSED = "closed" # Normal operation
153
+ OPEN = "open" # Rejecting requests
154
+ HALF_OPEN = "half_open" # Testing recovery
155
+
156
+
157
+ class CircuitBreakerState(BaseModel):
158
+ """Circuit breaker state for service protection (US5)."""
159
+
160
+ service_name: str = Field(..., description="Protected service identifier")
161
+ state: CircuitState = Field(default=CircuitState.CLOSED, description="Current state")
162
+ failure_count: int = Field(default=0, ge=0, description="Consecutive failures")
163
+ success_count: int = Field(default=0, ge=0, description="Consecutive successes")
164
+ failure_threshold: int = Field(default=5, ge=1, description="Failures to open circuit")
165
+ success_threshold: int = Field(default=2, ge=1, description="Successes to close circuit")
166
+ timeout_seconds: int = Field(default=60, ge=1, description="Timeout in OPEN state")
167
+ last_failure_time: Optional[datetime] = Field(default=None)
168
+ last_success_time: Optional[datetime] = Field(default=None)
169
+
170
+
171
+ # -----------------------------------------------------------------------------
172
+ # T016: LongTermMemory - US6 Three-Tier Memory System
173
+ # -----------------------------------------------------------------------------
174
+ class LongTermMemory(BaseModel):
175
+ """Long-term memory stored in .loom/CLAUDE.md (US6)."""
176
+
177
+ project_path: str = Field(..., description="Root project directory")
178
+ memory_file_path: str = Field(..., description="Path to .loom/CLAUDE.md")
179
+ sections: Dict[str, str] = Field(
180
+ default_factory=dict,
181
+ description="Markdown sections: code_style, architecture_decisions, user_preferences, etc.",
182
+ )
183
+ last_updated: datetime = Field(default_factory=datetime.utcnow)
184
+ checksum: Optional[str] = Field(default=None, description="MD5 checksum for integrity")
185
+ metadata: Dict[str, Any] = Field(default_factory=dict)
186
+
187
+
188
+ # -----------------------------------------------------------------------------
189
+ # T017: AgentEvent Types - Enhanced Events for Callbacks
190
+ # -----------------------------------------------------------------------------
191
+ class AgentEventType(str, Enum):
192
+ """Enhanced agent event types."""
193
+
194
+ TEXT_DELTA = "text_delta"
195
+ TOOL_CALL = "tool_call"
196
+ TOOL_RESULT = "tool_result"
197
+ COMPRESSION_START = "compression_start"
198
+ COMPRESSION_COMPLETE = "compression_complete"
199
+ SUBAGENT_SPAWNED = "subagent_spawned"
200
+ SUBAGENT_COMPLETED = "subagent_completed"
201
+ ERROR = "error"
202
+ COMPLETION = "completion"
203
+
204
+
205
+ class BaseAgentEvent(BaseModel):
206
+ """Base class for all agent events."""
207
+
208
+ event_type: AgentEventType = Field(..., description="Event type identifier")
209
+ correlation_id: Optional[str] = Field(default=None, description="Request correlation ID")
210
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
211
+ metadata: Dict[str, Any] = Field(default_factory=dict)
212
+
213
+
214
+ class TextDeltaEvent(BaseAgentEvent):
215
+ """Streaming text delta event."""
216
+
217
+ event_type: AgentEventType = Field(default=AgentEventType.TEXT_DELTA)
218
+ content: str = Field(..., description="Text delta content")
219
+
220
+
221
+ class ToolCallEvent(BaseAgentEvent):
222
+ """Tool call initiated event."""
223
+
224
+ event_type: AgentEventType = Field(default=AgentEventType.TOOL_CALL)
225
+ tool_call_id: str = Field(..., description="Tool call ID")
226
+ tool_name: str = Field(..., description="Tool name")
227
+ arguments: Dict[str, Any] = Field(..., description="Tool arguments")
228
+
229
+
230
+ class ToolResultEvent(BaseAgentEvent):
231
+ """Tool execution result event."""
232
+
233
+ event_type: AgentEventType = Field(default=AgentEventType.TOOL_RESULT)
234
+ tool_call_id: str = Field(..., description="Tool call ID")
235
+ status: str = Field(..., description="success | error | warning")
236
+ content: str = Field(..., description="Result content")
237
+ stages: List[ToolExecutionStageInfo] = Field(default_factory=list, description="Pipeline stages")
238
+
239
+
240
+ class CompressionEvent(BaseAgentEvent):
241
+ """Context compression event."""
242
+
243
+ event_type: AgentEventType = Field(default=AgentEventType.COMPRESSION_COMPLETE)
244
+ compression_metadata: CompressionMetadata = Field(..., description="Compression details")
245
+
246
+
247
+ class SubAgentSpawnedEvent(BaseAgentEvent):
248
+ """Sub-agent spawned event."""
249
+
250
+ event_type: AgentEventType = Field(default=AgentEventType.SUBAGENT_SPAWNED)
251
+ subagent_context: SubAgentContext = Field(..., description="Sub-agent details")
252
+
253
+
254
+ class ErrorEvent(BaseAgentEvent):
255
+ """Error event."""
256
+
257
+ event_type: AgentEventType = Field(default=AgentEventType.ERROR)
258
+ error_message: str = Field(..., description="Error description")
259
+ error_category: Optional[str] = Field(default=None, description="ErrorCategory enum value")
260
+ recoverable: bool = Field(default=False, description="Is error recoverable?")
261
+
262
+
263
+ class CompletionEvent(BaseAgentEvent):
264
+ """Agent completion event."""
265
+
266
+ event_type: AgentEventType = Field(default=AgentEventType.COMPLETION)
267
+ final_content: str = Field(..., description="Final response")
268
+ model_used: Optional[str] = Field(default=None, description="Model identifier (if fallback occurred)")
269
+