agentpool 2.1.9__py3-none-any.whl → 2.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -22,7 +22,7 @@ Example:
22
22
  compacted = await pipeline.apply(messages)
23
23
 
24
24
  # Or via config (for YAML)
25
- config = CompactionPipelineConfig(steps=[
25
+ config = CompactionConfig(steps=[
26
26
  FilterThinkingConfig(),
27
27
  TruncateToolOutputsConfig(max_length=1000),
28
28
  KeepLastMessagesConfig(count=10),
@@ -50,9 +50,8 @@ from __future__ import annotations
50
50
  from abc import ABC, abstractmethod
51
51
  from collections.abc import Sequence
52
52
  from dataclasses import dataclass, field, replace
53
- from typing import TYPE_CHECKING, Annotated, Any, Literal, Self, cast
53
+ from typing import TYPE_CHECKING, Any, Self, cast
54
54
 
55
- from pydantic import BaseModel, Field
56
55
  from pydantic_ai import (
57
56
  Agent,
58
57
  BinaryContent,
@@ -73,6 +72,8 @@ if TYPE_CHECKING:
73
72
  from pydantic_ai import ModelRequestPart, ModelResponsePart
74
73
  from tokonomics.model_names import ModelId
75
74
 
75
+ from agentpool.messaging.message_history import MessageHistory
76
+
76
77
  # Type aliases
77
78
  ModelMessage = ModelRequest | ModelResponse
78
79
  MessageSequence = Sequence[ModelMessage]
@@ -629,200 +630,6 @@ class WhenMessageCountExceeds(CompactionStep):
629
630
  return list(messages)
630
631
 
631
632
 
632
- # =============================================================================
633
- # Configuration Models - For YAML/JSON configuration
634
- # =============================================================================
635
-
636
-
637
- class FilterThinkingConfig(BaseModel):
638
- """Configuration for FilterThinking step."""
639
-
640
- type: Literal["filter_thinking"] = "filter_thinking"
641
-
642
- def build(self) -> FilterThinking:
643
- return FilterThinking()
644
-
645
-
646
- class FilterRetryPromptsConfig(BaseModel):
647
- """Configuration for FilterRetryPrompts step."""
648
-
649
- type: Literal["filter_retry_prompts"] = "filter_retry_prompts"
650
-
651
- def build(self) -> FilterRetryPrompts:
652
- return FilterRetryPrompts()
653
-
654
-
655
- class FilterBinaryContentConfig(BaseModel):
656
- """Configuration for FilterBinaryContent step."""
657
-
658
- type: Literal["filter_binary"] = "filter_binary"
659
- keep_references: bool = False
660
-
661
- def build(self) -> FilterBinaryContent:
662
- return FilterBinaryContent(keep_references=self.keep_references)
663
-
664
-
665
- class FilterToolCallsConfig(BaseModel):
666
- """Configuration for FilterToolCalls step."""
667
-
668
- type: Literal["filter_tools"] = "filter_tools"
669
- exclude_tools: list[str] = Field(default_factory=list)
670
- include_only: list[str] | None = None
671
-
672
- def build(self) -> FilterToolCalls:
673
- return FilterToolCalls(exclude_tools=self.exclude_tools, include_only=self.include_only)
674
-
675
-
676
- class FilterEmptyMessagesConfig(BaseModel):
677
- """Configuration for FilterEmptyMessages step."""
678
-
679
- type: Literal["filter_empty"] = "filter_empty"
680
-
681
- def build(self) -> FilterEmptyMessages:
682
- return FilterEmptyMessages()
683
-
684
-
685
- class TruncateToolOutputsConfig(BaseModel):
686
- """Configuration for TruncateToolOutputs step."""
687
-
688
- type: Literal["truncate_tool_outputs"] = "truncate_tool_outputs"
689
- max_length: int = 2000
690
- suffix: str = "\n... [truncated]"
691
-
692
- def build(self) -> TruncateToolOutputs:
693
- return TruncateToolOutputs(max_length=self.max_length, suffix=self.suffix)
694
-
695
-
696
- class TruncateTextPartsConfig(BaseModel):
697
- """Configuration for TruncateTextParts step."""
698
-
699
- type: Literal["truncate_text"] = "truncate_text"
700
- max_length: int = 5000
701
- suffix: str = "\n... [truncated]"
702
-
703
- def build(self) -> TruncateTextParts:
704
- return TruncateTextParts(max_length=self.max_length, suffix=self.suffix)
705
-
706
-
707
- class KeepLastMessagesConfig(BaseModel):
708
- """Configuration for KeepLastMessages step."""
709
-
710
- type: Literal["keep_last"] = "keep_last"
711
- count: int = 10
712
- count_pairs: bool = True
713
-
714
- def build(self) -> KeepLastMessages:
715
- return KeepLastMessages(count=self.count, count_pairs=self.count_pairs)
716
-
717
-
718
- class KeepFirstMessagesConfig(BaseModel):
719
- """Configuration for KeepFirstMessages step."""
720
-
721
- type: Literal["keep_first"] = "keep_first"
722
- count: int = 2
723
-
724
- def build(self) -> KeepFirstMessages:
725
- return KeepFirstMessages(count=self.count)
726
-
727
-
728
- class KeepFirstAndLastConfig(BaseModel):
729
- """Configuration for KeepFirstAndLast step."""
730
-
731
- type: Literal["keep_first_last"] = "keep_first_last"
732
- first_count: int = 2
733
- last_count: int = 5
734
-
735
- def build(self) -> KeepFirstAndLast:
736
- return KeepFirstAndLast(first_count=self.first_count, last_count=self.last_count)
737
-
738
-
739
- class TokenBudgetConfig(BaseModel):
740
- """Configuration for TokenBudget step."""
741
-
742
- type: Literal["token_budget"] = "token_budget"
743
- max_tokens: int = 4000
744
- model: str = "gpt-4o"
745
-
746
- def build(self) -> TokenBudget:
747
- return TokenBudget(max_tokens=self.max_tokens, model=self.model)
748
-
749
-
750
- class SummarizeConfig(BaseModel):
751
- """Configuration for Summarize step."""
752
-
753
- type: Literal["summarize"] = "summarize"
754
- model: str = "openai:gpt-4o-mini"
755
- threshold: int = 15
756
- keep_recent: int = 5
757
- summary_prompt: str | None = None
758
-
759
- def build(self) -> Summarize:
760
- kwargs: dict[str, Any] = {
761
- "model": self.model,
762
- "threshold": self.threshold,
763
- "keep_recent": self.keep_recent,
764
- }
765
- if self.summary_prompt:
766
- kwargs["summary_prompt"] = self.summary_prompt
767
- return Summarize(**kwargs)
768
-
769
-
770
- class WhenMessageCountExceedsConfig(BaseModel):
771
- """Configuration for WhenMessageCountExceeds wrapper."""
772
-
773
- type: Literal["when_count_exceeds"] = "when_count_exceeds"
774
- threshold: int = 20
775
- step: "CompactionStepConfig"
776
-
777
- def build(self) -> WhenMessageCountExceeds:
778
- return WhenMessageCountExceeds(step=self.step.build(), threshold=self.threshold)
779
-
780
-
781
- # Union of all config types with discriminator
782
- CompactionStepConfig = Annotated[
783
- FilterThinkingConfig
784
- | FilterRetryPromptsConfig
785
- | FilterBinaryContentConfig
786
- | FilterToolCallsConfig
787
- | FilterEmptyMessagesConfig
788
- | TruncateToolOutputsConfig
789
- | TruncateTextPartsConfig
790
- | KeepLastMessagesConfig
791
- | KeepFirstMessagesConfig
792
- | KeepFirstAndLastConfig
793
- | TokenBudgetConfig
794
- | SummarizeConfig
795
- | WhenMessageCountExceedsConfig,
796
- Field(discriminator="type"),
797
- ]
798
-
799
- # Update forward reference
800
- WhenMessageCountExceedsConfig.model_rebuild()
801
-
802
-
803
- class CompactionPipelineConfig(BaseModel):
804
- """Configuration for a complete compaction pipeline.
805
-
806
- Example YAML:
807
- ```yaml
808
- compaction:
809
- steps:
810
- - type: filter_thinking
811
- - type: truncate_tool_outputs
812
- max_length: 1000
813
- - type: keep_last
814
- count: 10
815
- ```
816
- """
817
-
818
- steps: list[CompactionStepConfig] = Field(default_factory=list)
819
- """Ordered list of compaction steps to apply."""
820
-
821
- def build(self) -> CompactionPipeline:
822
- """Build a CompactionPipeline from this configuration."""
823
- return CompactionPipeline(steps=[step.build() for step in self.steps])
824
-
825
-
826
633
  # =============================================================================
827
634
  # Preset Pipelines - Common configurations
828
635
  # =============================================================================
@@ -877,6 +684,74 @@ def summarizing_context(model: ModelId | str = "openai:gpt-4o-mini") -> Compacti
877
684
  # =============================================================================
878
685
 
879
686
 
687
+ async def compact_conversation(
688
+ pipeline: CompactionPipeline,
689
+ conversation: MessageHistory,
690
+ ) -> tuple[int, int]:
691
+ """Apply a compaction pipeline to a conversation's message history.
692
+
693
+ Extracts model messages from ChatMessages, applies the pipeline,
694
+ and rebuilds the conversation history with compacted messages.
695
+
696
+ Args:
697
+ pipeline: The compaction pipeline to apply
698
+ conversation: The MessageHistory to compact
699
+
700
+ Returns:
701
+ Tuple of (original_count, compacted_count) of model messages
702
+ """
703
+ from agentpool.messaging.messages import ChatMessage
704
+
705
+ chat_messages = conversation.get_history()
706
+ if not chat_messages:
707
+ return 0, 0
708
+
709
+ # Extract ModelRequest/ModelResponse from ChatMessage.messages
710
+ model_messages: list[ModelMessage] = []
711
+ for chat_msg in chat_messages:
712
+ if chat_msg.messages:
713
+ model_messages.extend(chat_msg.messages)
714
+
715
+ if not model_messages:
716
+ return 0, 0
717
+
718
+ original_count = len(model_messages)
719
+
720
+ # Apply the compaction pipeline
721
+ compacted = await pipeline.apply(model_messages)
722
+
723
+ # Rebuild ChatMessages from compacted model messages
724
+ new_chat_messages: list[ChatMessage[Any]] = []
725
+ current_msgs: list[ModelMessage] = []
726
+
727
+ for msg in compacted:
728
+ current_msgs.append(msg)
729
+ if isinstance(msg, ModelResponse):
730
+ new_chat_messages.append(
731
+ ChatMessage(
732
+ content="[compacted]",
733
+ role="assistant",
734
+ messages=list(current_msgs),
735
+ )
736
+ )
737
+ current_msgs = []
738
+
739
+ # Handle any remaining messages (incomplete pair)
740
+ if current_msgs:
741
+ new_chat_messages.append(
742
+ ChatMessage(
743
+ content="[compacted]",
744
+ role="user",
745
+ messages=list(current_msgs),
746
+ )
747
+ )
748
+
749
+ # Update the conversation history
750
+ conversation.set_history(new_chat_messages)
751
+
752
+ return original_count, len(compacted)
753
+
754
+
880
755
  def _extract_text_content(msg: ModelMessage) -> str:
881
756
  """Extract text content from a message for token counting."""
882
757
  parts_text: list[str] = []
@@ -6,7 +6,8 @@ from collections.abc import Sequence
6
6
  from contextlib import asynccontextmanager
7
7
  from typing import TYPE_CHECKING, Any, Self
8
8
 
9
- from psygnal import Signal
9
+ from anyenv.signals import Signal
10
+ from psygnal import Signal as Psygnal
10
11
  from psygnal.containers import EventedList
11
12
 
12
13
  from agentpool.log import get_logger
@@ -27,10 +28,10 @@ logger = get_logger(__name__)
27
28
  class ConnectionManager:
28
29
  """Manages connections for both Agents and Teams."""
29
30
 
30
- connection_processed = Signal(Talk.ConnectionProcessed)
31
+ connection_processed = Signal[Talk.ConnectionProcessed]()
31
32
 
32
- node_connected = Signal(object) # Node
33
- connection_added = Signal(Talk) # Agent
33
+ node_connected = Psygnal(object) # Node
34
+ connection_added = Psygnal(Talk) # Agent
34
35
 
35
36
  def __init__(self, owner: MessageNode[Any, Any]) -> None:
36
37
  self.owner = owner
@@ -54,9 +55,9 @@ class ConnectionManager:
54
55
  old.connection_processed.disconnect(self._handle_message_flow)
55
56
  new.connection_processed.connect(self._handle_message_flow)
56
57
 
57
- def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
58
+ async def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
58
59
  """Forward message flow to our aggregated signal."""
59
- self.connection_processed.emit(event)
60
+ await self.connection_processed.emit(event)
60
61
 
61
62
  def set_wait_state(self, target: MessageNode[Any, Any] | AgentName, wait: bool = True) -> None:
62
63
  """Set waiting behavior for target."""
@@ -308,10 +309,10 @@ class ConnectionManager:
308
309
  if __name__ == "__main__":
309
310
  from agentpool.agents import Agent
310
311
 
311
- agent = Agent("test_agent")
312
- agent_2 = Agent("test_agent_2")
313
- agent_3 = Agent("test_agent_3")
314
- agent_4 = Agent("test_agent_4")
312
+ agent = Agent("test_agent", model="openai:gpt-5-nano")
313
+ agent_2 = Agent("test_agent_2", model="openai:gpt-5-nano")
314
+ agent_3 = Agent("test_agent_3", model="openai:gpt-5-nano")
315
+ agent_4 = Agent("test_agent_4", model="openai:gpt-5-nano")
315
316
  _conn_1 = agent >> agent_2
316
317
  _conn_2 = agent >> agent_3
317
318
  _conn_3 = agent_2 >> agent_4
@@ -10,9 +10,9 @@ from functools import wraps
10
10
  import inspect
11
11
  from typing import TYPE_CHECKING, Any, Self
12
12
 
13
- from evented.configs import EmailConfig, FileWatchConfig, TimeEventConfig, WebhookConfig
13
+ from anyenv.signals import Signal
14
14
  from evented.event_data import EventData, FunctionResultEventData
15
- from psygnal import Signal
15
+ from evented_config import EmailConfig, FileWatchConfig, TimeEventConfig, WebhookConfig
16
16
  from pydantic import SecretStr
17
17
 
18
18
  from agentpool.log import get_logger
@@ -27,8 +27,8 @@ if TYPE_CHECKING:
27
27
  from types import TracebackType
28
28
 
29
29
  from evented.base import EventSource
30
- from evented.configs import EventConfig
31
30
  from evented.timed_watcher import TimeEventSource
31
+ from evented_config import EventConfig
32
32
 
33
33
 
34
34
  logger = get_logger(__name__)
@@ -40,7 +40,7 @@ type EventCallback = Callable[[EventData], None | Awaitable[None]]
40
40
  class EventManager:
41
41
  """Manages multiple event sources and their lifecycles."""
42
42
 
43
- event_processed = Signal(EventData)
43
+ event_processed = Signal[EventData]()
44
44
 
45
45
  def __init__(
46
46
  self,
@@ -83,7 +83,7 @@ class EventManager:
83
83
  except Exception:
84
84
  logger.exception("Error in event callback", name=get_fn_name(callback))
85
85
 
86
- self.event_processed.emit(event)
86
+ await self.event_processed.emit(event)
87
87
 
88
88
  async def add_file_watch(
89
89
  self,
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import itertools
6
5
  from typing import TYPE_CHECKING, Any, Literal
7
6
 
8
7
  from psygnal.containers import EventedList
@@ -145,36 +144,13 @@ class ChatMessageList(EventedList[ChatMessage[Any]]):
145
144
  message: Message to build flow DAG for
146
145
 
147
146
  Returns:
148
- Root DAGNode of the graph
147
+ Root DAGNode of the graph, or None
148
+
149
+ Note:
150
+ forwarded_from has been removed. This method now returns None.
151
+ Flow tracking can be reconstructed from parent_id chain or pool history.
149
152
  """
150
- from agentpool.utils.dag import DAGNode
151
-
152
- # Get messages from this conversation
153
- conv_messages = [m for m in self if m.conversation_id == message.conversation_id]
154
- nodes: dict[str, DAGNode] = {}
155
- for msg in conv_messages: # First create all nodes
156
- if msg.forwarded_from:
157
- chain = [*msg.forwarded_from, msg.name or "unknown"]
158
- for name in chain:
159
- if name not in nodes:
160
- nodes[name] = DAGNode(name)
161
-
162
- # Then set up parent relationships
163
- for msg in conv_messages:
164
- if msg.forwarded_from:
165
- chain = [*msg.forwarded_from, msg.name or "unknown"]
166
- # Connect consecutive nodes
167
- for parent_name, child_name in itertools.pairwise(chain):
168
- parent = nodes[parent_name]
169
- child = nodes[child_name]
170
- if parent not in child.parents:
171
- child.add_parent(parent)
172
-
173
- # Find root nodes (those without parents)
174
- roots = [node for node in nodes.values() if node.is_root]
175
- if not roots:
176
- return None
177
- return roots[0] # Return first root for now
153
+ return None
178
154
 
179
155
  def to_mermaid_graph(
180
156
  self,
@@ -6,10 +6,11 @@ import asyncio
6
6
  from collections import deque
7
7
  from contextlib import asynccontextmanager
8
8
  from dataclasses import dataclass, field
9
+ import json
9
10
  from typing import TYPE_CHECKING, Any, Self, assert_never
10
11
  from uuid import UUID, uuid4
11
12
 
12
- from psygnal import Signal
13
+ from anyenv.signals import Signal
13
14
  from upathtools import read_path, to_upath
14
15
 
15
16
  from agentpool.log import get_logger
@@ -24,6 +25,7 @@ if TYPE_CHECKING:
24
25
  from datetime import datetime
25
26
  from types import TracebackType
26
27
 
28
+ from fsspec.asyn import AsyncFileSystem
27
29
  from pydantic_ai import UserContent
28
30
  from toprompt import AnyPromptType
29
31
  from upathtools import JoinablePathLike
@@ -48,7 +50,7 @@ class MessageHistory:
48
50
  session_id: str
49
51
  timestamp: datetime = field(default_factory=get_now)
50
52
 
51
- history_cleared = Signal(HistoryCleared)
53
+ history_cleared = Signal[HistoryCleared]()
52
54
 
53
55
  def __init__(
54
56
  self,
@@ -94,6 +96,14 @@ class MessageHistory:
94
96
  # Note: max_messages and max_tokens will be handled in add_message/get_history
95
97
  # to maintain the rolling window during conversation
96
98
 
99
+ # Filesystem for message history
100
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
101
+ from fsspec.implementations.memory import MemoryFileSystem
102
+
103
+ self._memory_fs = MemoryFileSystem()
104
+ self._fs = AsyncFileSystemWrapper(self._memory_fs)
105
+ self._fs_initialized = False
106
+
97
107
  @property
98
108
  def storage(self) -> StorageManager:
99
109
  return self._storage
@@ -150,17 +160,20 @@ class MessageHistory:
150
160
  self,
151
161
  *,
152
162
  max_tokens: int | None = None,
153
- include_system: bool = False,
154
163
  format_template: str | None = None,
155
- num_messages: int | None = None, # Add this parameter
164
+ num_messages: int | None = None,
156
165
  ) -> str:
157
166
  """Format conversation history as a single context message.
158
167
 
159
168
  Args:
160
169
  max_tokens: Optional limit to include only last N tokens
161
- include_system: Whether to include system messages
162
170
  format_template: Optional custom format (defaults to agent/message pairs)
163
171
  num_messages: Optional limit to include only last N messages
172
+
173
+ Note:
174
+ System prompts are stored as metadata (ModelRequest.instructions),
175
+ not as separate messages with role="system". ChatMessage.role only
176
+ supports "user" and "assistant".
164
177
  """
165
178
  template = format_template or "Agent {agent}: {content}\n"
166
179
  messages: list[str] = []
@@ -323,14 +336,14 @@ class MessageHistory:
323
336
  self.chat_messages.clear()
324
337
  self.chat_messages.extend(history)
325
338
 
326
- def clear(self) -> None:
339
+ async def clear(self) -> None:
327
340
  """Clear conversation history and prompts."""
328
341
  from agentpool.messaging import ChatMessageList
329
342
 
330
343
  self.chat_messages = ChatMessageList()
331
344
  self._last_messages = []
332
345
  event = self.HistoryCleared(session_id=str(self.id))
333
- self.history_cleared.emit(event)
346
+ await self.history_cleared.emit(event)
334
347
 
335
348
  @asynccontextmanager
336
349
  async def temporary_state(
@@ -477,12 +490,90 @@ class MessageHistory:
477
490
  # Use cost_info if available
478
491
  return self.chat_messages.get_history_tokens()
479
492
 
493
+ def get_last_message_id(self) -> str | None:
494
+ """Get the message_id of the last message in history.
495
+
496
+ Used for setting parent_id on new messages to build the message tree.
497
+
498
+ Returns:
499
+ The message_id of the last message, or None if history is empty.
500
+ """
501
+ if not self.chat_messages:
502
+ return None
503
+ return self.chat_messages[-1].message_id
504
+
505
+ def _update_filesystem(self) -> None:
506
+ """Update filesystem with current message history."""
507
+ # Clear existing files
508
+ self._memory_fs.store.clear()
509
+ self._memory_fs.pseudo_dirs.clear()
510
+
511
+ # Create directory structure
512
+ self._memory_fs.makedirs("messages", exist_ok=True)
513
+ self._memory_fs.makedirs("by_role", exist_ok=True)
514
+
515
+ for msg in self.chat_messages:
516
+ # Format: {timestamp}_{role}_{message_id}
517
+ timestamp = msg.timestamp.strftime("%Y%m%d_%H%M%S_%f")
518
+ base_name = f"{timestamp}_{msg.role}_{msg.message_id}"
519
+
520
+ # Write message content
521
+ content_path = f"messages/{base_name}.txt"
522
+ content = str(msg.content)
523
+ self._memory_fs.pipe(content_path, content.encode("utf-8"))
524
+
525
+ # Write metadata
526
+ metadata = {
527
+ "message_id": msg.message_id,
528
+ "role": msg.role,
529
+ "timestamp": msg.timestamp.isoformat(),
530
+ "parent_id": msg.parent_id,
531
+ "model_name": msg.model_name,
532
+ "tokens": msg.usage.total_tokens if msg.usage else None,
533
+ "cost": float(msg.cost_info.total_cost) if msg.cost_info else None,
534
+ }
535
+ metadata_path = f"messages/{base_name}.json"
536
+ self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
537
+
538
+ # Create role-based directory symlinks (by storing paths)
539
+ role_dir = f"by_role/{msg.role}"
540
+ self._memory_fs.makedirs(role_dir, exist_ok=True)
541
+
542
+ # Write summary
543
+ summary = {
544
+ "session_id": self.id,
545
+ "total_messages": len(self.chat_messages),
546
+ "total_tokens": self.get_history_tokens(),
547
+ "total_cost": self.chat_messages.get_total_cost(),
548
+ "roles": {
549
+ "user": len([m for m in self.chat_messages if m.role == "user"]),
550
+ "assistant": len([m for m in self.chat_messages if m.role == "assistant"]),
551
+ },
552
+ }
553
+ self._memory_fs.pipe("summary.json", json.dumps(summary, indent=2).encode("utf-8"))
554
+
555
+ self._fs_initialized = True
556
+
557
+ def get_fs(self) -> AsyncFileSystem:
558
+ """Get filesystem view of message history.
559
+
560
+ Returns:
561
+ AsyncFileSystem containing:
562
+ - messages/{timestamp}_{role}_{message_id}.txt - Message content
563
+ - messages/{timestamp}_{role}_{message_id}.json - Message metadata
564
+ - by_role/{role}/ - Messages organized by role
565
+ - summary.json - Conversation statistics
566
+ """
567
+ # Update filesystem on access
568
+ self._update_filesystem()
569
+ return self._fs
570
+
480
571
 
481
572
  if __name__ == "__main__":
482
573
  from agentpool import Agent
483
574
 
484
575
  async def main() -> None:
485
- async with Agent() as agent:
576
+ async with Agent(model="openai:gpt-5-nano") as agent:
486
577
  await agent.conversation.add_context_from_path("E:/mcp_zed.yml")
487
578
  print(agent.conversation.get_history())
488
579