voodoo-framework 1.19.0__tar.gz → 2.0.0__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 (214) hide show
  1. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/PKG-INFO +1 -1
  2. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/__init__.py +15 -1
  3. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/adapters/voodoo_css.py +48 -0
  4. voodoo_framework-2.0.0/src/voodoo/agents/__init__.py +33 -0
  5. voodoo_framework-2.0.0/src/voodoo/agents/models.py +166 -0
  6. voodoo_framework-2.0.0/src/voodoo/agents/registry.py +384 -0
  7. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/agent.py +168 -7
  8. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/openai.py +34 -7
  9. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/__init__.py +2 -0
  10. voodoo_framework-2.0.0/src/voodoo/cli/agents.py +143 -0
  11. voodoo_framework-2.0.0/src/voodoo/memory/__init__.py +32 -0
  12. voodoo_framework-2.0.0/src/voodoo/memory/interfaces.py +303 -0
  13. voodoo_framework-2.0.0/src/voodoo/memory/sqlite.py +368 -0
  14. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/PKG-INFO +1 -1
  15. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/SOURCES.txt +9 -0
  16. voodoo_framework-2.0.0/tests/test_agent_registry.py +391 -0
  17. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_contract_api.py +14 -3
  18. voodoo_framework-2.0.0/tests/test_memory.py +415 -0
  19. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/LICENSE +0 -0
  20. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/README.md +0 -0
  21. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/pyproject.toml +0 -0
  22. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/setup.cfg +0 -0
  23. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/adapters/__init__.py +0 -0
  24. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/adapters/capabilities.py +0 -0
  25. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/adapters/registry.py +0 -0
  26. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/adapters/tailwind.py +0 -0
  27. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/agent.py +0 -0
  28. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/__init__.py +0 -0
  29. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/__init__.py +0 -0
  30. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/anthropic.py +0 -0
  31. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/gemini.py +0 -0
  32. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/mock.py +0 -0
  33. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/providers/ollama.py +0 -0
  34. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/tools/__init__.py +0 -0
  35. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ai/tools/registry.py +0 -0
  36. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/api.py +0 -0
  37. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/__init__.py +0 -0
  38. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/cookies.py +0 -0
  39. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/guards.py +0 -0
  40. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/jwt.py +0 -0
  41. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/middleware.py +0 -0
  42. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/passwords.py +0 -0
  43. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/auth/user.py +0 -0
  44. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/__main__.py +0 -0
  45. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/ai.py +0 -0
  46. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/auth.py +0 -0
  47. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/dev.py +0 -0
  48. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/doctor.py +0 -0
  49. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/executions.py +0 -0
  50. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/generate.py +0 -0
  51. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/inspect.py +0 -0
  52. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/new.py +0 -0
  53. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/objects.py +0 -0
  54. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/recover.py +0 -0
  55. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/routes.py +0 -0
  56. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/scaffolding.py +0 -0
  57. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/schedules.py +0 -0
  58. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/tasks.py +0 -0
  59. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/terminal.py +0 -0
  60. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/theme.py +0 -0
  61. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/cli/version.py +0 -0
  62. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/components.py +0 -0
  63. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/config.py +0 -0
  64. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/__init__.py +0 -0
  65. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/app.py +0 -0
  66. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/errors.py +0 -0
  67. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/events.py +0 -0
  68. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/render.py +0 -0
  69. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/routing.py +0 -0
  70. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/sitemap.py +0 -0
  71. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/core/state.py +0 -0
  72. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/data/__init__.py +0 -0
  73. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/data/base.py +0 -0
  74. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/data/model.py +0 -0
  75. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/i18n.py +0 -0
  76. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/mcp/__init__.py +0 -0
  77. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/mcp/client.py +0 -0
  78. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/mesh/__init__.py +0 -0
  79. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/mesh/client.py +0 -0
  80. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/mesh/event_bus.py +0 -0
  81. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/__init__.py +0 -0
  82. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/capability.py +0 -0
  83. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/compute.py +0 -0
  84. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/constraint.py +0 -0
  85. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/effect.py +0 -0
  86. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/intent.py +0 -0
  87. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/resource.py +0 -0
  88. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/state.py +0 -0
  89. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/primitives/time.py +0 -0
  90. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/py.typed +0 -0
  91. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/queue.py +0 -0
  92. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/routing/api.py +0 -0
  93. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/routing/pages.py +0 -0
  94. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/__init__.py +0 -0
  95. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/adaptive.py +0 -0
  96. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/capability.py +0 -0
  97. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/constraint.py +0 -0
  98. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/context.py +0 -0
  99. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/engine.py +0 -0
  100. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/errors.py +0 -0
  101. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/execution.py +0 -0
  102. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/graph.py +0 -0
  103. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/human.py +0 -0
  104. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/persistence.py +0 -0
  105. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/planner.py +0 -0
  106. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/scheduler.py +0 -0
  107. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/task.py +0 -0
  108. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/runtime/workflow.py +0 -0
  109. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/schedule.py +0 -0
  110. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/__init__.py +0 -0
  111. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/cors.py +0 -0
  112. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/csrf.py +0 -0
  113. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/headers.py +0 -0
  114. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/passwords.py +0 -0
  115. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/security/rate_limit.py +0 -0
  116. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/seo.py +0 -0
  117. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/static/client.js +0 -0
  118. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/status.py +0 -0
  119. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/__init__.py +0 -0
  120. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/cache/__init__.py +0 -0
  121. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/cache/interfaces.py +0 -0
  122. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/cache/memory.py +0 -0
  123. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/cache/redis.py +0 -0
  124. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/database/__init__.py +0 -0
  125. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/database/interfaces.py +0 -0
  126. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/database/postgres.py +0 -0
  127. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/database/sqlite.py +0 -0
  128. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/events/__init__.py +0 -0
  129. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/events/interfaces.py +0 -0
  130. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/events/local.py +0 -0
  131. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/events/postgres.py +0 -0
  132. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/events/sqlite.py +0 -0
  133. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/execution/__init__.py +0 -0
  134. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/execution/migrations.py +0 -0
  135. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/execution/postgres.py +0 -0
  136. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/execution/sqlite.py +0 -0
  137. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/manager.py +0 -0
  138. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/objects/__init__.py +0 -0
  139. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/objects/interfaces.py +0 -0
  140. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/objects/local.py +0 -0
  141. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/objects/s3.py +0 -0
  142. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/__init__.py +0 -0
  143. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/interfaces.py +0 -0
  144. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/memory.py +0 -0
  145. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/postgres.py +0 -0
  146. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/redis.py +0 -0
  147. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/queue/sqlite.py +0 -0
  148. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
  149. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
  150. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/telemetry/__init__.py +0 -0
  151. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/telemetry/api.py +0 -0
  152. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/telemetry/middleware.py +0 -0
  153. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/telemetry/store.py +0 -0
  154. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/theme.py +0 -0
  155. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/tools/__init__.py +0 -0
  156. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/tools/registry.py +0 -0
  157. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/__init__.py +0 -0
  158. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/component.py +0 -0
  159. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/events.py +0 -0
  160. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/library.py +0 -0
  161. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/markdown.py +0 -0
  162. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/rendering.py +0 -0
  163. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/state.py +0 -0
  164. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/styles/__init__.py +0 -0
  165. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/styles/presets.py +0 -0
  166. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/ui/styles/theme.py +0 -0
  167. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/workers/__init__.py +0 -0
  168. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo/workers/queue.py +0 -0
  169. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
  170. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
  171. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/requires.txt +0 -0
  172. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
  173. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_agent.py +0 -0
  174. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_agent_runtime.py +0 -0
  175. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_app.py +0 -0
  176. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_auth.py +0 -0
  177. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_checkpoint_resume.py +0 -0
  178. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_chrome.py +0 -0
  179. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_cli.py +0 -0
  180. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_cli_inspect.py +0 -0
  181. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_components.py +0 -0
  182. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_config.py +0 -0
  183. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_data.py +0 -0
  184. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_design_system.py +0 -0
  185. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_events.py +0 -0
  186. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_execution_sqlite_store.py +0 -0
  187. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_http_runtime.py +0 -0
  188. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_human.py +0 -0
  189. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_i18n.py +0 -0
  190. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_integration.py +0 -0
  191. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_mcp.py +0 -0
  192. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_mcp_runtime.py +0 -0
  193. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_mesh.py +0 -0
  194. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_mesh_event_bus.py +0 -0
  195. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_model.py +0 -0
  196. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_object_store.py +0 -0
  197. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_persistence.py +0 -0
  198. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_planner_adaptive.py +0 -0
  199. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_primitives.py +0 -0
  200. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_provider_switching.py +0 -0
  201. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_providers.py +0 -0
  202. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_queue.py +0 -0
  203. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_reactive.py +0 -0
  204. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_runtime.py +0 -0
  205. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_scheduler.py +0 -0
  206. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_security.py +0 -0
  207. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_seo.py +0 -0
  208. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_state.py +0 -0
  209. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_theme.py +0 -0
  210. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_theme_presets.py +0 -0
  211. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_tools.py +0 -0
  212. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_ui.py +0 -0
  213. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_websocket.py +0 -0
  214. {voodoo_framework-1.19.0 → voodoo_framework-2.0.0}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voodoo-framework
3
- Version: 1.19.0
3
+ Version: 2.0.0
4
4
  Summary: The programmable runtime for adaptive applications and operational systems — web, APIs, agents, workers, human workflows, distributed systems, and physical systems in one runtime.
5
5
  Author-email: Helder Perez <contact@helderperez.com>
6
6
  Maintainer-email: Helder Perez <contact@helderperez.com>
@@ -20,12 +20,14 @@ import warnings
20
20
  from typing import Any
21
21
 
22
22
  from .adapters import TailwindAdapter, VoodooCSSAdapter
23
+ from .agents import AgentEntity, AgentRegistry, AgentRunRecord, SQLiteAgentRegistry
23
24
  from .ai import LLMProvider, ModelDescriptor, VoodooModelProvider
24
25
  from .ai.agent import Agent, AgentRun
25
26
  from .ai.tools import ToolRegistry, ToolSpec, tool
26
27
  from .config import config
27
28
  from .core import App, create_app, event, page, register_event, state, ws_manager
28
29
  from .data import FK, BaseModel, Model
30
+ from .memory import MemoryEntry, MemoryLayer, MemoryStore, SQLiteMemoryStore
29
31
  from .mesh import mesh
30
32
  from .routing.api import api
31
33
  from .telemetry import trace
@@ -59,6 +61,7 @@ from .ui import (
59
61
  Header,
60
62
  Heading,
61
63
  Hero,
64
+ Html,
62
65
  Icon,
63
66
  Input,
64
67
  Label,
@@ -99,7 +102,7 @@ from .ui.styles import (
99
102
  from .ui.styles.theme import Theme, ThemeColors, create_theme
100
103
  from .workers import task
101
104
 
102
- __version__ = "1.19.0"
105
+ __version__ = "2.0.0"
103
106
 
104
107
  __all__ = [
105
108
  # Core runtime
@@ -127,6 +130,16 @@ __all__ = [
127
130
  "LLMProvider",
128
131
  "VoodooModelProvider",
129
132
  "ModelDescriptor",
133
+ # Memory (Sprint 16)
134
+ "MemoryEntry",
135
+ "MemoryLayer",
136
+ "MemoryStore",
137
+ "SQLiteMemoryStore",
138
+ # Agent registry (Sprint 17)
139
+ "AgentEntity",
140
+ "AgentRegistry",
141
+ "AgentRunRecord",
142
+ "SQLiteAgentRegistry",
130
143
  # Data
131
144
  "BaseModel",
132
145
  "Model",
@@ -167,6 +180,7 @@ __all__ = [
167
180
  # UI — icons & markdown
168
181
  "Icon",
169
182
  "Markdown",
183
+ "Html",
170
184
  # UI — chat primitives
171
185
  "MessageList",
172
186
  "ChatMessage",
@@ -486,6 +486,54 @@ button, input, select, textarea {{ font: inherit; color: inherit; }}
486
486
  color: var(--vd-color-text-muted); padding: 0 {s}xs);
487
487
  }}
488
488
 
489
+ /* App shell — full-viewport sidebar + main layout (chat apps) */
490
+ .vd-app-shell {{
491
+ display: flex; align-items: stretch;
492
+ width: 100%; height: 100dvh; overflow: hidden;
493
+ }}
494
+ .vd-app-shell > .vd-sidebar {{
495
+ height: 100dvh; border-right: 1px solid var(--vd-color-border);
496
+ }}
497
+ .vd-chat-main {{
498
+ flex: 1; min-width: 0; display: flex; flex-direction: column;
499
+ height: 100dvh; overflow: hidden;
500
+ padding: {s}lg); gap: {s}md);
501
+ }}
502
+ .vd-chat-scroll {{
503
+ flex: 1; min-height: 0; overflow-y: auto;
504
+ display: flex; flex-direction: column;
505
+ border: 1px solid var(--vd-color-border);
506
+ border-radius: {r}lg); background: var(--vd-color-surface);
507
+ padding: {s}md);
508
+ }}
509
+ .vd-chat-row {{
510
+ width: 100%; align-items: center;
511
+ }}
512
+ .vd-chat-row > .vd-button:first-child {{
513
+ flex: 1; justify-content: flex-start; text-align: left;
514
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
515
+ background: transparent; color: var(--vd-color-text);
516
+ border: none; font-weight: var(--vd-weight-normal);
517
+ }}
518
+ .vd-chat-row > .vd-button:first-child:hover {{
519
+ background: var(--vd-color-surface); color: var(--vd-color-primary);
520
+ }}
521
+ .vd-chat-row > .vd-button:last-child {{
522
+ flex-shrink: 0; background: transparent; border: none;
523
+ color: var(--vd-color-text-muted); padding: {s}xs);
524
+ }}
525
+ .vd-chat-row > .vd-button:last-child:hover {{ color: var(--vd-color-danger); }}
526
+ .vd-suggestions {{
527
+ display: flex; flex-wrap: wrap; gap: {s}sm);
528
+ }}
529
+ .vd-suggestions > .vd-button {{
530
+ background: var(--vd-color-surface); border: 1px solid var(--vd-color-border);
531
+ color: var(--vd-color-text); font-weight: var(--vd-weight-normal);
532
+ }}
533
+ .vd-suggestions > .vd-button:hover {{
534
+ border-color: var(--vd-color-primary); color: var(--vd-color-primary);
535
+ }}
536
+
489
537
  /* Markdown rendered inside components */
490
538
  .vd-markdown p {{ margin: 0 0 {s}sm); }}
491
539
  .vd-markdown p:last-child {{ margin-bottom: 0; }}
@@ -0,0 +1,33 @@
1
+ """Agents — durable entity registry (Sprint 17).
2
+
3
+ Agents are compute participants with stable identity, capabilities, state,
4
+ and queryable execution history. The registry persists agent metadata and
5
+ links every run to its execution record.
6
+
7
+ from voodoo import AgentEntity, AgentRegistry, SQLiteAgentRegistry
8
+
9
+ registry = SQLiteAgentRegistry("data/agents.db")
10
+ entity = AgentEntity(
11
+ agent_id="lead-scorer",
12
+ name="Lead Scorer",
13
+ model="openai:gpt-4o",
14
+ capabilities=["scoring.read"],
15
+ )
16
+ await registry.register(entity)
17
+ agent = Agent(model="openai:gpt-4o", agent_id="lead-scorer", registry=registry)
18
+ """
19
+
20
+ from voodoo.agents.models import AgentEntity, AgentRunRecord
21
+ from voodoo.agents.registry import (
22
+ AgentRegistry,
23
+ InMemoryAgentRegistry,
24
+ SQLiteAgentRegistry,
25
+ )
26
+
27
+ __all__ = [
28
+ "AgentEntity",
29
+ "AgentRegistry",
30
+ "AgentRunRecord",
31
+ "InMemoryAgentRegistry",
32
+ "SQLiteAgentRegistry",
33
+ ]
@@ -0,0 +1,166 @@
1
+ """Agent entity and run-record data models (Sprint 17).
2
+
3
+ ``AgentEntity`` is the durable identity of an agent — name, model policy,
4
+ tools, capabilities, permissions, configuration, and current state.
5
+ ``AgentRunRecord`` links a single agent run to its execution record.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ import uuid
12
+ from dataclasses import dataclass, field
13
+ from typing import Any
14
+
15
+ __all__ = ["AgentEntity", "AgentRunRecord"]
16
+
17
+
18
+ @dataclass
19
+ class AgentEntity:
20
+ """Durable agent identity.
21
+
22
+ Parameters
23
+ ----------
24
+ agent_id:
25
+ Unique identifier. Auto-generated UUID if not provided.
26
+ name:
27
+ Human-readable display name.
28
+ description:
29
+ What this agent does.
30
+ model:
31
+ ``"provider:model"`` string (e.g. ``"openai:gpt-4o"``).
32
+ system_prompt:
33
+ Default system prompt for all runs.
34
+ capabilities:
35
+ Capability names granted to this agent.
36
+ tools:
37
+ Tool names available to this agent.
38
+ permissions:
39
+ Permission strings required for sensitive tools.
40
+ config:
41
+ Arbitrary configuration dict (temperature, max_tokens, etc.).
42
+ state:
43
+ Current lifecycle state (``"active"``, ``"paused"``, ``"archived"``).
44
+ metadata:
45
+ Arbitrary key-value metadata.
46
+ created_at:
47
+ ISO timestamp of creation.
48
+ updated_at:
49
+ ISO timestamp of last update.
50
+ """
51
+
52
+ agent_id: str = field(default_factory=lambda: str(uuid.uuid4()))
53
+ name: str = ""
54
+ description: str = ""
55
+ model: str = ""
56
+ system_prompt: str | None = None
57
+ capabilities: list[str] = field(default_factory=list)
58
+ tools: list[str] = field(default_factory=list)
59
+ permissions: list[str] = field(default_factory=list)
60
+ config: dict[str, Any] = field(default_factory=dict)
61
+ state: str = "active"
62
+ metadata: dict[str, Any] = field(default_factory=dict)
63
+ created_at: str = field(default_factory=lambda: _iso_now())
64
+ updated_at: str = field(default_factory=lambda: _iso_now())
65
+
66
+ def to_dict(self) -> dict[str, Any]:
67
+ """Serialize to a plain dict (JSON-safe)."""
68
+ return {
69
+ "agent_id": self.agent_id,
70
+ "name": self.name,
71
+ "description": self.description,
72
+ "model": self.model,
73
+ "system_prompt": self.system_prompt,
74
+ "capabilities": self.capabilities,
75
+ "tools": self.tools,
76
+ "permissions": self.permissions,
77
+ "config": self.config,
78
+ "state": self.state,
79
+ "metadata": self.metadata,
80
+ "created_at": self.created_at,
81
+ "updated_at": self.updated_at,
82
+ }
83
+
84
+ @classmethod
85
+ def from_dict(cls, data: dict[str, Any]) -> AgentEntity:
86
+ """Deserialize from a plain dict."""
87
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
88
+
89
+
90
+ @dataclass
91
+ class AgentRunRecord:
92
+ """Links an agent run to its execution.
93
+
94
+ Parameters
95
+ ----------
96
+ run_id:
97
+ Unique run identifier (matches ``AgentRun.run_id``).
98
+ agent_id:
99
+ The agent that performed the run.
100
+ execution_id:
101
+ The execution record ID (from the execution store).
102
+ prompt:
103
+ The input prompt.
104
+ output:
105
+ The final output.
106
+ status:
107
+ Run outcome (``"completed"``, ``"error"``, ``"failed"``).
108
+ tokens_in:
109
+ Input token count.
110
+ tokens_out:
111
+ Output token count.
112
+ cost:
113
+ Estimated cost in USD.
114
+ tool_calls:
115
+ Tool call history for this run.
116
+ started_at:
117
+ Unix timestamp when the run started.
118
+ completed_at:
119
+ Unix timestamp when the run completed.
120
+ trace_id:
121
+ Correlation ID from telemetry.
122
+ """
123
+
124
+ run_id: str = field(default_factory=lambda: str(uuid.uuid4()))
125
+ agent_id: str = ""
126
+ execution_id: str | None = None
127
+ prompt: str = ""
128
+ output: str = ""
129
+ status: str = "completed"
130
+ tokens_in: int = 0
131
+ tokens_out: int = 0
132
+ cost: float = 0.0
133
+ tool_calls: list[dict[str, Any]] = field(default_factory=list)
134
+ started_at: float = field(default_factory=time.time)
135
+ completed_at: float | None = None
136
+ trace_id: str | None = None
137
+
138
+ def to_dict(self) -> dict[str, Any]:
139
+ """Serialize to a plain dict (JSON-safe)."""
140
+ return {
141
+ "run_id": self.run_id,
142
+ "agent_id": self.agent_id,
143
+ "execution_id": self.execution_id,
144
+ "prompt": self.prompt[:500],
145
+ "output": self.output[:500],
146
+ "status": self.status,
147
+ "tokens_in": self.tokens_in,
148
+ "tokens_out": self.tokens_out,
149
+ "cost": self.cost,
150
+ "tool_calls": self.tool_calls,
151
+ "started_at": self.started_at,
152
+ "completed_at": self.completed_at,
153
+ "trace_id": self.trace_id,
154
+ }
155
+
156
+ @classmethod
157
+ def from_dict(cls, data: dict[str, Any]) -> AgentRunRecord:
158
+ """Deserialize from a plain dict."""
159
+ return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
160
+
161
+
162
+ def _iso_now() -> str:
163
+ """Return an ISO-8601 timestamp (UTC, second precision)."""
164
+ from datetime import UTC, datetime
165
+
166
+ return datetime.now(UTC).isoformat(timespec="seconds")
@@ -0,0 +1,384 @@
1
+ """Agent registry — Protocol, in-memory, and SQLite implementations (Sprint 17).
2
+
3
+ The registry persists agent identity and links every run to its execution
4
+ history. The default backend is SQLite (WAL mode, busy_timeout=5000).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import sqlite3
11
+ from pathlib import Path
12
+ from typing import Any, Protocol
13
+
14
+ from voodoo.agents.models import AgentEntity, AgentRunRecord
15
+
16
+ __all__ = [
17
+ "AgentRegistry",
18
+ "InMemoryAgentRegistry",
19
+ "SQLiteAgentRegistry",
20
+ ]
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Protocol
25
+ # ---------------------------------------------------------------------------
26
+
27
+
28
+ class AgentRegistry(Protocol):
29
+ """Persistence contract for agent registries."""
30
+
31
+ async def register(self, entity: AgentEntity) -> None:
32
+ """Register or update an agent entity."""
33
+ ...
34
+
35
+ async def get(self, agent_id: str) -> AgentEntity | None:
36
+ """Fetch an agent by ID. Returns ``None`` if not found."""
37
+ ...
38
+
39
+ async def list_agents(
40
+ self,
41
+ state: str | None = None,
42
+ limit: int = 100,
43
+ ) -> list[AgentEntity]:
44
+ """List registered agents, optionally filtered by state."""
45
+ ...
46
+
47
+ async def update(self, entity: AgentEntity) -> None:
48
+ """Update an existing agent entity."""
49
+ ...
50
+
51
+ async def delete(self, agent_id: str) -> bool:
52
+ """Delete an agent. Returns ``True`` if deleted."""
53
+ ...
54
+
55
+ async def record_run(self, record: AgentRunRecord) -> None:
56
+ """Persist a run record linked to this agent."""
57
+ ...
58
+
59
+ async def get_runs(
60
+ self,
61
+ agent_id: str,
62
+ limit: int = 50,
63
+ ) -> list[AgentRunRecord]:
64
+ """List run history for an agent, newest first."""
65
+ ...
66
+
67
+ async def count_agents(self, state: str | None = None) -> int:
68
+ """Count registered agents."""
69
+ ...
70
+
71
+ async def count_runs(self, agent_id: str) -> int:
72
+ """Count runs for an agent."""
73
+ ...
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # In-memory implementation
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ class InMemoryAgentRegistry:
82
+ """Non-durable registry for tests."""
83
+
84
+ def __init__(self) -> None:
85
+ self._agents: dict[str, AgentEntity] = {}
86
+ self._runs: dict[str, list[AgentRunRecord]] = {}
87
+
88
+ async def register(self, entity: AgentEntity) -> None:
89
+ self._agents[entity.agent_id] = entity
90
+
91
+ async def get(self, agent_id: str) -> AgentEntity | None:
92
+ return self._agents.get(agent_id)
93
+
94
+ async def list_agents(
95
+ self,
96
+ state: str | None = None,
97
+ limit: int = 100,
98
+ ) -> list[AgentEntity]:
99
+ agents = list(self._agents.values())
100
+ if state:
101
+ agents = [a for a in agents if a.state == state]
102
+ return agents[:limit]
103
+
104
+ async def update(self, entity: AgentEntity) -> None:
105
+ if entity.agent_id not in self._agents:
106
+ raise KeyError(f"Agent '{entity.agent_id}' not found")
107
+ self._agents[entity.agent_id] = entity
108
+
109
+ async def delete(self, agent_id: str) -> bool:
110
+ if agent_id in self._agents:
111
+ del self._agents[agent_id]
112
+ self._runs.pop(agent_id, None)
113
+ return True
114
+ return False
115
+
116
+ async def record_run(self, record: AgentRunRecord) -> None:
117
+ self._runs.setdefault(record.agent_id, []).append(record)
118
+
119
+ async def get_runs(
120
+ self,
121
+ agent_id: str,
122
+ limit: int = 50,
123
+ ) -> list[AgentRunRecord]:
124
+ runs = self._runs.get(agent_id, [])
125
+ return list(reversed(runs[-limit:]))
126
+
127
+ async def count_agents(self, state: str | None = None) -> int:
128
+ if state:
129
+ return sum(1 for a in self._agents.values() if a.state == state)
130
+ return len(self._agents)
131
+
132
+ async def count_runs(self, agent_id: str) -> int:
133
+ return len(self._runs.get(agent_id, []))
134
+
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # SQLite implementation
138
+ # ---------------------------------------------------------------------------
139
+
140
+ _SCHEMA_SQL = """
141
+ CREATE TABLE IF NOT EXISTS agents (
142
+ agent_id TEXT PRIMARY KEY,
143
+ name TEXT NOT NULL DEFAULT '',
144
+ description TEXT NOT NULL DEFAULT '',
145
+ model TEXT NOT NULL DEFAULT '',
146
+ system_prompt TEXT,
147
+ capabilities TEXT NOT NULL DEFAULT '[]',
148
+ tools TEXT NOT NULL DEFAULT '[]',
149
+ permissions TEXT NOT NULL DEFAULT '[]',
150
+ config TEXT NOT NULL DEFAULT '{}',
151
+ state TEXT NOT NULL DEFAULT 'active',
152
+ metadata TEXT NOT NULL DEFAULT '{}',
153
+ created_at TEXT NOT NULL,
154
+ updated_at TEXT NOT NULL
155
+ );
156
+
157
+ CREATE TABLE IF NOT EXISTS agent_runs (
158
+ run_id TEXT PRIMARY KEY,
159
+ agent_id TEXT NOT NULL,
160
+ execution_id TEXT,
161
+ prompt TEXT NOT NULL DEFAULT '',
162
+ output TEXT NOT NULL DEFAULT '',
163
+ status TEXT NOT NULL DEFAULT 'completed',
164
+ tokens_in INTEGER NOT NULL DEFAULT 0,
165
+ tokens_out INTEGER NOT NULL DEFAULT 0,
166
+ cost REAL NOT NULL DEFAULT 0.0,
167
+ tool_calls TEXT NOT NULL DEFAULT '[]',
168
+ started_at REAL NOT NULL,
169
+ completed_at REAL,
170
+ trace_id TEXT,
171
+ FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE
172
+ );
173
+
174
+ CREATE INDEX IF NOT EXISTS idx_agent_runs_agent_id ON agent_runs(agent_id);
175
+ """
176
+
177
+
178
+ class SQLiteAgentRegistry:
179
+ """Durable SQLite-backed agent registry.
180
+
181
+ Parameters
182
+ ----------
183
+ db_path:
184
+ Path to the SQLite database file. Created on first use.
185
+ """
186
+
187
+ def __init__(self, db_path: str = "data/agents.db") -> None:
188
+ self._db_path = db_path
189
+ Path(db_path).parent.mkdir(parents=True, exist_ok=True)
190
+ self._conn = sqlite3.connect(db_path)
191
+ self._conn.execute("PRAGMA journal_mode=WAL")
192
+ self._conn.execute("PRAGMA busy_timeout=5000")
193
+ self._conn.executescript(_SCHEMA_SQL)
194
+ self._conn.commit()
195
+
196
+ def close(self) -> None:
197
+ """Close the database connection."""
198
+ self._conn.close()
199
+
200
+ # -- agents ------------------------------------------------------------
201
+
202
+ async def register(self, entity: AgentEntity) -> None:
203
+ """Insert or replace an agent entity."""
204
+ d = entity.to_dict()
205
+ self._conn.execute(
206
+ """
207
+ INSERT OR REPLACE INTO agents
208
+ (agent_id, name, description, model, system_prompt,
209
+ capabilities, tools, permissions, config, state,
210
+ metadata, created_at, updated_at)
211
+ VALUES
212
+ (:agent_id, :name, :description, :model, :system_prompt,
213
+ :capabilities, :tools, :permissions, :config, :state,
214
+ :metadata, :created_at, :updated_at)
215
+ """,
216
+ {
217
+ **d,
218
+ "capabilities": json.dumps(d["capabilities"]),
219
+ "tools": json.dumps(d["tools"]),
220
+ "permissions": json.dumps(d["permissions"]),
221
+ "config": json.dumps(d["config"]),
222
+ "metadata": json.dumps(d["metadata"]),
223
+ },
224
+ )
225
+ self._conn.commit()
226
+
227
+ async def get(self, agent_id: str) -> AgentEntity | None:
228
+ cur = self._conn.execute("SELECT * FROM agents WHERE agent_id = ?", (agent_id,))
229
+ row = cur.fetchone()
230
+ if row is None:
231
+ return None
232
+ return _row_to_entity(row, cur.description)
233
+
234
+ async def list_agents(
235
+ self,
236
+ state: str | None = None,
237
+ limit: int = 100,
238
+ ) -> list[AgentEntity]:
239
+ if state:
240
+ cur = self._conn.execute(
241
+ "SELECT * FROM agents WHERE state = ? ORDER BY created_at DESC LIMIT ?",
242
+ (state, limit),
243
+ )
244
+ else:
245
+ cur = self._conn.execute(
246
+ "SELECT * FROM agents ORDER BY created_at DESC LIMIT ?",
247
+ (limit,),
248
+ )
249
+ return [_row_to_entity(r, cur.description) for r in cur.fetchall()]
250
+
251
+ async def update(self, entity: AgentEntity) -> None:
252
+ entity.updated_at = _iso_now()
253
+ d = entity.to_dict()
254
+ cur = self._conn.execute(
255
+ "UPDATE agents SET name=:name, description=:description, "
256
+ "model=:model, system_prompt=:system_prompt, "
257
+ "capabilities=:capabilities, tools=:tools, permissions=:permissions, "
258
+ "config=:config, state=:state, metadata=:metadata, "
259
+ "updated_at=:updated_at WHERE agent_id=:agent_id",
260
+ {
261
+ **d,
262
+ "capabilities": json.dumps(d["capabilities"]),
263
+ "tools": json.dumps(d["tools"]),
264
+ "permissions": json.dumps(d["permissions"]),
265
+ "config": json.dumps(d["config"]),
266
+ "metadata": json.dumps(d["metadata"]),
267
+ },
268
+ )
269
+ if cur.rowcount == 0:
270
+ raise KeyError(f"Agent '{entity.agent_id}' not found")
271
+ self._conn.commit()
272
+
273
+ async def delete(self, agent_id: str) -> bool:
274
+ cur = self._conn.execute("DELETE FROM agents WHERE agent_id = ?", (agent_id,))
275
+ self._conn.commit()
276
+ return cur.rowcount > 0
277
+
278
+ # -- runs --------------------------------------------------------------
279
+
280
+ async def record_run(self, record: AgentRunRecord) -> None:
281
+ d = record.to_dict()
282
+ self._conn.execute(
283
+ """
284
+ INSERT OR REPLACE INTO agent_runs
285
+ (run_id, agent_id, execution_id, prompt, output, status,
286
+ tokens_in, tokens_out, cost, tool_calls,
287
+ started_at, completed_at, trace_id)
288
+ VALUES
289
+ (:run_id, :agent_id, :execution_id, :prompt, :output, :status,
290
+ :tokens_in, :tokens_out, :cost, :tool_calls,
291
+ :started_at, :completed_at, :trace_id)
292
+ """,
293
+ {**d, "tool_calls": json.dumps(d["tool_calls"])},
294
+ )
295
+ self._conn.commit()
296
+
297
+ async def get_runs(
298
+ self,
299
+ agent_id: str,
300
+ limit: int = 50,
301
+ ) -> list[AgentRunRecord]:
302
+ cur = self._conn.execute(
303
+ "SELECT * FROM agent_runs WHERE agent_id = ? "
304
+ "ORDER BY started_at DESC LIMIT ?",
305
+ (agent_id, limit),
306
+ )
307
+ return [_row_to_run(r, cur.description) for r in cur.fetchall()]
308
+
309
+ async def count_agents(self, state: str | None = None) -> int:
310
+ if state:
311
+ cur = self._conn.execute(
312
+ "SELECT COUNT(*) FROM agents WHERE state = ?", (state,)
313
+ )
314
+ else:
315
+ cur = self._conn.execute("SELECT COUNT(*) FROM agents")
316
+ return cur.fetchone()[0]
317
+
318
+ async def count_runs(self, agent_id: str) -> int:
319
+ cur = self._conn.execute(
320
+ "SELECT COUNT(*) FROM agent_runs WHERE agent_id = ?", (agent_id,)
321
+ )
322
+ return cur.fetchone()[0]
323
+
324
+
325
+ # ---------------------------------------------------------------------------
326
+ # Helpers
327
+ # ---------------------------------------------------------------------------
328
+
329
+ _AGENT_COLUMNS = [
330
+ "agent_id",
331
+ "name",
332
+ "description",
333
+ "model",
334
+ "system_prompt",
335
+ "capabilities",
336
+ "tools",
337
+ "permissions",
338
+ "config",
339
+ "state",
340
+ "metadata",
341
+ "created_at",
342
+ "updated_at",
343
+ ]
344
+
345
+ _RUN_COLUMNS = [
346
+ "run_id",
347
+ "agent_id",
348
+ "execution_id",
349
+ "prompt",
350
+ "output",
351
+ "status",
352
+ "tokens_in",
353
+ "tokens_out",
354
+ "cost",
355
+ "tool_calls",
356
+ "started_at",
357
+ "completed_at",
358
+ "trace_id",
359
+ ]
360
+
361
+
362
+ def _row_to_entity(row: Any, description: Any) -> AgentEntity:
363
+ """Convert a sqlite3 row to an AgentEntity."""
364
+ cols = [d[0] for d in description]
365
+ data = dict(zip(cols, row, strict=False))
366
+ for key in ("capabilities", "tools", "permissions", "config", "metadata"):
367
+ if isinstance(data.get(key), str):
368
+ data[key] = json.loads(data[key])
369
+ return AgentEntity.from_dict(data)
370
+
371
+
372
+ def _row_to_run(row: Any, description: Any) -> AgentRunRecord:
373
+ """Convert a sqlite3 row to an AgentRunRecord."""
374
+ cols = [d[0] for d in description]
375
+ data = dict(zip(cols, row, strict=False))
376
+ if isinstance(data.get("tool_calls"), str):
377
+ data["tool_calls"] = json.loads(data["tool_calls"])
378
+ return AgentRunRecord.from_dict(data)
379
+
380
+
381
+ def _iso_now() -> str:
382
+ from datetime import UTC, datetime
383
+
384
+ return datetime.now(UTC).isoformat(timespec="seconds")