agentpool 2.1.9__py3-none-any.whl → 2.2.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.
Files changed (174) hide show
  1. acp/__init__.py +13 -0
  2. acp/bridge/README.md +15 -2
  3. acp/bridge/__init__.py +3 -2
  4. acp/bridge/__main__.py +60 -19
  5. acp/bridge/ws_server.py +173 -0
  6. acp/bridge/ws_server_cli.py +89 -0
  7. acp/notifications.py +2 -1
  8. acp/stdio.py +39 -9
  9. acp/transports.py +362 -2
  10. acp/utils.py +15 -2
  11. agentpool/__init__.py +4 -1
  12. agentpool/agents/__init__.py +2 -0
  13. agentpool/agents/acp_agent/acp_agent.py +203 -88
  14. agentpool/agents/acp_agent/acp_converters.py +46 -21
  15. agentpool/agents/acp_agent/client_handler.py +157 -3
  16. agentpool/agents/acp_agent/session_state.py +4 -1
  17. agentpool/agents/agent.py +314 -107
  18. agentpool/agents/agui_agent/__init__.py +0 -2
  19. agentpool/agents/agui_agent/agui_agent.py +90 -21
  20. agentpool/agents/agui_agent/agui_converters.py +0 -131
  21. agentpool/agents/base_agent.py +163 -1
  22. agentpool/agents/claude_code_agent/claude_code_agent.py +626 -179
  23. agentpool/agents/claude_code_agent/converters.py +71 -3
  24. agentpool/agents/claude_code_agent/history.py +474 -0
  25. agentpool/agents/context.py +40 -0
  26. agentpool/agents/events/__init__.py +2 -0
  27. agentpool/agents/events/builtin_handlers.py +2 -1
  28. agentpool/agents/events/event_emitter.py +29 -2
  29. agentpool/agents/events/events.py +20 -0
  30. agentpool/agents/modes.py +54 -0
  31. agentpool/agents/tool_call_accumulator.py +213 -0
  32. agentpool/common_types.py +21 -0
  33. agentpool/config_resources/__init__.py +38 -1
  34. agentpool/config_resources/claude_code_agent.yml +3 -0
  35. agentpool/delegation/pool.py +37 -29
  36. agentpool/delegation/team.py +1 -0
  37. agentpool/delegation/teamrun.py +1 -0
  38. agentpool/diagnostics/__init__.py +53 -0
  39. agentpool/diagnostics/lsp_manager.py +1593 -0
  40. agentpool/diagnostics/lsp_proxy.py +41 -0
  41. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  42. agentpool/diagnostics/models.py +398 -0
  43. agentpool/mcp_server/__init__.py +0 -2
  44. agentpool/mcp_server/client.py +12 -3
  45. agentpool/mcp_server/manager.py +25 -31
  46. agentpool/mcp_server/registries/official_registry_client.py +25 -0
  47. agentpool/mcp_server/tool_bridge.py +78 -66
  48. agentpool/messaging/__init__.py +0 -2
  49. agentpool/messaging/compaction.py +72 -197
  50. agentpool/messaging/message_history.py +12 -0
  51. agentpool/messaging/messages.py +52 -9
  52. agentpool/messaging/processing.py +3 -1
  53. agentpool/models/acp_agents/base.py +0 -22
  54. agentpool/models/acp_agents/mcp_capable.py +8 -148
  55. agentpool/models/acp_agents/non_mcp.py +129 -72
  56. agentpool/models/agents.py +35 -13
  57. agentpool/models/claude_code_agents.py +33 -2
  58. agentpool/models/manifest.py +43 -0
  59. agentpool/repomap.py +1 -1
  60. agentpool/resource_providers/__init__.py +9 -1
  61. agentpool/resource_providers/aggregating.py +52 -3
  62. agentpool/resource_providers/base.py +57 -1
  63. agentpool/resource_providers/mcp_provider.py +23 -0
  64. agentpool/resource_providers/plan_provider.py +130 -41
  65. agentpool/resource_providers/pool.py +2 -0
  66. agentpool/resource_providers/static.py +2 -0
  67. agentpool/sessions/__init__.py +2 -1
  68. agentpool/sessions/manager.py +31 -2
  69. agentpool/sessions/models.py +50 -0
  70. agentpool/skills/registry.py +13 -8
  71. agentpool/storage/manager.py +217 -1
  72. agentpool/testing.py +537 -19
  73. agentpool/utils/file_watcher.py +269 -0
  74. agentpool/utils/identifiers.py +121 -0
  75. agentpool/utils/pydantic_ai_helpers.py +46 -0
  76. agentpool/utils/streams.py +690 -1
  77. agentpool/utils/subprocess_utils.py +155 -0
  78. agentpool/utils/token_breakdown.py +461 -0
  79. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/METADATA +27 -7
  80. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/RECORD +170 -112
  81. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/WHEEL +1 -1
  82. agentpool_cli/__main__.py +4 -0
  83. agentpool_cli/serve_acp.py +41 -20
  84. agentpool_cli/serve_agui.py +87 -0
  85. agentpool_cli/serve_opencode.py +119 -0
  86. agentpool_commands/__init__.py +30 -0
  87. agentpool_commands/agents.py +74 -1
  88. agentpool_commands/history.py +62 -0
  89. agentpool_commands/mcp.py +176 -0
  90. agentpool_commands/models.py +56 -3
  91. agentpool_commands/tools.py +57 -0
  92. agentpool_commands/utils.py +51 -0
  93. agentpool_config/builtin_tools.py +77 -22
  94. agentpool_config/commands.py +24 -1
  95. agentpool_config/compaction.py +258 -0
  96. agentpool_config/mcp_server.py +131 -1
  97. agentpool_config/storage.py +46 -1
  98. agentpool_config/tools.py +7 -1
  99. agentpool_config/toolsets.py +92 -148
  100. agentpool_server/acp_server/acp_agent.py +134 -150
  101. agentpool_server/acp_server/commands/acp_commands.py +216 -51
  102. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +10 -10
  103. agentpool_server/acp_server/server.py +23 -79
  104. agentpool_server/acp_server/session.py +181 -19
  105. agentpool_server/opencode_server/.rules +95 -0
  106. agentpool_server/opencode_server/ENDPOINTS.md +362 -0
  107. agentpool_server/opencode_server/__init__.py +27 -0
  108. agentpool_server/opencode_server/command_validation.py +172 -0
  109. agentpool_server/opencode_server/converters.py +869 -0
  110. agentpool_server/opencode_server/dependencies.py +24 -0
  111. agentpool_server/opencode_server/input_provider.py +269 -0
  112. agentpool_server/opencode_server/models/__init__.py +228 -0
  113. agentpool_server/opencode_server/models/agent.py +53 -0
  114. agentpool_server/opencode_server/models/app.py +60 -0
  115. agentpool_server/opencode_server/models/base.py +26 -0
  116. agentpool_server/opencode_server/models/common.py +23 -0
  117. agentpool_server/opencode_server/models/config.py +37 -0
  118. agentpool_server/opencode_server/models/events.py +647 -0
  119. agentpool_server/opencode_server/models/file.py +88 -0
  120. agentpool_server/opencode_server/models/mcp.py +25 -0
  121. agentpool_server/opencode_server/models/message.py +162 -0
  122. agentpool_server/opencode_server/models/parts.py +190 -0
  123. agentpool_server/opencode_server/models/provider.py +81 -0
  124. agentpool_server/opencode_server/models/pty.py +43 -0
  125. agentpool_server/opencode_server/models/session.py +99 -0
  126. agentpool_server/opencode_server/routes/__init__.py +25 -0
  127. agentpool_server/opencode_server/routes/agent_routes.py +442 -0
  128. agentpool_server/opencode_server/routes/app_routes.py +139 -0
  129. agentpool_server/opencode_server/routes/config_routes.py +241 -0
  130. agentpool_server/opencode_server/routes/file_routes.py +392 -0
  131. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  132. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  133. agentpool_server/opencode_server/routes/message_routes.py +705 -0
  134. agentpool_server/opencode_server/routes/pty_routes.py +299 -0
  135. agentpool_server/opencode_server/routes/session_routes.py +1205 -0
  136. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  137. agentpool_server/opencode_server/server.py +430 -0
  138. agentpool_server/opencode_server/state.py +121 -0
  139. agentpool_server/opencode_server/time_utils.py +8 -0
  140. agentpool_storage/__init__.py +16 -0
  141. agentpool_storage/base.py +103 -0
  142. agentpool_storage/claude_provider.py +907 -0
  143. agentpool_storage/file_provider.py +129 -0
  144. agentpool_storage/memory_provider.py +61 -0
  145. agentpool_storage/models.py +3 -0
  146. agentpool_storage/opencode_provider.py +730 -0
  147. agentpool_storage/project_store.py +325 -0
  148. agentpool_storage/session_store.py +6 -0
  149. agentpool_storage/sql_provider/__init__.py +4 -2
  150. agentpool_storage/sql_provider/models.py +48 -0
  151. agentpool_storage/sql_provider/sql_provider.py +134 -1
  152. agentpool_storage/sql_provider/utils.py +10 -1
  153. agentpool_storage/text_log_provider.py +1 -0
  154. agentpool_toolsets/builtin/__init__.py +0 -8
  155. agentpool_toolsets/builtin/code.py +95 -56
  156. agentpool_toolsets/builtin/debug.py +16 -21
  157. agentpool_toolsets/builtin/execution_environment.py +99 -103
  158. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  159. agentpool_toolsets/builtin/skills.py +86 -4
  160. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  161. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  162. agentpool_toolsets/fsspec_toolset/grep.py +74 -2
  163. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  164. agentpool_toolsets/fsspec_toolset/toolset.py +159 -38
  165. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  166. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  167. agentpool_toolsets/mcp_discovery/toolset.py +454 -0
  168. agentpool_toolsets/mcp_run_toolset.py +84 -6
  169. agentpool_toolsets/builtin/agent_management.py +0 -239
  170. agentpool_toolsets/builtin/history.py +0 -36
  171. agentpool_toolsets/builtin/integration.py +0 -85
  172. agentpool_toolsets/builtin/tool_management.py +0 -90
  173. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/entry_points.txt +0 -0
  174. {agentpool-2.1.9.dist-info → agentpool-2.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -8,12 +8,15 @@ from agentpool.agents.events import (
8
8
  CustomEvent,
9
9
  LocationContentItem,
10
10
  PlanUpdateEvent,
11
+ TextContentItem,
11
12
  ToolCallProgressEvent,
12
13
  ToolCallStartEvent,
13
14
  )
14
15
 
15
16
 
16
17
  if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
17
20
  from agentpool.agents.context import AgentContext
18
21
  from agentpool.agents.events import RichAgentStreamEvent, ToolCallContentItem
19
22
  from agentpool.resource_providers.plan_provider import PlanEntry
@@ -78,7 +81,7 @@ class StreamEventEmitter:
78
81
  title: str,
79
82
  *,
80
83
  status: Literal["pending", "in_progress", "completed", "failed"] = "in_progress",
81
- items: list[ToolCallContentItem] | None = None,
84
+ items: Sequence[ToolCallContentItem | str] | None = None,
82
85
  replace_content: bool = False,
83
86
  ) -> None:
84
87
  """Emit a progress event.
@@ -89,12 +92,26 @@ class StreamEventEmitter:
89
92
  items: Rich content items (terminals, diffs, locations, text)
90
93
  replace_content: If True, items replace existing content instead of appending
91
94
  """
95
+ # Record file changes from DiffContentItem for diff/revert support
96
+ if self._context.pool and self._context.pool.file_ops and items:
97
+ from agentpool.agents.events import DiffContentItem
98
+
99
+ for item in items:
100
+ if isinstance(item, DiffContentItem):
101
+ self._context.pool.file_ops.record_change(
102
+ path=item.path,
103
+ old_content=item.old_text,
104
+ new_content=item.new_text,
105
+ operation="create" if item.old_text is None else "write",
106
+ agent_name=self._context.node_name,
107
+ )
108
+
92
109
  event = ToolCallProgressEvent(
93
110
  tool_call_id=self._context.tool_call_id or "",
94
111
  tool_name=self._context.tool_name,
95
112
  status=status,
96
113
  title=title,
97
- items=items or [],
114
+ items=[TextContentItem(text=i) if isinstance(i, str) else i for i in items or []],
98
115
  replace_content=replace_content,
99
116
  )
100
117
  await self._emit(event)
@@ -163,6 +180,16 @@ class StreamEventEmitter:
163
180
  new_text: New file content
164
181
  status: Current status of the edit operation
165
182
  """
183
+ # Record file change for diff/revert support
184
+ if self._context.pool and self._context.pool.file_ops:
185
+ self._context.pool.file_ops.record_change(
186
+ path=path,
187
+ old_content=old_text,
188
+ new_content=new_text,
189
+ operation="edit",
190
+ agent_name=self._context.node_name,
191
+ )
192
+
166
193
  event = ToolCallProgressEvent.file_edit(
167
194
  tool_call_id=self._context.tool_call_id or "",
168
195
  tool_name=self._context.tool_name,
@@ -543,6 +543,25 @@ class PlanUpdateEvent:
543
543
  """Event type identifier."""
544
544
 
545
545
 
546
+ @dataclass(kw_only=True)
547
+ class CompactionEvent:
548
+ """Event indicating context compaction is starting or completed.
549
+
550
+ This is a semantic event that consumers (ACP, OpenCode) handle differently:
551
+ - ACP: Converts to a text message for display
552
+ - OpenCode: Emits session.compacted SSE event
553
+ """
554
+
555
+ session_id: str
556
+ """The session ID being compacted."""
557
+ trigger: Literal["auto", "manual"] = "auto"
558
+ """What triggered the compaction (auto = context overflow, manual = slash command)."""
559
+ phase: Literal["starting", "completed"] = "starting"
560
+ """Current phase of compaction."""
561
+ event_kind: Literal["compaction"] = "compaction"
562
+ """Event type identifier."""
563
+
564
+
546
565
  type RichAgentStreamEvent[OutputDataT] = (
547
566
  AgentStreamEvent
548
567
  | StreamCompleteEvent[OutputDataT]
@@ -552,6 +571,7 @@ type RichAgentStreamEvent[OutputDataT] = (
552
571
  | ToolCallProgressEvent
553
572
  | ToolCallCompleteEvent
554
573
  | PlanUpdateEvent
574
+ | CompactionEvent
555
575
  | CustomEvent[Any]
556
576
  )
557
577
 
@@ -0,0 +1,54 @@
1
+ """Mode definitions for agent behavior configuration.
2
+
3
+ Modes represent switchable behaviors/configurations that agents can expose
4
+ to clients. Each agent type can define its own mode categories.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ @dataclass
13
+ class ModeInfo:
14
+ """Information about a single mode option.
15
+
16
+ Represents one selectable option within a mode category.
17
+ """
18
+
19
+ id: str
20
+ """Unique identifier for this mode."""
21
+
22
+ name: str
23
+ """Human-readable display name."""
24
+
25
+ description: str = ""
26
+ """Optional description of what this mode does."""
27
+
28
+ category_id: str = ""
29
+ """ID of the category this mode belongs to."""
30
+
31
+
32
+ @dataclass
33
+ class ModeCategory:
34
+ """A category of modes that can be switched.
35
+
36
+ Represents a group of mutually exclusive modes. In the future,
37
+ ACP may support multiple categories rendered as separate dropdowns.
38
+
39
+ Examples:
40
+ - Permissions: default, acceptEdits
41
+ - Behavior: plan, code, architect
42
+ """
43
+
44
+ id: str
45
+ """Unique identifier for this category."""
46
+
47
+ name: str
48
+ """Human-readable display name for the category."""
49
+
50
+ available_modes: list[ModeInfo] = field(default_factory=list)
51
+ """List of available modes in this category."""
52
+
53
+ current_mode_id: str = ""
54
+ """ID of the currently active mode."""
@@ -0,0 +1,213 @@
1
+ """Tool call accumulator for streaming tool arguments.
2
+
3
+ This module provides utilities for accumulating streamed tool call arguments
4
+ from LLM APIs that stream JSON arguments incrementally (like Anthropic's
5
+ input_json_delta or OpenAI's function call streaming).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import anyenv
13
+
14
+
15
+ def repair_partial_json(buffer: str) -> str:
16
+ """Attempt to repair truncated JSON for preview purposes.
17
+
18
+ Handles common truncation cases:
19
+ - Unclosed strings
20
+ - Missing closing braces/brackets
21
+ - Trailing commas
22
+
23
+ Args:
24
+ buffer: Potentially incomplete JSON string
25
+
26
+ Returns:
27
+ Repaired JSON string (may still be invalid in edge cases)
28
+ """
29
+ if not buffer:
30
+ return "{}"
31
+
32
+ result = buffer.rstrip()
33
+
34
+ # Check if we're in the middle of a string by counting unescaped quotes
35
+ in_string = False
36
+ i = 0
37
+ while i < len(result):
38
+ char = result[i]
39
+ if char == "\\" and i + 1 < len(result):
40
+ i += 2 # Skip escaped character
41
+ continue
42
+ if char == '"':
43
+ in_string = not in_string
44
+ i += 1
45
+
46
+ # Close unclosed string
47
+ if in_string:
48
+ result += '"'
49
+
50
+ # Remove trailing comma (invalid JSON)
51
+ result = result.rstrip()
52
+ if result.endswith(","):
53
+ result = result[:-1]
54
+
55
+ # Balance braces and brackets
56
+ open_braces = result.count("{") - result.count("}")
57
+ open_brackets = result.count("[") - result.count("]")
58
+
59
+ result += "]" * max(0, open_brackets)
60
+ result += "}" * max(0, open_braces)
61
+
62
+ return result
63
+
64
+
65
+ class ToolCallAccumulator:
66
+ """Accumulates streamed tool call arguments.
67
+
68
+ LLM APIs stream tool call arguments as deltas. This class accumulates them
69
+ and provides the complete arguments when the tool call ends, as well as
70
+ best-effort partial argument parsing during streaming.
71
+
72
+ Example:
73
+ ```python
74
+ accumulator = ToolCallAccumulator()
75
+
76
+ # On content_block_start with tool_use
77
+ accumulator.start("toolu_123", "write_file")
78
+
79
+ # On input_json_delta events
80
+ accumulator.add_args("toolu_123", '{"path": "/tmp/')
81
+ accumulator.add_args("toolu_123", 'test.txt", "content"')
82
+ accumulator.add_args("toolu_123", ': "hello"}')
83
+
84
+ # Get partial args for UI preview (repairs incomplete JSON)
85
+ partial = accumulator.get_partial_args("toolu_123")
86
+
87
+ # On content_block_stop, get final parsed args
88
+ name, args = accumulator.complete("toolu_123")
89
+ ```
90
+ """
91
+
92
+ def __init__(self) -> None:
93
+ self._calls: dict[str, dict[str, Any]] = {}
94
+
95
+ def start(self, tool_call_id: str, tool_name: str) -> None:
96
+ """Start tracking a new tool call.
97
+
98
+ Args:
99
+ tool_call_id: Unique identifier for the tool call
100
+ tool_name: Name of the tool being called
101
+ """
102
+ self._calls[tool_call_id] = {"name": tool_name, "args_buffer": ""}
103
+
104
+ def add_args(self, tool_call_id: str, delta: str) -> None:
105
+ """Add argument delta to a tool call.
106
+
107
+ Args:
108
+ tool_call_id: Tool call identifier
109
+ delta: JSON string fragment to append
110
+ """
111
+ if tool_call_id in self._calls:
112
+ self._calls[tool_call_id]["args_buffer"] += delta
113
+
114
+ def complete(self, tool_call_id: str) -> tuple[str, dict[str, Any]] | None:
115
+ """Complete a tool call and return (tool_name, parsed_args).
116
+
117
+ Removes the tool call from tracking and returns the final parsed arguments.
118
+
119
+ Args:
120
+ tool_call_id: Tool call identifier
121
+
122
+ Returns:
123
+ Tuple of (tool_name, args_dict) or None if call not found
124
+ """
125
+ if tool_call_id not in self._calls:
126
+ return None
127
+
128
+ call_data = self._calls.pop(tool_call_id)
129
+ args_str = call_data["args_buffer"]
130
+ try:
131
+ args = anyenv.load_json(args_str) if args_str else {}
132
+ except anyenv.JsonLoadError:
133
+ args = {"_raw": args_str}
134
+ return call_data["name"], args
135
+
136
+ def get_pending(self, tool_call_id: str) -> tuple[str, str] | None:
137
+ """Get pending call data (tool_name, args_buffer) without completing.
138
+
139
+ Args:
140
+ tool_call_id: Tool call identifier
141
+
142
+ Returns:
143
+ Tuple of (tool_name, args_buffer) or None if not found
144
+ """
145
+ if tool_call_id not in self._calls:
146
+ return None
147
+ data = self._calls[tool_call_id]
148
+ return data["name"], data["args_buffer"]
149
+
150
+ def get_partial_args(self, tool_call_id: str) -> dict[str, Any]:
151
+ """Get best-effort parsed args from incomplete JSON stream.
152
+
153
+ Uses heuristics to complete truncated JSON for preview purposes.
154
+ Handles unclosed strings, missing braces/brackets, and trailing commas.
155
+
156
+ Args:
157
+ tool_call_id: Tool call ID
158
+
159
+ Returns:
160
+ Partially parsed arguments or empty dict
161
+ """
162
+ if tool_call_id not in self._calls:
163
+ return {}
164
+
165
+ buffer = self._calls[tool_call_id]["args_buffer"]
166
+ if not buffer:
167
+ return {}
168
+
169
+ # Try direct parse first
170
+ try:
171
+ result: dict[str, Any] = anyenv.load_json(buffer)
172
+ except anyenv.JsonLoadError:
173
+ pass
174
+ else:
175
+ return result
176
+
177
+ # Try to repair truncated JSON
178
+ try:
179
+ repaired = repair_partial_json(buffer)
180
+ result = anyenv.load_json(repaired)
181
+ except anyenv.JsonLoadError:
182
+ return {}
183
+ else:
184
+ return result
185
+
186
+ def is_pending(self, tool_call_id: str) -> bool:
187
+ """Check if a tool call is being tracked.
188
+
189
+ Args:
190
+ tool_call_id: Tool call identifier
191
+
192
+ Returns:
193
+ True if the tool call is being accumulated
194
+ """
195
+ return tool_call_id in self._calls
196
+
197
+ def get_tool_name(self, tool_call_id: str) -> str | None:
198
+ """Get the tool name for a pending call.
199
+
200
+ Args:
201
+ tool_call_id: Tool call identifier
202
+
203
+ Returns:
204
+ Tool name or None if not found
205
+ """
206
+ if tool_call_id not in self._calls:
207
+ return None
208
+ name: str = self._calls[tool_call_id]["name"]
209
+ return name
210
+
211
+ def clear(self) -> None:
212
+ """Clear all pending tool calls."""
213
+ self._calls.clear()
agentpool/common_types.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Awaitable, Callable
6
+ from dataclasses import dataclass
6
7
  from typing import (
7
8
  TYPE_CHECKING,
8
9
  Any,
@@ -50,6 +51,26 @@ type JsonObject = dict[str, JsonValue]
50
51
  type JsonArray = list[JsonValue]
51
52
 
52
53
 
54
+ MCPConnectionStatus = Literal["connected", "disconnected", "error"]
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class MCPServerStatus:
59
+ """Status information for an MCP server.
60
+
61
+ Attributes:
62
+ name: Server name/identifier
63
+ status: Connection status
64
+ server_type: Transport type (stdio, sse, http)
65
+ error: Error message if status is "error"
66
+ """
67
+
68
+ name: str
69
+ status: MCPConnectionStatus
70
+ server_type: str = "unknown"
71
+ error: str | None = None
72
+
73
+
53
74
  NodeName = str
54
75
  TeamName = str
55
76
  AgentName = str
@@ -7,10 +7,47 @@ from typing import Final
7
7
 
8
8
  _RESOURCES = importlib.resources.files("agentpool.config_resources")
9
9
 
10
+ # Template configuration
10
11
  AGENTS_TEMPLATE: Final[str] = str(_RESOURCES / "agents_template.yml")
11
12
  """Path to the agents template configuration."""
12
13
 
14
+ # Pool configurations
13
15
  ACP_ASSISTANT: Final[str] = str(_RESOURCES / "acp_assistant.yml")
14
16
  """Path to default ACP assistant configuration."""
15
17
 
16
- __all__ = ["ACP_ASSISTANT", "AGENTS_TEMPLATE"]
18
+ AGENTS: Final[str] = str(_RESOURCES / "agents.yml")
19
+ """Path to the main agents configuration."""
20
+
21
+ AGUI_TEST: Final[str] = str(_RESOURCES / "agui_test.yml")
22
+ """Path to AGUI test configuration."""
23
+
24
+ CLAUDE_CODE_ASSISTANT: Final[str] = str(_RESOURCES / "claude_code_agent.yml")
25
+ """Path to default Claude code assistant configuration."""
26
+
27
+ EXTERNAL_ACP_AGENTS: Final[str] = str(_RESOURCES / "external_acp_agents.yml")
28
+ """Path to external ACP agents configuration."""
29
+
30
+ TTS_TEST_AGENTS: Final[str] = str(_RESOURCES / "tts_test_agents.yml")
31
+ """Path to TTS test agents configuration."""
32
+
33
+ # All pool configuration paths for validation
34
+ ALL_POOL_CONFIGS: Final[tuple[str, ...]] = (
35
+ ACP_ASSISTANT,
36
+ AGENTS,
37
+ AGUI_TEST,
38
+ CLAUDE_CODE_ASSISTANT,
39
+ EXTERNAL_ACP_AGENTS,
40
+ TTS_TEST_AGENTS,
41
+ )
42
+ """All pool configuration file paths."""
43
+
44
+ __all__ = [
45
+ "ACP_ASSISTANT",
46
+ "AGENTS",
47
+ "AGENTS_TEMPLATE",
48
+ "AGUI_TEST",
49
+ "ALL_POOL_CONFIGS",
50
+ "CLAUDE_CODE_ASSISTANT",
51
+ "EXTERNAL_ACP_AGENTS",
52
+ "TTS_TEST_AGENTS",
53
+ ]
@@ -7,10 +7,13 @@ agents:
7
7
  type: claude_code
8
8
  display_name: "AI Assistant"
9
9
  builtin_tools: []
10
+ env:
11
+ ANTHROPIC_API_KEY: ""
10
12
  description: "Claude code agent with enhanced tools"
11
13
  toolsets:
12
14
  - type: execution
13
15
  - type: file_access
16
+ edit_tool: batch
14
17
  - type: code
15
18
  - type: search
16
19
  - type: plan
@@ -40,7 +40,6 @@ if TYPE_CHECKING:
40
40
  from pydantic_ai import ModelSettings
41
41
  from pydantic_ai.models import Model
42
42
  from pydantic_ai.output import OutputSpec
43
- from tokonomics.model_names import ModelId
44
43
  from upathtools import JoinablePathLike
45
44
 
46
45
  from agentpool.agents.acp_agent import ACPAgent
@@ -57,6 +56,7 @@ if TYPE_CHECKING:
57
56
  )
58
57
  from agentpool.delegation.base_team import BaseTeam
59
58
  from agentpool.mcp_server.tool_bridge import ToolManagerBridge
59
+ from agentpool.messaging.compaction import CompactionPipeline
60
60
  from agentpool.models import (
61
61
  AGUIAgentConfig,
62
62
  AnyAgentConfig,
@@ -118,6 +118,7 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
118
118
  from agentpool.sessions import SessionManager
119
119
  from agentpool.skills.manager import SkillsManager
120
120
  from agentpool.storage import StorageManager
121
+ from agentpool.utils.streams import FileOpsTracker, TodoTracker
121
122
 
122
123
  match manifest:
123
124
  case None:
@@ -131,6 +132,12 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
131
132
  raise ValueError(msg)
132
133
 
133
134
  registry.configure_observability(self.manifest.observability)
135
+
136
+ # Install memory log handler early so all logs are captured
137
+ from agentpool_toolsets.builtin.debug import install_memory_handler
138
+
139
+ self._memory_log_handler = install_memory_handler()
140
+
134
141
  self.shared_deps_type = shared_deps_type
135
142
  self._input_provider = input_provider
136
143
  self.exit_stack = AsyncExitStack()
@@ -150,14 +157,12 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
150
157
  self._tasks.register(name, task)
151
158
  self.process_manager = ProcessManager()
152
159
  self.pool_talk = TeamTalk[Any].from_nodes(list(self.nodes.values()))
153
-
160
+ self.file_ops = FileOpsTracker()
161
+ # Todo/plan tracker for task management
162
+ self.todos = TodoTracker()
154
163
  # Create all agents from unified manifest.agents dict
155
164
  for name, config in self.manifest.agents.items():
156
- agent = self._create_agent_from_config(
157
- name,
158
- config,
159
- deps_type=shared_deps_type,
160
- )
165
+ agent = self._create_agent_from_config(name, config, deps_type=shared_deps_type)
161
166
  agent.agent_pool = self
162
167
  self.register(name, agent)
163
168
 
@@ -179,32 +184,23 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
179
184
  await self.exit_stack.enter_async_context(self.mcp)
180
185
  await self.exit_stack.enter_async_context(self.skills)
181
186
  aggregating_provider = self.mcp.get_aggregating_provider()
182
- agents = list(self.agents.values())
183
- acp_agents = list(self.acp_agents.values())
184
- agui_agents = list(self.agui_agents.values())
185
- claude_code_agents = list(self.claude_code_agents.values())
187
+ agents = list(self.all_agents.values())
186
188
  teams = list(self.teams.values())
187
189
  for agent in agents:
188
190
  agent.tools.add_provider(aggregating_provider)
189
191
  # Collect remaining components to initialize (MCP already initialized)
190
- components: list[AbstractAsyncContextManager[Any]] = [
192
+ comps: list[AbstractAsyncContextManager[Any]] = [
191
193
  self.storage,
192
194
  self.sessions,
193
195
  *agents,
194
- *acp_agents,
195
- *agui_agents,
196
- *claude_code_agents,
197
196
  *teams,
198
197
  ]
199
- # MCP server is now managed externally - removed from pool
200
- # Initialize all components
198
+ node_inits = [self.exit_stack.enter_async_context(c) for c in comps]
201
199
  if self.parallel_load:
202
- await asyncio.gather(
203
- *(self.exit_stack.enter_async_context(c) for c in components)
204
- )
200
+ await asyncio.gather(*node_inits)
205
201
  else:
206
- for component in components:
207
- await self.exit_stack.enter_async_context(component)
202
+ for init in node_inits:
203
+ await init
208
204
 
209
205
  except Exception as e:
210
206
  await self.cleanup()
@@ -538,6 +534,15 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
538
534
 
539
535
  return {i.name: i for i in self._items.values() if isinstance(i, MessageNode)}
540
536
 
537
+ @property
538
+ def compaction_pipeline(self) -> CompactionPipeline | None:
539
+ """Get the configured compaction pipeline for message history management.
540
+
541
+ Returns:
542
+ CompactionPipeline instance or None if not configured
543
+ """
544
+ return self.manifest.get_compaction_pipeline()
545
+
541
546
  def _validate_item(self, item: MessageNode[Any, Any] | Any) -> MessageNode[Any, Any]:
542
547
  """Validate and convert items before registration.
543
548
 
@@ -639,7 +644,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
639
644
  agent: AgentName | Agent[Any, str],
640
645
  *,
641
646
  return_type: type[TResult] = str, # type: ignore
642
- model_override: ModelId | str | None = None,
643
647
  session: SessionIdType | SessionQuery = None,
644
648
  ) -> Agent[TPoolDeps, TResult]: ...
645
649
 
@@ -650,7 +654,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
650
654
  *,
651
655
  deps_type: type[TCustomDeps],
652
656
  return_type: type[TResult] = str, # type: ignore
653
- model_override: ModelId | str | None = None,
654
657
  session: SessionIdType | SessionQuery = None,
655
658
  ) -> Agent[TCustomDeps, TResult]: ...
656
659
 
@@ -660,7 +663,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
660
663
  *,
661
664
  deps_type: Any | None = None,
662
665
  return_type: Any = str,
663
- model_override: ModelId | str | None = None,
664
666
  session: SessionIdType | SessionQuery = None,
665
667
  ) -> Agent[Any, Any]:
666
668
  """Get or configure an agent from the pool.
@@ -673,7 +675,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
673
675
  agent: Either agent name or instance
674
676
  deps_type: Optional custom dependencies type (overrides shared deps)
675
677
  return_type: Optional type for structured responses
676
- model_override: Optional model override
677
678
  session: Optional session ID or query to recover conversation
678
679
 
679
680
  Returns:
@@ -684,6 +685,10 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
684
685
  Raises:
685
686
  KeyError: If agent name not found
686
687
  ValueError: If configuration is invalid
688
+
689
+ Note:
690
+ To change the model, call `await agent.set_model(model_id)` after
691
+ getting the agent.
687
692
  """
688
693
  from agentpool.agents import Agent
689
694
 
@@ -692,8 +697,6 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
692
697
  # base.context.data = deps if deps is not None else self.shared_deps
693
698
  base.deps_type = deps_type
694
699
  base.agent_pool = self
695
- if model_override:
696
- base.set_model(model_override)
697
700
  if session:
698
701
  base.conversation.load_history_from_database(session=session)
699
702
  if return_type not in {str, None}:
@@ -987,6 +990,9 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
987
990
  case _:
988
991
  model = None
989
992
  model_settings = None
993
+ # Extract pydantic-ai builtin tools from config
994
+ builtin_tools = config.get_builtin_tools()
995
+
990
996
  return Agent(
991
997
  # context=context,
992
998
  model=model,
@@ -1009,9 +1015,11 @@ class AgentPool[TPoolDeps = None](BaseRegistry[NodeName, MessageNode[Any, Any]])
1009
1015
  tool_mode=config.tool_mode,
1010
1016
  knowledge=config.knowledge,
1011
1017
  toolsets=toolsets_list,
1012
- auto_cache=config.auto_cache,
1013
1018
  hooks=config.hooks.get_agent_hooks() if config.hooks else None,
1014
1019
  tool_confirmation_mode=config.requires_tool_confirmation,
1020
+ builtin_tools=builtin_tools or None,
1021
+ usage_limits=config.usage_limits,
1022
+ providers=config.model_providers,
1015
1023
  )
1016
1024
 
1017
1025
  def create_acp_agent[TDeps](
@@ -176,6 +176,7 @@ class Team[TDeps = None](BaseTeam[TDeps, Any]):
176
176
  name=self.name,
177
177
  message_id=message_id,
178
178
  conversation_id=user_msg.conversation_id,
179
+ parent_id=user_msg.message_id,
179
180
  metadata={
180
181
  "agent_names": [r.agent_name for r in result],
181
182
  "errors": {name: str(error) for name, error in result.errors.items()},
@@ -172,6 +172,7 @@ class TeamRun[TDeps, TResult](BaseTeam[TDeps, TResult]):
172
172
  associated_messages=all_messages,
173
173
  message_id=message_id,
174
174
  conversation_id=user_msg.conversation_id,
175
+ parent_id=user_msg.message_id,
175
176
  metadata={
176
177
  "execution_order": [r.agent_name for r in result],
177
178
  "start_time": result.start_time.isoformat(),