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.
- {voodoo_framework-2.5.1/src/voodoo_framework.egg-info → voodoo_framework-2.6.0}/PKG-INFO +13 -1
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/README.md +10 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/pyproject.toml +3 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/__init__.py +1 -1
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/agent.py +220 -11
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/config.py +82 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/app.py +47 -0
- voodoo_framework-2.6.0/src/voodoo/edge/__init__.py +122 -0
- voodoo_framework-2.6.0/src/voodoo/edge/auth.py +243 -0
- voodoo_framework-2.6.0/src/voodoo/edge/errors.py +181 -0
- voodoo_framework-2.6.0/src/voodoo/edge/gateway.py +556 -0
- voodoo_framework-2.6.0/src/voodoo/edge/http.py +316 -0
- voodoo_framework-2.6.0/src/voodoo/edge/models.py +309 -0
- voodoo_framework-2.6.0/src/voodoo/edge/mqtt.py +239 -0
- voodoo_framework-2.6.0/src/voodoo/edge/protocol.py +282 -0
- voodoo_framework-2.6.0/src/voodoo/edge/simulator.py +341 -0
- voodoo_framework-2.6.0/src/voodoo/edge/store.py +835 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0/src/voodoo_framework.egg-info}/PKG-INFO +13 -1
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/SOURCES.txt +14 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/requires.txt +3 -0
- voodoo_framework-2.6.0/tests/test_edge_device.py +325 -0
- voodoo_framework-2.6.0/tests/test_edge_gateway.py +417 -0
- voodoo_framework-2.6.0/tests/test_edge_http.py +293 -0
- voodoo_framework-2.6.0/tests/test_edge_protocol.py +159 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/LICENSE +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/setup.cfg +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/capabilities.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/registry.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/tailwind.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/adapters/voodoo_css.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agent.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/models.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/agents/registry.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/anthropic.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/gemini.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/mock.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/ollama.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/providers/openai.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/tools/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ai/tools/registry.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/api.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/cookies.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/guards.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/jwt.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/middleware.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/passwords.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/auth/user.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/__main__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/agents.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/ai.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/approvals.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/auth.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/create.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/dev.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/doctor.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/executions.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/generate.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/inspect.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/new.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/objects.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/protocol.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/recover.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/routes.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/scaffolding.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/schedules.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/status.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/tasks.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/terminal.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/theme.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/version.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/cli/workers_cmd.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/components.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/errors.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/events.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/render.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/routing.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/sitemap.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/core/state.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/base.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/data/model.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/i18n.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mcp/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mcp/client.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/memory/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/client.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/mesh/event_bus.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/capability.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/compute.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/constraint.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/effect.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/intent.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/resource.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/state.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/primitives/time.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/export.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/protocol/schemas.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/py.typed +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/queue.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/routing/api.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/routing/pages.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/adaptive.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/capability.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/constraint.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/context.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/engine.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/errors.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/execution.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/graph.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/human.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/persistence.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/planner.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/scheduler.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/task.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/runtime/workflow.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/schedule.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/cors.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/csrf.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/headers.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/passwords.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/rate_limit.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/redaction.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/security/secrets.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/seo.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/static/client.js +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/status.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/memory.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/cache/redis.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/postgres.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/database/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/local.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/postgres.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/events/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/migrations.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/postgres.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/execution/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/manager.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/local.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/objects/s3.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/interfaces.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/memory.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/postgres.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/redis.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/queue/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/scheduler/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/storage/scheduler/sqlite.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/api.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/middleware.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/otlp.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/telemetry/store.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/theme.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/tools/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/tools/registry.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/component.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/events.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/library.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/markdown.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/rendering.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/state.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/presets.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/ui/styles/theme.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/workers/__init__.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo/workers/queue.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent_registry.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_agent_runtime.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_app.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_auth.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_capability_security.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_checkpoint_resume.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_chrome.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_cli.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_cli_inspect.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_components.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_config.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_contract_api.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_create.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_data.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_design_system.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_durable_hitl.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_events.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_execution_sqlite_store.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_http_runtime.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_human.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_i18n.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_integration.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mcp.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mcp_runtime.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_memory.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mesh.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_mesh_event_bus.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_model.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_object_store.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_observability.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_persistence.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_planner_adaptive.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_primitives.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_protocol.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_provider_switching.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_providers.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_queue.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_reactive.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_runtime.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_scheduler.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_security.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_seo.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_state.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_theme.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_theme_presets.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_tools.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_ui.py +0 -0
- {voodoo_framework-2.5.1 → voodoo_framework-2.6.0}/tests/test_websocket.py +0 -0
- {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.
|
|
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)
|
|
@@ -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
|
-
|
|
8
|
-
``
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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`.
|
|
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
|
-
|
|
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()
|