enya-agent 0.1.0
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.
- package/.env.example +20 -0
- package/.github/workflows/ci.yml +70 -0
- package/.github/workflows/publish.yml +250 -0
- package/.gitmodules +3 -0
- package/Cargo.lock +3584 -0
- package/Cargo.toml +97 -0
- package/crates/enact/Cargo.toml +27 -0
- package/crates/enact/src/lib.rs +60 -0
- package/crates/enact-a2a/Cargo.toml +25 -0
- package/crates/enact-a2a/src/lib.rs +411 -0
- package/crates/enact-channels/Cargo.toml +64 -0
- package/crates/enact-channels/examples/README.md +80 -0
- package/crates/enact-channels/examples/channel_bot.rs +169 -0
- package/crates/enact-channels/examples/telegram-echo.rs +34 -0
- package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
- package/crates/enact-channels/src/config.rs +213 -0
- package/crates/enact-channels/src/lib.rs +25 -0
- package/crates/enact-channels/src/runtime.rs +237 -0
- package/crates/enact-channels/src/security/mod.rs +5 -0
- package/crates/enact-channels/src/security/pairing.rs +205 -0
- package/crates/enact-channels/src/teams.rs +601 -0
- package/crates/enact-channels/src/telegram.rs +2833 -0
- package/crates/enact-channels/src/traits.rs +200 -0
- package/crates/enact-channels/src/webhook.rs +262 -0
- package/crates/enact-channels/src/whatsapp.rs +310 -0
- package/crates/enact-cli/Cargo.toml +40 -0
- package/crates/enact-cli/src/commands/doctor.rs +62 -0
- package/crates/enact-cli/src/commands/mod.rs +3 -0
- package/crates/enact-cli/src/commands/run.rs +69 -0
- package/crates/enact-cli/src/commands/serve.rs +81 -0
- package/crates/enact-cli/src/config.rs +2 -0
- package/crates/enact-cli/src/main.rs +79 -0
- package/crates/enact-config/Cargo.toml +36 -0
- package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
- package/crates/enact-config/QUICK_REFERENCE.md +92 -0
- package/crates/enact-config/README.md +107 -0
- package/crates/enact-config/TESTING.md +161 -0
- package/crates/enact-config/examples/test-env-vars.rs +100 -0
- package/crates/enact-config/src/config.rs +399 -0
- package/crates/enact-config/src/encrypted_store.rs +211 -0
- package/crates/enact-config/src/lib.rs +298 -0
- package/crates/enact-config/src/secrets.rs +149 -0
- package/crates/enact-config/src/sync.rs +260 -0
- package/crates/enact-config/test-env-vars.sh +34 -0
- package/crates/enact-config/tests/README.md +99 -0
- package/crates/enact-config/tests/config_integration_test.rs +202 -0
- package/crates/enact-config/tests/security_test.rs +140 -0
- package/crates/enact-context/Cargo.toml +41 -0
- package/crates/enact-context/src/budget.rs +314 -0
- package/crates/enact-context/src/calibrator.rs +535 -0
- package/crates/enact-context/src/compactor.rs +392 -0
- package/crates/enact-context/src/condenser.rs +826 -0
- package/crates/enact-context/src/lib.rs +94 -0
- package/crates/enact-context/src/segment.rs +238 -0
- package/crates/enact-context/src/step_context.rs +645 -0
- package/crates/enact-context/src/token_counter.rs +148 -0
- package/crates/enact-context/src/window.rs +372 -0
- package/crates/enact-core/Cargo.toml +42 -0
- package/crates/enact-core/README.md +98 -0
- package/crates/enact-core/src/background/executor.rs +524 -0
- package/crates/enact-core/src/background/mod.rs +48 -0
- package/crates/enact-core/src/background/target_binding.rs +390 -0
- package/crates/enact-core/src/background/trigger.rs +511 -0
- package/crates/enact-core/src/callable/callable.rs +152 -0
- package/crates/enact-core/src/callable/composite.rs +817 -0
- package/crates/enact-core/src/callable/graph.rs +104 -0
- package/crates/enact-core/src/callable/llm.rs +211 -0
- package/crates/enact-core/src/callable/mod.rs +64 -0
- package/crates/enact-core/src/callable/registry.rs +206 -0
- package/crates/enact-core/src/context/execution_context.rs +757 -0
- package/crates/enact-core/src/context/invocation.rs +99 -0
- package/crates/enact-core/src/context/mod.rs +50 -0
- package/crates/enact-core/src/context/tenant.rs +175 -0
- package/crates/enact-core/src/context/trace.rs +127 -0
- package/crates/enact-core/src/flow/conditional.rs +293 -0
- package/crates/enact-core/src/flow/mod.rs +43 -0
- package/crates/enact-core/src/flow/parallel.rs +437 -0
- package/crates/enact-core/src/flow/repeat.rs +534 -0
- package/crates/enact-core/src/flow/sequential.rs +248 -0
- package/crates/enact-core/src/graph/checkpoint.rs +79 -0
- package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
- package/crates/enact-core/src/graph/compiled.rs +189 -0
- package/crates/enact-core/src/graph/edge.rs +59 -0
- package/crates/enact-core/src/graph/graph_schema.rs +218 -0
- package/crates/enact-core/src/graph/loader.rs +155 -0
- package/crates/enact-core/src/graph/mod.rs +18 -0
- package/crates/enact-core/src/graph/node/function.rs +49 -0
- package/crates/enact-core/src/graph/node/mod.rs +48 -0
- package/crates/enact-core/src/graph/schema.rs +62 -0
- package/crates/enact-core/src/inbox/message.rs +405 -0
- package/crates/enact-core/src/inbox/mod.rs +31 -0
- package/crates/enact-core/src/inbox/store.rs +355 -0
- package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
- package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
- package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
- package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
- package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
- package/crates/enact-core/src/kernel/error.rs +1200 -0
- package/crates/enact-core/src/kernel/event.rs +1394 -0
- package/crates/enact-core/src/kernel/execution_model.rs +831 -0
- package/crates/enact-core/src/kernel/execution_state.rs +189 -0
- package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
- package/crates/enact-core/src/kernel/ids.rs +2086 -0
- package/crates/enact-core/src/kernel/interrupt.rs +125 -0
- package/crates/enact-core/src/kernel/kernel.rs +1283 -0
- package/crates/enact-core/src/kernel/mod.rs +205 -0
- package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
- package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
- package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
- package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
- package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
- package/crates/enact-core/src/kernel/reducer.rs +808 -0
- package/crates/enact-core/src/kernel/replay.rs +153 -0
- package/crates/enact-core/src/lib.rs +413 -0
- package/crates/enact-core/src/memory/episodic.rs +0 -0
- package/crates/enact-core/src/memory/mod.rs +6 -0
- package/crates/enact-core/src/memory/semantic.rs +0 -0
- package/crates/enact-core/src/memory/trait.rs +0 -0
- package/crates/enact-core/src/memory/vector_db.rs +0 -0
- package/crates/enact-core/src/memory/working.rs +0 -0
- package/crates/enact-core/src/policy/execution_policy.rs +292 -0
- package/crates/enact-core/src/policy/filters.rs +458 -0
- package/crates/enact-core/src/policy/input_processor.rs +407 -0
- package/crates/enact-core/src/policy/long_running.rs +134 -0
- package/crates/enact-core/src/policy/mod.rs +193 -0
- package/crates/enact-core/src/policy/pii_input.rs +274 -0
- package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
- package/crates/enact-core/src/policy/tool_policy.rs +407 -0
- package/crates/enact-core/src/providers/mod.rs +63 -0
- package/crates/enact-core/src/providers/trait.rs +292 -0
- package/crates/enact-core/src/runner/callbacks.rs +6 -0
- package/crates/enact-core/src/runner/execution_runner.rs +476 -0
- package/crates/enact-core/src/runner/loop.rs +117 -0
- package/crates/enact-core/src/runner/mod.rs +58 -0
- package/crates/enact-core/src/runner/protected_runner.rs +280 -0
- package/crates/enact-core/src/signal/inmemory.rs +231 -0
- package/crates/enact-core/src/signal/mod.rs +108 -0
- package/crates/enact-core/src/streaming/event_logger.rs +195 -0
- package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
- package/crates/enact-core/src/streaming/mod.rs +108 -0
- package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
- package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
- package/crates/enact-core/src/streaming/protection/context.rs +136 -0
- package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
- package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
- package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
- package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
- package/crates/enact-core/src/streaming/sse.rs +0 -0
- package/crates/enact-core/src/telemetry/exporter.rs +0 -0
- package/crates/enact-core/src/telemetry/init.rs +0 -0
- package/crates/enact-core/src/telemetry/mod.rs +49 -0
- package/crates/enact-core/src/telemetry/spans.rs +245 -0
- package/crates/enact-core/src/tool/agent_tool.rs +177 -0
- package/crates/enact-core/src/tool/browser/mod.rs +0 -0
- package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
- package/crates/enact-core/src/tool/cost.rs +247 -0
- package/crates/enact-core/src/tool/discovery.rs +0 -0
- package/crates/enact-core/src/tool/dispatcher.rs +347 -0
- package/crates/enact-core/src/tool/filesystem.rs +231 -0
- package/crates/enact-core/src/tool/function.rs +99 -0
- package/crates/enact-core/src/tool/git.rs +162 -0
- package/crates/enact-core/src/tool/http.rs +214 -0
- package/crates/enact-core/src/tool/mcp/client.rs +0 -0
- package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
- package/crates/enact-core/src/tool/mod.rs +51 -0
- package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
- package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
- package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
- package/crates/enact-core/src/tool/shell.rs +147 -0
- package/crates/enact-core/src/tool/trait.rs +33 -0
- package/crates/enact-core/src/tool/web_search.rs +277 -0
- package/crates/enact-core/src/util/config.rs +0 -0
- package/crates/enact-core/src/util/errors.rs +0 -0
- package/crates/enact-core/src/util/mod.rs +6 -0
- package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
- package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
- package/crates/enact-core/tests/e2e_test.rs +259 -0
- package/crates/enact-core/tests/graph_test.rs +130 -0
- package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
- package/crates/enact-cron/Cargo.toml +28 -0
- package/crates/enact-cron/src/lib.rs +44 -0
- package/crates/enact-cron/src/schedule.rs +156 -0
- package/crates/enact-cron/src/store.rs +589 -0
- package/crates/enact-cron/src/types.rs +148 -0
- package/crates/enact-gateway/Cargo.toml +31 -0
- package/crates/enact-gateway/README.md +30 -0
- package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
- package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
- package/crates/enact-gateway/src/lib.rs +582 -0
- package/crates/enact-mcp/Cargo.toml +24 -0
- package/crates/enact-mcp/src/lib.rs +178 -0
- package/crates/enact-memory/Cargo.toml +25 -0
- package/crates/enact-memory/src/backend.rs +20 -0
- package/crates/enact-memory/src/chunker.rs +230 -0
- package/crates/enact-memory/src/embeddings.rs +221 -0
- package/crates/enact-memory/src/lib.rs +67 -0
- package/crates/enact-memory/src/markdown.rs +127 -0
- package/crates/enact-memory/src/none.rs +61 -0
- package/crates/enact-memory/src/sqlite.rs +276 -0
- package/crates/enact-memory/src/traits.rs +65 -0
- package/crates/enact-memory/src/vector.rs +198 -0
- package/crates/enact-oauth/Cargo.toml +27 -0
- package/crates/enact-oauth/src/lib.rs +584 -0
- package/crates/enact-observability/Cargo.toml +22 -0
- package/crates/enact-observability/src/lib.rs +197 -0
- package/crates/enact-providers/Cargo.toml +33 -0
- package/crates/enact-providers/examples/hello-agent.rs +33 -0
- package/crates/enact-providers/src/anthropic.rs +182 -0
- package/crates/enact-providers/src/azure.rs +96 -0
- package/crates/enact-providers/src/bridge.rs +221 -0
- package/crates/enact-providers/src/gemini.rs +227 -0
- package/crates/enact-providers/src/http.rs +78 -0
- package/crates/enact-providers/src/lib.rs +53 -0
- package/crates/enact-providers/src/openai_compatible.rs +167 -0
- package/crates/enact-providers/src/openrouter.rs +33 -0
- package/crates/enact-runner/Cargo.toml +24 -0
- package/crates/enact-runner/README.md +76 -0
- package/crates/enact-runner/src/compaction.rs +225 -0
- package/crates/enact-runner/src/config.rs +118 -0
- package/crates/enact-runner/src/lib.rs +63 -0
- package/crates/enact-runner/src/loop_driver.rs +414 -0
- package/crates/enact-runner/src/parser.rs +421 -0
- package/crates/enact-runner/src/retry.rs +262 -0
- package/crates/enact-runner/tests/integration.rs +278 -0
- package/crates/enact-security/Cargo.toml +22 -0
- package/crates/enact-security/src/audit.rs +375 -0
- package/crates/enact-security/src/lib.rs +37 -0
- package/crates/enact-security/src/policy.rs +406 -0
- package/crates/enact-skills/Cargo.toml +25 -0
- package/crates/enact-skills/src/lib.rs +506 -0
- package/crates/enact-tools/Cargo.toml +22 -0
- package/crates/enact-tools/src/file_read.rs +166 -0
- package/crates/enact-tools/src/file_write.rs +216 -0
- package/crates/enact-tools/src/git_operations.rs +513 -0
- package/crates/enact-tools/src/http_request.rs +417 -0
- package/crates/enact-tools/src/lib.rs +104 -0
- package/crates/enact-tools/src/security.rs +227 -0
- package/crates/enact-tools/src/shell.rs +191 -0
- package/crates/enact-tools/src/traits.rs +159 -0
- package/docs/Makefile +74 -0
- package/docs/config.toml +62 -0
- package/docs/content/_index.md +174 -0
- package/docs/content/a2a/_index.md +431 -0
- package/docs/content/api/_index.md +323 -0
- package/docs/content/channels/_index.md +160 -0
- package/docs/content/channels/teams.md +205 -0
- package/docs/content/channels/telegram.md +182 -0
- package/docs/content/channels/webhook.md +423 -0
- package/docs/content/channels/whatsapp.md +240 -0
- package/docs/content/cli/_index.md +261 -0
- package/docs/content/concepts/_index.md +273 -0
- package/docs/content/configuration/_index.md +241 -0
- package/docs/content/cron/_index.md +248 -0
- package/docs/content/developers/_index.md +278 -0
- package/docs/content/getting-started/_index.md +180 -0
- package/docs/content/installation/_index.md +186 -0
- package/docs/content/installation/uninstall.md +101 -0
- package/docs/content/installation/updating.md +120 -0
- package/docs/content/mcp/_index.md +215 -0
- package/docs/content/memory/_index.md +163 -0
- package/docs/content/oauth/_index.md +515 -0
- package/docs/content/providers/_index.md +206 -0
- package/docs/content/roadmap/_index.md +199 -0
- package/docs/content/security/_index.md +219 -0
- package/docs/content/skills/_index.md +228 -0
- package/docs/content/tools/_index.md +485 -0
- package/docs/content/troubleshooting/_index.md +259 -0
- package/docs/content/yaml-schema/_index.md +294 -0
- package/docs/static/giallo-dark.css +91 -0
- package/docs/static/giallo-light.css +91 -0
- package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
- package/docs/themes/tanuki/LICENSE +21 -0
- package/docs/themes/tanuki/README.md +166 -0
- package/docs/themes/tanuki/examples/blog/config.toml +58 -0
- package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
- package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
- package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
- package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
- package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
- package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
- package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
- package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
- package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
- package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
- package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
- package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
- package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
- package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
- package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
- package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
- package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
- package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
- package/docs/themes/tanuki/examples/book/config.toml +17 -0
- package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
- package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
- package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
- package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
- package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
- package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
- package/docs/themes/tanuki/examples/book/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/config.toml +28 -0
- package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
- package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
- package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
- package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
- package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
- package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
- package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
- package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/examples/index.html +2104 -0
- package/docs/themes/tanuki/mise.toml +108 -0
- package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
- package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
- package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
- package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
- package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
- package/docs/themes/tanuki/sass/components/_code.scss +457 -0
- package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
- package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
- package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
- package/docs/themes/tanuki/sass/components/_print.scss +237 -0
- package/docs/themes/tanuki/sass/components/_search.scss +224 -0
- package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
- package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
- package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
- package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
- package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
- package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
- package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
- package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
- package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
- package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
- package/docs/themes/tanuki/sass/style.scss +47 -0
- package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
- package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
- package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
- package/docs/themes/tanuki/screenshot.png +0 -0
- package/docs/themes/tanuki/sentinel.kdl +59 -0
- package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
- package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
- package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
- package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
- package/docs/themes/tanuki/static/js/anchors.js +18 -0
- package/docs/themes/tanuki/static/js/app.js +274 -0
- package/docs/themes/tanuki/static/js/code.js +394 -0
- package/docs/themes/tanuki/static/js/navigation.js +778 -0
- package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
- package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
- package/docs/themes/tanuki/static/js/search.js +215 -0
- package/docs/themes/tanuki/static/js/theme.js +169 -0
- package/docs/themes/tanuki/static/syntax-dark.css +151 -0
- package/docs/themes/tanuki/static/syntax-light.css +151 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
- package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
- package/docs/themes/tanuki/templates/404.html +52 -0
- package/docs/themes/tanuki/templates/base.html +428 -0
- package/docs/themes/tanuki/templates/blog.html +66 -0
- package/docs/themes/tanuki/templates/home.html +108 -0
- package/docs/themes/tanuki/templates/index.html +178 -0
- package/docs/themes/tanuki/templates/landing.html +168 -0
- package/docs/themes/tanuki/templates/macros/nav.html +128 -0
- package/docs/themes/tanuki/templates/macros/posts.html +101 -0
- package/docs/themes/tanuki/templates/macros/ui.html +159 -0
- package/docs/themes/tanuki/templates/page.html +135 -0
- package/docs/themes/tanuki/templates/partials/footer.html +38 -0
- package/docs/themes/tanuki/templates/partials/header.html +366 -0
- package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
- package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
- package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
- package/docs/themes/tanuki/templates/partials/search.html +52 -0
- package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
- package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
- package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
- package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
- package/docs/themes/tanuki/templates/print.html +244 -0
- package/docs/themes/tanuki/templates/section.html +186 -0
- package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
- package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
- package/docs/themes/tanuki/theme.toml +58 -0
- package/examples/hello-agent.rs +55 -0
- package/package.json +36 -0
- package/proto/config.proto +60 -0
- package/proto/events.proto +0 -0
- package/proto/runtime.proto +215 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//! Persistence Layer - Storage Abstractions
|
|
2
|
+
//!
|
|
3
|
+
//! This module defines the core storage traits for the Enact execution engine.
|
|
4
|
+
//! The kernel interacts only with these traits; concrete implementations live
|
|
5
|
+
//! in the `enact-persistence` crate.
|
|
6
|
+
//!
|
|
7
|
+
//! ## Store Roles
|
|
8
|
+
//!
|
|
9
|
+
//! | Store | Role | Mutable? | Authoritative? |
|
|
10
|
+
//! |-------|------|----------|----------------|
|
|
11
|
+
//! | EventStore | Source of truth | Append-only | Yes |
|
|
12
|
+
//! | StateStore | Cache/snapshot | Yes | No |
|
|
13
|
+
//! | VectorStore | Semantic recall | Yes | No |
|
|
14
|
+
//!
|
|
15
|
+
//! ## Key Invariants
|
|
16
|
+
//!
|
|
17
|
+
//! 1. Kernel NEVER imports concrete implementations
|
|
18
|
+
//! 2. All stores implement `StorageBackend` for mode validation
|
|
19
|
+
//! 3. EventStore is append-only and authoritative
|
|
20
|
+
//! 4. StateStore/VectorStore are non-authoritative caches
|
|
21
|
+
//!
|
|
22
|
+
//! @see docs/TECHNICAL/14-PERSISTENCE-LAYER.md
|
|
23
|
+
|
|
24
|
+
mod event_store;
|
|
25
|
+
mod message_store;
|
|
26
|
+
mod state_store;
|
|
27
|
+
mod vector_store;
|
|
28
|
+
|
|
29
|
+
pub use event_store::{EventStore, ExecutionEventData, StoredEvent};
|
|
30
|
+
pub use message_store::{
|
|
31
|
+
CostInfo, ExecutionStats, FinishReason, InMemoryMessageStore, Message, MessageMetadata,
|
|
32
|
+
MessagePart, MessageRole, MessageStore, Thread, TokenUsage,
|
|
33
|
+
};
|
|
34
|
+
pub use state_store::{ExecutionSnapshot, StateStore, StateStoreJsonExt};
|
|
35
|
+
pub use vector_store::{
|
|
36
|
+
CollectionInfo, DistanceMetric, VectorDocument, VectorFilter, VectorSearchResult, VectorStore,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
use async_trait::async_trait;
|
|
40
|
+
|
|
41
|
+
/// Base trait for all storage backends
|
|
42
|
+
///
|
|
43
|
+
/// This trait provides metadata about the backend's capabilities,
|
|
44
|
+
/// enabling runtime mode validation (e.g., air-gapped mode rejection
|
|
45
|
+
/// of network-requiring backends).
|
|
46
|
+
#[async_trait]
|
|
47
|
+
pub trait StorageBackend: Send + Sync {
|
|
48
|
+
/// Human-readable name of this backend (e.g., "sqlite", "postgres", "qdrant")
|
|
49
|
+
fn name(&self) -> &str;
|
|
50
|
+
|
|
51
|
+
/// Does this backend require network access?
|
|
52
|
+
///
|
|
53
|
+
/// Returns `true` for backends like Postgres, Redis, remote Qdrant.
|
|
54
|
+
/// Returns `false` for SQLite, filesystem, embedded Qdrant.
|
|
55
|
+
fn requires_network(&self) -> bool;
|
|
56
|
+
|
|
57
|
+
/// Can this backend operate in air-gapped mode?
|
|
58
|
+
///
|
|
59
|
+
/// Default implementation returns `!requires_network()`.
|
|
60
|
+
fn supports_air_gapped(&self) -> bool {
|
|
61
|
+
!self.requires_network()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Health check - verify the backend is operational
|
|
65
|
+
///
|
|
66
|
+
/// Should return `Ok(())` if the backend is ready to accept operations.
|
|
67
|
+
/// Should return an error if the backend is unavailable or misconfigured.
|
|
68
|
+
async fn health_check(&self) -> anyhow::Result<()>;
|
|
69
|
+
|
|
70
|
+
/// Graceful shutdown
|
|
71
|
+
///
|
|
72
|
+
/// Called when the runtime is shutting down. Implementations should
|
|
73
|
+
/// flush any pending writes and close connections.
|
|
74
|
+
async fn shutdown(&self) -> anyhow::Result<()> {
|
|
75
|
+
Ok(())
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Store guarantees - documented contracts for each store type
|
|
80
|
+
pub mod guarantees {
|
|
81
|
+
//! # Store Guarantees
|
|
82
|
+
//!
|
|
83
|
+
//! These guarantees are contractual and must be tested.
|
|
84
|
+
//!
|
|
85
|
+
//! ## EventStore
|
|
86
|
+
//! - **Durability**: Event is durable before `append()` returns
|
|
87
|
+
//! - **Ordering**: Events are strictly ordered per execution
|
|
88
|
+
//! - **Immutability**: Events are append-only, never modified
|
|
89
|
+
//! - **Authority**: EventStore is the source of truth
|
|
90
|
+
//!
|
|
91
|
+
//! ## StateStore
|
|
92
|
+
//! - **Durability**: Best-effort - may be lost on failure
|
|
93
|
+
//! - **Freshness**: May be stale - always verify with EventStore
|
|
94
|
+
//! - **Authority**: Non-authoritative - cache only
|
|
95
|
+
//! - **Availability**: Designed for fast reads, not durability
|
|
96
|
+
//!
|
|
97
|
+
//! ## VectorStore
|
|
98
|
+
//! - **Consistency**: Eventually consistent
|
|
99
|
+
//! - **Authority**: Non-authoritative - for recall only
|
|
100
|
+
//! - **Availability**: Search may return stale results
|
|
101
|
+
//! - **Durability**: Documents are durable after `upsert()` returns
|
|
102
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
//! StateStore - Hot snapshots and working memory
|
|
2
|
+
//!
|
|
3
|
+
//! The StateStore provides fast access to execution state for resumption.
|
|
4
|
+
//! It is a **cache**, not a source of truth - the EventStore is authoritative.
|
|
5
|
+
//!
|
|
6
|
+
//! ## Guarantees
|
|
7
|
+
//!
|
|
8
|
+
//! - **Durability**: Best-effort - may be lost on failure
|
|
9
|
+
//! - **Freshness**: May be stale - always verify sequence with EventStore
|
|
10
|
+
//! - **Authority**: Non-authoritative - cache only
|
|
11
|
+
//! - **Availability**: Designed for fast reads, not durability
|
|
12
|
+
//!
|
|
13
|
+
//! ## Use Cases
|
|
14
|
+
//!
|
|
15
|
+
//! - Fast execution resumption (avoid replaying all events)
|
|
16
|
+
//! - Working memory during execution
|
|
17
|
+
//! - Temporary state that doesn't need event sourcing
|
|
18
|
+
//!
|
|
19
|
+
//! @see docs/TECHNICAL/14-PERSISTENCE-LAYER.md
|
|
20
|
+
|
|
21
|
+
use async_trait::async_trait;
|
|
22
|
+
use chrono::{DateTime, Utc};
|
|
23
|
+
use serde::{Deserialize, Serialize};
|
|
24
|
+
use std::collections::HashMap;
|
|
25
|
+
use std::time::Duration;
|
|
26
|
+
|
|
27
|
+
/// Extension trait for JSON operations on StateStore
|
|
28
|
+
///
|
|
29
|
+
/// This is separate from StateStore to maintain dyn-compatibility.
|
|
30
|
+
#[async_trait]
|
|
31
|
+
pub trait StateStoreJsonExt: StateStore {
|
|
32
|
+
/// Set a JSON value
|
|
33
|
+
async fn set_json<T: Serialize + Send + Sync>(
|
|
34
|
+
&self,
|
|
35
|
+
key: &str,
|
|
36
|
+
value: &T,
|
|
37
|
+
ttl: Option<Duration>,
|
|
38
|
+
) -> anyhow::Result<()> {
|
|
39
|
+
let bytes = serde_json::to_vec(value)?;
|
|
40
|
+
self.set(key, &bytes, ttl).await
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Get a JSON value
|
|
44
|
+
async fn get_json<T: for<'de> Deserialize<'de>>(&self, key: &str) -> anyhow::Result<Option<T>> {
|
|
45
|
+
match self.get(key).await? {
|
|
46
|
+
Some(bytes) => Ok(Some(serde_json::from_slice(&bytes)?)),
|
|
47
|
+
None => Ok(None),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Blanket implementation for all StateStore implementors
|
|
53
|
+
impl<S: StateStore + ?Sized> StateStoreJsonExt for S {}
|
|
54
|
+
|
|
55
|
+
use crate::kernel::{ExecutionId, ExecutionState, StepId, TenantId};
|
|
56
|
+
|
|
57
|
+
use super::StorageBackend;
|
|
58
|
+
|
|
59
|
+
/// Execution state snapshot
|
|
60
|
+
///
|
|
61
|
+
/// This captures the current state of an execution for fast resumption.
|
|
62
|
+
/// The `sequence_number` should match the EventStore sequence to verify freshness.
|
|
63
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
64
|
+
pub struct ExecutionSnapshot {
|
|
65
|
+
/// The execution this snapshot is for
|
|
66
|
+
pub execution_id: ExecutionId,
|
|
67
|
+
/// Tenant for multi-tenancy isolation
|
|
68
|
+
pub tenant_id: TenantId,
|
|
69
|
+
/// Current execution state
|
|
70
|
+
pub state: ExecutionState,
|
|
71
|
+
/// Currently executing step (if any)
|
|
72
|
+
pub current_step_id: Option<StepId>,
|
|
73
|
+
/// Outputs from completed steps
|
|
74
|
+
pub step_outputs: HashMap<StepId, serde_json::Value>,
|
|
75
|
+
/// Working variables
|
|
76
|
+
pub variables: HashMap<String, serde_json::Value>,
|
|
77
|
+
/// When this snapshot was created
|
|
78
|
+
pub timestamp: DateTime<Utc>,
|
|
79
|
+
/// EventStore sequence number at snapshot time
|
|
80
|
+
///
|
|
81
|
+
/// Used to verify freshness - if EventStore has more events,
|
|
82
|
+
/// the snapshot may be stale.
|
|
83
|
+
pub sequence_number: u64,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl ExecutionSnapshot {
|
|
87
|
+
/// Create a new snapshot
|
|
88
|
+
pub fn new(
|
|
89
|
+
execution_id: ExecutionId,
|
|
90
|
+
tenant_id: TenantId,
|
|
91
|
+
state: ExecutionState,
|
|
92
|
+
sequence_number: u64,
|
|
93
|
+
) -> Self {
|
|
94
|
+
Self {
|
|
95
|
+
execution_id,
|
|
96
|
+
tenant_id,
|
|
97
|
+
state,
|
|
98
|
+
current_step_id: None,
|
|
99
|
+
step_outputs: HashMap::new(),
|
|
100
|
+
variables: HashMap::new(),
|
|
101
|
+
timestamp: Utc::now(),
|
|
102
|
+
sequence_number,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Check if this snapshot is fresh compared to an EventStore sequence
|
|
107
|
+
pub fn is_fresh(&self, event_store_sequence: u64) -> bool {
|
|
108
|
+
self.sequence_number >= event_store_sequence
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// StateStore trait - mutable snapshot cache
|
|
113
|
+
///
|
|
114
|
+
/// Provides fast state access for execution resumption and working memory.
|
|
115
|
+
/// This is NOT the source of truth - always verify freshness against EventStore.
|
|
116
|
+
#[async_trait]
|
|
117
|
+
pub trait StateStore: StorageBackend {
|
|
118
|
+
// =========================================================================
|
|
119
|
+
// Snapshot operations
|
|
120
|
+
// =========================================================================
|
|
121
|
+
|
|
122
|
+
/// Save a state snapshot
|
|
123
|
+
///
|
|
124
|
+
/// # Guarantees
|
|
125
|
+
/// - Best-effort durability
|
|
126
|
+
/// - May be overwritten by subsequent saves
|
|
127
|
+
/// - May be lost on backend failure
|
|
128
|
+
async fn save_snapshot(&self, snapshot: ExecutionSnapshot) -> anyhow::Result<()>;
|
|
129
|
+
|
|
130
|
+
/// Load the latest snapshot for an execution
|
|
131
|
+
///
|
|
132
|
+
/// Returns `None` if no snapshot exists.
|
|
133
|
+
///
|
|
134
|
+
/// # Important
|
|
135
|
+
/// Always check `snapshot.sequence_number` against EventStore to verify freshness.
|
|
136
|
+
async fn load_snapshot(
|
|
137
|
+
&self,
|
|
138
|
+
execution_id: &ExecutionId,
|
|
139
|
+
) -> anyhow::Result<Option<ExecutionSnapshot>>;
|
|
140
|
+
|
|
141
|
+
/// Delete a snapshot
|
|
142
|
+
///
|
|
143
|
+
/// Called when an execution completes or is cleaned up.
|
|
144
|
+
async fn delete_snapshot(&self, execution_id: &ExecutionId) -> anyhow::Result<()>;
|
|
145
|
+
|
|
146
|
+
// =========================================================================
|
|
147
|
+
// Key-value operations (working memory)
|
|
148
|
+
// =========================================================================
|
|
149
|
+
|
|
150
|
+
/// Set a key-value pair
|
|
151
|
+
///
|
|
152
|
+
/// # Arguments
|
|
153
|
+
/// * `key` - The key (should be namespaced, e.g., "exec:{id}:var:{name}")
|
|
154
|
+
/// * `value` - The value as bytes
|
|
155
|
+
/// * `ttl` - Optional time-to-live (auto-delete after this duration)
|
|
156
|
+
async fn set(&self, key: &str, value: &[u8], ttl: Option<Duration>) -> anyhow::Result<()>;
|
|
157
|
+
|
|
158
|
+
/// Get a value by key
|
|
159
|
+
///
|
|
160
|
+
/// Returns `None` if the key doesn't exist or has expired.
|
|
161
|
+
async fn get(&self, key: &str) -> anyhow::Result<Option<Vec<u8>>>;
|
|
162
|
+
|
|
163
|
+
/// Delete a key
|
|
164
|
+
async fn delete(&self, key: &str) -> anyhow::Result<()>;
|
|
165
|
+
|
|
166
|
+
/// Check if a key exists
|
|
167
|
+
async fn exists(&self, key: &str) -> anyhow::Result<bool> {
|
|
168
|
+
Ok(self.get(key).await?.is_some())
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// =========================================================================
|
|
173
|
+
// Batch operations
|
|
174
|
+
// =========================================================================
|
|
175
|
+
|
|
176
|
+
/// Delete all state for an execution
|
|
177
|
+
///
|
|
178
|
+
/// Cleans up snapshot and any keys prefixed with the execution ID.
|
|
179
|
+
async fn delete_execution_state(&self, execution_id: &ExecutionId) -> anyhow::Result<()> {
|
|
180
|
+
self.delete_snapshot(execution_id).await
|
|
181
|
+
// Implementations may also clean up related keys
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// List snapshots for a tenant (for cleanup/admin)
|
|
185
|
+
async fn list_snapshots(
|
|
186
|
+
&self,
|
|
187
|
+
tenant_id: &TenantId,
|
|
188
|
+
limit: usize,
|
|
189
|
+
) -> anyhow::Result<Vec<ExecutionId>>;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[cfg(test)]
|
|
193
|
+
mod tests {
|
|
194
|
+
use super::*;
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_snapshot_freshness() {
|
|
198
|
+
let snapshot = ExecutionSnapshot::new(
|
|
199
|
+
ExecutionId::new(),
|
|
200
|
+
TenantId::from("test"),
|
|
201
|
+
ExecutionState::Running,
|
|
202
|
+
10,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
assert!(snapshot.is_fresh(10));
|
|
206
|
+
assert!(snapshot.is_fresh(5));
|
|
207
|
+
assert!(!snapshot.is_fresh(15));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn test_snapshot_serialization() {
|
|
212
|
+
let mut snapshot = ExecutionSnapshot::new(
|
|
213
|
+
ExecutionId::new(),
|
|
214
|
+
TenantId::from("test"),
|
|
215
|
+
ExecutionState::Running,
|
|
216
|
+
5,
|
|
217
|
+
);
|
|
218
|
+
snapshot
|
|
219
|
+
.variables
|
|
220
|
+
.insert("foo".to_string(), serde_json::json!("bar"));
|
|
221
|
+
|
|
222
|
+
let json = serde_json::to_string(&snapshot).unwrap();
|
|
223
|
+
let parsed: ExecutionSnapshot = serde_json::from_str(&json).unwrap();
|
|
224
|
+
|
|
225
|
+
assert_eq!(parsed.state, ExecutionState::Running);
|
|
226
|
+
assert_eq!(parsed.variables.get("foo").unwrap(), "bar");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
//! VectorStore - Semantic memory and RAG
|
|
2
|
+
//!
|
|
3
|
+
//! The VectorStore provides semantic search capabilities for long-term memory
|
|
4
|
+
//! and retrieval-augmented generation (RAG).
|
|
5
|
+
//!
|
|
6
|
+
//! ## Guarantees
|
|
7
|
+
//!
|
|
8
|
+
//! - **Consistency**: Eventually consistent
|
|
9
|
+
//! - **Authority**: Non-authoritative - for recall only
|
|
10
|
+
//! - **Availability**: Search may return stale results
|
|
11
|
+
//! - **Durability**: Documents are durable after `upsert()` returns
|
|
12
|
+
//!
|
|
13
|
+
//! ## Use Cases
|
|
14
|
+
//!
|
|
15
|
+
//! - Long-term agent memory
|
|
16
|
+
//! - Document retrieval for RAG
|
|
17
|
+
//! - Semantic search over execution history
|
|
18
|
+
//!
|
|
19
|
+
//! @see docs/TECHNICAL/14-PERSISTENCE-LAYER.md
|
|
20
|
+
|
|
21
|
+
use async_trait::async_trait;
|
|
22
|
+
use serde::{Deserialize, Serialize};
|
|
23
|
+
use std::collections::HashMap;
|
|
24
|
+
|
|
25
|
+
use super::StorageBackend;
|
|
26
|
+
|
|
27
|
+
/// A document with embedding for vector storage
|
|
28
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
29
|
+
pub struct VectorDocument {
|
|
30
|
+
/// Unique identifier for this document
|
|
31
|
+
pub id: String,
|
|
32
|
+
/// The text content
|
|
33
|
+
pub content: String,
|
|
34
|
+
/// Pre-computed embedding vector
|
|
35
|
+
///
|
|
36
|
+
/// Dimensions must match the collection's configured dimension.
|
|
37
|
+
pub embedding: Vec<f32>,
|
|
38
|
+
/// Arbitrary metadata for filtering
|
|
39
|
+
pub metadata: HashMap<String, serde_json::Value>,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl VectorDocument {
|
|
43
|
+
/// Create a new document
|
|
44
|
+
pub fn new(id: impl Into<String>, content: impl Into<String>, embedding: Vec<f32>) -> Self {
|
|
45
|
+
Self {
|
|
46
|
+
id: id.into(),
|
|
47
|
+
content: content.into(),
|
|
48
|
+
embedding,
|
|
49
|
+
metadata: HashMap::new(),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Add metadata
|
|
54
|
+
pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
|
|
55
|
+
self.metadata.insert(key.into(), value);
|
|
56
|
+
self
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Search result with similarity score
|
|
61
|
+
#[derive(Debug, Clone)]
|
|
62
|
+
pub struct VectorSearchResult {
|
|
63
|
+
/// The matched document
|
|
64
|
+
pub document: VectorDocument,
|
|
65
|
+
/// Similarity score (higher = more similar)
|
|
66
|
+
///
|
|
67
|
+
/// Score semantics depend on the distance metric:
|
|
68
|
+
/// - Cosine: 0.0 to 1.0 (1.0 = identical)
|
|
69
|
+
/// - Euclidean: 0.0 to infinity (0.0 = identical)
|
|
70
|
+
pub score: f32,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Single filter condition
|
|
74
|
+
#[derive(Debug, Clone)]
|
|
75
|
+
pub struct FilterCondition {
|
|
76
|
+
pub key: String,
|
|
77
|
+
pub value: serde_json::Value,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl FilterCondition {
|
|
81
|
+
pub fn new(key: impl Into<String>, value: serde_json::Value) -> Self {
|
|
82
|
+
Self {
|
|
83
|
+
key: key.into(),
|
|
84
|
+
value,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Filter for vector search
|
|
90
|
+
#[derive(Debug, Clone, Default)]
|
|
91
|
+
pub struct VectorFilter {
|
|
92
|
+
/// Metadata conditions (all must match)
|
|
93
|
+
pub conditions: HashMap<String, serde_json::Value>,
|
|
94
|
+
/// Any-of conditions (at least one must match)
|
|
95
|
+
pub any: Vec<FilterCondition>,
|
|
96
|
+
/// None-of conditions (must not match)
|
|
97
|
+
pub none: Vec<FilterCondition>,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl VectorFilter {
|
|
101
|
+
/// Create a new empty filter
|
|
102
|
+
pub fn new() -> Self {
|
|
103
|
+
Self::default()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Add a condition
|
|
107
|
+
pub fn with_condition(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
|
|
108
|
+
self.conditions.insert(key.into(), value);
|
|
109
|
+
self
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Add an any-of condition
|
|
113
|
+
pub fn with_any_condition(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
|
|
114
|
+
self.any.push(FilterCondition::new(key, value));
|
|
115
|
+
self
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Add a none-of condition
|
|
119
|
+
pub fn with_none_condition(
|
|
120
|
+
mut self,
|
|
121
|
+
key: impl Into<String>,
|
|
122
|
+
value: serde_json::Value,
|
|
123
|
+
) -> Self {
|
|
124
|
+
self.none.push(FilterCondition::new(key, value));
|
|
125
|
+
self
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Check if filter is empty
|
|
129
|
+
pub fn is_empty(&self) -> bool {
|
|
130
|
+
self.conditions.is_empty() && self.any.is_empty() && self.none.is_empty()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// VectorStore trait - semantic memory
|
|
135
|
+
///
|
|
136
|
+
/// Provides vector similarity search for RAG and long-term memory.
|
|
137
|
+
#[async_trait]
|
|
138
|
+
pub trait VectorStore: StorageBackend {
|
|
139
|
+
// =========================================================================
|
|
140
|
+
// Collection management
|
|
141
|
+
// =========================================================================
|
|
142
|
+
|
|
143
|
+
/// Create a collection if it doesn't exist
|
|
144
|
+
///
|
|
145
|
+
/// # Arguments
|
|
146
|
+
/// * `collection` - Collection name
|
|
147
|
+
/// * `dimension` - Vector dimension (must match embeddings)
|
|
148
|
+
async fn ensure_collection(&self, collection: &str, dimension: usize) -> anyhow::Result<()>;
|
|
149
|
+
|
|
150
|
+
/// Delete a collection and all its documents
|
|
151
|
+
async fn delete_collection(&self, collection: &str) -> anyhow::Result<()>;
|
|
152
|
+
|
|
153
|
+
/// List all collections
|
|
154
|
+
async fn list_collections(&self) -> anyhow::Result<Vec<String>>;
|
|
155
|
+
|
|
156
|
+
/// Check if a collection exists
|
|
157
|
+
async fn collection_exists(&self, collection: &str) -> anyhow::Result<bool>;
|
|
158
|
+
|
|
159
|
+
// =========================================================================
|
|
160
|
+
// Document operations
|
|
161
|
+
// =========================================================================
|
|
162
|
+
|
|
163
|
+
/// Upsert a document (insert or update)
|
|
164
|
+
///
|
|
165
|
+
/// If a document with the same ID exists, it is replaced.
|
|
166
|
+
async fn upsert(&self, collection: &str, document: VectorDocument) -> anyhow::Result<()>;
|
|
167
|
+
|
|
168
|
+
/// Upsert multiple documents atomically
|
|
169
|
+
async fn upsert_batch(
|
|
170
|
+
&self,
|
|
171
|
+
collection: &str,
|
|
172
|
+
documents: Vec<VectorDocument>,
|
|
173
|
+
) -> anyhow::Result<()>;
|
|
174
|
+
|
|
175
|
+
/// Get a document by ID
|
|
176
|
+
async fn get(&self, collection: &str, id: &str) -> anyhow::Result<Option<VectorDocument>>;
|
|
177
|
+
|
|
178
|
+
/// Delete a document by ID
|
|
179
|
+
async fn delete(&self, collection: &str, id: &str) -> anyhow::Result<()>;
|
|
180
|
+
|
|
181
|
+
/// Delete multiple documents by ID
|
|
182
|
+
async fn delete_batch(&self, collection: &str, ids: &[String]) -> anyhow::Result<()>;
|
|
183
|
+
|
|
184
|
+
// =========================================================================
|
|
185
|
+
// Search operations
|
|
186
|
+
// =========================================================================
|
|
187
|
+
|
|
188
|
+
/// Search for similar documents
|
|
189
|
+
///
|
|
190
|
+
/// # Arguments
|
|
191
|
+
/// * `collection` - Collection to search
|
|
192
|
+
/// * `query_embedding` - Query vector (must match collection dimension)
|
|
193
|
+
/// * `limit` - Maximum number of results
|
|
194
|
+
/// * `filter` - Optional metadata filter
|
|
195
|
+
///
|
|
196
|
+
/// # Returns
|
|
197
|
+
/// Results ordered by similarity (highest score first)
|
|
198
|
+
async fn search(
|
|
199
|
+
&self,
|
|
200
|
+
collection: &str,
|
|
201
|
+
query_embedding: &[f32],
|
|
202
|
+
limit: usize,
|
|
203
|
+
filter: Option<VectorFilter>,
|
|
204
|
+
) -> anyhow::Result<Vec<VectorSearchResult>>;
|
|
205
|
+
|
|
206
|
+
/// Search with score threshold
|
|
207
|
+
///
|
|
208
|
+
/// Only returns results with score >= min_score.
|
|
209
|
+
async fn search_with_threshold(
|
|
210
|
+
&self,
|
|
211
|
+
collection: &str,
|
|
212
|
+
query_embedding: &[f32],
|
|
213
|
+
limit: usize,
|
|
214
|
+
min_score: f32,
|
|
215
|
+
filter: Option<VectorFilter>,
|
|
216
|
+
) -> anyhow::Result<Vec<VectorSearchResult>> {
|
|
217
|
+
let results = self.search(collection, query_embedding, limit, filter).await?;
|
|
218
|
+
Ok(results.into_iter().filter(|r| r.score >= min_score).collect())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// =========================================================================
|
|
222
|
+
// Utility operations
|
|
223
|
+
// =========================================================================
|
|
224
|
+
|
|
225
|
+
/// Count documents in a collection
|
|
226
|
+
async fn count(&self, collection: &str) -> anyhow::Result<u64>;
|
|
227
|
+
|
|
228
|
+
/// Get collection info (dimension, count, etc.)
|
|
229
|
+
async fn collection_info(&self, collection: &str) -> anyhow::Result<CollectionInfo>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Information about a collection
|
|
233
|
+
#[derive(Debug, Clone)]
|
|
234
|
+
pub struct CollectionInfo {
|
|
235
|
+
/// Collection name
|
|
236
|
+
pub name: String,
|
|
237
|
+
/// Vector dimension
|
|
238
|
+
pub dimension: usize,
|
|
239
|
+
/// Number of documents
|
|
240
|
+
pub count: u64,
|
|
241
|
+
/// Distance metric used
|
|
242
|
+
pub distance_metric: DistanceMetric,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Distance metric for similarity calculation
|
|
246
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
247
|
+
pub enum DistanceMetric {
|
|
248
|
+
/// Cosine similarity (default)
|
|
249
|
+
#[default]
|
|
250
|
+
Cosine,
|
|
251
|
+
/// Euclidean distance
|
|
252
|
+
Euclidean,
|
|
253
|
+
/// Dot product
|
|
254
|
+
DotProduct,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[cfg(test)]
|
|
258
|
+
mod tests {
|
|
259
|
+
use super::*;
|
|
260
|
+
|
|
261
|
+
#[test]
|
|
262
|
+
fn test_document_creation() {
|
|
263
|
+
let doc = VectorDocument::new("doc1", "Hello world", vec![0.1, 0.2, 0.3])
|
|
264
|
+
.with_metadata("source", serde_json::json!("test"))
|
|
265
|
+
.with_metadata("page", serde_json::json!(1));
|
|
266
|
+
|
|
267
|
+
assert_eq!(doc.id, "doc1");
|
|
268
|
+
assert_eq!(doc.content, "Hello world");
|
|
269
|
+
assert_eq!(doc.embedding.len(), 3);
|
|
270
|
+
assert_eq!(doc.metadata.get("source").unwrap(), "test");
|
|
271
|
+
assert_eq!(doc.metadata.get("page").unwrap(), 1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
fn test_filter_creation() {
|
|
276
|
+
let filter = VectorFilter::new()
|
|
277
|
+
.with_condition("tenant_id", serde_json::json!("acme"))
|
|
278
|
+
.with_condition("status", serde_json::json!("active"))
|
|
279
|
+
.with_any_condition("visibility", serde_json::json!("team:legal"))
|
|
280
|
+
.with_none_condition("denyScopes", serde_json::json!("team:blocked"));
|
|
281
|
+
|
|
282
|
+
assert!(!filter.is_empty());
|
|
283
|
+
assert_eq!(filter.conditions.len(), 2);
|
|
284
|
+
assert_eq!(filter.any.len(), 1);
|
|
285
|
+
assert_eq!(filter.none.len(), 1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#[test]
|
|
289
|
+
fn test_document_serialization() {
|
|
290
|
+
let doc = VectorDocument::new("doc1", "Test content", vec![0.1, 0.2])
|
|
291
|
+
.with_metadata("key", serde_json::json!("value"));
|
|
292
|
+
|
|
293
|
+
let json = serde_json::to_string(&doc).unwrap();
|
|
294
|
+
let parsed: VectorDocument = serde_json::from_str(&json).unwrap();
|
|
295
|
+
|
|
296
|
+
assert_eq!(parsed.id, "doc1");
|
|
297
|
+
assert_eq!(parsed.embedding, vec![0.1, 0.2]);
|
|
298
|
+
}
|
|
299
|
+
}
|