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