cade-cli 0.16.7__tar.gz → 0.16.9__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 (204) hide show
  1. {cade_cli-0.16.7 → cade_cli-0.16.9}/PKG-INFO +8 -10
  2. {cade_cli-0.16.7 → cade_cli-0.16.9}/README.md +7 -9
  3. {cade_cli-0.16.7 → cade_cli-0.16.9}/pyproject.toml +1 -1
  4. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ai/prompts.py +1 -1
  5. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/mem.py +46 -31
  6. cade_cli-0.16.9/src/cadecoder/core/memory.py +312 -0
  7. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/server.py +42 -1
  8. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/services.py +14 -0
  9. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/parallel.py +14 -3
  10. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/composite.py +93 -2
  11. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/config.py +4 -0
  12. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/mcp.py +18 -1
  13. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/session.py +11 -2
  14. cade_cli-0.16.7/src/cadecoder/core/memory.py +0 -76
  15. {cade_cli-0.16.7 → cade_cli-0.16.9}/.gitignore +0 -0
  16. {cade_cli-0.16.7 → cade_cli-0.16.9}/LICENSE +0 -0
  17. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/__init__.py +0 -0
  18. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/__main__.py +0 -0
  19. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/_metadata.py +0 -0
  20. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/config.py +0 -0
  21. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/errors.py +0 -0
  22. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/grants.py +0 -0
  23. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/resources.py +0 -0
  24. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/sandbox.py +0 -0
  25. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/server.py +0 -0
  26. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/__init__.py +0 -0
  27. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/_read_cache.py +0 -0
  28. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/access.py +0 -0
  29. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/agents.py +0 -0
  30. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/context.py +0 -0
  31. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/filesystem.py +0 -0
  32. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/git.py +0 -0
  33. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/notifications.py +0 -0
  34. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/questions.py +0 -0
  35. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/results.py +0 -0
  36. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/schemas.py +0 -0
  37. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/search.py +0 -0
  38. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/shell.py +0 -0
  39. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/snippets.py +0 -0
  40. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/tasks.py +0 -0
  41. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/web_fetch.py +0 -0
  42. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/tools/web_search.py +0 -0
  43. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/utils.py +0 -0
  44. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/__init__.py +0 -0
  45. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/arcade_client.py +0 -0
  46. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/cache.py +0 -0
  47. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/extract.py +0 -0
  48. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/markdown.py +0 -0
  49. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/preapproved.py +0 -0
  50. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/prompts.py +0 -0
  51. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/scrape.py +0 -0
  52. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/search/__init__.py +0 -0
  53. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/search/arcade.py +0 -0
  54. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/search/base.py +0 -0
  55. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cade_mcp_local/web/search/http.py +0 -0
  56. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/__init__.py +0 -0
  57. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ai/__init__.py +0 -0
  58. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/__init__.py +0 -0
  59. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/app.py +0 -0
  60. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/auth.py +0 -0
  61. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/__init__.py +0 -0
  62. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/account.py +0 -0
  63. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/agent_group.py +0 -0
  64. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/agents.py +0 -0
  65. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/auth.py +0 -0
  66. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/chat.py +0 -0
  67. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/cron.py +0 -0
  68. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/daemon.py +0 -0
  69. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/grant.py +0 -0
  70. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/hooks.py +0 -0
  71. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/mcp.py +0 -0
  72. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/tasks.py +0 -0
  73. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/thread.py +0 -0
  74. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/commands/tools.py +0 -0
  75. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/mcp_hosts.py +0 -0
  76. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/cli/onboarding.py +0 -0
  77. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/__init__.py +0 -0
  78. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/agent.py +0 -0
  79. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/comms.py +0 -0
  80. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/config.py +0 -0
  81. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/constants.py +0 -0
  82. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/errors.py +0 -0
  83. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/frozen.py +0 -0
  84. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/git.py +0 -0
  85. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/inbox.py +0 -0
  86. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/logging.py +0 -0
  87. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/model_specs.py +0 -0
  88. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/names.py +0 -0
  89. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/paths.py +0 -0
  90. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/runtime.py +0 -0
  91. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/secrets.py +0 -0
  92. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/spawn.py +0 -0
  93. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/core/types.py +0 -0
  94. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/__init__.py +0 -0
  95. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/__init__.py +0 -0
  96. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/base.py +0 -0
  97. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/factory.py +0 -0
  98. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/imessage.py +0 -0
  99. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/registry.py +0 -0
  100. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/stub.py +0 -0
  101. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/adapters/telegram.py +0 -0
  102. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/allowlist.py +0 -0
  103. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/attach.py +0 -0
  104. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/chunker.py +0 -0
  105. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/client.py +0 -0
  106. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/commands.py +0 -0
  107. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/config.py +0 -0
  108. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/control.py +0 -0
  109. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/cron.py +0 -0
  110. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/elicitation.py +0 -0
  111. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/ensure.py +0 -0
  112. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/format.py +0 -0
  113. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/__init__.py +0 -0
  114. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/app.py +0 -0
  115. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/arcade_export.py +0 -0
  116. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/auth.py +0 -0
  117. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/constants.py +0 -0
  118. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/elicitation.py +0 -0
  119. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/exposure.py +0 -0
  120. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/limits.py +0 -0
  121. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/middleware.py +0 -0
  122. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/resources.py +0 -0
  123. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/subscriptions.py +0 -0
  124. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/gateway/turn_bridge.py +0 -0
  125. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/grants.py +0 -0
  126. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/inbox_cron.py +0 -0
  127. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/install.py +0 -0
  128. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/live.py +0 -0
  129. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/lock.py +0 -0
  130. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/mesh.py +0 -0
  131. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/message_pump.py +0 -0
  132. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/observability.py +0 -0
  133. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/owner.py +0 -0
  134. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/progress_sink.py +0 -0
  135. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/registry.py +0 -0
  136. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/router.py +0 -0
  137. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/runner.py +0 -0
  138. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/runner_main.py +0 -0
  139. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/secrets.py +0 -0
  140. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/secrets_broker.py +0 -0
  141. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/shim.py +0 -0
  142. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/sinks.py +0 -0
  143. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/supervisor.py +0 -0
  144. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/tokens.py +0 -0
  145. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/traffic.py +0 -0
  146. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/turns.py +0 -0
  147. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/daemon/wake.py +0 -0
  148. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/__init__.py +0 -0
  149. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/context_window.py +0 -0
  150. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/mcp_schema_index.py +0 -0
  151. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/orchestrator.py +0 -0
  152. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/tool_result_store.py +0 -0
  153. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/execution/tool_schema_cache.py +0 -0
  154. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/__init__.py +0 -0
  155. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/config.py +0 -0
  156. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/engine.py +0 -0
  157. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/__init__.py +0 -0
  158. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/callback.py +0 -0
  159. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/command.py +0 -0
  160. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/http.py +0 -0
  161. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/prompt.py +0 -0
  162. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/executors/ssrf_guard.py +0 -0
  163. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/matchers.py +0 -0
  164. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/registry.py +0 -0
  165. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/hooks/types.py +0 -0
  166. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/__init__.py +0 -0
  167. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/anthropic.py +0 -0
  168. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/base.py +0 -0
  169. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/http_client.py +0 -0
  170. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/ollama.py +0 -0
  171. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/providers/openai.py +0 -0
  172. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/serve/__init__.py +0 -0
  173. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/storage/__init__.py +0 -0
  174. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/storage/threads.py +0 -0
  175. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tasks/__init__.py +0 -0
  176. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tasks/scheduler.py +0 -0
  177. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tasks/store.py +0 -0
  178. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tasks/types.py +0 -0
  179. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/login_failed.html +0 -0
  180. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/login_success.html +0 -0
  181. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/serve/launchd.plist.tmpl +0 -0
  182. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/serve/serve.toml.example +0 -0
  183. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/serve/systemd.service.tmpl +0 -0
  184. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/templates/styles.css +0 -0
  185. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/__init__.py +0 -0
  186. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/__init__.py +0 -0
  187. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/_request_ctx.py +0 -0
  188. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/manager/base.py +0 -0
  189. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/search/__init__.py +0 -0
  190. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/search/discovered.py +0 -0
  191. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/search/scoring.py +0 -0
  192. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/tools/search/service.py +0 -0
  193. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/__init__.py +0 -0
  194. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/completion.py +0 -0
  195. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/display.py +0 -0
  196. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/elicitation.py +0 -0
  197. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/input.py +0 -0
  198. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/ui/slash.py +0 -0
  199. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/__init__.py +0 -0
  200. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/audio.py +0 -0
  201. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/cleanup.py +0 -0
  202. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/session.py +0 -0
  203. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/stt.py +0 -0
  204. {cade_cli-0.16.7 → cade_cli-0.16.9}/src/cadecoder/voice/tts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cade-cli
3
- Version: 0.16.7
3
+ Version: 0.16.9
4
4
  Summary: Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)
5
5
  Project-URL: Homepage, https://arcade.dev
6
6
  Project-URL: Documentation, https://arcadeagent.dev/docs
@@ -129,7 +129,7 @@ The script detects your OS/arch, downloads the matching prebuilt binary
129
129
  `~/.local/bin`. Pin a version or install location:
130
130
 
131
131
  ```bash
132
- CADE_VERSION=0.16.7 CADE_INSTALL_DIR=/usr/local/bin \
132
+ CADE_VERSION=0.16.9 CADE_INSTALL_DIR=/usr/local/bin \
133
133
  sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
134
134
  ```
135
135
 
@@ -252,18 +252,16 @@ cade agent list
252
252
  cade agent use reviewer
253
253
  cade agent info / cade agent edit reviewer
254
254
 
255
+ cade agent mem add ~/Notes # one step: register + index
255
256
  cade agent mem # status
256
- cade agent mem add ~/notes
257
257
  cade agent mem search "auth design"
258
- cade agent mem warm # pre-load semantic search
259
258
  ```
260
259
 
261
- Memory is per-agent — each agent has its own
262
- `~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
263
- notes never leak into another. Cade advertises `Memory_*` tools without starting
264
- the memory server; the first actual memory call starts it and keeps it warm for
265
- later calls. Use `cade agent mem warm` when you want to pay that semantic-search
266
- warmup before a conversation.
260
+ Memory is per-agent under
261
+ `~/.cade/data/agents/<id>/memory/<agent>/.librarian/`. Cade keeps the index and
262
+ embedder ready (auto-heal empty indexes, warm after add/update, longer first
263
+ search timeouts). The model uses `Memory_*` for notes recall — not Local_*
264
+ filesystem tools. Details: [`docs/agents.md`](./docs/agents.md#memory).
267
265
 
268
266
  ---
269
267
 
@@ -38,7 +38,7 @@ The script detects your OS/arch, downloads the matching prebuilt binary
38
38
  `~/.local/bin`. Pin a version or install location:
39
39
 
40
40
  ```bash
41
- CADE_VERSION=0.16.7 CADE_INSTALL_DIR=/usr/local/bin \
41
+ CADE_VERSION=0.16.9 CADE_INSTALL_DIR=/usr/local/bin \
42
42
  sh -c "$(curl -fsSL https://arcadeagent.dev/install)"
43
43
  ```
44
44
 
@@ -161,18 +161,16 @@ cade agent list
161
161
  cade agent use reviewer
162
162
  cade agent info / cade agent edit reviewer
163
163
 
164
+ cade agent mem add ~/Notes # one step: register + index
164
165
  cade agent mem # status
165
- cade agent mem add ~/notes
166
166
  cade agent mem search "auth design"
167
- cade agent mem warm # pre-load semantic search
168
167
  ```
169
168
 
170
- Memory is per-agent — each agent has its own
171
- `~/.cade/data/agents/<id>/memory/.librarian/` index, so a `reviewer` agent's
172
- notes never leak into another. Cade advertises `Memory_*` tools without starting
173
- the memory server; the first actual memory call starts it and keeps it warm for
174
- later calls. Use `cade agent mem warm` when you want to pay that semantic-search
175
- warmup before a conversation.
169
+ Memory is per-agent under
170
+ `~/.cade/data/agents/<id>/memory/<agent>/.librarian/`. Cade keeps the index and
171
+ embedder ready (auto-heal empty indexes, warm after add/update, longer first
172
+ search timeouts). The model uses `Memory_*` for notes recall — not Local_*
173
+ filesystem tools. Details: [`docs/agents.md`](./docs/agents.md#memory).
176
174
 
177
175
  ---
178
176
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cade-cli"
3
- version = "0.16.7"
3
+ version = "0.16.9"
4
4
  description = "Cade - The CLI Agent from Arcade.dev (install: curl -fsSL https://arcadeagent.dev/install | sh)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -174,7 +174,7 @@ Tool names are versioned (e.g. Slack.SendMessage@2.3.0). Pass the full string to
174
174
 
175
175
  Re-call SelectTools when the domain shifts or the first result missed. Tools registered earlier in the session persist; you don't need to rediscover them.
176
176
 
177
- Routing: external info/actions on third-party services → discovery + Arcade_UseTool | external pages on the open web → Local_WebFetch (read a URL) and Local_WebSearch (find URLs); pair them — search to find, fetch to read | local code → Local_Search, Local_ReadFile, Local_ListFiles, other Local_* tools. Never grep local files for external-service data, and never guess URLs — search or ask first.
177
+ Routing: external info/actions on third-party services → discovery + Arcade_UseTool | external pages on the open web → Local_WebFetch (read a URL) and Local_WebSearch (find URLs); pair them — search to find, fetch to read | agent notes / persistent library → Memory_SearchLibrary (and other Memory_* tools); never use Local_Search / Local_ReadFile as a fallback for vault or notes recall | local code → Local_Search, Local_ReadFile, Local_ListFiles, other Local_* tools. Never grep local files for external-service data, and never guess URLs — search or ask first.
178
178
 
179
179
  Anti-patterns: Search → Select chained reflexively; one SelectTools per step instead of per workflow; invoking discovered tools by name instead of through Arcade_UseTool; stripping @version; ignoring an auth URL and retrying.
180
180
  """
@@ -1,21 +1,25 @@
1
- """``cade mem`` — manage the current agent's memory, backed by agent-library.
1
+ """``cade agent mem`` — manage the current agent's memory, backed by agent-library.
2
2
 
3
- Drives the librarian Typer app in-process with ``prog_name="cade mem"`` (so the
4
- help tree reads as ``cade mem`` rather than ``librarian``/``libr``), scoped to
5
- the current agent's memory directory. The same on-disk catalogue is what the
6
- agent's ``Memory_*`` MCP tools read and write.
3
+ Preferred path: ``cade agent mem …``. Flat ``cade mem …`` remains a hidden
4
+ alias. Drives the librarian Typer app in-process (help branded as ``cade mem``),
5
+ scoped to the current agent's memory directory — the same catalogue the
6
+ agent's ``Memory_*`` MCP tools use.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- import os
12
11
  from pathlib import Path
13
12
  from typing import Annotated, Any
14
13
 
15
14
  import typer
16
15
  from rich.console import Console
17
16
 
18
- from cadecoder.core.memory import ensure_memory_dir
17
+ from cadecoder.core.memory import (
18
+ apply_memory_env,
19
+ ensure_memory_dir,
20
+ ensure_memory_ready,
21
+ warm_memory_embedder,
22
+ )
19
23
  from cadecoder.core.runtime import get_runtime
20
24
 
21
25
  console = Console(stderr=True)
@@ -36,18 +40,7 @@ def _apply_memory_env() -> Path:
36
40
  Returns:
37
41
  The agent's memory directory (parent of ``.librarian``).
38
42
  """
39
- runtime = get_runtime()
40
- persona_dir = ensure_memory_dir(runtime.persona_name)
41
- librarian_dir = persona_dir / ".librarian"
42
- os.environ["DATABASE_PATH"] = str(librarian_dir / "index.db")
43
- os.environ["DOCUMENTS_PATH"] = str(librarian_dir / "documents")
44
- os.environ["SOURCES_CONFIG_PATH"] = str(librarian_dir / "sources.json")
45
- os.environ["HOME"] = str(persona_dir)
46
- # Rebrand librarian's usage hints (e.g. "Add a source with: libr add ...")
47
- # to "cade mem". Must be set before librarian.cli is imported, which only
48
- # happens later via _load_librarian_app().
49
- os.environ["LIBRARIAN_PROG_NAME"] = "cade mem"
50
- return persona_dir
43
+ return apply_memory_env()
51
44
 
52
45
 
53
46
  def _load_librarian_app() -> Any:
@@ -62,6 +55,13 @@ def _load_librarian_app() -> Any:
62
55
  * ``list`` shows the agent's memory **documents** (what users expect from
63
56
  "list"), while the source-directory listing moves to ``sources``.
64
57
  """
58
+ # ``mem update`` / ``mem add`` import ``librarian.server`` (tool registration
59
+ # via arcade-core). Frozen binaries must patch return-value detection before
60
+ # that import or ``add_tool`` raises ``ValueError: Source code not found``.
61
+ from cadecoder.core.frozen import patch_frozen_return_detection
62
+
63
+ patch_frozen_return_detection()
64
+
65
65
  from librarian.cli import app
66
66
 
67
67
  _remap_index_to_update(app)
@@ -178,7 +178,7 @@ def _mem_status() -> None:
178
178
  """Render the current agent's memory under "Agent Memory" branding.
179
179
 
180
180
  Shared by ``cade mem`` (CLI) and the in-chat ``/mem`` command so the view
181
- is always the same.
181
+ is always the same. Auto-heals an empty index when sources are registered.
182
182
  """
183
183
  from datetime import datetime
184
184
 
@@ -186,14 +186,18 @@ def _mem_status() -> None:
186
186
  from rich.table import Table
187
187
 
188
188
  runtime = get_runtime()
189
- memory_dir = ensure_memory_dir(runtime.persona_name)
189
+ ready = ensure_memory_ready(runtime.persona_name, heal=True, warm=False)
190
+ memory_dir = ready.persona_dir
190
191
  db_path = memory_dir / ".librarian" / "index.db"
191
192
 
192
193
  counts = _read_memory_stats(db_path)
193
- if db_path.exists():
194
+ document_count = int(counts.get("document_count", ready.document_count) or 0)
195
+ if db_path.exists() and document_count > 0:
194
196
  last_updated = datetime.fromtimestamp(db_path.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
197
+ elif ready.sources and document_count == 0:
198
+ last_updated = "empty — sources registered but nothing indexed yet"
195
199
  else:
196
- last_updated = "never — run 'cade agent mem update'"
200
+ last_updated = "never"
197
201
 
198
202
  agent_id = runtime.agent_id or "-"
199
203
 
@@ -206,10 +210,18 @@ def _mem_status() -> None:
206
210
  table = Table(show_header=False, box=None, pad_edge=False)
207
211
  table.add_column(style="green")
208
212
  table.add_column(style="white")
209
- table.add_row("Count", str(counts.get("document_count", 0)))
213
+ table.add_row("Count", str(document_count))
214
+ table.add_row("Sources", str(len(ready.sources)))
210
215
  table.add_row("Last updated", last_updated)
211
216
  table.add_row("Location", str(memory_dir))
212
217
  out.print(table)
218
+ if ready.healed:
219
+ out.print("[dim]Index was empty and has been rebuilt from registered sources.[/dim]")
220
+ elif not ready.sources:
221
+ out.print(
222
+ "[dim]No notes folder yet — add one with "
223
+ "[bold]cade agent mem add ~/Notes[/bold].[/dim]"
224
+ )
213
225
 
214
226
 
215
227
  def _mem_list(*, json_output: bool = False, limit: int = 50) -> None:
@@ -265,7 +277,7 @@ def _mem_list(*, json_output: bool = False, limit: int = 50) -> None:
265
277
  if not documents:
266
278
  out.print(
267
279
  "[dim]No documents in this agent's memory yet — "
268
- "add docs or run 'cade agent mem update'.[/dim]"
280
+ "add a notes folder with 'cade agent mem add ~/Notes'.[/dim]"
269
281
  )
270
282
  return
271
283
 
@@ -351,29 +363,32 @@ def _warm_memory() -> int:
351
363
 
352
364
 
353
365
  def mem_command(ctx: typer.Context) -> None:
354
- """Run a ``cade mem`` subcommand scoped to the current agent.
366
+ """Run a ``cade agent mem`` subcommand scoped to the current agent.
355
367
 
356
368
  Examples:
357
- cade mem # show the cade mem help
358
- cade mem search "topic" # search the current agent's memory
359
- cade mem add ./docs # add a source to the current agent's memory
369
+ cade agent mem # status (auto-heals empty index)
370
+ cade agent mem add ~/Notes # register + index a notes folder
371
+ cade agent mem search "topic" # search the current agent's memory
360
372
  """
361
373
  memory_home = _apply_memory_env()
362
374
  forwarded = list(ctx.args)
363
- # Bare `cade mem` defaults to the Agent Memory status view (which already
375
+ # Bare `cade agent mem` defaults to the Agent Memory status view (which already
364
376
  # names the current agent) rather than a help+error panel.
365
377
  if not forwarded:
366
378
  forwarded = ["status"]
367
379
  elif forwarded[0] in {"--help", "-h"}:
368
380
  agent_name = get_runtime().persona_name
369
381
  console.print(
370
- f"[dim]cade mem: scoped to agent [bold]{agent_name}[/bold] "
382
+ f"[dim]cade agent mem: scoped to agent [bold]{agent_name}[/bold] "
371
383
  f"(storage={memory_home})[/dim]\n"
372
384
  )
373
385
  elif forwarded[0] == "warm":
374
386
  rc = _warm_memory()
375
387
  raise typer.Exit(rc)
376
388
  rc = _run_mem(forwarded)
389
+ # After sources change or a rebuild, warm so the next Memory_* search is not cold.
390
+ if rc == 0 and forwarded and forwarded[0] in {"update", "add"}:
391
+ warm_memory_embedder(quiet=True)
377
392
  raise typer.Exit(rc)
378
393
 
379
394
 
@@ -0,0 +1,312 @@
1
+ """Per-agent memory directory management for the bundled agent-library server.
2
+
3
+ The current runtime (agent-backed) scopes memory under
4
+ ``data/agents/<id>/memory/<persona>/`` where ``<persona>`` is the agent name.
5
+ All librarian storage lives in a ``.librarian/`` subfolder so that pointing
6
+ ``HOME`` at the persona directory makes librarian's hardcoded
7
+ ``Path.home() / ".librarian"`` resolve to the same files the ``DATABASE_PATH`` /
8
+ ``DOCUMENTS_PATH`` / ``SOURCES_CONFIG_PATH`` env vars point at — the ``Memory_*``
9
+ MCP tools and ``cade mem`` then share one on-disk catalogue.
10
+
11
+ ``ensure_memory_ready`` is the invisible contract: sources without an index are
12
+ healed automatically, and the embedder can be prewarmed so the first search
13
+ does not time out.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import shutil
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+
24
+ from cadecoder.core.logging import log
25
+ from cadecoder.core.paths import validate_name
26
+ from cadecoder.core.runtime import get_runtime
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class MemoryReadyState:
31
+ """Snapshot of an agent's memory after readiness checks."""
32
+
33
+ persona_dir: Path
34
+ librarian_dir: Path
35
+ sources: list[dict] = field(default_factory=list)
36
+ document_count: int = 0
37
+ healed: bool = False
38
+ missing_sources: list[str] = field(default_factory=list)
39
+ warmed: bool = False
40
+
41
+ @property
42
+ def has_sources(self) -> bool:
43
+ """True when at least one source is registered."""
44
+ return bool(self.sources)
45
+
46
+ @property
47
+ def is_ready(self) -> bool:
48
+ """True when sources exist and the index has documents."""
49
+ return self.has_sources and self.document_count > 0
50
+
51
+
52
+ def ensure_memory_dir(persona_name: str) -> Path:
53
+ """Create the memory directory for a persona/agent and return it.
54
+
55
+ Two one-time bootstraps keep a fresh persona from being starved of state:
56
+
57
+ 1. **Flat-layout migration** of any top-level ``index.db`` / ``sources.json``
58
+ / ``settings.json`` / ``documents/`` into ``.librarian/``.
59
+ 2. **Global librarian bootstrap**: copy ``~/.librarian/sources.json`` and
60
+ ``settings.json`` (if present) into a brand-new persona so documents
61
+ indexed outside Cade aren't orphaned. The index itself is never copied —
62
+ call :func:`ensure_memory_ready` to rebuild it.
63
+
64
+ Both skip when the target already exists, so repeated calls never churn data.
65
+
66
+ Args:
67
+ persona_name: The persona/agent name scoping the memory.
68
+
69
+ Returns:
70
+ The persona memory directory (the parent of ``.librarian``).
71
+ """
72
+ safe_name = validate_name(persona_name, kind="persona")
73
+ persona_dir = get_runtime().memory_dir(safe_name)
74
+ persona_dir.mkdir(parents=True, exist_ok=True)
75
+
76
+ librarian_dir = persona_dir / ".librarian"
77
+ librarian_dir.mkdir(exist_ok=True)
78
+ (librarian_dir / "documents").mkdir(exist_ok=True)
79
+
80
+ # 1) Flat-layout migration.
81
+ for filename in ("index.db", "sources.json", "settings.json"):
82
+ old = persona_dir / filename
83
+ new = librarian_dir / filename
84
+ if old.is_file() and not new.exists():
85
+ old.rename(new)
86
+
87
+ old_documents = persona_dir / "documents"
88
+ new_documents = librarian_dir / "documents"
89
+ if old_documents.is_dir() and old_documents.resolve() != new_documents.resolve():
90
+ for child in list(old_documents.iterdir()):
91
+ target = new_documents / child.name
92
+ if not target.exists():
93
+ child.rename(target)
94
+ try:
95
+ old_documents.rmdir()
96
+ except OSError:
97
+ pass
98
+
99
+ # 2) Global librarian bootstrap (sources/settings only — never index/docs).
100
+ global_librarian = Path.home() / ".librarian"
101
+ if global_librarian.is_dir() and global_librarian.resolve() != librarian_dir.resolve():
102
+ for filename in ("sources.json", "settings.json"):
103
+ source_file = global_librarian / filename
104
+ target_file = librarian_dir / filename
105
+ if source_file.is_file() and not target_file.exists():
106
+ shutil.copy2(source_file, target_file)
107
+
108
+ return persona_dir
109
+
110
+
111
+ def librarian_dir_for(persona_name: str | None = None) -> Path:
112
+ """Return ``.librarian`` for the persona, creating the layout if needed."""
113
+ runtime = get_runtime()
114
+ name = persona_name or runtime.persona_name
115
+ return ensure_memory_dir(name) / ".librarian"
116
+
117
+
118
+ def load_memory_sources(persona_name: str | None = None) -> list[dict]:
119
+ """Load registered memory sources for a persona.
120
+
121
+ Args:
122
+ persona_name: Persona/agent name; defaults to the active runtime persona.
123
+
124
+ Returns:
125
+ List of source dicts from ``sources.json``, or ``[]`` when missing/invalid.
126
+ """
127
+ sources_path = librarian_dir_for(persona_name) / "sources.json"
128
+ if not sources_path.is_file():
129
+ return []
130
+ try:
131
+ data = json.loads(sources_path.read_text(encoding="utf-8"))
132
+ except (OSError, json.JSONDecodeError):
133
+ return []
134
+ return data if isinstance(data, list) else []
135
+
136
+
137
+ def memory_has_sources(persona_name: str | None = None) -> bool:
138
+ """Return True when the persona has at least one registered source."""
139
+ return bool(load_memory_sources(persona_name))
140
+
141
+
142
+ def read_memory_document_count(persona_name: str | None = None) -> int:
143
+ """Return indexed document count without mutating process env.
144
+
145
+ Args:
146
+ persona_name: Persona/agent name; defaults to the active runtime persona.
147
+
148
+ Returns:
149
+ Document count, or ``0`` when the index is missing or unreadable.
150
+ """
151
+ db_path = librarian_dir_for(persona_name) / "index.db"
152
+ if not db_path.exists():
153
+ return 0
154
+ try:
155
+ from librarian.storage.database import Database
156
+
157
+ stats = Database(db_path=str(db_path)).get_stats()
158
+ return int(stats.get("document_count", 0) or 0)
159
+ except Exception: # noqa: BLE001
160
+ return 0
161
+
162
+
163
+ def missing_memory_sources(persona_name: str | None = None) -> list[str]:
164
+ """Return source paths that are registered but missing on disk."""
165
+ missing: list[str] = []
166
+ for source in load_memory_sources(persona_name):
167
+ path = source.get("path")
168
+ if not path:
169
+ continue
170
+ if not Path(str(path)).expanduser().exists():
171
+ name = str(source.get("name") or path)
172
+ missing.append(name)
173
+ return missing
174
+
175
+
176
+ def apply_memory_env(persona_name: str | None = None) -> Path:
177
+ """Point librarian storage env vars at the persona's ``.librarian`` dir.
178
+
179
+ Args:
180
+ persona_name: Persona/agent name; defaults to the active runtime persona.
181
+
182
+ Returns:
183
+ The persona memory directory (parent of ``.librarian``).
184
+ """
185
+ runtime = get_runtime()
186
+ name = persona_name or runtime.persona_name
187
+ persona_dir = ensure_memory_dir(name)
188
+ librarian_dir = persona_dir / ".librarian"
189
+ os.environ["DATABASE_PATH"] = str(librarian_dir / "index.db")
190
+ os.environ["DOCUMENTS_PATH"] = str(librarian_dir / "documents")
191
+ os.environ["SOURCES_CONFIG_PATH"] = str(librarian_dir / "sources.json")
192
+ os.environ["HOME"] = str(persona_dir)
193
+ os.environ["LIBRARIAN_PROG_NAME"] = "cade mem"
194
+ return persona_dir
195
+
196
+
197
+ def rebuild_memory_index(persona_name: str | None = None) -> int:
198
+ """Non-interactively rebuild the memory index from registered sources.
199
+
200
+ Args:
201
+ persona_name: Persona/agent name; defaults to the active runtime persona.
202
+
203
+ Returns:
204
+ Indexed document count after rebuild (best-effort; ``0`` on failure).
205
+ """
206
+ apply_memory_env(persona_name)
207
+ try:
208
+ from librarian.cli import index_build
209
+ except ImportError:
210
+ log.debug("agent-library not installed; cannot rebuild memory index")
211
+ return 0
212
+
213
+ try:
214
+ index_build(source=None, confirm=True, verbose=False)
215
+ except SystemExit as exc:
216
+ code = exc.code
217
+ if code not in (None, 0):
218
+ log.warning(f"memory index rebuild exited with code {code}")
219
+ return read_memory_document_count(persona_name)
220
+ except Exception as exc: # noqa: BLE001
221
+ log.warning(f"memory index rebuild failed: {exc}")
222
+ return read_memory_document_count(persona_name)
223
+
224
+ return read_memory_document_count(persona_name)
225
+
226
+
227
+ def warm_memory_embedder(*, quiet: bool = True) -> bool:
228
+ """Load the text embedding model so the next search is not cold.
229
+
230
+ Args:
231
+ quiet: When True, only log failures at debug; otherwise raise-free warn.
232
+
233
+ Returns:
234
+ True when the embedder loaded successfully.
235
+ """
236
+ try:
237
+ from librarian.processing.embed import get_embedder
238
+ except ImportError:
239
+ if not quiet:
240
+ log.warning("agent-library not installed; cannot warm memory embedder")
241
+ return False
242
+
243
+ try:
244
+ embedder = get_embedder()
245
+ _ = embedder.dimension
246
+ log.debug(
247
+ f"memory embedder warm (model={getattr(embedder, 'model_name', '?')}, "
248
+ f"dimension={embedder.dimension})"
249
+ )
250
+ return True
251
+ except Exception as exc: # noqa: BLE001
252
+ if quiet:
253
+ log.debug(f"memory embedder warm failed: {exc}")
254
+ else:
255
+ log.warning(f"memory embedder warm failed: {exc}")
256
+ return False
257
+
258
+
259
+ def ensure_memory_ready(
260
+ persona_name: str | None = None,
261
+ *,
262
+ heal: bool = True,
263
+ warm: bool = False,
264
+ ) -> MemoryReadyState:
265
+ """Ensure persona memory is usable: layout, non-empty index, optional warm.
266
+
267
+ When sources are registered but ``document_count == 0``, rebuilds the index
268
+ non-interactively so bootstrap-from-global-sources never presents an empty
269
+ catalogue as ready. Missing source paths are recorded quietly (logged) and
270
+ skipped by the rebuild.
271
+
272
+ Args:
273
+ persona_name: Persona/agent name; defaults to the active runtime persona.
274
+ heal: Rebuild when sources exist but the index is empty.
275
+ warm: Preload the embedding model after heal (or when already ready).
276
+
277
+ Returns:
278
+ :class:`MemoryReadyState` describing the post-check catalogue.
279
+ """
280
+ runtime = get_runtime()
281
+ name = persona_name or runtime.persona_name
282
+ persona_dir = ensure_memory_dir(name)
283
+ librarian_dir = persona_dir / ".librarian"
284
+ sources = load_memory_sources(name)
285
+ missing = missing_memory_sources(name)
286
+ if missing:
287
+ log.debug(f"memory sources missing on disk (skipped): {', '.join(missing)}")
288
+
289
+ document_count = read_memory_document_count(name)
290
+ healed = False
291
+ if heal and sources and document_count == 0:
292
+ log.info(
293
+ f"memory index empty with {len(sources)} source(s) for agent "
294
+ f"{name!r}; rebuilding automatically"
295
+ )
296
+ document_count = rebuild_memory_index(name)
297
+ healed = True
298
+
299
+ warmed = False
300
+ if warm and (document_count > 0 or healed):
301
+ apply_memory_env(name)
302
+ warmed = warm_memory_embedder(quiet=True)
303
+
304
+ return MemoryReadyState(
305
+ persona_dir=persona_dir,
306
+ librarian_dir=librarian_dir,
307
+ sources=sources,
308
+ document_count=document_count,
309
+ healed=healed,
310
+ missing_sources=missing,
311
+ warmed=warmed,
312
+ )
@@ -269,9 +269,32 @@ def _resolve_sandbox_root(cfg: Any) -> Any:
269
269
  return base.resolve()
270
270
 
271
271
 
272
+ async def _prewarm_shared_memory(memory_url: str) -> None:
273
+ """Best-effort warm of the shared memory HTTP service (embedder load)."""
274
+ try:
275
+ from cadecoder.tools.manager.composite import build_builtin_http_config
276
+ from cadecoder.tools.manager.mcp import MCPToolManager
277
+
278
+ config = build_builtin_http_config("__builtin_memory__", memory_url)
279
+ manager = MCPToolManager(config, None)
280
+ try:
281
+ await manager.initialize()
282
+ # Keyword search still loads the hybrid stack / embedder on many builds.
283
+ await manager.execute(
284
+ "Memory_SearchLibrary",
285
+ {"query": "cade memory warm", "mode": "keyword", "limit": 1},
286
+ )
287
+ log.debug("shared memory service prewarmed")
288
+ finally:
289
+ await manager.close()
290
+ except Exception as e: # noqa: BLE001
291
+ log.debug(f"shared memory prewarm skipped: {e}")
292
+
293
+
272
294
  async def _start_builtin_services(persona: str) -> None:
273
295
  """Promote stdio local-tools to a shared loopback HTTP service when possible."""
274
296
  try:
297
+ from cadecoder.core.memory import ensure_memory_ready, memory_has_sources
275
298
  from cadecoder.daemon.services import BuiltinServices
276
299
  from cadecoder.tools.manager.composite import (
277
300
  BUILTIN_LOCAL_NAME,
@@ -282,19 +305,37 @@ async def _start_builtin_services(persona: str) -> None:
282
305
  log.debug(f"builtin services unavailable: {e}")
283
306
  return
284
307
 
308
+ # Heal empty index before any worker can search; warm in-process is a
309
+ # best-effort hint — the shared memory HTTP process warms on first use /
310
+ # via ensure_memory_service prewarm.
311
+ try:
312
+ await asyncio.to_thread(ensure_memory_ready, persona, heal=True, warm=False)
313
+ except Exception as e: # noqa: BLE001
314
+ log.debug(f"memory readiness at daemon boot skipped: {e}")
315
+
285
316
  servers = {s.name: s for s in effective_mcp_servers(persona=persona)}
286
317
  local = servers.get(BUILTIN_LOCAL_NAME)
287
318
  start_local = local is not None and local.transport == MCPTransportType.STDIO
288
319
  if not start_local:
289
320
  return
290
321
 
291
- services = BuiltinServices(persona=persona, start_local=True, start_memory=False)
322
+ start_memory = False
323
+ try:
324
+ start_memory = memory_has_sources(persona)
325
+ except Exception: # noqa: BLE001
326
+ start_memory = False
327
+
328
+ services = BuiltinServices(persona=persona, start_local=True, start_memory=start_memory)
292
329
  if not await services.start():
293
330
  return
294
331
  if services.local_url:
295
332
  os.environ["CADE_BUILTIN_LOCAL_URL"] = services.local_url
296
333
  if services.memory_url:
297
334
  os.environ["CADE_BUILTIN_MEMORY_URL"] = services.memory_url
335
+ asyncio.create_task(
336
+ _prewarm_shared_memory(services.memory_url),
337
+ name="cade-daemon-memory-prewarm",
338
+ )
298
339
 
299
340
 
300
341
  async def _start_cron(supervisor: Supervisor):
@@ -161,6 +161,20 @@ class BuiltinServices:
161
161
  return True
162
162
 
163
163
  async def _start_memory(self) -> None:
164
+ # Heal empty index in the parent before the memory subprocess starts so
165
+ # the first client never sees sources-with-zero-docs as ready.
166
+ try:
167
+ from cadecoder.core.memory import ensure_memory_ready
168
+
169
+ await asyncio.to_thread(
170
+ ensure_memory_ready,
171
+ self.persona,
172
+ heal=True,
173
+ warm=False,
174
+ )
175
+ except Exception as e: # noqa: BLE001
176
+ log.debug(f"memory readiness before shared service skipped: {e}")
177
+
164
178
  config = _build_memory_config(self.persona)
165
179
  if config is None:
166
180
  return # agent-library not installed; nothing to share