crow-cli 0.1.37__tar.gz → 0.1.38__tar.gz

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 (452) hide show
  1. {crow_cli-0.1.37 → crow_cli-0.1.38}/PKG-INFO +7 -5
  2. crow_cli-0.1.38/PLAN.md +93 -0
  3. {crow_cli-0.1.37 → crow_cli-0.1.38}/README.md +4 -4
  4. crow_cli-0.1.38/TODO.md +73 -0
  5. {crow_cli-0.1.37 → crow_cli-0.1.38}/pyproject.toml +4 -1
  6. crow_cli-0.1.38/scripts/e2e_image_store_live.py +177 -0
  7. crow_cli-0.1.38/scripts/e2e_postgres_live.py +204 -0
  8. crow_cli-0.1.38/src/crow_cli/agent/context.py +107 -0
  9. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/main.py +1 -2
  10. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/memory.py +13 -4
  11. crow_cli-0.1.38/src/crow_cli/agent/prompt.py +507 -0
  12. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/react.py +38 -114
  13. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/session.py +16 -117
  14. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/tools.py +41 -110
  15. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/init_cmd.py +114 -3
  16. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/config.py +10 -1
  17. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/defaults.py +150 -8
  18. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/main.py +1 -1
  19. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/store.py +4 -3
  20. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/__init__.py +23 -6
  21. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/db.py +30 -11
  22. crow_cli-0.1.38/src/crow_cli/memory/fts.py +94 -0
  23. crow_cli-0.1.38/src/crow_cli/memory/image_store.py +170 -0
  24. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/messages.py +18 -19
  25. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/models.py +5 -5
  26. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/reads.py +15 -25
  27. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/writes.py +16 -27
  28. crow_cli-0.1.38/tests/memory/test_db.py +53 -0
  29. crow_cli-0.1.38/tests/memory/test_image_store.py +136 -0
  30. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_store.py +26 -25
  31. crow_cli-0.1.38/tests/unit/test_init_postgres.py +133 -0
  32. crow_cli-0.1.38/tests/unit/test_init_rustfs.py +59 -0
  33. crow_cli-0.1.38/tests/unit/test_prompt.py +280 -0
  34. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_react.py +14 -24
  35. crow_cli-0.1.38/tests/unit/test_session.py +56 -0
  36. crow_cli-0.1.38/tests/unit/test_system_prompt_skills.py +79 -0
  37. crow_cli-0.1.38/tests/unit/test_uv_project_enforcement.py +119 -0
  38. {crow_cli-0.1.37 → crow_cli-0.1.38}/uv.lock +453 -4
  39. crow_cli-0.1.37/PLAN.md +0 -263
  40. crow_cli-0.1.37/TODO.md +0 -165
  41. crow_cli-0.1.37/src/crow_cli/agent/context.py +0 -109
  42. crow_cli-0.1.37/src/crow_cli/agent/prompt.py +0 -165
  43. crow_cli-0.1.37/tests/unit/test_session.py +0 -153
  44. crow_cli-0.1.37/tests/unit/test_system_prompt_skills.py +0 -26
  45. crow_cli-0.1.37/tests/unit/test_uv_project_enforcement.py +0 -159
  46. {crow_cli-0.1.37 → crow_cli-0.1.38}/.env.example +0 -0
  47. {crow_cli-0.1.37 → crow_cli-0.1.38}/.github/workflows/build-binaries.yml +0 -0
  48. {crow_cli-0.1.37 → crow_cli-0.1.38}/.github/workflows/publish-crow-cli.yml +0 -0
  49. {crow_cli-0.1.37 → crow_cli-0.1.38}/.gitignore +0 -0
  50. {crow_cli-0.1.37 → crow_cli-0.1.38}/.python-version +0 -0
  51. {crow_cli-0.1.37 → crow_cli-0.1.38}/AGENTS.md +0 -0
  52. {crow_cli-0.1.37 → crow_cli-0.1.38}/LICENSE.md +0 -0
  53. {crow_cli-0.1.37 → crow_cli-0.1.38}/PYINSTALLER_BUILD.md +0 -0
  54. {crow_cli-0.1.37 → crow_cli-0.1.38}/agent.json +0 -0
  55. {crow_cli-0.1.37 → crow_cli-0.1.38}/crow-cli.spec +0 -0
  56. {crow_cli-0.1.37 → crow_cli-0.1.38}/dev-crow-2.yaml +0 -0
  57. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/CNAME +0 -0
  58. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/getting-started/installation.md +0 -0
  59. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/getting-started/quickstart.md +0 -0
  60. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/index.md +0 -0
  61. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/memory-tool-design.md +0 -0
  62. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/myst.yml +0 -0
  63. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-cli-docs/styles/purple.css +0 -0
  64. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/crow-logo.svg +0 -0
  65. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/get-llamacpp-working.md +0 -0
  66. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/getting-started/installation.md +0 -0
  67. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/getting-started/quickstart.md +0 -0
  68. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/icon.ico +0 -0
  69. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/crow-icon-purple.svg +0 -0
  70. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/crow-logo-crop.png +0 -0
  71. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/icon.svg +0 -0
  72. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/img/whiteboard_capture.png +0 -0
  73. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/index.html +0 -0
  74. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/index.md +0 -0
  75. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/03May2026.md +0 -0
  76. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/19Feb2026.md +0 -0
  77. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/20Feb2026.md +0 -0
  78. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/journal/22Feb2026.md +0 -0
  79. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/llm-wiki.md +0 -0
  80. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/myst.yml +0 -0
  81. {crow_cli-0.1.37 → crow_cli-0.1.38}/docs/styles/purple.css +0 -0
  82. {crow_cli-0.1.37 → crow_cli-0.1.38}/examples/mc_escher_loop.py +0 -0
  83. {crow_cli-0.1.37 → crow_cli-0.1.38}/examples/quick_test.py +0 -0
  84. {crow_cli-0.1.37 → crow_cli-0.1.38}/run_tests.sh +0 -0
  85. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/.python-version +0 -0
  86. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/README.md +0 -0
  87. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/SUCCESS.md +0 -0
  88. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/TODO.md +0 -0
  89. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/agent_client.py +0 -0
  90. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/echo_agent.py +0 -0
  91. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/fire_up_echo.py +0 -0
  92. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/fire_up_simple.py +0 -0
  93. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/main.py +0 -0
  94. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/pyproject.toml +0 -0
  95. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/run_bridge.py +0 -0
  96. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/simple_agent.py +0 -0
  97. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/stdio_to_ws.py +0 -0
  98. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_acp_ws_client.py +0 -0
  99. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_agent_client.py +0 -0
  100. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge.py +0 -0
  101. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge_and_client.py +0 -0
  102. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_bridge_direct.py +0 -0
  103. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_client.py +0 -0
  104. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_date_terminal.py +0 -0
  105. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_echo_agent.py +0 -0
  106. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_fs_terminal.py +0 -0
  107. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_http_multisession.py +0 -0
  108. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_raw_stdio.py +0 -0
  109. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_simple.py +0 -0
  110. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/test_ws_client.py +0 -0
  111. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/two_sessions_same_question.py +0 -0
  112. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/agent-client/uv.lock +0 -0
  113. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/.python-version +0 -0
  114. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/README.md +0 -0
  115. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/db_integrity_test.py +0 -0
  116. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/e2e_tool_integrity.py +0 -0
  117. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/persistence_integrity_test.py +0 -0
  118. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/pyproject.toml +0 -0
  119. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/streaming_async_react.py +0 -0
  120. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/streaming_async_react_with_cancellation.py +0 -0
  121. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/test_usage.py +0 -0
  122. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/tool_call_integrity_test.py +0 -0
  123. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/async-react/uv.lock +0 -0
  124. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/README.md +0 -0
  125. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/agent.py +0 -0
  126. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/client.py +0 -0
  127. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/duet.py +0 -0
  128. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/echo_agent.py +0 -0
  129. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/fire_up_echo.py +0 -0
  130. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/gemini.py +0 -0
  131. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/pyproject.toml +0 -0
  132. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/terminal_agent.py +0 -0
  133. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/test_acp.py +0 -0
  134. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-acp-learning/uv.lock +0 -0
  135. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/.python-version +0 -0
  136. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/README.md +0 -0
  137. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/graph.png +0 -0
  138. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/graph.py +0 -0
  139. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/main.py +0 -0
  140. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/pyproject.toml +0 -0
  141. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/crow-test/uv.lock +0 -0
  142. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/.python-version +0 -0
  143. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/README.md +0 -0
  144. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/main.py +0 -0
  145. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/pyproject.toml +0 -0
  146. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/dirtree/uv.lock +0 -0
  147. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/00-architecture-overview.md +0 -0
  148. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/01-parallel-tool-calling.md +0 -0
  149. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/02-toad-integration.md +0 -0
  150. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/03-session-persistence.md +0 -0
  151. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/04-cancellation-and-state.md +0 -0
  152. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/05-loop-detection.md +0 -0
  153. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/06-finish-tool.md +0 -0
  154. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/07-python-repl.md +0 -0
  155. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/08-system-prompt-skills.md +0 -0
  156. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/09-session-continuity-and-compaction.md +0 -0
  157. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/AGENTS_OLD.md +0 -0
  158. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/ARCHITECTURE.md +0 -0
  159. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_IMPLEMENTATION.md +0 -0
  160. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_SANDBOX_COMPARISON.md +0 -0
  161. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CANCELLATION_SUMMARY.md +0 -0
  162. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CRITICAL_CLARIFICATIONS.md +0 -0
  163. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/CROW_SDK_ANALYSIS.md +0 -0
  164. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/EXTENSION_DISCUSSION.md +0 -0
  165. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_PLAN.md +0 -0
  166. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_SUMMARY.md +0 -0
  167. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/IMPLEMENTATION_SUMMARY_ACP_SESSIONS.md +0 -0
  168. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/INSTRUCTIONS_FOR_KIMI.md +0 -0
  169. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PROJECT_VISION.md +0 -0
  170. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PROMPT.md +0 -0
  171. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/PULSE_CHECK.md +0 -0
  172. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/REPL_GUIDE.md +0 -0
  173. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/TABLE_SCHEMA.md +0 -0
  174. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/THOUGHTS.md +0 -0
  175. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/check_acp_agent.py +0 -0
  176. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/conversation-wrapper-analysis.md +0 -0
  177. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow-agent-good-stuff.md +0 -0
  178. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow_agent_example.py +0 -0
  179. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/crow_inspect.py +0 -0
  180. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/database.py +0 -0
  181. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/00-file-editor-semantics-and-fastmcp.md +0 -0
  182. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/01-mcp-server-package-structure.md +0 -0
  183. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/02-hooks-realization-and-agent-core.md +0 -0
  184. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/03-where-we-are-now-and-whats-next.md +0 -0
  185. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/04-acp-mcp-configuration.md +0 -0
  186. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/04-hooks-as-first-class-citizens.md +0 -0
  187. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/05-hook-design-analysis.md +0 -0
  188. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/05-python-sdk-that-emerged.md +0 -0
  189. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/06-acp-native-sessions.md +0 -0
  190. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/07-extension-api-design.md +0 -0
  191. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/08-flask-extension-pattern-with-context-variables.md +0 -0
  192. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/09-git-commit-violation-last-testament.md +0 -0
  193. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/10-monorepo-refactor-to-avoid-cyclic-dependencies.md +0 -0
  194. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/11-acp-terminal-tool-swapping.md +0 -0
  195. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/12-hook-based-architecture-and-the-frameworkless-framework.md +0 -0
  196. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/13-acp-terminal-architecture.md +0 -0
  197. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/14-terminal-mcp-implementation.md +0 -0
  198. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/15-terminal-mcp-complete.md +0 -0
  199. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/16-fractal-filesystem-sandbox-parallel-tool-calling.md +0 -0
  200. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/17-composition-over-inheritance-extension-design.md +0 -0
  201. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/18-self-injection-the-god-mode-pattern.md +0 -0
  202. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/19-pluggy-the-missing-piece.md +0 -0
  203. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/20-terminal-tool-failure-bug-report.md +0 -0
  204. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/21-callback-architecture-multi-agent-bridges.md +0 -0
  205. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/22-the-agent-was-always-here.md +0 -0
  206. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/23-cancellation-solid-plan.md +0 -0
  207. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/24-async-tool-hanging-debug-thoughts.md +0 -0
  208. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/25-acp-client-terminal-integration.md +0 -0
  209. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/26-graceful-cancellation-streaming.md +0 -0
  210. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/essays/llms-txt-and-agent-skills.md +0 -0
  211. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/fire_up_echo.py +0 -0
  212. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/kernel_kernel.py +0 -0
  213. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/merging-agents-guide.md +0 -0
  214. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/plans/acp-tool-enhancements.md +0 -0
  215. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/quick_test.py +0 -0
  216. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/research-findings.md +0 -0
  217. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/search.py +0 -0
  218. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/simple_crow_agent.py +0 -0
  219. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_async_react.py +0 -0
  220. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_async_react_with_db.py +0 -0
  221. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/streaming_react.py +0 -0
  222. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/terminal_demo.py +0 -0
  223. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_client_caps.py +0 -0
  224. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_crow_e2e.py +0 -0
  225. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/test_terminal_client.py +0 -0
  226. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/docs/ancestors/zai_tool_integration.py +0 -0
  227. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/.gitignore +0 -0
  228. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/.python-version +0 -0
  229. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/README.md +0 -0
  230. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/client.py +0 -0
  231. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/docs/essays/00-fractal-filesystem-sandbox-parallel-tool-calling.md +0 -0
  232. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/pyproject.toml +0 -0
  233. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/__init__.py +0 -0
  234. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/server/__init__.py +0 -0
  235. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/server/main.py +0 -0
  236. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/tool/__init__.py +0 -0
  237. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/src/fastmcp_test/tool/main.py +0 -0
  238. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/fastmcp-test/uv.lock +0 -0
  239. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/.gitignore +0 -0
  240. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/.python-version +0 -0
  241. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/README.md +0 -0
  242. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_colpali.py +0 -0
  243. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_dual_model.py +0 -0
  244. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/bench_latency.py +0 -0
  245. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/lancedb_session.py +0 -0
  246. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/load_crow_db.py +0 -0
  247. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/main.py +0 -0
  248. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/multivector.py +0 -0
  249. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/pyproject.toml +0 -0
  250. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/lancedb-testing/uv.lock +0 -0
  251. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/.python-version +0 -0
  252. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/README.md +0 -0
  253. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/explore.py +0 -0
  254. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/main.py +0 -0
  255. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/pyproject.toml +0 -0
  256. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/langfuse-test/uv.lock +0 -0
  257. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/.python-version +0 -0
  258. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/README.md +0 -0
  259. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/main.py +0 -0
  260. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/pyproject.toml +0 -0
  261. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_all_models_reasoning.py +0 -0
  262. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_fix_correctness.py +0 -0
  263. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_llamacpp_reasoning.py +0 -0
  264. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_rc_empty.py +0 -0
  265. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_reasoning_tool_transition.py +0 -0
  266. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/test_reasoning_transition.py +0 -0
  267. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-dashscope/uv.lock +0 -0
  268. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/.python-version +0 -0
  269. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/README.md +0 -0
  270. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/config.yaml +0 -0
  271. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/main.py +0 -0
  272. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/pyproject.toml +0 -0
  273. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/litellm-proxy/uv.lock +0 -0
  274. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/murder-architecture.md +0 -0
  275. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/.python-version +0 -0
  276. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/README.md +0 -0
  277. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/main.py +0 -0
  278. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/pyproject.toml +0 -0
  279. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/src/pathlib_test/test.py +0 -0
  280. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pathlib-test/uv.lock +0 -0
  281. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/.python-version +0 -0
  282. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/main.py +0 -0
  283. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/pyproject.toml +0 -0
  284. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/test.spec +0 -0
  285. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/pyinstaller-async-test/uv.lock +0 -0
  286. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/compose.yaml +0 -0
  287. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/config.yaml +0 -0
  288. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/litellm/config.yaml +0 -0
  289. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.crow/searxng/settings.yml +0 -0
  290. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.gitignore +0 -0
  291. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/.python-version +0 -0
  292. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/AGENT_CLIENT_INTEGRATION.md +0 -0
  293. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/README.md +0 -0
  294. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/client.py +0 -0
  295. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_agent.py +0 -0
  296. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_e2e_test.py +0 -0
  297. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_01_system_user_only.json +0 -0
  298. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_02_reasoning_only.json +0 -0
  299. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_03_content_only.json +0 -0
  300. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_04_unexecuted_tools.json +0 -0
  301. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_05_mid_conversation_all_responded.json +0 -0
  302. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_06_partial_tool_responses.json +0 -0
  303. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_07_complete_turn.json +0 -0
  304. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_08_reasoning_ending.json +0 -0
  305. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/case_09_content_ending.json +0 -0
  306. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/compact_fixtures/chubby_full_conversation.jsonl +0 -0
  307. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/AGENT_CLIENT_PLAN.md +0 -0
  308. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/COMPACT_AND_SCHEMA_REWRITE.md +0 -0
  309. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/docs/STRATEGIC_PIVOT.md +0 -0
  310. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/generate_compact_fixtures.py +0 -0
  311. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement.py +0 -0
  312. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement_agent.py +0 -0
  313. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/iterative_refinement_mcp.py +0 -0
  314. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/main.py +0 -0
  315. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/multi_compact.py +0 -0
  316. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/orchestrator.py +0 -0
  317. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/pyproject.toml +0 -0
  318. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/run_iterative_refinement_agent.py +0 -0
  319. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/run_repl_clients.py +0 -0
  320. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/scratch_db.py +0 -0
  321. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/task_description.yaml +0 -0
  322. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_fixtures.py +0 -0
  323. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_one.py +0 -0
  324. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_real.py +0 -0
  325. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_compact_run.py +0 -0
  326. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_drift_instrumentation.py +0 -0
  327. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_hook.py +0 -0
  328. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_iterative_refinement.py +0 -0
  329. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_multi_compact_client.py +0 -0
  330. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_refinement_agent.py +0 -0
  331. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/test_repl_client.py +0 -0
  332. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/repl-agent/uv.lock +0 -0
  333. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/.python-version +0 -0
  334. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/README.md +0 -0
  335. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/main.py +0 -0
  336. {crow_cli-0.1.37 → crow_cli-0.1.38}/sandbox/yaml-configs/pyproject.toml +0 -0
  337. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/consolidate_history.py +0 -0
  338. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/e2e_memory_live.py +0 -0
  339. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/mcp_ram_repro.py +0 -0
  340. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/merge_live_into_migrated.py +0 -0
  341. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/migrate_taskmaster.py +0 -0
  342. {crow_cli-0.1.37 → crow_cli-0.1.38}/scripts/migrate_v5.py +0 -0
  343. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/__init__.py +0 -0
  344. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/__init__.py +0 -0
  345. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/compact.py +0 -0
  346. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/hooks.py +0 -0
  347. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/llm.py +0 -0
  348. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/logger.py +0 -0
  349. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/mcp_client.py +0 -0
  350. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/model_routing.py +0 -0
  351. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent/slash.py +0 -0
  352. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/agent_runner.py +0 -0
  353. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/__init__.py +0 -0
  354. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/install.py +0 -0
  355. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/cli/main.py +0 -0
  356. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/__init__.py +0 -0
  357. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/main.py +0 -0
  358. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/subagent.py +0 -0
  359. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/client/terminal.py +0 -0
  360. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/__init__.py +0 -0
  361. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/__init__.py +0 -0
  362. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/config/default/searxng_settings.py +0 -0
  363. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/__init__.py +0 -0
  364. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/README.md +0 -0
  365. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/editor/__init__.py +0 -0
  366. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/__init__.py +0 -0
  367. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/client.py +0 -0
  368. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/memory/main.py +0 -0
  369. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/README.md +0 -0
  370. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/__init__.py +0 -0
  371. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/read/main.py +0 -0
  372. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/README.md +0 -0
  373. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/__init__.py +0 -0
  374. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/app.py +0 -0
  375. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/logger.py +0 -0
  376. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/server/main.py +0 -0
  377. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/task/__init__.py +0 -0
  378. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/task/main.py +0 -0
  379. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/README.md +0 -0
  380. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/__init__.py +0 -0
  381. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/backend.py +0 -0
  382. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/constants.py +0 -0
  383. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/logging_config.py +0 -0
  384. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/main.py +0 -0
  385. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/metadata.py +0 -0
  386. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/terminal/session.py +0 -0
  387. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/__init__.py +0 -0
  388. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/client.py +0 -0
  389. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/vision/main.py +0 -0
  390. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/README.md +0 -0
  391. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/__init__.py +0 -0
  392. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_fetch/main.py +0 -0
  393. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/README.md +0 -0
  394. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/__init__.py +0 -0
  395. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/web_search/main.py +0 -0
  396. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/README.md +0 -0
  397. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/__init__.py +0 -0
  398. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/mcp/write/main.py +0 -0
  399. {crow_cli-0.1.37 → crow_cli-0.1.38}/src/crow_cli/memory/ids.py +0 -0
  400. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/__init__.py +0 -0
  401. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/analyze_payload_cache_invalidation.py +0 -0
  402. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/analyze_payload_deep.py +0 -0
  403. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/conftest.py +0 -0
  404. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_react_live.py +0 -0
  405. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_session_update_transmission.py +0 -0
  406. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_high_priority_preemption.py +0 -0
  407. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_mcp_launch.py +0 -0
  408. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/e2e/test_task_race_regression.py +0 -0
  409. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_acp_http.py +0 -0
  410. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_agent_init.py +0 -0
  411. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_auth_validation.py +0 -0
  412. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_cancel_integrity.py +0 -0
  413. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_delegation_hold.py +0 -0
  414. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_react_loop_tool_round.py +0 -0
  415. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/integration/test_reasoning_effort_react_loop.py +0 -0
  416. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/integration/test_server_stdio.py +0 -0
  417. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_edit.py +0 -0
  418. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_memory.py +0 -0
  419. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_memory_store.py +0 -0
  420. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_read.py +0 -0
  421. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_server.py +0 -0
  422. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_task_tool.py +0 -0
  423. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_terminal.py +0 -0
  424. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_vision.py +0 -0
  425. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_web_fetch.py +0 -0
  426. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_web_search.py +0 -0
  427. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/mcp/test_write.py +0 -0
  428. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_migrate_taskmaster.py +0 -0
  429. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_migrate_v5.py +0 -0
  430. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_session_mcp_servers.py +0 -0
  431. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/memory/test_task_state.py +0 -0
  432. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_cli_fork_flags.py +0 -0
  433. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_cli_telemetry.py +0 -0
  434. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_client_terminal.py +0 -0
  435. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact.py +0 -0
  436. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact_last_messages_list_content.json +0 -0
  437. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_compact_last_messages_list_content.py +0 -0
  438. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_configure.py +0 -0
  439. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_fork.py +0 -0
  440. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_json_repair.py +0 -0
  441. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_mcp_client.py +0 -0
  442. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_model_override.py +0 -0
  443. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_model_routing.py +0 -0
  444. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_per_model_compact_threshold.py +0 -0
  445. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_reasoning_effort.py +0 -0
  446. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_request_logging.py +0 -0
  447. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_session_mcp_servers_wiring.py +0 -0
  448. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_stream_processing.py +0 -0
  449. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_subagent_parse_servers.py +0 -0
  450. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_task_tool_schema.py +0 -0
  451. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_usage_update.py +0 -0
  452. {crow_cli-0.1.37 → crow_cli-0.1.38}/tests/unit/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: crow-cli
3
- Version: 0.1.37
3
+ Version: 0.1.38
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/crow-cli/crow-cli
6
6
  Project-URL: Repository, https://github.com/crow-cli/crow-cli
@@ -9,6 +9,7 @@ Project-URL: Documentation, https://github.com/crow-cli/crow-cli#readme
9
9
  License-File: LICENSE.md
10
10
  Requires-Python: >=3.14
11
11
  Requires-Dist: agent-client-protocol[http]<0.13.0,>=0.12.0
12
+ Requires-Dist: boto3>=1.40.0
12
13
  Requires-Dist: coolname>=2.2.0
13
14
  Requires-Dist: directory-tree>=1.0.0
14
15
  Requires-Dist: docker>=7.2.0
@@ -19,6 +20,7 @@ Requires-Dist: jinja2>=3.1.6
19
20
  Requires-Dist: markdownify>=0.14.0
20
21
  Requires-Dist: openai>=2.21.0
21
22
  Requires-Dist: opencv-python>=4.13.0.92
23
+ Requires-Dist: psycopg[binary]>=3.1
22
24
  Requires-Dist: pydantic>=2.12.5
23
25
  Requires-Dist: pyyaml>=6.0.3
24
26
  Requires-Dist: readabilipy>=0.3.0
@@ -122,15 +124,15 @@ The agent detects client capabilities (terminals, file read/write) and uses the
122
124
 
123
125
  An ACP-native agent: a streaming ReAct loop with tool calling, cancellation, conversation compaction, and multimodal input. Provider and model configuration lives in `~/.agents/crow/config.yaml`.
124
126
 
125
- ### Persistence — sqlite memory
127
+ ### Persistence — SQL memory (sqlite or PostgreSQL)
126
128
 
127
- Sessions persist to a single sqlite database (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search. Images in messages are written to `~/.agents/crow/images/` and referenced by path; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The same database backs the memory API, exposed to agents as three tools:
129
+ Sessions persist to a SQL database reached via the `db_uri` in `config.yaml`. The default is a single sqlite file (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search — zero setup, one file. Point `db_uri` at PostgreSQL instead (`postgresql+psycopg://…`; a `postgres` service ships in `compose.yaml`) and the same schema runs there for memory shared across machines — agents on any box see the same sessions, messages and task mailboxes. The dialect seams are isolated: keyword search is FTS5/BM25 on sqlite and tsvector/GIN on postgres behind the same `search_messages` contract, and the MCP read-only path is enforced per dialect (sqlite `mode=ro` file URI, postgres session `READ ONLY`). Images never live in the database: they are content-addressed (`<sha256hex><ext>`, so duplicates dedupe for free) in an image store and referenced by key; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The default store is the filesystem (`~/.agents/crow/images/`). Optionally, point `image_store.s3` in `config.yaml` at an S3 endpoint (a RustFS service ships in `compose.yaml`) and images go there instead — crow probes the endpoint once at startup and falls back to the filesystem when it is down, and reads always fall back to the filesystem too, so images stored before the switch keep working. The same database backs the memory API, exposed to agents as three tools:
128
130
 
129
131
  - `list_sessions()` — sessions ordered by recent activity (who's working on what)
130
132
  - `query_memory(query)` — find which session discussed something, across all sessions
131
133
  - `query_session(session_id)` — read or search within one session (spans all of that session's agents)
132
134
 
133
- This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. No service to run — the sqlite file is the integration point.
135
+ This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. On sqlite there is no service to run — the file is the integration point. On PostgreSQL the server is the integration point, and every agent on the network shares one authoritative memory.
134
136
 
135
137
  ### Built-in MCP tool server
136
138
 
@@ -199,7 +201,7 @@ uv run pytest tests/e2e # live LLM calls (costs $)
199
201
  src/crow_cli/ the agent — ACP server, ReAct loop, CLI
200
202
  src/crow_cli/config/ config loading, defaults, overrides (shared by every layer)
201
203
  src/crow_cli/mcp/ built-in MCP tool server (`crow-cli mcp`)
202
- src/crow_cli/memory/ shared SQL persistence (sqlite default, postgres-ready)
204
+ src/crow_cli/memory/ shared SQL persistence (sqlite default, PostgreSQL supported)
203
205
  ```
204
206
 
205
207
  ## License
@@ -0,0 +1,93 @@
1
+ # PLAN — PostgreSQL memory backend (same treatment as RustFS) — COMPLETE 2026-08-26
2
+
3
+ ## **DO NOT ASK USER FOR FEEDBACK — THIS IS THE USER FEEDBACK.**
4
+ ## **DO NOT ASK USER FOR NEXT STEPS — THESE ARE THE NEXT STEPS.**
5
+
6
+ Build/test gate (every phase boundary):
7
+ `./run_tests.sh tests/unit tests/memory tests/mcp tests/integration -q`
8
+ (rustfs sprint ended at 446 green on this gate; live-LLM e2e excluded.)
9
+
10
+ Trajectory: 1 → 2 → 3 → 4 → 5. Commit after each phase, Session-Id trailer.
11
+
12
+ ## Phase 1 — mechanical portability — DONE 2026-08-26
13
+
14
+ 1. models.py: `from sqlalchemy import JSON` (drop the sqlite dialect import).
15
+ 2. db.py `_require_v5`: `sqlalchemy.inspect(engine).get_columns("agents")`
16
+ instead of `PRAGMA table_info` — portable, same fail-fast on v4 DBs.
17
+ 3. pyproject: `psycopg[binary]` dep; `uv --project . sync`.
18
+ - Verify: gate green (sqlite behavior untouched). Commit.
19
+
20
+ ## Phase 2 — the FTS seam — DONE 2026-08-26
21
+
22
+ 1. New `src/crow_cli/memory/fts.py`: `create_fts(conn, engine)`,
23
+ `insert_fts(conn, engine, row_id, agent_id, role, fork_idx, text)`,
24
+ `search_fts(conn, engine, query, limit) -> list[(rowid, rank)]`
25
+ (best-first, lower=better on BOTH backends — postgres negates ts_rank).
26
+ sqlite branch = exact current SQL; postgres branch =
27
+ `CREATE TABLE IF NOT EXISTS messages_fts (rowid BIGINT PRIMARY KEY,
28
+ tsv tsvector)` + GIN index; `to_tsvector('simple', :t)` insert;
29
+ `plainto_tsquery('simple', :q)` + `ts_rank` search.
30
+ 2. db.py create_database → fts.create_fts (drop FTS_DDL constant).
31
+ 3. writes.py add_message → fts.insert_fts.
32
+ 4. reads.py search_messages → fts.search_fts (drop inline MATCH SQL).
33
+ - Verify: gate green — sqlite search tests pass unchanged. Commit.
34
+
35
+ ## Phase 3 — MCP dialect awareness — DONE 2026-08-26
36
+
37
+ 1. db.py: `get_ro_engine(db_uri)` — sqlite → `?mode=ro&uri=true` rewrite;
38
+ postgres → connect listener `SET SESSION CHARACTERISTICS AS TRANSACTION
39
+ READ ONLY`. Export from memory/__init__.
40
+ 2. mcp/memory/store.py `_ro_engine`: keep sqlite file-existence check,
41
+ delegate engine creation to get_ro_engine.
42
+ - Verify: gate green + new unit tests (sqlite rewrite unchanged; postgres
43
+ branch registers the listener — engine creation needs no live server).
44
+ Commit.
45
+
46
+ ## Phase 4 — init/compose/config (the rustfs treatment) — DONE 2026-08-26
47
+
48
+ 1. defaults.py COMPOSE_YAML: postgres service — postgres:17-alpine,
49
+ `${POSTGRES_PORT}:5432`, POSTGRES_USER/PASSWORD/DB env refs,
50
+ pg_isready healthcheck, postgres_data volume.
51
+ 2. init_cmd.py Step 2c (mirror 2b): --yes → install; YES_INSTALL_POSTGRES;
52
+ else Confirm.ask. env_vars: POSTGRES_PORT=5432, POSTGRES_USER=crow,
53
+ POSTGRES_DB=crow, POSTGRES_PASSWORD=<env or secrets.token_hex(16)>.
54
+ setup_postgres → db_uri = postgresql+psycopg://${POSTGRES_USER}:
55
+ ${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB};
56
+ compose writer includes postgres; review + done panels reflect backend.
57
+ 3. config.py apply_config_overrides: resolve_env_vars on db_uri.
58
+ 4. CONFIG_YAML template: commented postgres db_uri example.
59
+ - Verify: tests/unit/test_init_postgres.py (mirror test_init_rustfs.py) +
60
+ gate green. Commit.
61
+
62
+ ## Phase 5 — live e2e + docs — DONE 2026-08-26
63
+
64
+ 1. scripts/e2e_postgres_live.py: ephemeral postgres:17-alpine container
65
+ (port 15432, wait pg_isready); create_database on the postgres URI;
66
+ add_message → search_messages round trip (FTS works, lower=better);
67
+ JSON data round trip byte-identical; claim_deliveries with 2 concurrent
68
+ claimers → exactly one wins; get_ro_engine SELECT ok / INSERT raises;
69
+ kill container in finally; print E2E-POSTGRES-OK.
70
+ 2. README persistence section: postgres option + when to use it
71
+ (multi-machine state). memory/__init__.py docstring: both backends now.
72
+ - Verify: script passes against live container; gate green; commit.
73
+
74
+ ## Evidence log
75
+
76
+ - Phase 1 (58061203, 2026-08-26): generic JSON + inspector v5 check +
77
+ psycopg 3.3.4 installed; gate 446 passed.
78
+ - Phase 2 (73823339): memory/fts.py seam; sqlite search behavior unchanged —
79
+ gate 446 passed.
80
+ - Phase 3 (80e72807): get_ro_engine dialect-aware; tests/memory/test_db.py
81
+ (sqlite write actually refused by OS); gate 450 passed.
82
+ - Phase 4 (0de9c278): compose service + wizard Step 2c + db_uri env
83
+ resolution; tests/unit/test_init_postgres.py (5 tests); rendered compose
84
+ passes `docker compose config` with live .env interpolation; gate 455.
85
+ - Phase 5 (this commit): scripts/e2e_postgres_live.py → E2E-POSTGRES-OK
86
+ against a REAL postgres:17-alpine container: schema+GIN index created,
87
+ tsvector search round trip (lower=better), JSON round trip identical,
88
+ 2 concurrent claimers → exactly one delivery claimed, RO engine SELECT ok
89
+ / INSERT refused by the server. README persistence section rewritten.
90
+ Gate 455 passed.
91
+
92
+ Sprint complete: db_uri picks the backend — postgres config → shared
93
+ postgres; absent → sqlite + local FS, exactly as before.
@@ -93,15 +93,15 @@ The agent detects client capabilities (terminals, file read/write) and uses the
93
93
 
94
94
  An ACP-native agent: a streaming ReAct loop with tool calling, cancellation, conversation compaction, and multimodal input. Provider and model configuration lives in `~/.agents/crow/config.yaml`.
95
95
 
96
- ### Persistence — sqlite memory
96
+ ### Persistence — SQL memory (sqlite or PostgreSQL)
97
97
 
98
- Sessions persist to a single sqlite database (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search. Images in messages are written to `~/.agents/crow/images/` and referenced by path; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The same database backs the memory API, exposed to agents as three tools:
98
+ Sessions persist to a SQL database reached via the `db_uri` in `config.yaml`. The default is a single sqlite file (`~/.agents/crow/crow.db`, schema v5, WAL mode) with an FTS5 index for BM25 keyword search — zero setup, one file. Point `db_uri` at PostgreSQL instead (`postgresql+psycopg://…`; a `postgres` service ships in `compose.yaml`) and the same schema runs there for memory shared across machines — agents on any box see the same sessions, messages and task mailboxes. The dialect seams are isolated: keyword search is FTS5/BM25 on sqlite and tsvector/GIN on postgres behind the same `search_messages` contract, and the MCP read-only path is enforced per dialect (sqlite `mode=ro` file URI, postgres session `READ ONLY`). Images never live in the database: they are content-addressed (`<sha256hex><ext>`, so duplicates dedupe for free) in an image store and referenced by key; they are hydrated to base64 data URLs only when the conversation is sent to the LLM. The default store is the filesystem (`~/.agents/crow/images/`). Optionally, point `image_store.s3` in `config.yaml` at an S3 endpoint (a RustFS service ships in `compose.yaml`) and images go there instead — crow probes the endpoint once at startup and falls back to the filesystem when it is down, and reads always fall back to the filesystem too, so images stored before the switch keep working. The same database backs the memory API, exposed to agents as three tools:
99
99
 
100
100
  - `list_sessions()` — sessions ordered by recent activity (who's working on what)
101
101
  - `query_memory(query)` — find which session discussed something, across all sessions
102
102
  - `query_session(session_id)` — read or search within one session (spans all of that session's agents)
103
103
 
104
- This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. No service to run — the sqlite file is the integration point.
104
+ This is what makes multi-agent delegation work: launch a worker, then read its thoughts from any other agent. On sqlite there is no service to run — the file is the integration point. On PostgreSQL the server is the integration point, and every agent on the network shares one authoritative memory.
105
105
 
106
106
  ### Built-in MCP tool server
107
107
 
@@ -170,7 +170,7 @@ uv run pytest tests/e2e # live LLM calls (costs $)
170
170
  src/crow_cli/ the agent — ACP server, ReAct loop, CLI
171
171
  src/crow_cli/config/ config loading, defaults, overrides (shared by every layer)
172
172
  src/crow_cli/mcp/ built-in MCP tool server (`crow-cli mcp`)
173
- src/crow_cli/memory/ shared SQL persistence (sqlite default, postgres-ready)
173
+ src/crow_cli/memory/ shared SQL persistence (sqlite default, PostgreSQL supported)
174
174
  ```
175
175
 
176
176
  ## License
@@ -0,0 +1,73 @@
1
+ # TODO — PostgreSQL memory backend (same treatment as RustFS)
2
+
3
+ ## **DO NOT ASK USER FOR FEEDBACK — THIS IS THE USER FEEDBACK.**
4
+ ## **DO NOT ASK USER FOR NEXT STEPS — THESE ARE THE NEXT STEPS.**
5
+
6
+ Goal: `db_uri` decides the backend. A postgres `db_uri` in config → postgres
7
+ (compose service offered by init, FTS via tsvector/GIN, MCP read-only path
8
+ enforced per-dialect). Absent that → sqlite + local FS, exactly as today.
9
+ Motivation: central agent state shared across machines (coast-after-3 + this
10
+ box) — sqlite has no cross-host story; the task/delivery mailbox design is
11
+ already row-level-safe under postgres.
12
+
13
+ Prior sprint (image object store / RustFS) is COMPLETE — see git history
14
+ 86c1c474..f114d966. This file replaces it.
15
+
16
+ ## Items (unordered)
17
+
18
+ - [x] models.py: swap `sqlalchemy.dialects.sqlite.JSON` → generic
19
+ `sqlalchemy.JSON` (no JSON-operator queries anywhere, so generic is
20
+ correct; keep schema dialect-free).
21
+ - [x] db.py `_require_v5`: PRAGMA table_info is sqlite-only → use SQLAlchemy
22
+ inspector (portable).
23
+ - [x] pyproject: add `psycopg[binary]` dependency (sync driver; engine is
24
+ sync SQLAlchemy throughout). URI form `postgresql+psycopg://`.
25
+ - [x] FTS seam: new `memory/fts.py` owning ALL dialect-specific full-text
26
+ code — create_fts / insert_fts / search_fts. sqlite = FTS5+bm25
27
+ (unchanged behavior); postgres = messages_fts(rowid BIGINT PK, tsv
28
+ tsvector) + GIN index, to_tsvector('simple'), plainto_tsquery,
29
+ ts_rank NEGATED so the contract stays "lower = better, best first"
30
+ (mcp/memory/main.py:359 negates again for display — keep it working).
31
+ Wire into db.create_database, writes.add_message, reads.search_messages.
32
+ - [x] MCP dialect awareness: `get_ro_engine` in memory/db.py — sqlite keeps
33
+ `?mode=ro&uri=true`; postgres gets a connect listener
34
+ `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`. store.py
35
+ `_ro_engine` delegates; sqlite file-existence check stays.
36
+ - [x] defaults.py COMPOSE_YAML: postgres service (postgres:17-alpine,
37
+ POSTGRES_USER/PASSWORD/DB env refs, pg_isready healthcheck,
38
+ postgres_data volume) — same env-ref-credentials pattern as rustfs.
39
+ - [x] init_cmd.py: Step 2c PostgreSQL (mirror Step 2b rustfs: --yes default,
40
+ YES_INSTALL_POSTGRES, Confirm.ask; POSTGRES_PORT/USER/DB/PASSWORD into
41
+ env_vars, password random-default via secrets; db_uri becomes
42
+ postgresql+psycopg://${...}@localhost:${POSTGRES_PORT}/${POSTGRES_DB};
43
+ compose writer includes postgres; review/done panels reflect it).
44
+ - [x] config.py apply_config_overrides: resolve_env_vars on db_uri (load()
45
+ already resolves whole file at line 335).
46
+ - [x] CONFIG_YAML template: db_uri comment shows the postgres option.
47
+ - [x] Tests: tests/unit/test_init_postgres.py (mirror test_init_rustfs.py);
48
+ RO-engine dialect branch tests; fts seam tests (sqlite path = existing
49
+ suite; postgres path = live e2e tier).
50
+ - [x] Live e2e: scripts/e2e_postgres_live.py — ephemeral postgres:17-alpine
51
+ container; create_database; add_message→search_messages round trip;
52
+ JSON data round trip; claim_deliveries exactly-once with 2 concurrent
53
+ claimers; RO engine refuses writes; print E2E-POSTGRES-OK.
54
+ - [x] Docs: README persistence section (postgres option), memory/__init__.py
55
+ docstring ("except FTS5" is now false — both backends supported).
56
+ - [x] Full suite green at every phase boundary; commit per phase with
57
+ Session-Id trailer.
58
+
59
+ ## Decisions (locked)
60
+
61
+ - Generic `sqlalchemy.JSON`, not JSONB variant: no JSON-operator queries
62
+ exist; keep the schema dialect-free.
63
+ - Postgres FTS mirrors the sqlite architecture (side table maintained from
64
+ Python in the same transaction) — NO trigger on messages.data, because
65
+ message_text() extraction logic lives in Python.
66
+ - 'simple' ts config for keyword parity with FTS5 (no stemming surprises).
67
+ - Score contract preserved: search_messages returns lower=better on both
68
+ backends (ts_rank negated).
69
+ - No sqlite→postgres history migration tool this sprint (fresh postgres DB;
70
+ migration is a future task if wanted).
71
+ - postgres compose service has NO custom network (default compose network is
72
+ fine; rustfs-network stays rustfs-only).
73
+ ## Status: COMPLETE 2026-08-26 — all items done; evidence in PLAN.md + commits 58061203..HEAD.
@@ -1,11 +1,12 @@
1
1
  [project]
2
2
  name = "crow-cli"
3
- version = "0.1.37"
3
+ version = "0.1.38"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
7
7
  dependencies = [
8
8
  "agent-client-protocol[http]>=0.12.0,<0.13.0",
9
+ "boto3>=1.40.0",
9
10
  "hypercorn>=0.17",
10
11
  "coolname>=2.2.0",
11
12
  "directory-tree>=1.0.0",
@@ -16,6 +17,7 @@ dependencies = [
16
17
  "markdownify>=0.14.0",
17
18
  "openai>=2.21.0",
18
19
  "opencv-python>=4.13.0.92",
20
+ "psycopg[binary]>=3.1",
19
21
  "pydantic>=2.12.5",
20
22
  "pyyaml>=6.0.3",
21
23
  "readabilipy>=0.3.0",
@@ -43,6 +45,7 @@ packages = ["src/crow_cli"]
43
45
  [dependency-groups]
44
46
  dev = [
45
47
  "ipython>=9.10.0",
48
+ "moto[s3,server]>=5.0.0",
46
49
  "pytest>=9.0.2",
47
50
  "pytest-asyncio>=0.23.0",
48
51
  "pyinstaller>=6.0.0",
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env python
2
+ """Live e2e: image store against a REAL RustFS container.
3
+
4
+ Boots an ephemeral rustfs/rustfs container (SNSD), then drives the real
5
+ MemoryClient through the full seam:
6
+ 1. S3 reachable -> resolve_image_store picks HybridReadStore(S3, FS)
7
+ 2. save a message with an inline base64 image -> object lands in the bucket
8
+ 3. load + hydrate -> identical base64 comes back
9
+ 4. legacy FS image (pre-placed) still hydrates through the hybrid read
10
+ 5. container killed -> next init falls back to FsImageStore
11
+
12
+ Usage:
13
+ uv --project . run python scripts/e2e_image_store_live.py
14
+ """
15
+
16
+ import asyncio
17
+ import base64
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+ import time
22
+ from pathlib import Path
23
+
24
+ import boto3
25
+ import httpx
26
+
27
+ from crow_cli.agent.memory import MemoryClient
28
+ from crow_cli.memory.image_store import FsImageStore, HybridReadStore
29
+
30
+ PORT = 19000
31
+ ENDPOINT = f"http://127.0.0.1:{PORT}"
32
+ CONTAINER = "crow-rustfs-e2e"
33
+ BUCKET = "crow-images"
34
+ ACCESS_KEY = "crowe2e"
35
+ SECRET_KEY = "crowe2e"
36
+
37
+ PNG_BYTES = bytes([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 1, 2, 3, 4])
38
+ PNG_B64 = base64.b64encode(PNG_BYTES).decode()
39
+
40
+
41
+ def docker(*args: str) -> str:
42
+ return subprocess.run(
43
+ ["docker", *args], capture_output=True, text=True, check=True
44
+ ).stdout.strip()
45
+
46
+
47
+ def start_rustfs() -> None:
48
+ subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
49
+ docker(
50
+ "run", "-d", "--name", CONTAINER,
51
+ "-p", f"{PORT}:9000",
52
+ "-e", f"RUSTFS_ACCESS_KEY={ACCESS_KEY}",
53
+ "-e", f"RUSTFS_SECRET_KEY={SECRET_KEY}",
54
+ "-e", "RUSTFS_ADDRESS=0.0.0.0:9000",
55
+ "-e", "RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true",
56
+ "rustfs/rustfs:latest", "/data",
57
+ )
58
+ for _ in range(60):
59
+ try:
60
+ r = httpx.get(f"{ENDPOINT}/health", timeout=1.0)
61
+ if r.status_code < 500:
62
+ print(f"[ok] rustfs healthy at {ENDPOINT}")
63
+ return
64
+ except httpx.HTTPError:
65
+ pass
66
+ time.sleep(1)
67
+ raise SystemExit("[FAIL] rustfs never became healthy")
68
+
69
+
70
+ def stop_rustfs() -> None:
71
+ subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
72
+ print("[ok] container removed")
73
+
74
+
75
+ def write_config(config_dir: Path) -> None:
76
+ (config_dir / "config.yaml").write_text(
77
+ f"db_uri: sqlite:///{config_dir / 'crow.db'}\n"
78
+ "image_store:\n"
79
+ " s3:\n"
80
+ f" endpoint: {ENDPOINT}\n"
81
+ f" bucket: {BUCKET}\n"
82
+ f" access_key: {ACCESS_KEY}\n"
83
+ f" secret_key: {SECRET_KEY}\n"
84
+ )
85
+
86
+
87
+ def image_msg() -> dict:
88
+ return {
89
+ "role": "tool",
90
+ "tool_call_id": "e2e-c1",
91
+ "content": [
92
+ {"type": "text", "text": "live rustfs drill"},
93
+ {
94
+ "type": "image_url",
95
+ "image_url": {"url": f"data:image/png;base64,{PNG_B64}"},
96
+ },
97
+ ],
98
+ }
99
+
100
+
101
+ async def run() -> None:
102
+ tmp = Path(tempfile.mkdtemp(prefix="crow-rustfs-e2e-"))
103
+ write_config(tmp)
104
+
105
+ # ---- 1-3: S3 up — store choice, write into bucket, hydrate back ----
106
+ async with MemoryClient(config_dir=tmp) as client:
107
+ assert isinstance(client.image_store, HybridReadStore), (
108
+ f"expected HybridReadStore, got {type(client.image_store).__name__}"
109
+ )
110
+ print("[ok] resolve_image_store picked S3 (HybridReadStore)")
111
+
112
+ agent_id = "e2e-rustfs-1-1"
113
+ await client.create_agent(
114
+ agent_id=agent_id, session_id="e2e-rustfs", agent_idx=1,
115
+ cwd="/tmp", system_prompt="sp", tool_definitions=[],
116
+ request_params={}, model_identifier="m",
117
+ )
118
+ await client.add_message(agent_id, image_msg())
119
+
120
+ # object actually landed in the bucket (checked via independent client)
121
+ s3 = boto3.client(
122
+ "s3", endpoint_url=ENDPOINT,
123
+ aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY,
124
+ region_name="us-east-1",
125
+ )
126
+ keys = [o["Key"] for o in s3.list_objects_v2(Bucket=BUCKET).get("Contents", [])]
127
+ assert len(keys) == 1 and keys[0].endswith(".png"), f"bucket keys: {keys}"
128
+ print(f"[ok] object in bucket: {keys[0]}")
129
+
130
+ # stored row carries an image_ref, NOT base64
131
+ _, stored = await client.load(agent_id)
132
+ ref = stored[0]["content"][1]
133
+ assert ref["type"] == "image_ref" and ref["path"] == keys[0]
134
+ print("[ok] sqlite row carries image_ref only")
135
+
136
+ # hydrated load round-trips the exact bytes
137
+ _, hydrated = await client.load(agent_id, hydrate=True)
138
+ url = hydrated[0]["content"][1]["image_url"]["url"]
139
+ assert url == f"data:image/png;base64,{PNG_B64}"
140
+ print("[ok] hydrate round-trip: identical base64")
141
+
142
+ # ---- 4: legacy FS image still hydrates through the hybrid read ----
143
+ images_dir = client.images_dir
144
+ fs = FsImageStore(images_dir)
145
+ fs.put("legacydeadbeef.png", PNG_BYTES)
146
+ await client.add_message(
147
+ agent_id,
148
+ {
149
+ "role": "tool",
150
+ "tool_call_id": "e2e-c2",
151
+ "content": [{"type": "image_ref", "path": "legacydeadbeef.png", "mime": "image/png"}],
152
+ },
153
+ )
154
+ _, hydrated2 = await client.load(agent_id, hydrate=True)
155
+ url2 = hydrated2[1]["content"][0]["image_url"]["url"]
156
+ assert url2 == f"data:image/png;base64,{PNG_B64}"
157
+ print("[ok] legacy FS image hydrates via hybrid read-fallback")
158
+
159
+ # ---- 5: kill the container -> next init falls back to FS ----
160
+ stop_rustfs()
161
+ async with MemoryClient(config_dir=tmp) as client:
162
+ assert isinstance(client.image_store, FsImageStore), (
163
+ f"expected FsImageStore fallback, got {type(client.image_store).__name__}"
164
+ )
165
+ print("[ok] endpoint down -> FsImageStore fallback")
166
+
167
+ print("\nE2E-IMAGE-STORE-OK")
168
+
169
+
170
+ if __name__ == "__main__":
171
+ if subprocess.run(["docker", "pull", "rustfs/rustfs:latest"], capture_output=True).returncode != 0:
172
+ print("[..] pull failed — trying cached image")
173
+ start_rustfs()
174
+ try:
175
+ asyncio.run(run())
176
+ finally:
177
+ stop_rustfs()
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env python
2
+ """Live e2e: memory layer against a REAL PostgreSQL container.
3
+
4
+ Boots an ephemeral postgres:17-alpine container, then drives the real
5
+ memory package through the postgres path of every dialect seam:
6
+ 1. create_database -> tables + messages_fts tsvector table + GIN index
7
+ 2. add_message/search_messages -> tsvector FTS round trip, lower=better
8
+ 3. JSON column round trip (Message.data) byte-identical
9
+ 4. claim_deliveries with 2 concurrent claimers -> exactly one wins
10
+ (row-level atomicity on postgres — the multi-machine mailbox story)
11
+ 5. get_ro_engine -> SELECT ok, INSERT refused by the server
12
+
13
+ Usage:
14
+ uv --project . run python scripts/e2e_postgres_live.py
15
+ """
16
+
17
+ import subprocess
18
+ import threading
19
+ import time
20
+
21
+ from sqlalchemy import text
22
+ from sqlalchemy.exc import SQLAlchemyError
23
+
24
+ from crow_cli.memory import (
25
+ add_message,
26
+ claim_deliveries,
27
+ create_agent,
28
+ create_database,
29
+ finish_task,
30
+ get_engine,
31
+ get_ro_engine,
32
+ launch_task,
33
+ load_messages,
34
+ search_messages,
35
+ )
36
+
37
+ PORT = 15432
38
+ CONTAINER = "crow-pg-e2e"
39
+ USER = PASSWORD = DB = "crowe2e"
40
+ URI = f"postgresql+psycopg://{USER}:{PASSWORD}@127.0.0.1:{PORT}/{DB}"
41
+
42
+
43
+ def docker(*args: str) -> str:
44
+ return subprocess.run(
45
+ ["docker", *args], capture_output=True, text=True, check=True
46
+ ).stdout.strip()
47
+
48
+
49
+ def start_postgres() -> None:
50
+ subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
51
+ docker(
52
+ "run", "-d", "--name", CONTAINER,
53
+ "-p", f"{PORT}:5432",
54
+ "-e", f"POSTGRES_USER={USER}",
55
+ "-e", f"POSTGRES_PASSWORD={PASSWORD}",
56
+ "-e", f"POSTGRES_DB={DB}",
57
+ "postgres:17-alpine",
58
+ )
59
+ # Probe with a REAL connection: pg_isready can succeed against the
60
+ # throwaway server the image runs during initdb, before the restart.
61
+ for _ in range(60):
62
+ try:
63
+ probe = get_engine(URI)
64
+ with probe.connect() as conn:
65
+ conn.execute(text("SELECT 1"))
66
+ probe.dispose()
67
+ print(f"[ok] postgres accepting connections at 127.0.0.1:{PORT}")
68
+ return
69
+ except SQLAlchemyError:
70
+ time.sleep(1)
71
+ raise SystemExit("[FAIL] postgres never became ready")
72
+
73
+
74
+ def stop_postgres() -> None:
75
+ subprocess.run(["docker", "rm", "-f", CONTAINER], capture_output=True)
76
+ print("[ok] container removed")
77
+
78
+
79
+ def run() -> None:
80
+ # ---- 1: schema + FTS objects on postgres ----
81
+ create_database(URI)
82
+ engine = get_engine(URI)
83
+ with engine.connect() as conn:
84
+ tables = {
85
+ r[0]
86
+ for r in conn.execute(
87
+ text(
88
+ "SELECT table_name FROM information_schema.tables "
89
+ "WHERE table_schema = 'public'"
90
+ )
91
+ )
92
+ }
93
+ indexes = {
94
+ r[0]
95
+ for r in conn.execute(
96
+ text("SELECT indexname FROM pg_indexes WHERE schemaname = 'public'")
97
+ )
98
+ }
99
+ for t in ("agents", "messages", "tasks", "task_deliveries", "prompts", "messages_fts"):
100
+ assert t in tables, f"missing table {t}: {tables}"
101
+ assert "idx_messages_fts_tsv" in indexes, f"missing GIN index: {indexes}"
102
+ print("[ok] create_database: tables + messages_fts + GIN index")
103
+
104
+ # ---- 2: FTS write/search round trip, lower=better contract ----
105
+ create_agent(
106
+ engine,
107
+ agent_id="e2e-pg-1-1",
108
+ session_id="e2e-pg",
109
+ agent_idx=1,
110
+ cwd="/tmp",
111
+ system_prompt="sp",
112
+ tool_definitions=[],
113
+ request_params={},
114
+ model_identifier="m",
115
+ )
116
+ add_message(
117
+ engine, "e2e-pg-1-1",
118
+ {"role": "user", "content": "the quick brown fox jumps over the lazy dog"},
119
+ )
120
+ add_message(
121
+ engine, "e2e-pg-1-1",
122
+ {"role": "user", "content": "postgres full text search drill"},
123
+ )
124
+ hits = search_messages(engine, "postgres search", limit=10)
125
+ assert len(hits) == 1, f"expected 1 hit, got {len(hits)}"
126
+ assert hits[0]["session_id"] == "e2e-pg"
127
+ assert hits[0]["score"] < 0, f"ts_rank must be negated (lower=better): {hits[0]['score']}"
128
+ hits2 = search_messages(engine, "fox dog", limit=10)
129
+ assert len(hits2) == 1 and "fox" in hits2[0]["data"]["content"]
130
+ assert search_messages(engine, " ", limit=10) == []
131
+ print("[ok] search_messages: tsvector round trip, lower=better, empty-safe")
132
+
133
+ # ---- 3: JSON column round trip ----
134
+ rich_msg = {
135
+ "role": "assistant",
136
+ "content": [
137
+ {"type": "text", "text": "json round trip"},
138
+ {"type": "tool_call", "name": "terminal", "arguments": {"command": "ls -la"}},
139
+ ],
140
+ "reasoning_content": "nested dicts survive",
141
+ }
142
+ add_message(engine, "e2e-pg-1-1", rich_msg)
143
+ rows = load_messages(engine, "e2e-pg-1-1")
144
+ stored = [r for r in rows if r.get("reasoning_content")]
145
+ assert len(stored) == 1 and stored[0] == rich_msg, f"round trip mismatch: {stored}"
146
+ print("[ok] Message.data JSON round trip: identical dict")
147
+
148
+ # ---- 4: mailbox exactly-once under concurrent claimers ----
149
+ launch_task(
150
+ engine,
151
+ task_id="e2e-t1",
152
+ owner_session="e2e-pg",
153
+ tool_call_id="tc1",
154
+ prompt="drill",
155
+ )
156
+ assert finish_task(engine, "e2e-t1", result="done", content="completion payload")
157
+
158
+ barrier = threading.Barrier(2)
159
+ results: list[list] = [[], []]
160
+
161
+ def claim(i: int) -> None:
162
+ barrier.wait()
163
+ results[i] = claim_deliveries(engine, "e2e-pg")
164
+
165
+ threads = [threading.Thread(target=claim, args=(i,)) for i in range(2)]
166
+ for t in threads:
167
+ t.start()
168
+ for t in threads:
169
+ t.join()
170
+ total = [d for r in results for d in r]
171
+ assert len(total) == 1, f"delivery claimed {len(total)} times: {results}"
172
+ assert total[0]["content"] == "completion payload"
173
+ print("[ok] claim_deliveries: 2 concurrent claimers, exactly one won")
174
+
175
+ # ---- 5: read-only engine enforced by the server ----
176
+ ro = get_ro_engine(URI)
177
+ with ro.connect() as conn:
178
+ assert conn.execute(text("SELECT COUNT(*) FROM messages")).scalar() == 3
179
+ try:
180
+ with ro.begin() as conn:
181
+ conn.execute(
182
+ text(
183
+ "INSERT INTO prompts(id, name, template, created_at) "
184
+ "VALUES ('x', 'x', 'x', 'x')"
185
+ )
186
+ )
187
+ raise AssertionError("read-only engine accepted a write")
188
+ except SQLAlchemyError as e:
189
+ assert "read-only" in str(e).lower(), f"unexpected error: {e}"
190
+ print("[ok] get_ro_engine: SELECT ok, INSERT refused by postgres")
191
+
192
+ engine.dispose()
193
+ ro.dispose()
194
+ print("\nE2E-POSTGRES-OK")
195
+
196
+
197
+ if __name__ == "__main__":
198
+ if subprocess.run(["docker", "pull", "postgres:17-alpine"], capture_output=True).returncode != 0:
199
+ print("[..] pull failed — trying cached image")
200
+ start_postgres()
201
+ try:
202
+ run()
203
+ finally:
204
+ stop_postgres()