voodoo-framework 2.5.1__tar.gz → 2.6.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 (244) hide show
  1. {voodoo_framework-2.5.1/src/voodoo_framework.egg-info → voodoo_framework-2.6.0}/PKG-INFO +13 -1
  2. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/README.md +10 -0
  3. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/pyproject.toml +3 -0
  4. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/__init__.py +1 -1
  5. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/agent.py +220 -11
  6. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/config.py +82 -0
  7. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/app.py +47 -0
  8. voodoo_framework-2.6.0/src/voodoo/edge/__init__.py +122 -0
  9. voodoo_framework-2.6.0/src/voodoo/edge/auth.py +243 -0
  10. voodoo_framework-2.6.0/src/voodoo/edge/errors.py +181 -0
  11. voodoo_framework-2.6.0/src/voodoo/edge/gateway.py +556 -0
  12. voodoo_framework-2.6.0/src/voodoo/edge/http.py +316 -0
  13. voodoo_framework-2.6.0/src/voodoo/edge/models.py +309 -0
  14. voodoo_framework-2.6.0/src/voodoo/edge/mqtt.py +239 -0
  15. voodoo_framework-2.6.0/src/voodoo/edge/protocol.py +282 -0
  16. voodoo_framework-2.6.0/src/voodoo/edge/simulator.py +341 -0
  17. voodoo_framework-2.6.0/src/voodoo/edge/store.py +835 -0
  18. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0/src/voodoo_framework.egg-info}/PKG-INFO +13 -1
  19. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/SOURCES.txt +14 -0
  20. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/requires.txt +3 -0
  21. voodoo_framework-2.6.0/tests/test_edge_device.py +325 -0
  22. voodoo_framework-2.6.0/tests/test_edge_gateway.py +417 -0
  23. voodoo_framework-2.6.0/tests/test_edge_http.py +293 -0
  24. voodoo_framework-2.6.0/tests/test_edge_protocol.py +159 -0
  25. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/LICENSE +0 -0
  26. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/setup.cfg +0 -0
  27. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/__init__.py +0 -0
  28. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/capabilities.py +0 -0
  29. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/registry.py +0 -0
  30. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/tailwind.py +0 -0
  31. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/voodoo_css.py +0 -0
  32. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agent.py +0 -0
  33. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/__init__.py +0 -0
  34. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/models.py +0 -0
  35. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/registry.py +0 -0
  36. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/__init__.py +0 -0
  37. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/__init__.py +0 -0
  38. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/anthropic.py +0 -0
  39. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/gemini.py +0 -0
  40. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/mock.py +0 -0
  41. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/ollama.py +0 -0
  42. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/openai.py +0 -0
  43. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/tools/__init__.py +0 -0
  44. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/tools/registry.py +0 -0
  45. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/api.py +0 -0
  46. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/__init__.py +0 -0
  47. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/cookies.py +0 -0
  48. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/guards.py +0 -0
  49. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/jwt.py +0 -0
  50. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/middleware.py +0 -0
  51. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/passwords.py +0 -0
  52. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/user.py +0 -0
  53. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/__init__.py +0 -0
  54. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/__main__.py +0 -0
  55. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/agents.py +0 -0
  56. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/ai.py +0 -0
  57. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/approvals.py +0 -0
  58. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/auth.py +0 -0
  59. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/create.py +0 -0
  60. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/dev.py +0 -0
  61. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/doctor.py +0 -0
  62. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/executions.py +0 -0
  63. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/generate.py +0 -0
  64. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/inspect.py +0 -0
  65. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/new.py +0 -0
  66. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/objects.py +0 -0
  67. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/protocol.py +0 -0
  68. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/recover.py +0 -0
  69. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/routes.py +0 -0
  70. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/scaffolding.py +0 -0
  71. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/schedules.py +0 -0
  72. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/status.py +0 -0
  73. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/tasks.py +0 -0
  74. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/terminal.py +0 -0
  75. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/theme.py +0 -0
  76. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/version.py +0 -0
  77. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/workers_cmd.py +0 -0
  78. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/components.py +0 -0
  79. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/__init__.py +0 -0
  80. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/errors.py +0 -0
  81. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/events.py +0 -0
  82. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/render.py +0 -0
  83. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/routing.py +0 -0
  84. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/sitemap.py +0 -0
  85. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/state.py +0 -0
  86. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/__init__.py +0 -0
  87. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/base.py +0 -0
  88. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/model.py +0 -0
  89. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/i18n.py +0 -0
  90. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mcp/__init__.py +0 -0
  91. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mcp/client.py +0 -0
  92. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/__init__.py +0 -0
  93. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/interfaces.py +0 -0
  94. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/sqlite.py +0 -0
  95. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/__init__.py +0 -0
  96. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/client.py +0 -0
  97. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/event_bus.py +0 -0
  98. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/__init__.py +0 -0
  99. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/capability.py +0 -0
  100. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/compute.py +0 -0
  101. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/constraint.py +0 -0
  102. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/effect.py +0 -0
  103. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/intent.py +0 -0
  104. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/resource.py +0 -0
  105. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/state.py +0 -0
  106. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/time.py +0 -0
  107. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/__init__.py +0 -0
  108. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/export.py +0 -0
  109. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/schemas.py +0 -0
  110. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/py.typed +0 -0
  111. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/queue.py +0 -0
  112. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/routing/api.py +0 -0
  113. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/routing/pages.py +0 -0
  114. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/__init__.py +0 -0
  115. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/adaptive.py +0 -0
  116. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/capability.py +0 -0
  117. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/constraint.py +0 -0
  118. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/context.py +0 -0
  119. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/engine.py +0 -0
  120. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/errors.py +0 -0
  121. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/execution.py +0 -0
  122. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/graph.py +0 -0
  123. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/human.py +0 -0
  124. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/persistence.py +0 -0
  125. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/planner.py +0 -0
  126. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/scheduler.py +0 -0
  127. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/task.py +0 -0
  128. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/workflow.py +0 -0
  129. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/schedule.py +0 -0
  130. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/__init__.py +0 -0
  131. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/cors.py +0 -0
  132. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/csrf.py +0 -0
  133. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/headers.py +0 -0
  134. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/passwords.py +0 -0
  135. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/rate_limit.py +0 -0
  136. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/redaction.py +0 -0
  137. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/secrets.py +0 -0
  138. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/seo.py +0 -0
  139. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/static/client.js +0 -0
  140. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/status.py +0 -0
  141. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/__init__.py +0 -0
  142. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/__init__.py +0 -0
  143. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/interfaces.py +0 -0
  144. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/memory.py +0 -0
  145. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/redis.py +0 -0
  146. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/__init__.py +0 -0
  147. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/interfaces.py +0 -0
  148. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/postgres.py +0 -0
  149. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/sqlite.py +0 -0
  150. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/__init__.py +0 -0
  151. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/interfaces.py +0 -0
  152. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/local.py +0 -0
  153. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/postgres.py +0 -0
  154. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/sqlite.py +0 -0
  155. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/__init__.py +0 -0
  156. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/migrations.py +0 -0
  157. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/postgres.py +0 -0
  158. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/sqlite.py +0 -0
  159. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/manager.py +0 -0
  160. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/__init__.py +0 -0
  161. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/interfaces.py +0 -0
  162. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/local.py +0 -0
  163. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/s3.py +0 -0
  164. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/__init__.py +0 -0
  165. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/interfaces.py +0 -0
  166. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/memory.py +0 -0
  167. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/postgres.py +0 -0
  168. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/redis.py +0 -0
  169. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/sqlite.py +0 -0
  170. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
  171. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
  172. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/__init__.py +0 -0
  173. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/api.py +0 -0
  174. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/middleware.py +0 -0
  175. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/otlp.py +0 -0
  176. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/store.py +0 -0
  177. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/theme.py +0 -0
  178. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/tools/__init__.py +0 -0
  179. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/tools/registry.py +0 -0
  180. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/__init__.py +0 -0
  181. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/component.py +0 -0
  182. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/events.py +0 -0
  183. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/library.py +0 -0
  184. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/markdown.py +0 -0
  185. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/rendering.py +0 -0
  186. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/state.py +0 -0
  187. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/__init__.py +0 -0
  188. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/presets.py +0 -0
  189. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/theme.py +0 -0
  190. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/workers/__init__.py +0 -0
  191. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/workers/queue.py +0 -0
  192. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
  193. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
  194. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
  195. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent.py +0 -0
  196. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent_registry.py +0 -0
  197. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent_runtime.py +0 -0
  198. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_app.py +0 -0
  199. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_auth.py +0 -0
  200. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_capability_security.py +0 -0
  201. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_checkpoint_resume.py +0 -0
  202. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_chrome.py +0 -0
  203. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_cli.py +0 -0
  204. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_cli_inspect.py +0 -0
  205. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_components.py +0 -0
  206. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_config.py +0 -0
  207. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_contract_api.py +0 -0
  208. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_create.py +0 -0
  209. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_data.py +0 -0
  210. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_design_system.py +0 -0
  211. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_durable_hitl.py +0 -0
  212. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_events.py +0 -0
  213. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_execution_sqlite_store.py +0 -0
  214. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_http_runtime.py +0 -0
  215. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_human.py +0 -0
  216. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_i18n.py +0 -0
  217. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_integration.py +0 -0
  218. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mcp.py +0 -0
  219. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mcp_runtime.py +0 -0
  220. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_memory.py +0 -0
  221. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mesh.py +0 -0
  222. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mesh_event_bus.py +0 -0
  223. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_model.py +0 -0
  224. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_object_store.py +0 -0
  225. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_observability.py +0 -0
  226. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_persistence.py +0 -0
  227. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_planner_adaptive.py +0 -0
  228. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_primitives.py +0 -0
  229. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_protocol.py +0 -0
  230. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_provider_switching.py +0 -0
  231. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_providers.py +0 -0
  232. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_queue.py +0 -0
  233. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_reactive.py +0 -0
  234. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_runtime.py +0 -0
  235. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_scheduler.py +0 -0
  236. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_security.py +0 -0
  237. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_seo.py +0 -0
  238. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_state.py +0 -0
  239. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_theme.py +0 -0
  240. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_theme_presets.py +0 -0
  241. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_tools.py +0 -0
  242. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_ui.py +0 -0
  243. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_websocket.py +0 -0
  244. {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voodoo-framework
3
- Version: 2.5.1
3
+ Version: 2.6.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>
@@ -59,6 +59,8 @@ Requires-Dist: boto3>=1.34; extra == "s3"
59
59
  Requires-Dist: botocore>=1.34; extra == "s3"
60
60
  Provides-Extra: redis
61
61
  Requires-Dist: redis>=5.0; extra == "redis"
62
+ Provides-Extra: edge
63
+ Requires-Dist: paho-mqtt>=2.0; extra == "edge"
62
64
  Provides-Extra: otel
63
65
  Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
64
66
  Requires-Dist: opentelemetry-sdk>=1.20; extra == "otel"
@@ -232,6 +234,7 @@ if __name__ == "__main__":
232
234
  | **Observability everywhere** | Correlation IDs and telemetry built into every subsystem |
233
235
  | **Unified runtime engine** | Every operation (HTTP, Agent, Tool, MCP, Worker, Human, Event) produces an `Execution` record with full traceability |
234
236
  | **Human-in-the-Loop** | `ask_human()` + `approve()`/`deny()` — humans as compute participants, not afterthoughts |
237
+ | **Physical devices as Entities** | `voodoo-edge/v1` protocol + Device Gateway over HTTP/MQTT — ESP32s, robots, and sensors join the same execution model as agents and workers |
235
238
  | **Adaptive execution** | Planner resolves capabilities to compute participants; supervisor steers with retry, fallback, budget control |
236
239
  | **Durable by default** | Tasks, executions, schedules, and events survive process restarts — backed by SQLite out of the box |
237
240
  | **Zero-config runtime** | `voodoo create` → `voodoo dev` → working app. No build step. Add `voodoo.toml` when you need configuration |
@@ -284,6 +287,7 @@ and [`ARCHITECTURE.md`](ARCHITECTURE.md) for the implementation model.
284
287
  - **Auth** — JWT tokens, API keys, session cookies, RBAC route guards
285
288
  - **Workers** — `@task` decorator with retries, timeout, and telemetry spans
286
289
  - **Voodoo Mesh** — Realtime event bus with local + remote boundaries
290
+ - **Edge Devices** — Device Gateway (`voodoo-edge/v1` over HTTP & MQTT): devices emit events, trigger Executions, receive Effects — see [`docs/edge/overview.md`](docs/edge/overview.md)
287
291
 
288
292
  ### Runtime & Infrastructure
289
293
  - **Runtime Engine** — Unified `ExecutionEngine` producing `Execution` records for every operation
@@ -437,6 +441,14 @@ voodoo auth secret-key
437
441
  - [Mesh](docs/mesh.md)
438
442
  - [Workers](docs/workers.md)
439
443
 
444
+ ### Edge Devices
445
+ - [Edge Overview](docs/edge/overview.md)
446
+ - [Edge Protocol v1](docs/edge/protocol.md)
447
+ - [HTTP Transport](docs/edge/http.md)
448
+ - [MQTT Transport](docs/edge/mqtt.md)
449
+ - [Device Security](docs/edge/security.md)
450
+ - [Reliability](docs/edge/reliability.md)
451
+
440
452
  ### Runtime & Operations
441
453
  - [Runtime Engine](docs/runtime.md)
442
454
  - [Planner & Adaptive Runtime](docs/adaptive.md)
@@ -165,6 +165,7 @@ if __name__ == "__main__":
165
165
  | **Observability everywhere** | Correlation IDs and telemetry built into every subsystem |
166
166
  | **Unified runtime engine** | Every operation (HTTP, Agent, Tool, MCP, Worker, Human, Event) produces an `Execution` record with full traceability |
167
167
  | **Human-in-the-Loop** | `ask_human()` + `approve()`/`deny()` — humans as compute participants, not afterthoughts |
168
+ | **Physical devices as Entities** | `voodoo-edge/v1` protocol + Device Gateway over HTTP/MQTT — ESP32s, robots, and sensors join the same execution model as agents and workers |
168
169
  | **Adaptive execution** | Planner resolves capabilities to compute participants; supervisor steers with retry, fallback, budget control |
169
170
  | **Durable by default** | Tasks, executions, schedules, and events survive process restarts — backed by SQLite out of the box |
170
171
  | **Zero-config runtime** | `voodoo create` → `voodoo dev` → working app. No build step. Add `voodoo.toml` when you need configuration |
@@ -217,6 +218,7 @@ and [`ARCHITECTURE.md`](ARCHITECTURE.md) for the implementation model.
217
218
  - **Auth** — JWT tokens, API keys, session cookies, RBAC route guards
218
219
  - **Workers** — `@task` decorator with retries, timeout, and telemetry spans
219
220
  - **Voodoo Mesh** — Realtime event bus with local + remote boundaries
221
+ - **Edge Devices** — Device Gateway (`voodoo-edge/v1` over HTTP & MQTT): devices emit events, trigger Executions, receive Effects — see [`docs/edge/overview.md`](docs/edge/overview.md)
220
222
 
221
223
  ### Runtime & Infrastructure
222
224
  - **Runtime Engine** — Unified `ExecutionEngine` producing `Execution` records for every operation
@@ -370,6 +372,14 @@ voodoo auth secret-key
370
372
  - [Mesh](docs/mesh.md)
371
373
  - [Workers](docs/workers.md)
372
374
 
375
+ ### Edge Devices
376
+ - [Edge Overview](docs/edge/overview.md)
377
+ - [Edge Protocol v1](docs/edge/protocol.md)
378
+ - [HTTP Transport](docs/edge/http.md)
379
+ - [MQTT Transport](docs/edge/mqtt.md)
380
+ - [Device Security](docs/edge/security.md)
381
+ - [Reliability](docs/edge/reliability.md)
382
+
373
383
  ### Runtime & Operations
374
384
  - [Runtime Engine](docs/runtime.md)
375
385
  - [Planner & Adaptive Runtime](docs/adaptive.md)
@@ -93,6 +93,9 @@ s3 = [
93
93
  redis = [
94
94
  "redis>=5.0",
95
95
  ]
96
+ edge = [
97
+ "paho-mqtt>=2.0",
98
+ ]
96
99
  otel = [
97
100
  "opentelemetry-api>=1.20",
98
101
  "opentelemetry-sdk>=1.20",
@@ -102,7 +102,7 @@ from .ui.styles import (
102
102
  from .ui.styles.theme import Theme, ThemeColors, create_theme
103
103
  from .workers import task
104
104
 
105
- __version__ = "2.5.1"
105
+ __version__ = "2.6.0"
106
106
 
107
107
  __all__ = [
108
108
  # Core runtime
@@ -4,14 +4,22 @@ The Agent turns a prompt into a final answer by iterating:
4
4
 
5
5
  prompt → provider → tool calls (via registry) → final
6
6
 
7
- ``run()`` returns a full :class:`AgentRun` record with token/cost accounting;
8
- ``stream()`` yields normalized :class:`AgentEvent` objects so the UI can react
9
- to agent activity in real time. Lifecycle states (created → configured →
10
- running → tool_call ⇄ thinking → completed | error → retry/failed) are
11
- captured for telemetry and surfaced through mesh events (S7-2).
12
-
13
- The ``context`` parameter is an explicit, opaque dict passed to every tool
14
- call; it is neither memory nor a database — keep the concepts separate.
7
+ The Agent is a *participant* in the runtime, not a parallel execution
8
+ framework. ``run()`` creates an :class:`~voodoo.runtime.execution.Execution`
9
+ via the :class:`~voodoo.runtime.engine.ExecutionEngine` so that agent
10
+ runs share the same lifecycle, persistence, recovery, authorization and
11
+ observability as every other execution path (HTTP, Worker, Task, MCP, …).
12
+
13
+ ``run()`` returns a full :class:`AgentRun` record with token/cost
14
+ accounting; ``stream()`` yields normalized :class:`AgentEvent` objects
15
+ so the UI can react to agent activity in real time. Lifecycle states
16
+ (created → configured → running → tool_call ⇄ thinking → completed |
17
+ error → retry/failed) are captured for telemetry and surfaced through
18
+ mesh events (S7-2).
19
+
20
+ The ``context`` parameter is an explicit, opaque dict passed to every
21
+ tool call; it is neither memory nor a database — keep the concepts
22
+ separate.
15
23
  """
16
24
 
17
25
  from __future__ import annotations
@@ -67,7 +75,15 @@ class AgentState(StrEnum):
67
75
 
68
76
  @dataclass
69
77
  class AgentRun:
70
- """Full run record with token/cost accounting."""
78
+ """Full run record with token/cost accounting.
79
+
80
+ This is a *projection* of the underlying
81
+ :class:`~voodoo.runtime.execution.Execution` — it exists for
82
+ backward-compatible telemetry and agent-specific accounting, not as
83
+ a second source of truth. The ``execution_id`` links this record
84
+ to the canonical Execution; ``trace_id`` is the correlation id
85
+ propagated through the entire stack.
86
+ """
71
87
 
72
88
  run_id: str
73
89
  model: str
@@ -84,6 +100,7 @@ class AgentRun:
84
100
  started_at: float = field(default_factory=time.time)
85
101
  completed_at: float | None = None
86
102
  trace_id: str | None = None
103
+ execution_id: str | None = None
87
104
 
88
105
 
89
106
  @dataclass
@@ -151,6 +168,7 @@ class Agent:
151
168
  agent_id: str | None = None,
152
169
  name: str = "",
153
170
  agent_registry: Any | None = None,
171
+ engine: Any | None = None,
154
172
  **provider_kwargs: Any,
155
173
  ) -> None:
156
174
  self.model = model or default_model()
@@ -171,6 +189,11 @@ class Agent:
171
189
  self._agent_registry: Any | None = agent_registry
172
190
  self._registered: bool = False
173
191
 
192
+ # Execution engine — when set, Agent.run() creates an Execution
193
+ # via the engine so the run shares lifecycle, persistence,
194
+ # recovery and observability with every other execution path.
195
+ self._engine: Any | None = engine
196
+
174
197
  # Resolve tool specs/names into a list of names for tool calling.
175
198
  self._tool_names: list[str] = []
176
199
  if tools:
@@ -336,9 +359,22 @@ class Agent:
336
359
  """Invoke a registered tool through the runtime authorization path.
337
360
 
338
361
  Flow: Agent → Intent → capability check → Tool → Effect → Mesh.
362
+
363
+ When running inside an
364
+ :class:`~voodoo.runtime.engine.ExecutionEngine` Execution, each
365
+ tool call creates a *child* Execution so the full agent activity
366
+ is visible in the execution graph:
367
+
368
+ ::
369
+
370
+ Agent Execution
371
+ ├── Tool Execution #1
372
+ ├── Tool Execution #2
373
+ └── Tool Execution #3
374
+
339
375
  Tools that declare ``permissions`` require a matching capability,
340
376
  granted either to this agent or held by the active runtime
341
- :class:`~voodoo.runtime.context.ExecutionContext`. Unauthorized
377
+ :class:`~voodoo.runtime.context.ExecutionContext`. Unauthorized
342
378
  tool calls are denied before any side effect executes.
343
379
  """
344
380
  from voodoo.primitives.effect import Effect
@@ -371,6 +407,15 @@ class Agent:
371
407
  f"{', '.join(missing)}"
372
408
  }
373
409
 
410
+ # When inside an engine Execution, create a child Execution for
411
+ # this tool call so it appears in the execution graph.
412
+ engine = ctx.engine if ctx else None
413
+ if engine is not None:
414
+ return await self._execute_tool_as_child_execution(
415
+ engine, name, arguments, spec, ctx
416
+ )
417
+
418
+ # Standalone path: record as an Effect on the current context.
374
419
  effect = Effect(
375
420
  name=f"tool.{name}", capability_name=required[0] if required else None
376
421
  )
@@ -391,6 +436,56 @@ class Agent:
391
436
  await self._broadcast("tool.completed", {"tool": name, "status": "succeeded"})
392
437
  return result
393
438
 
439
+ async def _execute_tool_as_child_execution(
440
+ self,
441
+ engine: Any,
442
+ name: str,
443
+ arguments: dict[str, Any],
444
+ spec: Any,
445
+ parent_ctx: Any,
446
+ ) -> Any:
447
+ """Execute a tool call as a child Execution on the engine.
448
+
449
+ Creates a child Execution with its own lifecycle, capability
450
+ context and effect record. The parent agent Execution's
451
+ capabilities propagate to the child (Step 4: capability
452
+ propagation).
453
+ """
454
+ from voodoo.primitives.intent import Intent
455
+ from voodoo.runtime.context import ExecutionContext
456
+
457
+ intent = Intent(
458
+ name=f"tool.{name}",
459
+ description=f"Tool call: {name}",
460
+ params={"tool": name, "arguments": arguments},
461
+ requires=list(spec.permissions if spec else []),
462
+ )
463
+
464
+ async def _tool_compute(ctx: ExecutionContext) -> Any:
465
+ await self._broadcast("tool.called", {"tool": name, "arguments": arguments})
466
+ try:
467
+ result = await self.registry.call(name, **arguments)
468
+ await self._broadcast(
469
+ "tool.completed",
470
+ {"tool": name, "status": "succeeded"},
471
+ )
472
+ return result
473
+ except Exception as e: # noqa: BLE001
474
+ await self._broadcast(
475
+ "tool.completed",
476
+ {"tool": name, "status": "failed", "error": str(e)},
477
+ )
478
+ return {"error": str(e)}
479
+
480
+ execution = await engine.execute(
481
+ intent,
482
+ _tool_compute,
483
+ actor=f"agent:{self.agent_id}",
484
+ capabilities=list(spec.permissions if spec else []),
485
+ parent=parent_ctx,
486
+ )
487
+ return execution.result
488
+
394
489
  # -- run ---------------------------------------------------------------
395
490
 
396
491
  async def run(
@@ -401,12 +496,126 @@ class Agent:
401
496
  ) -> AgentRun:
402
497
  """Execute prompt → provider → tool calls → final; return AgentRun.
403
498
 
499
+ When an :class:`~voodoo.runtime.engine.ExecutionEngine` is
500
+ available (either passed at construction or attached to the
501
+ active :class:`~voodoo.runtime.context.ExecutionContext`), the
502
+ run creates a first-class
503
+ :class:`~voodoo.runtime.execution.Execution` so it shares
504
+ lifecycle, persistence, recovery and observability with every
505
+ other execution path.
506
+
404
507
  ``history`` prepends prior conversation turns (a list of ``Message``
405
508
  dicts) before the new user prompt, enabling multi-turn chat.
406
509
  """
407
- run_id = str(uuid.uuid4())
510
+ from voodoo.runtime.context import current_context
511
+
512
+ # Resolve the engine: explicit on the agent, or from the active
513
+ # context (set by ExecutionEngine._run_compute), or None.
514
+ ctx = current_context()
515
+ engine = self._engine or (ctx.engine if ctx else None)
516
+
517
+ if engine is not None:
518
+ return await self._run_via_engine(
519
+ engine, prompt, context=context, history=history, parent_ctx=ctx
520
+ )
521
+ return await self._run_standalone(prompt, context=context, history=history)
522
+
523
+ # -- engine-backed run -------------------------------------------------
524
+
525
+ async def _run_via_engine(
526
+ self,
527
+ engine: Any,
528
+ prompt: str,
529
+ *,
530
+ context: dict | None = None,
531
+ history: list[Message] | None = None,
532
+ parent_ctx: Any | None = None,
533
+ ) -> AgentRun:
534
+ """Run the agent loop inside an ExecutionEngine Execution.
535
+
536
+ The engine owns lifecycle, persistence, recovery and
537
+ observability. The agent's provider loop is the *compute*
538
+ participant — it receives an
539
+ :class:`~voodoo.runtime.context.ExecutionContext` and returns a
540
+ :class:`~voodoo.runtime.engine.ComputeResult`.
541
+ """
542
+ from voodoo.primitives.intent import Intent
543
+ from voodoo.runtime.context import ExecutionContext
544
+
545
+ # Build the intent that represents this agent run.
546
+ intent = Intent(
547
+ name=f"agent.{self.name or self.agent_id}",
548
+ description=prompt[:200],
549
+ params={"prompt": prompt, "model": self.model},
550
+ )
551
+
552
+ # The compute function encapsulates the entire provider loop.
553
+ async def _agent_compute(ctx: ExecutionContext) -> Any:
554
+ return await self._provider_loop(
555
+ prompt, context=context, history=history, run_id=ctx.execution_id
556
+ )
557
+
558
+ # Execute through the engine — this creates the Execution,
559
+ # manages lifecycle, persists, and records telemetry.
560
+ execution = await engine.execute(
561
+ intent,
562
+ _agent_compute,
563
+ actor=f"agent:{self.agent_id}",
564
+ capabilities=self.capabilities or None,
565
+ parent=parent_ctx,
566
+ )
567
+
568
+ # Build the AgentRun projection from the Execution.
569
+ compute_result = execution.result
570
+ if isinstance(compute_result, AgentRun):
571
+ compute_result.execution_id = execution.id
572
+ return compute_result
573
+
574
+ # Fallback: wrap unexpected result types.
575
+ return AgentRun(
576
+ run_id=execution.id,
577
+ model=self.model,
578
+ provider=self.provider.name,
579
+ prompt=prompt,
580
+ output=str(compute_result) if compute_result is not None else "",
581
+ status=execution.status.value,
582
+ error=execution.error,
583
+ started_at=execution._started_ts or time.time(),
584
+ completed_at=execution._completed_ts,
585
+ trace_id=execution.trace_id,
586
+ execution_id=execution.id,
587
+ )
588
+
589
+ async def _run_standalone(
590
+ self,
591
+ prompt: str,
592
+ *,
593
+ context: dict | None = None,
594
+ history: list[Message] | None = None,
595
+ ) -> AgentRun:
596
+ """Run the agent loop without an engine (backward-compatible).
597
+
598
+ Used when no ExecutionEngine is available. Still produces a
599
+ valid AgentRun with telemetry and memory writes.
600
+ """
601
+ return await self._provider_loop(prompt, context=context, history=history)
602
+
603
+ async def _provider_loop(
604
+ self,
605
+ prompt: str,
606
+ *,
607
+ context: dict | None = None,
608
+ history: list[Message] | None = None,
609
+ run_id: str | None = None,
610
+ ) -> AgentRun:
611
+ """The core provider loop shared by engine-backed and standalone runs.
612
+
613
+ When ``run_id`` is ``None``, a new UUID is generated. When
614
+ called inside an engine Execution, ``run_id`` is the execution id.
615
+ """
408
616
  from voodoo.telemetry import telemetry_store
409
617
 
618
+ run_id = run_id or str(uuid.uuid4())
410
619
  trace_id = (
411
620
  telemetry_store.trace_id_var.get()
412
621
  if hasattr(telemetry_store, "trace_id_var")
@@ -255,6 +255,37 @@ class AIConfig(BaseModel):
255
255
  aliases: dict[str, str] = Field(default_factory=dict)
256
256
 
257
257
 
258
+ class EdgeConfig(BaseModel):
259
+ """Edge device gateway configuration (``edge:`` block) — Sprint 23.
260
+
261
+ Edge is disabled by default: users who never enable it pay zero
262
+ overhead and need no MQTT broker, device tables, or gateway
263
+ (EDGE §68 — backwards compatibility).
264
+ """
265
+
266
+ enabled: bool = False
267
+ http_enabled: bool = True
268
+ mqtt_enabled: bool = False
269
+ # Optional separate listener for the edge gateway; when empty the
270
+ # edge routes mount onto the main app instead.
271
+ http_host: str = ""
272
+ http_port: int = 0
273
+ # MQTT broker connection (EDGE §35, §38).
274
+ mqtt_broker_url: str = ""
275
+ mqtt_port: int = 1883
276
+ mqtt_tls: bool = False
277
+ mqtt_username: str = ""
278
+ mqtt_password: str = ""
279
+ mqtt_client_id: str = "voodoo-runtime"
280
+ mqtt_keepalive: int = 60
281
+ mqtt_qos: int = 1
282
+ # Resource limits (EDGE §76).
283
+ max_message_size: int = 262144 # 256 KiB
284
+ max_state_size: int = 65536 # 64 KiB
285
+ max_pending_effects: int = 1000
286
+ heartbeat_interval: int = 30
287
+
288
+
258
289
  class VoodooConfig(BaseModel):
259
290
  """Core configuration for the Voodoo framework."""
260
291
 
@@ -276,6 +307,7 @@ class VoodooConfig(BaseModel):
276
307
  cache: CacheConfig = Field(default_factory=CacheConfig)
277
308
  models: ModelsConfig = Field(default_factory=ModelsConfig)
278
309
  ai: AIConfig = Field(default_factory=AIConfig)
310
+ edge: EdgeConfig = Field(default_factory=EdgeConfig)
279
311
 
280
312
  seo: SEOConfig = Field(default_factory=SEOConfig)
281
313
  auth: AuthConfig = Field(default_factory=AuthConfig)
@@ -458,6 +490,54 @@ def _build_ai_config(file_data: dict[str, Any]) -> AIConfig:
458
490
  )
459
491
 
460
492
 
493
+ def _build_edge_config(file_data: dict[str, Any]) -> EdgeConfig:
494
+ """Merge file ``edge:`` block with ``VOODOO_EDGE_*``/``MQTT_*`` env vars."""
495
+ edge_data = file_data.get("edge") or {}
496
+ if not isinstance(edge_data, dict):
497
+ edge_data = {"enabled": bool(edge_data)}
498
+
499
+ def _flag(key: str, env: str) -> bool | None:
500
+ if key in edge_data:
501
+ return bool(edge_data[key])
502
+ val = _env_flag(env)
503
+ return val
504
+
505
+ enabled = _flag("enabled", "VOODOO_EDGE_ENABLED")
506
+ mqtt = edge_data.get("mqtt") or {}
507
+ if not isinstance(mqtt, dict):
508
+ mqtt = {}
509
+
510
+ kwargs: dict[str, Any] = {
511
+ "enabled": enabled if enabled is not None else False,
512
+ "http_enabled": (_flag("http_enabled", "VOODOO_EDGE_HTTP_ENABLED") or False)
513
+ if "http_enabled" in edge_data or "VOODOO_EDGE_HTTP_ENABLED" in os.environ
514
+ else True,
515
+ "mqtt_enabled": (_flag("mqtt_enabled", "VOODOO_EDGE_MQTT_ENABLED") or False)
516
+ if "mqtt_enabled" in edge_data or "VOODOO_EDGE_MQTT_ENABLED" in os.environ
517
+ else (bool(mqtt) or "MQTT_BROKER_URL" in os.environ),
518
+ "mqtt_broker_url": mqtt.get("broker_url") or os.getenv("MQTT_BROKER_URL", ""),
519
+ "mqtt_port": int(mqtt.get("port") or os.getenv("MQTT_PORT", "1883")),
520
+ "mqtt_tls": bool(mqtt.get("tls") or _env_flag("MQTT_TLS") or False),
521
+ "mqtt_username": mqtt.get("username") or os.getenv("MQTT_USERNAME", ""),
522
+ "mqtt_password": mqtt.get("password") or os.getenv("MQTT_PASSWORD", ""),
523
+ "mqtt_client_id": mqtt.get("client_id")
524
+ or os.getenv("MQTT_CLIENT_ID", "voodoo-runtime"),
525
+ "mqtt_keepalive": int(mqtt.get("keepalive", 60)),
526
+ "mqtt_qos": int(mqtt.get("qos", 1)),
527
+ }
528
+ for key in (
529
+ "max_message_size",
530
+ "max_state_size",
531
+ "max_pending_effects",
532
+ "heartbeat_interval",
533
+ "http_host",
534
+ "http_port",
535
+ ):
536
+ if key in edge_data:
537
+ kwargs[key] = edge_data[key]
538
+ return EdgeConfig(**kwargs)
539
+
540
+
461
541
  def _pick(
462
542
  file_data: dict[str, Any],
463
543
  key: str,
@@ -551,6 +631,7 @@ def get_config(
551
631
  config_args["cache"] = _build_cache_config(file_data)
552
632
  config_args["models"] = _build_models_config(file_data)
553
633
  config_args["ai"] = _build_ai_config(file_data)
634
+ config_args["edge"] = _build_edge_config(file_data)
554
635
 
555
636
  # Existing sub-configurations
556
637
  if "seo" in file_data and isinstance(file_data["seo"], dict):
@@ -578,6 +659,7 @@ def get_config(
578
659
  "cache",
579
660
  "models",
580
661
  "ai",
662
+ "edge",
581
663
  "seo",
582
664
  "auth",
583
665
  "security",
@@ -241,6 +241,22 @@ def create_app(app_dir: str = "app") -> Starlette: # noqa: C901
241
241
 
242
242
  routes.extend(voodoo_api.routes)
243
243
 
244
+ # --- Edge device gateway (Sprint 23, EDGE §68) -------------------------
245
+ # Entirely optional: when ``edge.enabled`` is False, no store, gateway,
246
+ # routes, or MQTT client are created — regular apps pay zero overhead.
247
+ edge_gateway: list = [] # mutable holder so lifespan can share/replace the gateway
248
+ if config.edge.enabled and config.edge.http_enabled:
249
+ from voodoo.edge import DeviceGateway, SQLiteDeviceStore
250
+
251
+ edge_store = SQLiteDeviceStore(".voodoo/state/devices.db")
252
+ from voodoo.runtime.engine import engine as _runtime_engine
253
+
254
+ gateway = DeviceGateway(edge_store, _runtime_engine)
255
+ edge_gateway.append(gateway)
256
+ from voodoo.edge.http import build_edge_routes as _build_edge_routes
257
+
258
+ routes.extend(_build_edge_routes(gateway))
259
+
244
260
  from voodoo.auth import AuthMiddleware
245
261
  from voodoo.i18n import I18nMiddleware
246
262
  from voodoo.security import (
@@ -290,8 +306,39 @@ def create_app(app_dir: str = "app") -> Starlette: # noqa: C901
290
306
 
291
307
  if _workers:
292
308
  worker_task = asyncio.create_task(start_workers())
309
+
310
+ # Edge MQTT transport (Sprint 23) — optional, only when configured.
311
+ mqtt_transport = None
312
+ if config.edge.enabled and config.edge.mqtt_enabled:
313
+ try:
314
+ from voodoo.edge import DeviceGateway as _DG
315
+ from voodoo.edge import SQLiteDeviceStore as _SDS
316
+ from voodoo.edge.mqtt import EdgeMQTTTransport
317
+ from voodoo.runtime.engine import engine as _rt_engine
318
+
319
+ gateway = (
320
+ edge_gateway[0]
321
+ if edge_gateway
322
+ else _DG(_SDS(".voodoo/state/devices.db"), _rt_engine)
323
+ )
324
+ mqtt_transport = EdgeMQTTTransport(
325
+ gateway,
326
+ broker_url=config.edge.mqtt_broker_url or "localhost",
327
+ port=config.edge.mqtt_port,
328
+ tls=config.edge.mqtt_tls,
329
+ username=config.edge.mqtt_username or None,
330
+ password=config.edge.mqtt_password or None,
331
+ client_id=config.edge.mqtt_client_id,
332
+ qos=config.edge.mqtt_qos,
333
+ )
334
+ await mqtt_transport.start()
335
+ except ImportError:
336
+ # paho-mqtt not installed — edge stays HTTP-only.
337
+ pass
293
338
  yield
294
339
  # Shutdown
340
+ if mqtt_transport is not None:
341
+ await mqtt_transport.stop()
295
342
  await stop_workers()
296
343
  if worker_task:
297
344
  worker_task.cancel()