agentpool 2.2.3__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 (250) hide show
  1. acp/__init__.py +0 -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/client/connection.py +38 -29
  7. acp/client/implementations/default_client.py +3 -2
  8. acp/client/implementations/headless_client.py +2 -2
  9. acp/connection.py +2 -2
  10. acp/notifications.py +18 -49
  11. acp/schema/__init__.py +2 -0
  12. acp/schema/agent_responses.py +21 -0
  13. acp/schema/client_requests.py +3 -3
  14. acp/schema/session_state.py +63 -29
  15. acp/task/supervisor.py +2 -2
  16. acp/utils.py +2 -2
  17. agentpool/__init__.py +2 -0
  18. agentpool/agents/acp_agent/acp_agent.py +278 -263
  19. agentpool/agents/acp_agent/acp_converters.py +150 -17
  20. agentpool/agents/acp_agent/client_handler.py +35 -24
  21. agentpool/agents/acp_agent/session_state.py +14 -6
  22. agentpool/agents/agent.py +471 -643
  23. agentpool/agents/agui_agent/agui_agent.py +104 -107
  24. agentpool/agents/agui_agent/helpers.py +3 -4
  25. agentpool/agents/base_agent.py +485 -32
  26. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  27. agentpool/agents/claude_code_agent/__init__.py +13 -1
  28. agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
  29. agentpool/agents/claude_code_agent/converters.py +4 -141
  30. agentpool/agents/claude_code_agent/models.py +77 -0
  31. agentpool/agents/claude_code_agent/static_info.py +100 -0
  32. agentpool/agents/claude_code_agent/usage.py +242 -0
  33. agentpool/agents/events/__init__.py +22 -0
  34. agentpool/agents/events/builtin_handlers.py +65 -0
  35. agentpool/agents/events/event_emitter.py +3 -0
  36. agentpool/agents/events/events.py +84 -3
  37. agentpool/agents/events/infer_info.py +145 -0
  38. agentpool/agents/events/processors.py +254 -0
  39. agentpool/agents/interactions.py +41 -6
  40. agentpool/agents/modes.py +13 -0
  41. agentpool/agents/slashed_agent.py +5 -4
  42. agentpool/agents/tool_wrapping.py +18 -6
  43. agentpool/common_types.py +35 -21
  44. agentpool/config_resources/acp_assistant.yml +2 -2
  45. agentpool/config_resources/agents.yml +3 -0
  46. agentpool/config_resources/agents_template.yml +1 -0
  47. agentpool/config_resources/claude_code_agent.yml +9 -8
  48. agentpool/config_resources/external_acp_agents.yml +2 -1
  49. agentpool/delegation/base_team.py +4 -30
  50. agentpool/delegation/pool.py +104 -265
  51. agentpool/delegation/team.py +57 -57
  52. agentpool/delegation/teamrun.py +50 -55
  53. agentpool/functional/run.py +10 -4
  54. agentpool/mcp_server/client.py +73 -38
  55. agentpool/mcp_server/conversions.py +54 -13
  56. agentpool/mcp_server/manager.py +9 -23
  57. agentpool/mcp_server/registries/official_registry_client.py +10 -1
  58. agentpool/mcp_server/tool_bridge.py +114 -79
  59. agentpool/messaging/connection_manager.py +11 -10
  60. agentpool/messaging/event_manager.py +5 -5
  61. agentpool/messaging/message_container.py +6 -30
  62. agentpool/messaging/message_history.py +87 -8
  63. agentpool/messaging/messagenode.py +52 -14
  64. agentpool/messaging/messages.py +2 -26
  65. agentpool/messaging/processing.py +10 -22
  66. agentpool/models/__init__.py +1 -1
  67. agentpool/models/acp_agents/base.py +6 -2
  68. agentpool/models/acp_agents/mcp_capable.py +124 -15
  69. agentpool/models/acp_agents/non_mcp.py +0 -23
  70. agentpool/models/agents.py +66 -66
  71. agentpool/models/agui_agents.py +1 -1
  72. agentpool/models/claude_code_agents.py +111 -17
  73. agentpool/models/file_parsing.py +0 -1
  74. agentpool/models/manifest.py +70 -50
  75. agentpool/prompts/conversion_manager.py +1 -1
  76. agentpool/prompts/prompts.py +5 -2
  77. agentpool/resource_providers/__init__.py +2 -0
  78. agentpool/resource_providers/aggregating.py +4 -2
  79. agentpool/resource_providers/base.py +13 -3
  80. agentpool/resource_providers/codemode/code_executor.py +72 -5
  81. agentpool/resource_providers/codemode/helpers.py +2 -2
  82. agentpool/resource_providers/codemode/provider.py +64 -12
  83. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  84. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  85. agentpool/resource_providers/filtering.py +3 -1
  86. agentpool/resource_providers/mcp_provider.py +66 -12
  87. agentpool/resource_providers/plan_provider.py +111 -18
  88. agentpool/resource_providers/pool.py +5 -3
  89. agentpool/resource_providers/resource_info.py +111 -0
  90. agentpool/resource_providers/static.py +2 -2
  91. agentpool/sessions/__init__.py +2 -0
  92. agentpool/sessions/manager.py +2 -3
  93. agentpool/sessions/models.py +9 -6
  94. agentpool/sessions/protocol.py +28 -0
  95. agentpool/sessions/session.py +11 -55
  96. agentpool/storage/manager.py +361 -54
  97. agentpool/talk/registry.py +4 -4
  98. agentpool/talk/talk.py +9 -10
  99. agentpool/testing.py +1 -1
  100. agentpool/tool_impls/__init__.py +6 -0
  101. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  102. agentpool/tool_impls/agent_cli/tool.py +95 -0
  103. agentpool/tool_impls/bash/__init__.py +64 -0
  104. agentpool/tool_impls/bash/helpers.py +35 -0
  105. agentpool/tool_impls/bash/tool.py +171 -0
  106. agentpool/tool_impls/delete_path/__init__.py +70 -0
  107. agentpool/tool_impls/delete_path/tool.py +142 -0
  108. agentpool/tool_impls/download_file/__init__.py +80 -0
  109. agentpool/tool_impls/download_file/tool.py +183 -0
  110. agentpool/tool_impls/execute_code/__init__.py +55 -0
  111. agentpool/tool_impls/execute_code/tool.py +163 -0
  112. agentpool/tool_impls/grep/__init__.py +80 -0
  113. agentpool/tool_impls/grep/tool.py +200 -0
  114. agentpool/tool_impls/list_directory/__init__.py +73 -0
  115. agentpool/tool_impls/list_directory/tool.py +197 -0
  116. agentpool/tool_impls/question/__init__.py +42 -0
  117. agentpool/tool_impls/question/tool.py +127 -0
  118. agentpool/tool_impls/read/__init__.py +104 -0
  119. agentpool/tool_impls/read/tool.py +305 -0
  120. agentpool/tools/__init__.py +2 -1
  121. agentpool/tools/base.py +114 -34
  122. agentpool/tools/manager.py +57 -1
  123. agentpool/ui/base.py +2 -2
  124. agentpool/ui/mock_provider.py +2 -2
  125. agentpool/ui/stdlib_provider.py +2 -2
  126. agentpool/utils/streams.py +21 -96
  127. agentpool/vfs_registry.py +7 -2
  128. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
  129. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
  130. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  131. agentpool_cli/__main__.py +20 -0
  132. agentpool_cli/create.py +1 -1
  133. agentpool_cli/serve_acp.py +59 -1
  134. agentpool_cli/serve_opencode.py +1 -1
  135. agentpool_cli/ui.py +557 -0
  136. agentpool_commands/__init__.py +12 -5
  137. agentpool_commands/agents.py +1 -1
  138. agentpool_commands/pool.py +260 -0
  139. agentpool_commands/session.py +1 -1
  140. agentpool_commands/text_sharing/__init__.py +119 -0
  141. agentpool_commands/text_sharing/base.py +123 -0
  142. agentpool_commands/text_sharing/github_gist.py +80 -0
  143. agentpool_commands/text_sharing/opencode.py +462 -0
  144. agentpool_commands/text_sharing/paste_rs.py +59 -0
  145. agentpool_commands/text_sharing/pastebin.py +116 -0
  146. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  147. agentpool_commands/utils.py +31 -32
  148. agentpool_config/__init__.py +30 -2
  149. agentpool_config/agentpool_tools.py +498 -0
  150. agentpool_config/converters.py +1 -1
  151. agentpool_config/event_handlers.py +42 -0
  152. agentpool_config/events.py +1 -1
  153. agentpool_config/forward_targets.py +1 -4
  154. agentpool_config/jinja.py +3 -3
  155. agentpool_config/mcp_server.py +1 -5
  156. agentpool_config/nodes.py +1 -1
  157. agentpool_config/observability.py +44 -0
  158. agentpool_config/session.py +0 -3
  159. agentpool_config/storage.py +38 -39
  160. agentpool_config/task.py +3 -3
  161. agentpool_config/tools.py +11 -28
  162. agentpool_config/toolsets.py +22 -90
  163. agentpool_server/a2a_server/agent_worker.py +307 -0
  164. agentpool_server/a2a_server/server.py +23 -18
  165. agentpool_server/acp_server/acp_agent.py +125 -56
  166. agentpool_server/acp_server/commands/acp_commands.py +46 -216
  167. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
  168. agentpool_server/acp_server/event_converter.py +651 -0
  169. agentpool_server/acp_server/input_provider.py +53 -10
  170. agentpool_server/acp_server/server.py +1 -11
  171. agentpool_server/acp_server/session.py +90 -410
  172. agentpool_server/acp_server/session_manager.py +8 -34
  173. agentpool_server/agui_server/server.py +3 -1
  174. agentpool_server/mcp_server/server.py +5 -2
  175. agentpool_server/opencode_server/ENDPOINTS.md +53 -14
  176. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  177. agentpool_server/opencode_server/__init__.py +0 -8
  178. agentpool_server/opencode_server/converters.py +132 -26
  179. agentpool_server/opencode_server/input_provider.py +160 -8
  180. agentpool_server/opencode_server/models/__init__.py +42 -20
  181. agentpool_server/opencode_server/models/app.py +12 -0
  182. agentpool_server/opencode_server/models/events.py +203 -29
  183. agentpool_server/opencode_server/models/mcp.py +19 -0
  184. agentpool_server/opencode_server/models/message.py +18 -1
  185. agentpool_server/opencode_server/models/parts.py +134 -1
  186. agentpool_server/opencode_server/models/question.py +56 -0
  187. agentpool_server/opencode_server/models/session.py +13 -1
  188. agentpool_server/opencode_server/routes/__init__.py +4 -0
  189. agentpool_server/opencode_server/routes/agent_routes.py +33 -2
  190. agentpool_server/opencode_server/routes/app_routes.py +66 -3
  191. agentpool_server/opencode_server/routes/config_routes.py +66 -5
  192. agentpool_server/opencode_server/routes/file_routes.py +184 -5
  193. agentpool_server/opencode_server/routes/global_routes.py +1 -1
  194. agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
  195. agentpool_server/opencode_server/routes/message_routes.py +122 -66
  196. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  197. agentpool_server/opencode_server/routes/pty_routes.py +23 -22
  198. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  199. agentpool_server/opencode_server/routes/session_routes.py +139 -68
  200. agentpool_server/opencode_server/routes/tui_routes.py +1 -1
  201. agentpool_server/opencode_server/server.py +47 -2
  202. agentpool_server/opencode_server/state.py +30 -0
  203. agentpool_storage/__init__.py +0 -4
  204. agentpool_storage/base.py +81 -2
  205. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  206. agentpool_storage/claude_provider/__init__.py +42 -0
  207. agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
  208. agentpool_storage/file_provider.py +149 -15
  209. agentpool_storage/memory_provider.py +132 -12
  210. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  211. agentpool_storage/opencode_provider/__init__.py +16 -0
  212. agentpool_storage/opencode_provider/helpers.py +414 -0
  213. agentpool_storage/opencode_provider/provider.py +895 -0
  214. agentpool_storage/session_store.py +20 -6
  215. agentpool_storage/sql_provider/sql_provider.py +135 -2
  216. agentpool_storage/sql_provider/utils.py +2 -12
  217. agentpool_storage/zed_provider/__init__.py +16 -0
  218. agentpool_storage/zed_provider/helpers.py +281 -0
  219. agentpool_storage/zed_provider/models.py +130 -0
  220. agentpool_storage/zed_provider/provider.py +442 -0
  221. agentpool_storage/zed_provider.py +803 -0
  222. agentpool_toolsets/__init__.py +0 -2
  223. agentpool_toolsets/builtin/__init__.py +2 -4
  224. agentpool_toolsets/builtin/code.py +4 -4
  225. agentpool_toolsets/builtin/debug.py +115 -40
  226. agentpool_toolsets/builtin/execution_environment.py +54 -165
  227. agentpool_toolsets/builtin/skills.py +0 -77
  228. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  229. agentpool_toolsets/builtin/workers.py +4 -2
  230. agentpool_toolsets/composio_toolset.py +2 -2
  231. agentpool_toolsets/entry_points.py +3 -1
  232. agentpool_toolsets/fsspec_toolset/grep.py +25 -5
  233. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  234. agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
  235. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  236. agentpool_toolsets/mcp_discovery/toolset.py +74 -17
  237. agentpool_toolsets/mcp_run_toolset.py +8 -11
  238. agentpool_toolsets/notifications.py +33 -33
  239. agentpool_toolsets/openapi.py +3 -1
  240. agentpool_toolsets/search_toolset.py +3 -1
  241. agentpool_config/resources.py +0 -33
  242. agentpool_server/acp_server/acp_tools.py +0 -43
  243. agentpool_server/acp_server/commands/spawn.py +0 -210
  244. agentpool_storage/opencode_provider.py +0 -730
  245. agentpool_storage/text_log_provider.py +0 -276
  246. agentpool_toolsets/builtin/chain.py +0 -288
  247. agentpool_toolsets/builtin/user_interaction.py +0 -52
  248. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  249. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  250. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,433 @@
1
+ # Claude Code Storage Architecture
2
+
3
+ This document explains how Claude Code persists conversation data, sessions, and agent state to the filesystem.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ ~/.claude/
9
+ ├── projects/ # Per-project conversation storage
10
+ │ ├── {encoded-project-path}/ # One directory per project
11
+ │ │ ├── {sessionId}.jsonl # Main session conversation log
12
+ │ │ ├── agent-{shortId}.jsonl # Sub-agent conversation logs
13
+ │ │ └── ... # Multiple sessions per project
14
+ │ └── ...
15
+ ├── todos/ # Per-agent todo/plan persistence
16
+ │ ├── {sessionId}-agent-{sessionId}.json # Main agent todos
17
+ │ ├── {sessionId}-agent-{agentId}.json # Sub-agent todos
18
+ │ └── ...
19
+ ├── session-env/ # Session environment state
20
+ │ └── {sessionId}/ # Environment per session (often empty)
21
+ ├── plans/ # Standalone plan documents
22
+ │ └── {name}.md # Named plans (legacy?)
23
+ ├── file-history/ # File edit history tracking
24
+ ├── history.jsonl # Global command history
25
+ └── settings.json # User settings
26
+ ```
27
+
28
+ ## Core Concepts
29
+
30
+ ### 1. Projects
31
+
32
+ **Project Directory**: `~/.claude/projects/{encoded-project-path}/`
33
+
34
+ - The project path is URL-encoded to create a safe directory name
35
+ - Example: `/home/user/myproject` → `-home-user-myproject`
36
+ - All sessions for a given working directory are stored together
37
+ - Multiple sessions can exist per project (different conversations over time)
38
+
39
+ ### 2. Sessions
40
+
41
+ **Session ID**: UUID (e.g., `e8d973c7-481e-43d9-809d-9a4880e8cebc`)
42
+
43
+ A session represents a single conversation thread with Claude. Each session:
44
+
45
+ - Has a unique UUID identifier
46
+ - Is tied to a specific project/working directory
47
+ - Contains a sequence of messages (user, assistant, tool calls, etc.)
48
+ - Has one **main agent** and zero or more **sub-agents**
49
+ - Persists to a JSONL file: `{sessionId}.jsonl`
50
+
51
+ **Session File Format**: JSONL (JSON Lines)
52
+ - Each line is a separate JSON object representing one entry
53
+ - Entries are appended chronologically
54
+ - Enables efficient streaming writes and incremental reads
55
+
56
+ ### 3. Agents
57
+
58
+ #### Main Agent
59
+ - **Agent ID** = **Session ID** (same UUID)
60
+ - The primary Claude instance handling the conversation
61
+ - Session file: `{sessionId}.jsonl`
62
+ - Todo file: `{sessionId}-agent-{sessionId}.json`
63
+
64
+ #### Sub-Agents (Delegated Tasks)
65
+ - **Agent ID**: Short hex identifier (e.g., `a753668` - 7 characters)
66
+ - Created when tasks are delegated via the Task tool
67
+ - Run independently with their own context
68
+ - Session file: `agent-{shortId}.jsonl`
69
+ - Todo file: `{sessionId}-agent-{shortId}.json`
70
+ - Still belong to parent session (share sessionId in metadata)
71
+
72
+ **Key Pattern**: All agents in a session share the parent `sessionId`:
73
+ ```
74
+ Session: e8d973c7-481e-43d9-809d-9a4880e8cebc
75
+ ├── Main Agent: e8d973c7-481e-43d9-809d-9a4880e8cebc
76
+ │ └── Todo: e8d973c7-481e-43d9-809d-9a4880e8cebc-agent-e8d973c7-481e-43d9-809d-9a4880e8cebc.json
77
+ └── Sub-Agent: a753668
78
+ └── Todo: e8d973c7-481e-43d9-809d-9a4880e8cebc-agent-a753668.json
79
+ ```
80
+
81
+ ### 4. Todo/Plan Storage
82
+
83
+ **Location**: `~/.claude/todos/`
84
+
85
+ **Naming**: `{sessionId}-agent-{agentId}.json`
86
+
87
+ **Format**: JSON array of todo entries
88
+ ```json
89
+ [
90
+ {
91
+ "content": "Task description",
92
+ "status": "pending" | "in_progress" | "completed",
93
+ "priority": "high" | "medium" | "low",
94
+ "activeForm": "Current action description (optional)"
95
+ }
96
+ ]
97
+ ```
98
+
99
+ **Persistence Pattern**:
100
+ - Todos are scoped to a specific agent within a session
101
+ - Main agent's todos use redundant naming: `{sessionId}-agent-{sessionId}.json`
102
+ - Sub-agent todos use: `{sessionId}-agent-{shortId}.json`
103
+ - All todos for a session share the session ID prefix
104
+ - Survives across conversation resumes
105
+
106
+ ## Entry Types
107
+
108
+ Claude Code uses different entry types in JSONL session files:
109
+
110
+ ### User Entry
111
+ ```json
112
+ {
113
+ "type": "user",
114
+ "sessionId": "uuid",
115
+ "uuid": "message-uuid",
116
+ "parentUuid": "parent-message-uuid",
117
+ "timestamp": "ISO-8601",
118
+ "message": {
119
+ "role": "user",
120
+ "content": "text or array of content blocks"
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Assistant Entry
126
+ ```json
127
+ {
128
+ "type": "assistant",
129
+ "sessionId": "uuid",
130
+ "uuid": "message-uuid",
131
+ "parentUuid": "parent-message-uuid",
132
+ "timestamp": "ISO-8601",
133
+ "message": {
134
+ "role": "assistant",
135
+ "content": [
136
+ {"type": "text", "text": "..."},
137
+ {"type": "tool_use", "id": "...", "name": "...", "input": {...}}
138
+ ]
139
+ },
140
+ "requestId": "req_...",
141
+ "model": "claude-..."
142
+ }
143
+ ```
144
+
145
+ ### Queue Operation Entry
146
+ ```json
147
+ {
148
+ "type": "queue-operation",
149
+ "operation": "enqueue" | "dequeue",
150
+ "sessionId": "uuid",
151
+ "timestamp": "ISO-8601"
152
+ }
153
+ ```
154
+
155
+ Marks session lifecycle events (start/end of processing).
156
+
157
+ ### System Entry
158
+ ```json
159
+ {
160
+ "type": "system",
161
+ "sessionId": "uuid",
162
+ "timestamp": "ISO-8601",
163
+ "message": {
164
+ "role": "system",
165
+ "content": "System message text"
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### Tool Result Entry
171
+ Embedded within user entries as tool_result content blocks:
172
+ ```json
173
+ {
174
+ "type": "user",
175
+ "message": {
176
+ "role": "user",
177
+ "content": [
178
+ {
179
+ "type": "tool_result",
180
+ "tool_use_id": "toolu_...",
181
+ "content": "result text or structured data"
182
+ }
183
+ ]
184
+ },
185
+ "toolUseResult": ["..."] // Duplicate for UI
186
+ }
187
+ ```
188
+
189
+ ### Summary Entry
190
+ ```json
191
+ {
192
+ "type": "summary",
193
+ "sessionId": "uuid",
194
+ "timestamp": "ISO-8601",
195
+ "content": {
196
+ "summary": "Condensed conversation summary",
197
+ "messageCount": 10,
198
+ "summarizedUntil": "message-uuid"
199
+ }
200
+ }
201
+ ```
202
+
203
+ Used for conversation compaction/context management.
204
+
205
+ ### File History Entry
206
+ ```json
207
+ {
208
+ "type": "file-history",
209
+ "sessionId": "uuid",
210
+ "timestamp": "ISO-8601",
211
+ "path": "/absolute/path/to/file",
212
+ "operation": "edit" | "create" | "delete",
213
+ "snippet": "Preview of changes..."
214
+ }
215
+ ```
216
+
217
+ Tracks file operations for undo/history features.
218
+
219
+ ## Message Flow & Ancestry
220
+
221
+ ### Parent-Child Relationships
222
+
223
+ Every message (except the first) has a `parentUuid` that references the previous message:
224
+
225
+ ```
226
+ User Message 1 (uuid: a)
227
+ └─> Assistant Reply (uuid: b, parentUuid: a)
228
+ └─> Tool Results (uuid: c, parentUuid: b)
229
+ └─> Assistant Response (uuid: d, parentUuid: c)
230
+ ```
231
+
232
+ This creates a **linked list** of messages forming the conversation thread.
233
+
234
+ ### Branching (Sidechains)
235
+
236
+ Field: `isSidechain: boolean`
237
+
238
+ When a conversation is forked or edited:
239
+ - Original messages remain unchanged
240
+ - New branch starts with `isSidechain: true`
241
+ - Allows exploring alternative conversation paths
242
+ - Main chain: `isSidechain: false` or omitted
243
+
244
+ ### Conversation Reconstruction
245
+
246
+ To read a conversation:
247
+ 1. Parse all entries from `{sessionId}.jsonl`
248
+ 2. Filter by `type` (user, assistant, etc.)
249
+ 3. Follow the parent chain from latest message backward
250
+ 4. Stop at session start or desired depth
251
+ 5. Reverse to get chronological order
252
+
253
+ ## Storage Provider Implementation
254
+
255
+ The `ClaudeStorageProvider` class bridges between:
256
+ - **Claude's storage format** (JSONL entries)
257
+ - **Agentpool's abstractions** (ChatMessage, ConversationStats, etc.)
258
+
259
+ ### Key Responsibilities
260
+
261
+ 1. **Path Management**
262
+ - Encode/decode project paths to safe directory names
263
+ - Ensure project directories exist
264
+
265
+ 2. **Session Management**
266
+ - List all sessions for a project
267
+ - Read session entries (with optional filtering)
268
+ - Write new entries atomically
269
+
270
+ 3. **Message Conversion**
271
+ - JSONL Entry → `ChatMessage` (for agentpool)
272
+ - `ChatMessage` → JSONL Entry (for persistence)
273
+ - Handle tool calls, results, and metadata
274
+
275
+ 4. **Conversation Queries**
276
+ - Get message count, token usage stats
277
+ - Retrieve specific messages by UUID
278
+ - Trace message ancestry
279
+ - Fork conversations at specific points
280
+
281
+ 5. **Filtering & Compaction**
282
+ - Filter by message type, date range
283
+ - Skip tool calls or system messages
284
+ - Enable conversation summarization
285
+
286
+ ## Data Consistency
287
+
288
+ ### Atomic Writes
289
+ - Each entry is appended as a complete line
290
+ - JSONL format allows crash-safe appends
291
+ - No partial writes visible to readers
292
+
293
+ ### Concurrent Access
294
+ - Multiple sessions can write to different files safely
295
+ - Same session should have single writer (main process)
296
+ - Readers can stream entries while writing continues
297
+
298
+ ### Cleanup
299
+ - Empty session files may accumulate
300
+ - No automatic garbage collection (user-managed)
301
+ - `reset()` method can delete project history
302
+
303
+ ## Integration Points
304
+
305
+ ### With Agentpool
306
+ - `ClaudeStorageProvider` implements `StorageProvider` protocol
307
+ - Converts between storage formats and domain models
308
+ - Enables conversation persistence for agents
309
+
310
+ ### With TodoTracker
311
+ - Separate persistence in `~/.claude/todos/`
312
+ - Not integrated with main conversation storage
313
+ - Must be loaded/saved independently
314
+ - File naming links todos to session + agent
315
+
316
+ ### With File History
317
+ - Separate tracking in `~/.claude/file-history/`
318
+ - Records file operations during conversation
319
+ - Enables undo/diff features
320
+ - Cross-referenced by session ID
321
+
322
+ ## Usage Patterns
323
+
324
+ ### Starting a New Session
325
+ ```python
326
+ provider = ClaudeStorageProvider(base_dir="~/.claude")
327
+ session_id = str(uuid.uuid4())
328
+
329
+ # Log initial user message
330
+ await provider.log_message(
331
+ project_path="/path/to/project",
332
+ message=ChatMessage(role="user", content="Hello"),
333
+ session_id=session_id,
334
+ )
335
+ ```
336
+
337
+ ### Resuming a Session
338
+ ```python
339
+ # Get existing sessions
340
+ conversations = await provider.get_conversations(
341
+ project_path="/path/to/project"
342
+ )
343
+
344
+ # Load latest session
345
+ messages = await provider.get_conversation_messages(
346
+ project_path="/path/to/project",
347
+ session_id=conversations[0]["sessionId"],
348
+ limit=50 # Last 50 messages
349
+ )
350
+ ```
351
+
352
+ ### Creating a Sub-Agent
353
+ ```python
354
+ # Sub-agent gets own short ID
355
+ agent_id = "a" + secrets.token_hex(3) # e.g., "a753668"
356
+
357
+ # But shares parent sessionId
358
+ await provider.log_message(
359
+ project_path="/path/to/project",
360
+ message=agent_message,
361
+ session_id=parent_session_id, # Same as parent!
362
+ agent_id=agent_id, # Different agent ID
363
+ )
364
+
365
+ # Creates: agent-a753668.jsonl in project dir
366
+ # Creates: {sessionId}-agent-a753668.json in todos/
367
+ ```
368
+
369
+ ### Saving Todos
370
+ ```python
371
+ # Not part of ClaudeStorageProvider!
372
+ # Separate JSON file management:
373
+
374
+ todo_path = (
375
+ f"~/.claude/todos/{session_id}-agent-{agent_id}.json"
376
+ )
377
+ with open(todo_path, "w") as f:
378
+ json.dump(todos, f)
379
+ ```
380
+
381
+ ## Design Rationale
382
+
383
+ ### Why JSONL?
384
+ - **Streamable**: Can read/write incrementally
385
+ - **Crash-safe**: Each line is atomic
386
+ - **Human-readable**: Easy debugging
387
+ - **Flexible schema**: Each entry can evolve independently
388
+
389
+ ### Why Separate Agent Files?
390
+ - **Isolation**: Sub-agents run independently
391
+ - **Parallelism**: Multiple agents can write concurrently
392
+ - **Clarity**: Easy to see which agent did what
393
+ - **Performance**: Don't need to parse entire session for sub-agent context
394
+
395
+ ### Why Redundant Session ID in Main Agent Todos?
396
+ - **Consistency**: All todos follow `{sessionId}-agent-{agentId}` pattern
397
+ - **Simplicity**: Single naming rule, no special cases
398
+ - **Glob-friendly**: Easy to find all todos for a session: `{sessionId}-agent-*.json`
399
+
400
+ ### Why Separate Todos from Conversation Log?
401
+ - **Orthogonal concerns**: Todos are working memory, not conversation history
402
+ - **Update frequency**: Todos change frequently, conversations append-only
403
+ - **Size**: Todos stay small, conversations grow large
404
+ - **Format**: Todos are mutable array, conversations are immutable log
405
+
406
+ ## Future Considerations
407
+
408
+ ### Scalability
409
+ - Large projects accumulate many session files
410
+ - May need session archival/cleanup strategies
411
+ - Consider session indexing for faster queries
412
+
413
+ ### Consistency
414
+ - No transactional guarantees across files
415
+ - Todo updates not synchronized with conversation log
416
+ - Sub-agent todos could become orphaned
417
+
418
+ ### Migration
419
+ - Current format lacks version markers
420
+ - Schema evolution requires careful handling
421
+ - Consider adding format version to entries
422
+
423
+ ### Compression
424
+ - JSONL files can grow large
425
+ - Consider gzip compression for archived sessions
426
+ - Balance between size and read performance
427
+
428
+ ---
429
+
430
+ **Related Files:**
431
+ - Implementation: [`claude_provider.py`](./claude_provider.py)
432
+ - Models: [`models.py`](./models.py)
433
+ - Base Protocol: [`../agentpool/storage/storage_provider.py`](../agentpool/storage/storage_provider.py)
@@ -0,0 +1,42 @@
1
+ """Claude Code storage provider.
2
+
3
+ This package implements the storage backend compatible with Claude Code's
4
+ filesystem-based persistence format.
5
+
6
+ See ARCHITECTURE.md for detailed documentation of the storage format and
7
+ design decisions.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from agentpool_storage.claude_provider.provider import (
13
+ ClaudeApiMessage,
14
+ ClaudeAssistantEntry,
15
+ ClaudeBaseModel,
16
+ ClaudeFileHistoryEntry,
17
+ ClaudeMessageContent,
18
+ ClaudeMessageEntryBase,
19
+ ClaudeQueueOperationEntry,
20
+ ClaudeStorageProvider,
21
+ ClaudeSummaryEntry,
22
+ ClaudeSystemEntry,
23
+ ClaudeUsage,
24
+ ClaudeUserEntry,
25
+ ClaudeUserMessage,
26
+ )
27
+
28
+ __all__ = [
29
+ "ClaudeApiMessage",
30
+ "ClaudeAssistantEntry",
31
+ "ClaudeBaseModel",
32
+ "ClaudeFileHistoryEntry",
33
+ "ClaudeMessageContent",
34
+ "ClaudeMessageEntryBase",
35
+ "ClaudeQueueOperationEntry",
36
+ "ClaudeStorageProvider",
37
+ "ClaudeSummaryEntry",
38
+ "ClaudeSystemEntry",
39
+ "ClaudeUsage",
40
+ "ClaudeUserEntry",
41
+ "ClaudeUserMessage",
42
+ ]
@@ -1,4 +1,16 @@
1
- """Claude Code storage provider - reads/writes to ~/.claude format."""
1
+ """Claude Code storage provider.
2
+
3
+ This module implements storage compatible with Claude Code's filesystem format,
4
+ enabling interoperability between agentpool and Claude Code.
5
+
6
+ Key features:
7
+ - JSONL-based conversation logs per project
8
+ - Multi-agent support (main + sub-agents)
9
+ - Message ancestry tracking
10
+ - Conversation forking and branching
11
+
12
+ See ARCHITECTURE.md for detailed documentation of the storage format.
13
+ """
2
14
 
3
15
  from __future__ import annotations
4
16
 
@@ -556,14 +568,14 @@ class ClaudeStorageProvider(StorageProvider):
556
568
  return ClaudeUserEntry(
557
569
  type="user",
558
570
  uuid=msg_uuid,
559
- parentUuid=parent_uuid,
571
+ parent_uuid=parent_uuid,
560
572
  sessionId=session_id,
561
573
  timestamp=timestamp,
562
574
  message=user_msg,
563
575
  cwd=cwd or "",
564
576
  version="agentpool",
565
- userType="external",
566
- isSidechain=False,
577
+ user_type="external",
578
+ is_sidechain=False,
567
579
  )
568
580
 
569
581
  # Assistant message
@@ -584,14 +596,14 @@ class ClaudeStorageProvider(StorageProvider):
584
596
  return ClaudeAssistantEntry(
585
597
  type="assistant",
586
598
  uuid=msg_uuid,
587
- parentUuid=parent_uuid,
599
+ parent_uuid=parent_uuid,
588
600
  sessionId=session_id,
589
601
  timestamp=timestamp,
590
602
  message=assistant_msg,
591
603
  cwd=cwd or "",
592
604
  version="agentpool",
593
- userType="external",
594
- isSidechain=False,
605
+ user_type="external",
606
+ is_sidechain=False,
595
607
  )
596
608
 
597
609
  async def filter_messages(self, query: SessionQuery) -> list[ChatMessage[str]]:
@@ -652,7 +664,6 @@ class ClaudeStorageProvider(StorageProvider):
652
664
  cost_info: TokenCost | None = None,
653
665
  model: str | None = None,
654
666
  response_time: float | None = None,
655
- forwarded_from: list[str] | None = None,
656
667
  provider_name: str | None = None,
657
668
  provider_response_id: str | None = None,
658
669
  messages: str | None = None,
@@ -905,3 +916,174 @@ class ClaudeStorageProvider(StorageProvider):
905
916
  msg_count += len(message_entries)
906
917
 
907
918
  return conv_count, msg_count
919
+
920
+ async def get_conversation_messages(
921
+ self,
922
+ conversation_id: str,
923
+ *,
924
+ include_ancestors: bool = False,
925
+ ) -> list[ChatMessage[str]]:
926
+ """Get all messages for a conversation.
927
+
928
+ Args:
929
+ conversation_id: Session ID (conversation ID in Claude format)
930
+ include_ancestors: If True, traverse parent_uuid chain to include
931
+ messages from ancestor conversations
932
+
933
+ Returns:
934
+ List of messages ordered by timestamp
935
+ """
936
+ # Find the session file
937
+ sessions = self._list_sessions()
938
+ session_path = None
939
+ for sid, spath in sessions:
940
+ if sid == conversation_id:
941
+ session_path = spath
942
+ break
943
+
944
+ if not session_path:
945
+ return []
946
+
947
+ # Read entries and convert to messages
948
+ entries = self._read_session(session_path)
949
+ tool_mapping = self._build_tool_id_mapping(entries)
950
+
951
+ messages: list[ChatMessage[str]] = []
952
+ for entry in entries:
953
+ msg = self._entry_to_chat_message(entry, conversation_id, tool_mapping)
954
+ if msg:
955
+ messages.append(msg)
956
+
957
+ # Sort by timestamp
958
+ messages.sort(key=lambda m: m.timestamp or get_now())
959
+
960
+ if not include_ancestors or not messages:
961
+ return messages
962
+
963
+ # Get ancestor chain if first message has parent_id
964
+ first_msg = messages[0]
965
+ if first_msg.parent_id:
966
+ ancestors = await self.get_message_ancestry(first_msg.parent_id)
967
+ return ancestors + messages
968
+
969
+ return messages
970
+
971
+ async def get_message(self, message_id: str) -> ChatMessage[str] | None:
972
+ """Get a single message by ID.
973
+
974
+ Args:
975
+ message_id: UUID of the message
976
+
977
+ Returns:
978
+ The message if found, None otherwise
979
+ """
980
+ # Search all sessions for the message
981
+ sessions = self._list_sessions()
982
+
983
+ for session_id, session_path in sessions:
984
+ entries = self._read_session(session_path)
985
+ tool_mapping = self._build_tool_id_mapping(entries)
986
+
987
+ for entry in entries:
988
+ if (
989
+ isinstance(entry, (ClaudeUserEntry, ClaudeAssistantEntry))
990
+ and entry.uuid == message_id
991
+ ):
992
+ return self._entry_to_chat_message(entry, session_id, tool_mapping)
993
+
994
+ return None
995
+
996
+ async def get_message_ancestry(self, message_id: str) -> list[ChatMessage[str]]:
997
+ """Get the ancestry chain of a message.
998
+
999
+ Traverses parent_uuid chain to build full history.
1000
+
1001
+ Args:
1002
+ message_id: UUID of the message
1003
+
1004
+ Returns:
1005
+ List of messages from oldest ancestor to the specified message
1006
+ """
1007
+ ancestors: list[ChatMessage[str]] = []
1008
+ current_id: str | None = message_id
1009
+
1010
+ while current_id:
1011
+ msg = await self.get_message(current_id)
1012
+ if not msg:
1013
+ break
1014
+ ancestors.append(msg)
1015
+ current_id = msg.parent_id
1016
+
1017
+ # Reverse to get oldest first
1018
+ ancestors.reverse()
1019
+ return ancestors
1020
+
1021
+ async def fork_conversation(
1022
+ self,
1023
+ *,
1024
+ source_conversation_id: str,
1025
+ new_conversation_id: str,
1026
+ fork_from_message_id: str | None = None,
1027
+ new_agent_name: str | None = None,
1028
+ ) -> str | None:
1029
+ """Fork a conversation at a specific point.
1030
+
1031
+ Creates a new session file. The fork point message_id is returned
1032
+ so callers can set it as parent_uuid for new messages.
1033
+
1034
+ Args:
1035
+ source_conversation_id: Source session ID
1036
+ new_conversation_id: New session ID
1037
+ fork_from_message_id: UUID to fork from. If None, forks from last message
1038
+ new_agent_name: Not used in Claude format (no agent metadata in sessions)
1039
+
1040
+ Returns:
1041
+ The UUID of the fork point message
1042
+ """
1043
+ # Find source session
1044
+ sessions = self._list_sessions()
1045
+ source_path = None
1046
+ for sid, spath in sessions:
1047
+ if sid == source_conversation_id:
1048
+ source_path = spath
1049
+ break
1050
+
1051
+ if not source_path:
1052
+ msg = f"Source conversation not found: {source_conversation_id}"
1053
+ raise ValueError(msg)
1054
+
1055
+ # Read source entries
1056
+ entries = self._read_session(source_path)
1057
+
1058
+ # Find fork point
1059
+ fork_point_id: str | None = None
1060
+ if fork_from_message_id:
1061
+ # Verify message exists
1062
+ found = False
1063
+ for entry in entries:
1064
+ if (
1065
+ isinstance(entry, (ClaudeUserEntry, ClaudeAssistantEntry))
1066
+ and entry.uuid == fork_from_message_id
1067
+ ):
1068
+ found = True
1069
+ fork_point_id = fork_from_message_id
1070
+ break
1071
+ if not found:
1072
+ err = f"Message {fork_from_message_id} not found in conversation"
1073
+ raise ValueError(err)
1074
+ else:
1075
+ # Find last message
1076
+ message_entries = [
1077
+ e for e in entries if isinstance(e, (ClaudeUserEntry, ClaudeAssistantEntry))
1078
+ ]
1079
+ if message_entries:
1080
+ fork_point_id = message_entries[-1].uuid
1081
+
1082
+ # Create new session file (empty for now - will be populated when messages added)
1083
+ # Determine project from source path structure
1084
+ project_name = source_path.parent.name
1085
+ new_path = self.projects_path / project_name / f"{new_conversation_id}.jsonl"
1086
+ new_path.parent.mkdir(parents=True, exist_ok=True)
1087
+ new_path.touch()
1088
+
1089
+ return fork_point_id