marvin 3.2.2__tar.gz → 3.2.4__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 (316) hide show
  1. {marvin-3.2.2 → marvin-3.2.4}/.gitignore +1 -0
  2. {marvin-3.2.2 → marvin-3.2.4}/PKG-INFO +4 -4
  3. {marvin-3.2.2 → marvin-3.2.4}/README.md +2 -2
  4. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-database.mdx +6 -5
  5. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/agents.mdx +0 -15
  6. {marvin-3.2.2 → marvin-3.2.4}/docs/installation.mdx +0 -2
  7. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/memory.mdx +5 -5
  8. marvin-3.2.4/examples/provider_specific/aimlapi/run_agent.py +49 -0
  9. marvin-3.2.4/examples/provider_specific/aimlapi/structured_output.py +54 -0
  10. marvin-3.2.4/examples/provider_specific/aimlapi/tools_agent.py +58 -0
  11. marvin-3.2.4/examples/slackbot/Dockerfile.slackbot +25 -0
  12. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/README.md +2 -6
  13. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/pyproject.toml +1 -0
  14. marvin-3.2.4/examples/slackbot/src/slackbot/_internal/constants.py +1 -0
  15. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/api.py +1 -6
  16. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/models.py +1 -1
  17. marvin-3.2.4/examples/slackbot/src/slackbot/research_agent.py +127 -0
  18. {marvin-3.2.2 → marvin-3.2.4}/examples/timeseries.py +19 -7
  19. {marvin-3.2.2 → marvin-3.2.4}/pyproject.toml +1 -1
  20. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/database.py +21 -6
  21. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/streaming.py +8 -0
  22. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_database.py +41 -0
  23. marvin-3.2.4/uv.lock +7263 -0
  24. marvin-3.2.2/.cursor/rules/docs.mdc +0 -14
  25. marvin-3.2.2/.cursor/rules/general.mdc +0 -12
  26. marvin-3.2.2/.cursor/rules/python.mdc +0 -35
  27. marvin-3.2.2/examples/slackbot/Dockerfile.slackbot +0 -18
  28. marvin-3.2.2/examples/slackbot/src/slackbot/_internal/constants.py +0 -4
  29. marvin-3.2.2/examples/slackbot/src/slackbot/research_agent.py +0 -151
  30. marvin-3.2.2/uv.lock +0 -6310
  31. {marvin-3.2.2 → marvin-3.2.4}/.github/labeler.yml +0 -0
  32. {marvin-3.2.2 → marvin-3.2.4}/.github/release.yml +0 -0
  33. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/deploy-cloudrun-revision-community.yaml +0 -0
  34. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/deploy-cloudrun-revision-internal.yaml +0 -0
  35. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/image-build-and-push-community.yaml +0 -0
  36. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/image-build-and-push-internal.yaml +0 -0
  37. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/labeler.yml +0 -0
  38. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/publish.yml +0 -0
  39. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/run-tests.yml +0 -0
  40. {marvin-3.2.2 → marvin-3.2.4}/.github/workflows/static-analysis.yml +0 -0
  41. {marvin-3.2.2 → marvin-3.2.4}/.pre-commit-config.yaml +0 -0
  42. {marvin-3.2.2 → marvin-3.2.4}/CLAUDE.md +0 -0
  43. {marvin-3.2.2 → marvin-3.2.4}/LICENSE +0 -0
  44. {marvin-3.2.2 → marvin-3.2.4}/alembic.ini +0 -0
  45. {marvin-3.2.2 → marvin-3.2.4}/docs/README.md +0 -0
  46. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-actor.mdx +0 -0
  47. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-agent.mdx +0 -0
  48. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-names.mdx +0 -0
  49. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents-team.mdx +0 -0
  50. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-agents.mdx +0 -0
  51. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-beta.mdx +0 -0
  52. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-config.mdx +0 -0
  53. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-dev.mdx +0 -0
  54. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-main.mdx +0 -0
  55. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli-migrations.mdx +0 -0
  56. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-cli.mdx +0 -0
  57. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-defaults.mdx +0 -0
  58. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-end_turn.mdx +0 -0
  59. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-events.mdx +0 -0
  60. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-llm.mdx +0 -0
  61. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-orchestrator.mdx +0 -0
  62. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine-streaming.mdx +0 -0
  63. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-engine.mdx +0 -0
  64. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-cast.mdx +0 -0
  65. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-classify.mdx +0 -0
  66. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-extract.mdx +0 -0
  67. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-fn.mdx +0 -0
  68. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-generate.mdx +0 -0
  69. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-plan.mdx +0 -0
  70. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-run.mdx +0 -0
  71. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-say.mdx +0 -0
  72. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns-summarize.mdx +0 -0
  73. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-fns.mdx +0 -0
  74. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-handlers.mdx +0 -0
  75. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-print_handler.mdx +0 -0
  76. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers-queue_handler.mdx +0 -0
  77. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-handlers.mdx +0 -0
  78. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-instructions.mdx +0 -0
  79. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-memory.mdx +0 -0
  80. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-chroma.mdx +0 -0
  81. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-lance.mdx +0 -0
  82. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-postgres.mdx +0 -0
  83. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers-qdrant.mdx +0 -0
  84. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory-providers.mdx +0 -0
  85. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-memory.mdx +0 -0
  86. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-prompts.mdx +0 -0
  87. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-settings.mdx +0 -0
  88. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tasks-task.mdx +0 -0
  89. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tasks.mdx +0 -0
  90. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-thread.mdx +0 -0
  91. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tools-thread.mdx +0 -0
  92. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-tools.mdx +0 -0
  93. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-asyncio.mdx +0 -0
  94. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-jinja.mdx +0 -0
  95. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-jsonschema.mdx +0 -0
  96. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-logging.mdx +0 -0
  97. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-models.mdx +0 -0
  98. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-testing.mdx +0 -0
  99. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-tools.mdx +0 -0
  100. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities-types.mdx +0 -0
  101. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin-utilities.mdx +0 -0
  102. {marvin-3.2.2 → marvin-3.2.4}/docs/api-reference/marvin.mdx +0 -0
  103. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/brand/marvin_mascot.jpeg +0 -0
  104. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/code.css +0 -0
  105. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/ai_model.png +0 -0
  106. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/dont_panic.png +0 -0
  107. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/it_hates_me.png +0 -0
  108. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/life_the_universe_and_ai.png +0 -0
  109. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/pip_install_marvin_hero.png +0 -0
  110. {marvin-3.2.2 → marvin-3.2.4}/docs/assets/img/quotes/sort_of_thing_you_enjoy.png +0 -0
  111. {marvin-3.2.2 → marvin-3.2.4}/docs/community.mdx +0 -0
  112. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/memory.mdx +0 -0
  113. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/tasks.mdx +0 -0
  114. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/teams.mdx +0 -0
  115. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/threads.mdx +0 -0
  116. {marvin-3.2.2 → marvin-3.2.4}/docs/concepts/tools-and-context.mdx +0 -0
  117. {marvin-3.2.2 → marvin-3.2.4}/docs/docs.json +0 -0
  118. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/cast.mdx +0 -0
  119. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/classify.mdx +0 -0
  120. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/extract.mdx +0 -0
  121. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/fn.mdx +0 -0
  122. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/generate.mdx +0 -0
  123. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/plan.mdx +0 -0
  124. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/run.mdx +0 -0
  125. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/say.mdx +0 -0
  126. {marvin-3.2.2 → marvin-3.2.4}/docs/functions/summarize.mdx +0 -0
  127. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/building-a-chatbot.mdx +0 -0
  128. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/cli.mdx +0 -0
  129. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/configuration.mdx +0 -0
  130. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/configure-llms.mdx +0 -0
  131. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/database-migrations.mdx +0 -0
  132. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/mcp.mdx +0 -0
  133. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/migration-guide.mdx +0 -0
  134. {marvin-3.2.2 → marvin-3.2.4}/docs/guides/multi-step-workflow.mdx +0 -0
  135. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/instructions.mdx +0 -0
  136. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/interactivity.mdx +0 -0
  137. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/running-tasks.mdx +0 -0
  138. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/task-results.mdx +0 -0
  139. {marvin-3.2.2 → marvin-3.2.4}/docs/patterns/tools.mdx +0 -0
  140. {marvin-3.2.2 → marvin-3.2.4}/docs/quickstart.mdx +0 -0
  141. {marvin-3.2.2 → marvin-3.2.4}/docs/snippets/version-badge.mdx +0 -0
  142. {marvin-3.2.2 → marvin-3.2.4}/docs/style.css +0 -0
  143. {marvin-3.2.2 → marvin-3.2.4}/docs/welcome.mdx +0 -0
  144. {marvin-3.2.2 → marvin-3.2.4}/examples/agent_mcp.py +0 -0
  145. {marvin-3.2.2 → marvin-3.2.4}/examples/custom_handler.py +0 -0
  146. {marvin-3.2.2 → marvin-3.2.4}/examples/custom_prompt.py +0 -0
  147. {marvin-3.2.2 → marvin-3.2.4}/examples/deepseek_chat.py +0 -0
  148. {marvin-3.2.2 → marvin-3.2.4}/examples/english_to_schema.py +0 -0
  149. {marvin-3.2.2 → marvin-3.2.4}/examples/fastmcp/hello.py +0 -0
  150. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_agent.py +0 -0
  151. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_cast.py +0 -0
  152. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_classify.py +0 -0
  153. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_extract.py +0 -0
  154. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_fn.py +0 -0
  155. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_generate.py +0 -0
  156. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_marvin.py +0 -0
  157. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_run.py +0 -0
  158. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_task.py +0 -0
  159. {marvin-3.2.2 → marvin-3.2.4}/examples/hello_team.py +0 -0
  160. {marvin-3.2.2 → marvin-3.2.4}/examples/pandas/data.csv +0 -0
  161. {marvin-3.2.2 → marvin-3.2.4}/examples/pandas/dataframe.py +0 -0
  162. {marvin-3.2.2 → marvin-3.2.4}/examples/provider_specific/azure/run_agent.py +0 -0
  163. {marvin-3.2.2 → marvin-3.2.4}/examples/provider_specific/openai/run_agent.py +0 -0
  164. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/.dockerignore +0 -0
  165. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/CLAUDE.md +0 -0
  166. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/__init__.py +0 -0
  167. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/__main__.py +0 -0
  168. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/__init__.py +0 -0
  169. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/monkey_patch.py +0 -0
  170. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/templates.py +0 -0
  171. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/thread_status.py +0 -0
  172. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/_internal/tool_tracking.py +0 -0
  173. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/assets.py +0 -0
  174. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/core.py +0 -0
  175. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/__init__.py +0 -0
  176. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/client.py +0 -0
  177. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/discussions.py +0 -0
  178. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/exceptions.py +0 -0
  179. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/github/issues.py +0 -0
  180. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/prompts.py +0 -0
  181. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/py.typed +0 -0
  182. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/search.py +0 -0
  183. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/settings.py +0 -0
  184. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/slack.py +0 -0
  185. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/strings.py +0 -0
  186. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/types.py +0 -0
  187. {marvin-3.2.2 → marvin-3.2.4}/examples/slackbot/src/slackbot/wrap.py +0 -0
  188. {marvin-3.2.2 → marvin-3.2.4}/examples/talk_to_marvin.py +0 -0
  189. {marvin-3.2.2 → marvin-3.2.4}/examples/task_retries.py +0 -0
  190. {marvin-3.2.2 → marvin-3.2.4}/examples/you_have_been_goosed/README.md +0 -0
  191. {marvin-3.2.2 → marvin-3.2.4}/examples/you_have_been_goosed/goose_em.py +0 -0
  192. {marvin-3.2.2 → marvin-3.2.4}/justfile +0 -0
  193. {marvin-3.2.2 → marvin-3.2.4}/migrations/README.md +0 -0
  194. {marvin-3.2.2 → marvin-3.2.4}/migrations/env.py +0 -0
  195. {marvin-3.2.2 → marvin-3.2.4}/migrations/script.py.mako +0 -0
  196. {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250301_141903_initial_schema.py +0 -0
  197. {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250302_123716_improve_message_tracking.py +0 -0
  198. {marvin-3.2.2 → marvin-3.2.4}/migrations/versions/20250302_135101_add_llmcall_message_tracking.py +0 -0
  199. {marvin-3.2.2 → marvin-3.2.4}/scripts/check_for_breakpoints.py +0 -0
  200. {marvin-3.2.2 → marvin-3.2.4}/scripts/create_mintlify_api_ref.py +0 -0
  201. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/__init__.py +0 -0
  202. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/deprecation.py +0 -0
  203. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/README.md +0 -0
  204. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/fastmcp.py +0 -0
  205. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/_internal/integrations/mcp.py +0 -0
  206. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/CLAUDE.md +0 -0
  207. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/__init__.py +0 -0
  208. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/actor.py +0 -0
  209. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/agent.py +0 -0
  210. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/names.py +0 -0
  211. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/agents/team.py +0 -0
  212. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/beta/__init__.py +0 -0
  213. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/__init__.py +0 -0
  214. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/config.py +0 -0
  215. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/dev.py +0 -0
  216. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/main.py +0 -0
  217. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/cli/migrations.py +0 -0
  218. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/defaults.py +0 -0
  219. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/CLAUDE.md +0 -0
  220. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/__init__.py +0 -0
  221. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/end_turn.py +0 -0
  222. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/events.py +0 -0
  223. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/llm.py +0 -0
  224. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/engine/orchestrator.py +0 -0
  225. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/__init__.py +0 -0
  226. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/cast.py +0 -0
  227. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/classify.py +0 -0
  228. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/extract.py +0 -0
  229. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/fn.py +0 -0
  230. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/generate.py +0 -0
  231. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/plan.py +0 -0
  232. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/run.py +0 -0
  233. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/say.py +0 -0
  234. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/fns/summarize.py +0 -0
  235. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/__init__.py +0 -0
  236. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/handlers.py +0 -0
  237. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/print_handler.py +0 -0
  238. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/handlers/queue_handler.py +0 -0
  239. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/instructions.md +0 -0
  240. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/instructions.py +0 -0
  241. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/__init__.py +0 -0
  242. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/memory.py +0 -0
  243. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/__init__.py +0 -0
  244. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/chroma.py +0 -0
  245. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/lance.py +0 -0
  246. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/postgres.py +0 -0
  247. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/memory/providers/qdrant.py +0 -0
  248. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/prompts.py +0 -0
  249. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/py.typed +0 -0
  250. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/settings.py +0 -0
  251. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/CLAUDE.md +0 -0
  252. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/__init__.py +0 -0
  253. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tasks/task.py +0 -0
  254. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/agent.jinja +0 -0
  255. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/memory.jinja +0 -0
  256. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/system.jinja +0 -0
  257. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/task.jinja +0 -0
  258. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/templates/team.jinja +0 -0
  259. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/thread.py +0 -0
  260. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/CLAUDE.md +0 -0
  261. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/__init__.py +0 -0
  262. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/interactive/cli.py +0 -0
  263. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/tools/thread.py +0 -0
  264. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/__init__.py +0 -0
  265. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/asyncio.py +0 -0
  266. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/jinja.py +0 -0
  267. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/jsonschema.py +0 -0
  268. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/logging.py +0 -0
  269. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/models.py +0 -0
  270. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/testing.py +0 -0
  271. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/tools.py +0 -0
  272. {marvin-3.2.2 → marvin-3.2.4}/src/marvin/utilities/types.py +0 -0
  273. {marvin-3.2.2 → marvin-3.2.4}/tests/__init__.py +0 -0
  274. {marvin-3.2.2 → marvin-3.2.4}/tests/agents/test_mcp_integration.py +0 -0
  275. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/README.md +0 -0
  276. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/__init__.py +0 -0
  277. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/__init__.py +0 -0
  278. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_cast.py +0 -0
  279. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_classify.py +0 -0
  280. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_customization.py +0 -0
  281. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_extract.py +0 -0
  282. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_fn.py +0 -0
  283. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_generate.py +0 -0
  284. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_run.py +0 -0
  285. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_say.py +0 -0
  286. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/fns/test_summarize.py +0 -0
  287. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/memory/__init__.py +0 -0
  288. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/memory/test_memory.py +0 -0
  289. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/tasks/__init__.py +0 -0
  290. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/tasks/test_tasks.py +0 -0
  291. {marvin-3.2.2 → marvin-3.2.4}/tests/ai/test_mcp.py +0 -0
  292. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/README.md +0 -0
  293. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/__init__.py +0 -0
  294. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/__init__.py +0 -0
  295. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/test_agent.py +0 -0
  296. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/actors/test_team.py +0 -0
  297. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/conftest.py +0 -0
  298. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/__init__.py +0 -0
  299. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_events.py +0 -0
  300. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_orchestrator.py +0 -0
  301. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_streaming.py +0 -0
  302. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/engine/test_threads.py +0 -0
  303. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/memory/__init__.py +0 -0
  304. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/memory/test_memory.py +0 -0
  305. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/tasks/__init__.py +0 -0
  306. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/tasks/test_tasks.py +0 -0
  307. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/test_instructions.py +0 -0
  308. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/test_run.py +0 -0
  309. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/__init__.py +0 -0
  310. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_jsonschema.py +0 -0
  311. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_tools.py +0 -0
  312. {marvin-3.2.2 → marvin-3.2.4}/tests/basic/utilities/test_types.py +0 -0
  313. {marvin-3.2.2 → marvin-3.2.4}/tests/conftest.py +0 -0
  314. {marvin-3.2.2 → marvin-3.2.4}/tests/data/youre-funny.wav +0 -0
  315. {marvin-3.2.2 → marvin-3.2.4}/tests/settings/test_settings_object.py +0 -0
  316. {marvin-3.2.2 → marvin-3.2.4}/tests/test_concurrent_execution.py +0 -0
@@ -25,3 +25,4 @@ wheels/
25
25
  **/*.db
26
26
  **/*.sqlite
27
27
  sandbox/
28
+ .research_cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marvin
3
- Version: 3.2.2
3
+ Version: 3.2.4
4
4
  Summary: a simple and powerful tool to get things done with AI
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -209,7 +209,7 @@ Requires-Dist: aiosqlite>=0.20.0
209
209
  Requires-Dist: alembic>=1.12.0
210
210
  Requires-Dist: jinja2>=3.1.4
211
211
  Requires-Dist: partial-json-parser>=0.2.1.1.post5
212
- Requires-Dist: pydantic-ai>=0.7.2
212
+ Requires-Dist: pydantic-ai>=1.9.0
213
213
  Requires-Dist: pydantic-settings>=2.6.1
214
214
  Requires-Dist: pydantic[email]>=2.10.6
215
215
  Requires-Dist: rich>=13.9.4
@@ -238,10 +238,10 @@ Marvin provides an intuitive API for defining workflows and delegating work to L
238
238
 
239
239
  ## Installation
240
240
 
241
- Install `marvin`:
241
+ Marvin is available on [PyPI](https://pypi.org/project/marvin/):
242
242
 
243
243
  ```bash
244
- uv pip install marvin
244
+ uv add marvin
245
245
  ```
246
246
 
247
247
  Configure your LLM provider (Marvin uses OpenAI by default but natively supports [all Pydantic AI models](https://ai.pydantic.dev/models/)):
@@ -13,10 +13,10 @@ Marvin provides an intuitive API for defining workflows and delegating work to L
13
13
 
14
14
  ## Installation
15
15
 
16
- Install `marvin`:
16
+ Marvin is available on [PyPI](https://pypi.org/project/marvin/):
17
17
 
18
18
  ```bash
19
- uv pip install marvin
19
+ uv add marvin
20
20
  ```
21
21
 
22
22
  Configure your LLM provider (Marvin uses OpenAI by default but natively supports [all Pydantic AI models](https://ai.pydantic.dev/models/)):
@@ -120,15 +120,16 @@ Custom type for Usage objects that stores them as JSON in the database.
120
120
 
121
121
  ### `create_db_and_tables`
122
122
  ```python
123
- def create_db_and_tables(force: bool = False)
123
+ def create_db_and_tables(force: bool = False, dispose_engine: bool = False)
124
124
  ```
125
- Create all database tables synchronously.
126
-
127
- This is a synchronous alternative to create_db_and_tables() that can be used
128
- in contexts where asyncio.run() cannot be called.
125
+ Create all database tables.
129
126
 
130
127
  Args:
131
128
  force: If True, drops all existing tables before creating new ones.
129
+ dispose_engine: If True, dispose the engine after creating tables.
130
+ This is useful when called from asyncio.run() to ensure the
131
+ aiosqlite worker thread is cleaned up and doesn't prevent
132
+ Python from exiting.
132
133
 
133
134
  ### `ensure_db_tables_exist`
134
135
  ```python
@@ -109,21 +109,6 @@ agent = marvin.Agent(
109
109
  )
110
110
  ```
111
111
 
112
- ### Delegates
113
-
114
- Agents can be configured with a list of other agents that they can delegate work to. This is particularly useful when working with teams of agents:
115
-
116
- ```python
117
- import marvin
118
-
119
- researcher = marvin.Agent(name="Researcher")
120
- writer = marvin.Agent(name="Writer")
121
- editor = marvin.Agent(
122
- name="Editor",
123
- delegates=[researcher, writer]
124
- )
125
- ```
126
-
127
112
  ## Assigning agents to tasks
128
113
 
129
114
  Agents must be assigned to tasks in order to work on them. You can assign agents by passing them to the `agents` parameter when creating a task:
@@ -18,8 +18,6 @@ The easiest way to install Marvin is a package manager like `pip` or [`uv`](http
18
18
  ```bash uv
19
19
  # add marvin to your project
20
20
  uv add marvin
21
- # add marvin to your virtual environment
22
- uv pip install marvin
23
21
  # add marvin to your script
24
22
  uv add --script some_script.py marvin
25
23
  # run a python process with marvin installed ephemerally
@@ -140,7 +140,7 @@ pip install "marvin[chroma]"
140
140
  ```
141
141
 
142
142
  ```bash uv
143
- uv pip install "marvin[chroma]"
143
+ uv add "marvin[chroma]"
144
144
  ```
145
145
  </CodeGroup>
146
146
 
@@ -170,7 +170,7 @@ pip install "marvin[lance]"
170
170
  ```
171
171
 
172
172
  ```bash uv
173
- uv pip install "marvin[lance]"
173
+ uv add "marvin[lance]"
174
174
  ```
175
175
 
176
176
  </CodeGroup>
@@ -198,7 +198,7 @@ pip install "marvin[postgres]"
198
198
  ```
199
199
 
200
200
  ```bash uv
201
- uv pip install "marvin[postgres]"
201
+ uv add "marvin[postgres]"
202
202
  ```
203
203
 
204
204
  </CodeGroup>
@@ -229,7 +229,7 @@ pip install "marvin" "qdrant-client[fastembed]"
229
229
  ```
230
230
 
231
231
  ```bash uv
232
- uv pip install "marvin" "qdrant-client[fastembed]"
232
+ uv add marvin "qdrant-client[fastembed]"
233
233
  ```
234
234
 
235
235
  </CodeGroup>
@@ -259,6 +259,6 @@ pip install "marvin[memory]" # Includes chroma, lance, postgres, qdrant
259
259
  ```
260
260
 
261
261
  ```bash uv
262
- uv pip install "marvin[memory]" # Includes chroma, lance, postgres and qdrant
262
+ uv add "marvin[memory]" # Includes chroma, lance, postgres and qdrant
263
263
  ```
264
264
  </CodeGroup>
@@ -0,0 +1,49 @@
1
+ """
2
+ » AIML_API_KEY=your-api-key \
3
+ uv run examples/provider_specific/aimlapi/run_agent.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from pathlib import Path
10
+
11
+ from pydantic_ai.models.openai import OpenAIModel
12
+ from pydantic_ai.providers.openai import OpenAIProvider
13
+
14
+ import marvin
15
+
16
+ AIML_API_URL = "https://api.aimlapi.com/v1"
17
+
18
+
19
+ def get_provider() -> OpenAIProvider:
20
+ api_key = os.getenv("AIML_API_KEY")
21
+ if not api_key:
22
+ raise RuntimeError(
23
+ "Set AIML_API_KEY environment variable to your AI/ML API key."
24
+ )
25
+ return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
26
+
27
+
28
+ def write_file(path: str, content: str) -> None:
29
+ """Write content to a file."""
30
+ Path(path).write_text(content)
31
+
32
+
33
+ def main() -> None:
34
+ writer = marvin.Agent(
35
+ model=OpenAIModel("gpt-4o", provider=get_provider()),
36
+ name="AI/ML Writer",
37
+ instructions="Write concise, engaging content for developers",
38
+ tools=[write_file],
39
+ )
40
+
41
+ result = marvin.run(
42
+ "how to use pydantic? write haiku to docs.md",
43
+ agents=[writer],
44
+ )
45
+ print(result)
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main()
@@ -0,0 +1,54 @@
1
+ """
2
+ » AIML_API_KEY=your-api-key \
3
+ uv run examples/provider_specific/aimlapi/structured_output.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+
10
+ from pydantic_ai.models.openai import OpenAIModel
11
+ from pydantic_ai.providers.openai import OpenAIProvider
12
+ from typing_extensions import TypedDict
13
+
14
+ import marvin
15
+
16
+ AIML_API_URL = "https://api.aimlapi.com/v1"
17
+
18
+
19
+ class LearningResource(TypedDict):
20
+ title: str
21
+ url: str
22
+ summary: str
23
+
24
+
25
+ def get_provider() -> OpenAIProvider:
26
+ api_key = os.getenv("AIML_API_KEY")
27
+ if not api_key:
28
+ raise RuntimeError(
29
+ "Set AIML_API_KEY environment variable to your AI/ML API key."
30
+ )
31
+ return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
32
+
33
+
34
+ def main() -> None:
35
+ researcher = marvin.Agent(
36
+ model=OpenAIModel("gpt-4o", provider=get_provider()),
37
+ name="Resource Researcher",
38
+ instructions=(
39
+ "Return structured JSON describing useful developer resources for the AI/ML API."
40
+ ),
41
+ )
42
+
43
+ resources = marvin.run(
44
+ "share three quickstart resources for building with the AI/ML API",
45
+ result_type=list[LearningResource],
46
+ agents=[researcher],
47
+ )
48
+
49
+ for resource in resources:
50
+ print(f"- {resource['title']}\n {resource['url']}\n {resource['summary']}\n")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,58 @@
1
+ """
2
+ » AIML_API_KEY=your-api-key \
3
+ uv run examples/provider_specific/aimlapi/tools_agent.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ from datetime import date, timedelta
10
+
11
+ from pydantic_ai.models.openai import OpenAIModel
12
+ from pydantic_ai.providers.openai import OpenAIProvider
13
+
14
+ import marvin
15
+
16
+ AIML_API_URL = "https://api.aimlapi.com/v1"
17
+
18
+
19
+ def get_provider() -> OpenAIProvider:
20
+ api_key = os.getenv("AIML_API_KEY")
21
+ if not api_key:
22
+ raise RuntimeError(
23
+ "Set AIML_API_KEY environment variable to your AI/ML API key."
24
+ )
25
+ return OpenAIProvider(api_key=api_key, base_url=AIML_API_URL)
26
+
27
+
28
+ def get_event_date(offset_days: int = 0) -> str:
29
+ """Return an ISO formatted date offset from today."""
30
+ today = date.today()
31
+ return (today + timedelta(days=offset_days)).isoformat()
32
+
33
+
34
+ def mock_weather_lookup(city: str) -> str:
35
+ """Pretend to look up weather for a city."""
36
+ return f"The forecast in {city} calls for mild temperatures and light winds."
37
+
38
+
39
+ def main() -> None:
40
+ planner = marvin.Agent(
41
+ model=OpenAIModel("gpt-4o", provider=get_provider()),
42
+ name="AI/ML Event Planner",
43
+ instructions=(
44
+ "Plan concise community events for AI/ML API enthusiasts."
45
+ " Use the available tools for dates and weather when helpful."
46
+ ),
47
+ tools=[get_event_date, mock_weather_lookup],
48
+ )
49
+
50
+ plan = marvin.run(
51
+ "Design a Saturday workshop introducing the AI/ML API in Berlin",
52
+ agents=[planner],
53
+ )
54
+ print(plan)
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
@@ -0,0 +1,25 @@
1
+ FROM python:3.13-slim
2
+
3
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
4
+ COPY --from=oven/bun:latest /usr/local/bin/bun /usr/local/bin/bun
5
+
6
+ WORKDIR /app
7
+
8
+ COPY . /app
9
+
10
+ RUN apt-get update && \
11
+ apt-get install -y git build-essential curl && \
12
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
13
+ apt-get install -y nodejs && \
14
+ apt-get clean && \
15
+ rm -rf /var/lib/apt/lists/*
16
+
17
+ RUN uv sync --extra slackbot --no-dev
18
+
19
+ RUN bun install -g @anthropic-ai/claude-code
20
+
21
+ ENV PATH="/root/.bun/bin:${PATH}"
22
+
23
+ EXPOSE 4200
24
+
25
+ CMD ["uv", "run", "--no-sync", "-m", "slackbot"]
@@ -18,12 +18,8 @@ The slackbot can be run locally with minimal setup.
18
18
  ### Local Development
19
19
 
20
20
  ```console
21
- # Create and activate a virtual environment with uv
22
- uv venv --python 3.12
23
- source .venv/bin/activate
24
-
25
- # Install dependencies
26
- uv pip install -e ".[slackbot]" -U
21
+ # Install dependencies (from the repo root)
22
+ uv sync --extra slackbot
27
23
  ```
28
24
 
29
25
  ### Configuration
@@ -41,6 +41,7 @@ dependencies = [
41
41
  "numpy",
42
42
  "raggy[tpuf] @ git+https://github.com/zzstoatzz/raggy.git",
43
43
  "pretty-mod",
44
+ "claude-agent-sdk",
44
45
  ]
45
46
 
46
47
 
@@ -0,0 +1 @@
1
+ WORKSPACE_TO_CHANNEL_ID: dict[str, str] = {}
@@ -279,12 +279,7 @@ async def handle_message(payload: SlackPayload, db: Database):
279
279
  channel_id=event.channel,
280
280
  thread_ts=thread_ts,
281
281
  )
282
- # Still return completed so we don't retry
283
- return Completed(
284
- message="Error during agent execution",
285
- name="ERROR_HANDLED",
286
- data=dict(error=str(e), user_context=user_context),
287
- )
282
+ raise
288
283
  finally:
289
284
  try:
290
285
  await mark_thread_completed(db, message_ts)
@@ -18,7 +18,7 @@ class GitHubLabel(BaseModel):
18
18
 
19
19
  name: str = Field(default="")
20
20
  color: str = Field(default="")
21
- description: str = Field(default="")
21
+ description: str | None = Field(default="")
22
22
 
23
23
 
24
24
  class GitHubComment(BaseModel):
@@ -0,0 +1,127 @@
1
+ """Research agent for improved information gathering.
2
+
3
+ Now uses Claude Agent SDK for direct code inspection instead of relying on
4
+ documentation search alone. This prevents hallucination by allowing the agent
5
+ to actually read Prefect source code.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
11
+ from claude_agent_sdk.types import AssistantMessage, TextBlock
12
+ from prefect import task
13
+ from prefect.cache_policies import INPUTS
14
+
15
+
16
+ async def research_topic_with_code_access(question: str, version: str = "3.x") -> str:
17
+ """
18
+ Research a Prefect topic using Claude Agent SDK with direct source code access.
19
+
20
+ The agent will clone the Prefect repo to .research_cache/prefect if it doesn't exist,
21
+ or update it if it does. This works consistently in both local and Docker environments.
22
+
23
+ Args:
24
+ question: The research question
25
+ version: Prefect version ("2.x" or "3.x")
26
+
27
+ Returns:
28
+ Research findings as a formatted string
29
+ """
30
+ # Use current working directory for cache
31
+ # Locally: <wherever you run from>/.research_cache/prefect
32
+ # Docker: /app/.research_cache/prefect (since WORKDIR is /app)
33
+ cache_dir = Path.cwd() / ".research_cache"
34
+ prefect_repo = cache_dir / "prefect"
35
+
36
+ version_context = "Prefect 3.x" if version.startswith("3") else "Prefect 2.x"
37
+ branch = "main" if version.startswith("3") else "2.x"
38
+
39
+ system_prompt = f"""You are a specialized research agent for {version_context}.
40
+ Your job is to thoroughly research topics by reading the actual Prefect source code, documentation, and community discussions.
41
+
42
+ IMPORTANT: Before researching, ensure you have the Prefect source code:
43
+ 1. If {prefect_repo} doesn't exist: clone it with `git clone https://github.com/PrefectHQ/prefect.git {prefect_repo}`
44
+ 2. If it already exists: update it with `cd {prefect_repo} && git checkout {branch} && git pull`
45
+ 3. Then search/read both:
46
+ - Source code in {prefect_repo}/src/prefect/
47
+ - Documentation in {prefect_repo}/docs/
48
+
49
+ DOCUMENTATION:
50
+ - Read docs from {prefect_repo}/docs/v3/ (or {prefect_repo}/docs/ on 2.x branch)
51
+ - Use {prefect_repo}/docs/docs.json to map file paths to published URLs at https://docs.prefect.io/
52
+ - Verify all doc links with `curl -I <url>` before providing them - never hallucinate URLs
53
+
54
+ If cloning fails (e.g., disk space), fall back to searching the installed package in your current environment.
55
+
56
+ ADDITIONAL RESEARCH TOOLS:
57
+ - Use `gh` CLI to search GitHub discussions and issues for community-verified solutions
58
+ - Example: `gh search issues --repo PrefectHQ/prefect "custom state event"`
59
+ - This helps find real-world patterns and edge cases that users have discovered
60
+
61
+ Use your tools to search and read the actual implementation - do not make assumptions.
62
+ Be thorough - verify everything by reading the source.
63
+
64
+ CRITICAL VERSION-SPECIFIC RULES:
65
+ - **DEFAULT TO {version_context}**: Do NOT suggest deprecated patterns
66
+ - **NEVER** suggest `Deployment.build_from_flow()` for Prefect 3.x - it's COMPLETELY REMOVED
67
+ - **NEVER** suggest `prefect deployment build` CLI command for 3.x - use `prefect deploy` instead
68
+ - The correct deployment pattern in 3.x is: `flow.from_source(...).deploy(...)`
69
+ - Default to Prefect 3.x patterns unless user explicitly states they're using 2.x
70
+ - If user is on 2.x, suggest upgrading to 3.x or using workers instead of deprecated patterns
71
+
72
+ Remember: You are the research specialist. The main agent relies on you for accurate, comprehensive information.
73
+ Be thorough - use tools repeatedly until you have complete information.
74
+ Do not use any Prefect syntax you have not verified by reading the actual source code."""
75
+
76
+ options = ClaudeAgentOptions(
77
+ allowed_tools=["Read", "Grep", "Glob", "Bash"],
78
+ cwd=str(Path.cwd()),
79
+ model="claude-haiku-4-5-20251001",
80
+ system_prompt=system_prompt,
81
+ )
82
+
83
+ research_output = []
84
+
85
+ async with ClaudeSDKClient(options=options) as client:
86
+ await client.query(question)
87
+
88
+ async for message in client.receive_response():
89
+ if isinstance(message, AssistantMessage):
90
+ for block in message.content:
91
+ if isinstance(block, TextBlock):
92
+ research_output.append(block.text)
93
+
94
+ return "\n".join(research_output)
95
+
96
+
97
+ def research_prefect_topic(question: str, topic: str, version: str = "3.x") -> str:
98
+ """
99
+ Thoroughly research a Prefect topic using Claude Agent SDK with source code access.
100
+
101
+ This tool uses Claude Agent SDK to give the research agent direct access to:
102
+ - Actual Prefect source code via Read tool
103
+ - Code search via Grep/Bash (rg)
104
+ - File discovery via Glob
105
+ - Shell commands for exploration
106
+
107
+ This eliminates hallucination by allowing the agent to verify everything
108
+ against the actual implementation.
109
+
110
+ Args:
111
+ question: The specific question or topic to research
112
+ topic: A short display name for the topic based on the question (for bookkeeping)
113
+ version: Prefect version ("2.x" or "3.x")
114
+ """
115
+ try:
116
+ result = (
117
+ task(task_run_name=f"Researching {topic}", cache_policy=INPUTS)(
118
+ research_topic_with_code_access
119
+ )
120
+ .submit(question, version)
121
+ .result()
122
+ )
123
+
124
+ return f"**Research Findings (Code-Verified)**\n\n{result}"
125
+
126
+ except Exception as e:
127
+ return f"Research failed: {str(e)}. The agent may not have access to the source code."
@@ -2,9 +2,9 @@ import json
2
2
  import re
3
3
  from typing import Any, TypedDict
4
4
 
5
- from atproto import Client
5
+ from atproto import Client, models
6
6
  from atproto.exceptions import BadRequestError
7
- from atproto_client.models.app.bsky.feed.defs import ThreadViewPost
7
+ from pydantic import field_validator
8
8
  from pydantic_ai import ImageUrl
9
9
  from pydantic_settings import BaseSettings, SettingsConfigDict
10
10
 
@@ -21,6 +21,15 @@ class Settings(BaseSettings):
21
21
 
22
22
  bsky_handle: str
23
23
  bsky_password: str
24
+ atproto_pds_url: str = "https://bsky.social"
25
+
26
+ @field_validator("atproto_pds_url")
27
+ @classmethod
28
+ def ensure_url_protocol(cls, v: str) -> str:
29
+ """Ensure the PDS URL has a protocol"""
30
+ if not v.startswith(("http://", "https://")):
31
+ return f"https://{v}"
32
+ return v
24
33
 
25
34
 
26
35
  class Snapshot(TypedDict):
@@ -48,7 +57,7 @@ def extract_post_id(bluesky_url: str) -> tuple[str, str]:
48
57
  return match.group(1), match.group(2)
49
58
 
50
59
 
51
- def build_context(thread: ThreadViewPost) -> dict[str, Any]:
60
+ def build_context(thread: models.AppBskyFeedDefs.ThreadViewPost) -> dict[str, Any]:
52
61
  context: dict[str, Any] = {}
53
62
  if thread and thread.post:
54
63
  context["bsky post"] = {
@@ -65,7 +74,7 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
65
74
  image_description_result = visual_extraction_agent.run(
66
75
  [
67
76
  "summarize this image concisely, include direct quotes from the image",
68
- ImageUrl(url=img.fullsize),
77
+ ImageUrl(url=img.fullsize, media_type="image/jpeg"),
69
78
  ]
70
79
  )
71
80
  context["bsky post"]["embed"] = image_description_result
@@ -80,7 +89,10 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
80
89
  "embed": visual_extraction_agent.run(
81
90
  "summarize this image concisely, include direct quotes from the image",
82
91
  attachments=[
83
- ImageUrl(url=reply_post.embed.images[0].fullsize),
92
+ ImageUrl(
93
+ url=reply_post.embed.images[0].fullsize,
94
+ media_type="image/jpeg",
95
+ ),
84
96
  ],
85
97
  )
86
98
  }
@@ -99,7 +111,7 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
99
111
  def explain_bsky_post(
100
112
  bsky_post_url: str, details: dict[str, Any] | None = None
101
113
  ) -> None:
102
- client = Client()
114
+ client = Client(base_url=settings.atproto_pds_url)
103
115
  client.login(settings.bsky_handle, settings.bsky_password)
104
116
 
105
117
  try:
@@ -111,7 +123,7 @@ def explain_bsky_post(
111
123
  logger.error(f"Error fetching thread: {e}")
112
124
  return
113
125
 
114
- assert isinstance(thread, ThreadViewPost)
126
+ assert isinstance(thread, models.AppBskyFeedDefs.ThreadViewPost)
115
127
  context = build_context(thread)
116
128
 
117
129
  if details:
@@ -10,7 +10,7 @@ dependencies = [
10
10
  "alembic>=1.12.0",
11
11
  "jinja2>=3.1.4",
12
12
  "partial-json-parser>=0.2.1.1.post5",
13
- "pydantic-ai>=0.7.2",
13
+ "pydantic-ai>=1.9.0",
14
14
  "pydantic-settings>=2.6.1",
15
15
  "pydantic[email]>=2.10.6",
16
16
  "rich>=13.9.4",
@@ -416,14 +416,17 @@ def _run_migrations(alembic_log_level: str = "WARNING") -> bool:
416
416
  return False
417
417
 
418
418
 
419
- async def create_db_and_tables(*, force: bool = False) -> None:
420
- """Create all database tables synchronously.
421
-
422
- This is a synchronous alternative to create_db_and_tables() that can be used
423
- in contexts where asyncio.run() cannot be called.
419
+ async def create_db_and_tables(
420
+ *, force: bool = False, dispose_engine: bool = False
421
+ ) -> None:
422
+ """Create all database tables.
424
423
 
425
424
  Args:
426
425
  force: If True, drops all existing tables before creating new ones.
426
+ dispose_engine: If True, dispose the engine after creating tables.
427
+ This is useful when called from asyncio.run() to ensure the
428
+ aiosqlite worker thread is cleaned up and doesn't prevent
429
+ Python from exiting.
427
430
  """
428
431
  engine = get_async_engine()
429
432
 
@@ -435,6 +438,15 @@ async def create_db_and_tables(*, force: bool = False) -> None:
435
438
  await conn.run_sync(Base.metadata.create_all)
436
439
  logger.debug("Database tables created.")
437
440
 
441
+ if dispose_engine:
442
+ await engine.dispose()
443
+ # Remove the engine from the cache since it's disposed
444
+ try:
445
+ loop = asyncio.get_running_loop()
446
+ except RuntimeError:
447
+ loop = None
448
+ _async_engine_cache.pop(loop, None)
449
+
438
450
 
439
451
  def init_database_if_necessary():
440
452
  """Initialize the database file if necessary.
@@ -543,7 +555,10 @@ def ensure_db_tables_exist():
543
555
  # Create tables synchronously using asyncio.run() if we're not in an async context
544
556
  try:
545
557
  with _create_tables_lock:
546
- asyncio.run(create_db_and_tables())
558
+ # Use dispose_engine=True to clean up the aiosqlite worker thread
559
+ # after table creation. Without this, the cached engine's worker
560
+ # thread could prevent Python from exiting cleanly.
561
+ asyncio.run(create_db_and_tables(dispose_engine=True))
547
562
  logger.debug("Database tables created successfully.")
548
563
  except Exception as e:
549
564
  logger.error(f"Failed to create database tables: {e}")
@@ -10,6 +10,7 @@ from pydantic_ai.messages import (
10
10
  FunctionToolResultEvent,
11
11
  ModelResponsePart,
12
12
  PartDeltaEvent,
13
+ PartEndEvent,
13
14
  PartStartEvent,
14
15
  RetryPromptPart,
15
16
  TextPartDelta,
@@ -236,6 +237,13 @@ def _process_pydantic_event(
236
237
  tool_call_id=snapshot.tool_call_id if snapshot else None,
237
238
  tool=tools_map.get(event.part.tool_name),
238
239
  )
240
+ # Handle Part End Events
241
+ elif isinstance(event, PartEndEvent):
242
+ # Part end events signal that a part is complete. We don't need to
243
+ # emit a Marvin event for this since we handle completion via other
244
+ # events (FunctionToolCallEvent, FinalResultEvent, etc.)
245
+ return None
246
+
239
247
  # Handle Part Delta Events
240
248
  elif isinstance(event, PartDeltaEvent):
241
249
  # Process a delta update to an existing part