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,148 @@
|
|
|
1
|
+
//! Token Counter
|
|
2
|
+
//!
|
|
3
|
+
//! Token counting using tiktoken for accurate context management.
|
|
4
|
+
|
|
5
|
+
use thiserror::Error;
|
|
6
|
+
use tiktoken_rs::{cl100k_base, CoreBPE};
|
|
7
|
+
|
|
8
|
+
/// Token counter errors
|
|
9
|
+
#[derive(Debug, Error)]
|
|
10
|
+
pub enum TokenCounterError {
|
|
11
|
+
#[error("Failed to initialize tokenizer: {0}")]
|
|
12
|
+
InitError(String),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Token counter using tiktoken
|
|
16
|
+
///
|
|
17
|
+
/// Uses cl100k_base encoding (GPT-4, GPT-3.5-turbo, text-embedding-ada-002)
|
|
18
|
+
pub struct TokenCounter {
|
|
19
|
+
bpe: CoreBPE,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl TokenCounter {
|
|
23
|
+
/// Create a new token counter with cl100k_base encoding
|
|
24
|
+
pub fn new() -> Result<Self, TokenCounterError> {
|
|
25
|
+
let bpe = cl100k_base().map_err(|e| TokenCounterError::InitError(e.to_string()))?;
|
|
26
|
+
Ok(Self { bpe })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Count tokens in a string
|
|
30
|
+
pub fn count(&self, text: &str) -> usize {
|
|
31
|
+
self.bpe.encode_with_special_tokens(text).len()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Count tokens for a chat message (includes role overhead)
|
|
35
|
+
///
|
|
36
|
+
/// Approximates the token overhead for chat formatting:
|
|
37
|
+
/// - Each message has ~4 tokens overhead (role, separators)
|
|
38
|
+
/// - System messages have additional overhead
|
|
39
|
+
pub fn count_message(&self, role: &str, content: &str) -> usize {
|
|
40
|
+
let content_tokens = self.count(content);
|
|
41
|
+
let role_overhead = match role {
|
|
42
|
+
"system" => 4,
|
|
43
|
+
"user" => 4,
|
|
44
|
+
"assistant" => 4,
|
|
45
|
+
"function" | "tool" => 5,
|
|
46
|
+
_ => 4,
|
|
47
|
+
};
|
|
48
|
+
content_tokens + role_overhead
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Count tokens for multiple messages
|
|
52
|
+
pub fn count_messages(&self, messages: &[(String, String)]) -> usize {
|
|
53
|
+
messages
|
|
54
|
+
.iter()
|
|
55
|
+
.map(|(role, content)| self.count_message(role, content))
|
|
56
|
+
.sum::<usize>()
|
|
57
|
+
+ 3 // priming tokens
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Truncate text to fit within a token limit
|
|
61
|
+
///
|
|
62
|
+
/// Returns the truncated text and the actual token count.
|
|
63
|
+
pub fn truncate(&self, text: &str, max_tokens: usize) -> (String, usize) {
|
|
64
|
+
let tokens = self.bpe.encode_with_special_tokens(text);
|
|
65
|
+
if tokens.len() <= max_tokens {
|
|
66
|
+
return (text.to_string(), tokens.len());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let truncated_tokens = &tokens[..max_tokens];
|
|
70
|
+
let truncated_text = self.bpe.decode(truncated_tokens.to_vec())
|
|
71
|
+
.unwrap_or_else(|_| text[..text.len() / 2].to_string());
|
|
72
|
+
|
|
73
|
+
(truncated_text, max_tokens)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Split text into chunks of approximately equal token size
|
|
77
|
+
pub fn chunk(&self, text: &str, chunk_size: usize) -> Vec<String> {
|
|
78
|
+
let tokens = self.bpe.encode_with_special_tokens(text);
|
|
79
|
+
let mut chunks = Vec::new();
|
|
80
|
+
|
|
81
|
+
for chunk_tokens in tokens.chunks(chunk_size) {
|
|
82
|
+
if let Ok(chunk_text) = self.bpe.decode(chunk_tokens.to_vec()) {
|
|
83
|
+
chunks.push(chunk_text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
chunks
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Estimate if text will fit within budget
|
|
91
|
+
pub fn will_fit(&self, text: &str, budget: usize) -> bool {
|
|
92
|
+
self.count(text) <= budget
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl Default for TokenCounter {
|
|
97
|
+
fn default() -> Self {
|
|
98
|
+
Self::new().expect("Failed to initialize default token counter")
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#[cfg(test)]
|
|
103
|
+
mod tests {
|
|
104
|
+
use super::*;
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn test_count_tokens() {
|
|
108
|
+
let counter = TokenCounter::new().unwrap();
|
|
109
|
+
|
|
110
|
+
// Empty string
|
|
111
|
+
assert_eq!(counter.count(""), 0);
|
|
112
|
+
|
|
113
|
+
// Simple text
|
|
114
|
+
let count = counter.count("Hello, world!");
|
|
115
|
+
assert!(count > 0);
|
|
116
|
+
assert!(count < 10);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[test]
|
|
120
|
+
fn test_count_message() {
|
|
121
|
+
let counter = TokenCounter::new().unwrap();
|
|
122
|
+
|
|
123
|
+
let content_tokens = counter.count("Hello");
|
|
124
|
+
let message_tokens = counter.count_message("user", "Hello");
|
|
125
|
+
|
|
126
|
+
// Message should have overhead
|
|
127
|
+
assert!(message_tokens > content_tokens);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[test]
|
|
131
|
+
fn test_truncate() {
|
|
132
|
+
let counter = TokenCounter::new().unwrap();
|
|
133
|
+
|
|
134
|
+
let long_text = "This is a long text that we want to truncate to a smaller size.";
|
|
135
|
+
let (truncated, count) = counter.truncate(long_text, 5);
|
|
136
|
+
|
|
137
|
+
assert!(count <= 5);
|
|
138
|
+
assert!(truncated.len() < long_text.len());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn test_will_fit() {
|
|
143
|
+
let counter = TokenCounter::new().unwrap();
|
|
144
|
+
|
|
145
|
+
assert!(counter.will_fit("Hello", 100));
|
|
146
|
+
assert!(!counter.will_fit("Hello ".repeat(1000).as_str(), 10));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
//! Context Window Management
|
|
2
|
+
//!
|
|
3
|
+
//! The ContextWindow manages all segments within token budget limits.
|
|
4
|
+
//!
|
|
5
|
+
//! @see packages/enact-schemas/src/context.schemas.ts
|
|
6
|
+
|
|
7
|
+
use crate::budget::{BudgetHealth, ContextBudget};
|
|
8
|
+
use crate::compactor::{CompactionResult, CompactionStrategyType, Compactor};
|
|
9
|
+
use crate::segment::{ContextSegment, ContextSegmentType};
|
|
10
|
+
use crate::token_counter::TokenCounter;
|
|
11
|
+
use chrono::{DateTime, Utc};
|
|
12
|
+
use enact_core::kernel::ExecutionId;
|
|
13
|
+
use serde::{Deserialize, Serialize};
|
|
14
|
+
use std::time::Instant;
|
|
15
|
+
use thiserror::Error;
|
|
16
|
+
|
|
17
|
+
/// Context window errors
|
|
18
|
+
#[derive(Debug, Error)]
|
|
19
|
+
pub enum ContextWindowError {
|
|
20
|
+
#[error("Token counter error: {0}")]
|
|
21
|
+
TokenCounter(String),
|
|
22
|
+
|
|
23
|
+
#[error("Budget exceeded: need {needed} tokens, only {available} available")]
|
|
24
|
+
BudgetExceeded { needed: usize, available: usize },
|
|
25
|
+
|
|
26
|
+
#[error("Segment budget exceeded for {segment_type:?}: need {needed}, max {max}")]
|
|
27
|
+
SegmentBudgetExceeded {
|
|
28
|
+
segment_type: ContextSegmentType,
|
|
29
|
+
needed: usize,
|
|
30
|
+
max: usize,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
#[error("Compaction failed: {0}")]
|
|
34
|
+
CompactionFailed(String),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Context window state
|
|
38
|
+
///
|
|
39
|
+
/// Matches `contextWindowStateSchema` in @enact/schemas
|
|
40
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
41
|
+
#[serde(rename_all = "camelCase")]
|
|
42
|
+
pub struct ContextWindowState {
|
|
43
|
+
/// Execution ID
|
|
44
|
+
pub execution_id: ExecutionId,
|
|
45
|
+
|
|
46
|
+
/// All segments currently in context
|
|
47
|
+
pub segments: Vec<ContextSegment>,
|
|
48
|
+
|
|
49
|
+
/// Current budget state
|
|
50
|
+
pub budget: ContextBudget,
|
|
51
|
+
|
|
52
|
+
/// Compaction history
|
|
53
|
+
pub compaction_history: Vec<CompactionResult>,
|
|
54
|
+
|
|
55
|
+
/// Number of compactions performed
|
|
56
|
+
pub compaction_count: u32,
|
|
57
|
+
|
|
58
|
+
/// Total tokens saved by compaction
|
|
59
|
+
pub total_tokens_saved: usize,
|
|
60
|
+
|
|
61
|
+
/// Current health status
|
|
62
|
+
pub health: BudgetHealth,
|
|
63
|
+
|
|
64
|
+
/// Last updated timestamp
|
|
65
|
+
pub updated_at: DateTime<Utc>,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Context Window - manages segments within budget
|
|
69
|
+
pub struct ContextWindow {
|
|
70
|
+
/// Execution ID
|
|
71
|
+
execution_id: ExecutionId,
|
|
72
|
+
|
|
73
|
+
/// All segments in the context
|
|
74
|
+
segments: Vec<ContextSegment>,
|
|
75
|
+
|
|
76
|
+
/// Token budget
|
|
77
|
+
budget: ContextBudget,
|
|
78
|
+
|
|
79
|
+
/// Token counter
|
|
80
|
+
token_counter: TokenCounter,
|
|
81
|
+
|
|
82
|
+
/// Compaction history
|
|
83
|
+
compaction_history: Vec<CompactionResult>,
|
|
84
|
+
|
|
85
|
+
/// Next sequence number
|
|
86
|
+
next_sequence: u64,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl ContextWindow {
|
|
90
|
+
/// Create a new context window
|
|
91
|
+
pub fn new(budget: ContextBudget) -> Result<Self, ContextWindowError> {
|
|
92
|
+
let token_counter = TokenCounter::new()
|
|
93
|
+
.map_err(|e| ContextWindowError::TokenCounter(e.to_string()))?;
|
|
94
|
+
|
|
95
|
+
Ok(Self {
|
|
96
|
+
execution_id: budget.execution_id.clone(),
|
|
97
|
+
segments: Vec::new(),
|
|
98
|
+
budget,
|
|
99
|
+
token_counter,
|
|
100
|
+
compaction_history: Vec::new(),
|
|
101
|
+
next_sequence: 0,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Create with a preset budget
|
|
106
|
+
pub fn with_preset_gpt4_128k(execution_id: ExecutionId) -> Result<Self, ContextWindowError> {
|
|
107
|
+
Self::new(ContextBudget::preset_gpt4_128k(execution_id))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Create with Claude 200K preset
|
|
111
|
+
pub fn with_preset_claude_200k(execution_id: ExecutionId) -> Result<Self, ContextWindowError> {
|
|
112
|
+
Self::new(ContextBudget::preset_claude_200k(execution_id))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Create with default (8K) preset
|
|
116
|
+
pub fn with_preset_default(execution_id: ExecutionId) -> Result<Self, ContextWindowError> {
|
|
117
|
+
Self::new(ContextBudget::preset_default(execution_id))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// Get the execution ID
|
|
121
|
+
pub fn execution_id(&self) -> &ExecutionId {
|
|
122
|
+
&self.execution_id
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Get all segments
|
|
126
|
+
pub fn segments(&self) -> &[ContextSegment] {
|
|
127
|
+
&self.segments
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Get the budget
|
|
131
|
+
pub fn budget(&self) -> &ContextBudget {
|
|
132
|
+
&self.budget
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Get mutable budget
|
|
136
|
+
pub fn budget_mut(&mut self) -> &mut ContextBudget {
|
|
137
|
+
&mut self.budget
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Count tokens for text
|
|
141
|
+
pub fn count_tokens(&self, text: &str) -> usize {
|
|
142
|
+
self.token_counter.count(text)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Add a segment with automatic token counting
|
|
146
|
+
pub fn add_segment_auto(&mut self, mut segment: ContextSegment) -> Result<(), ContextWindowError> {
|
|
147
|
+
// Count tokens if not already set
|
|
148
|
+
if segment.token_count == 0 {
|
|
149
|
+
segment.token_count = self.token_counter.count(&segment.content);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
self.add_segment(segment)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Add a segment to the context window
|
|
156
|
+
pub fn add_segment(&mut self, mut segment: ContextSegment) -> Result<(), ContextWindowError> {
|
|
157
|
+
// Check segment budget
|
|
158
|
+
if let Some(seg_budget) = self.budget.get_segment(segment.segment_type) {
|
|
159
|
+
let new_usage = seg_budget.current_tokens + segment.token_count;
|
|
160
|
+
if new_usage > seg_budget.max_tokens {
|
|
161
|
+
return Err(ContextWindowError::SegmentBudgetExceeded {
|
|
162
|
+
segment_type: segment.segment_type,
|
|
163
|
+
needed: segment.token_count,
|
|
164
|
+
max: seg_budget.max_tokens - seg_budget.current_tokens,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check total budget
|
|
170
|
+
let new_total = self.budget.used_tokens + segment.token_count;
|
|
171
|
+
if new_total > self.budget.available_tokens {
|
|
172
|
+
return Err(ContextWindowError::BudgetExceeded {
|
|
173
|
+
needed: segment.token_count,
|
|
174
|
+
available: self.budget.remaining(),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Assign sequence number
|
|
179
|
+
segment.sequence = self.next_sequence;
|
|
180
|
+
self.next_sequence += 1;
|
|
181
|
+
|
|
182
|
+
// Update budget
|
|
183
|
+
self.budget.add_tokens(segment.segment_type, segment.token_count);
|
|
184
|
+
|
|
185
|
+
// Add segment
|
|
186
|
+
self.segments.push(segment);
|
|
187
|
+
|
|
188
|
+
Ok(())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Remove a segment by ID
|
|
192
|
+
pub fn remove_segment(&mut self, segment_id: &str) -> bool {
|
|
193
|
+
if let Some(pos) = self.segments.iter().position(|s| s.id == segment_id) {
|
|
194
|
+
let segment = self.segments.remove(pos);
|
|
195
|
+
self.budget.remove_tokens(segment.segment_type, segment.token_count);
|
|
196
|
+
true
|
|
197
|
+
} else {
|
|
198
|
+
false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Get segments of a specific type
|
|
203
|
+
pub fn segments_of_type(&self, segment_type: ContextSegmentType) -> Vec<&ContextSegment> {
|
|
204
|
+
self.segments
|
|
205
|
+
.iter()
|
|
206
|
+
.filter(|s| s.segment_type == segment_type)
|
|
207
|
+
.collect()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Total tokens currently used
|
|
211
|
+
pub fn used_tokens(&self) -> usize {
|
|
212
|
+
self.budget.used_tokens
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Remaining tokens available
|
|
216
|
+
pub fn remaining_tokens(&self) -> usize {
|
|
217
|
+
self.budget.remaining()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Check if the window needs compaction
|
|
221
|
+
pub fn needs_compaction(&self) -> bool {
|
|
222
|
+
self.budget.is_warning()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Check if the window is in critical state
|
|
226
|
+
pub fn is_critical(&self) -> bool {
|
|
227
|
+
self.budget.is_critical()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Get health status
|
|
231
|
+
pub fn health(&self) -> BudgetHealth {
|
|
232
|
+
self.budget.health()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Compact the context using the given compactor
|
|
236
|
+
pub fn compact(&mut self, compactor: &Compactor) -> Result<CompactionResult, ContextWindowError> {
|
|
237
|
+
let start = Instant::now();
|
|
238
|
+
let tokens_before = self.budget.used_tokens;
|
|
239
|
+
|
|
240
|
+
let result = match compactor.strategy().strategy_type {
|
|
241
|
+
CompactionStrategyType::Truncate => {
|
|
242
|
+
compactor.compact_truncate(&mut self.segments, tokens_before)
|
|
243
|
+
}
|
|
244
|
+
CompactionStrategyType::SlidingWindow => {
|
|
245
|
+
compactor.compact_sliding_window(&mut self.segments)
|
|
246
|
+
}
|
|
247
|
+
_ => {
|
|
248
|
+
// Other strategies not implemented yet
|
|
249
|
+
return Err(ContextWindowError::CompactionFailed(
|
|
250
|
+
format!("Strategy {:?} not implemented", compactor.strategy().strategy_type)
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
let duration_ms = start.elapsed().as_millis() as u64;
|
|
256
|
+
|
|
257
|
+
match result {
|
|
258
|
+
Ok(tokens_removed) => {
|
|
259
|
+
// Recalculate budget
|
|
260
|
+
self.recalculate_budget();
|
|
261
|
+
|
|
262
|
+
let tokens_after = self.budget.used_tokens;
|
|
263
|
+
let segments_compacted = (tokens_removed > 0) as usize;
|
|
264
|
+
|
|
265
|
+
let compaction_result = CompactionResult::success(
|
|
266
|
+
self.execution_id.clone(),
|
|
267
|
+
compactor.strategy().strategy_type,
|
|
268
|
+
tokens_before,
|
|
269
|
+
tokens_after,
|
|
270
|
+
segments_compacted,
|
|
271
|
+
duration_ms,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
self.compaction_history.push(compaction_result.clone());
|
|
275
|
+
Ok(compaction_result)
|
|
276
|
+
}
|
|
277
|
+
Err(e) => {
|
|
278
|
+
let compaction_result = CompactionResult::failure(
|
|
279
|
+
self.execution_id.clone(),
|
|
280
|
+
compactor.strategy().strategy_type,
|
|
281
|
+
tokens_before,
|
|
282
|
+
e.to_string(),
|
|
283
|
+
duration_ms,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
self.compaction_history.push(compaction_result.clone());
|
|
287
|
+
Err(ContextWindowError::CompactionFailed(e.to_string()))
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Recalculate budget from current segments
|
|
293
|
+
fn recalculate_budget(&mut self) {
|
|
294
|
+
// Reset all segment budgets
|
|
295
|
+
for seg_budget in &mut self.budget.segments {
|
|
296
|
+
seg_budget.current_tokens = 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Sum up tokens by segment type
|
|
300
|
+
for segment in &self.segments {
|
|
301
|
+
self.budget.add_tokens(segment.segment_type, segment.token_count);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// Build the context as a single string (for LLM calls)
|
|
306
|
+
pub fn build_context(&self) -> String {
|
|
307
|
+
let mut parts: Vec<&str> = Vec::new();
|
|
308
|
+
|
|
309
|
+
// Sort segments by sequence
|
|
310
|
+
let mut sorted: Vec<&ContextSegment> = self.segments.iter().collect();
|
|
311
|
+
sorted.sort_by_key(|s| s.sequence);
|
|
312
|
+
|
|
313
|
+
for segment in sorted {
|
|
314
|
+
parts.push(&segment.content);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
parts.join("\n\n")
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/// Get the current state (for serialization)
|
|
321
|
+
pub fn state(&self) -> ContextWindowState {
|
|
322
|
+
ContextWindowState {
|
|
323
|
+
execution_id: self.execution_id.clone(),
|
|
324
|
+
segments: self.segments.clone(),
|
|
325
|
+
budget: self.budget.clone(),
|
|
326
|
+
compaction_history: self.compaction_history.clone(),
|
|
327
|
+
compaction_count: self.compaction_history.len() as u32,
|
|
328
|
+
total_tokens_saved: self.compaction_history.iter().map(|r| r.tokens_saved).sum(),
|
|
329
|
+
health: self.budget.health(),
|
|
330
|
+
updated_at: Utc::now(),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[cfg(test)]
|
|
336
|
+
mod tests {
|
|
337
|
+
use super::*;
|
|
338
|
+
|
|
339
|
+
fn test_execution_id() -> ExecutionId {
|
|
340
|
+
ExecutionId::new()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[test]
|
|
344
|
+
fn test_create_window() {
|
|
345
|
+
let budget = ContextBudget::preset_default(test_execution_id());
|
|
346
|
+
let window = ContextWindow::new(budget).unwrap();
|
|
347
|
+
|
|
348
|
+
assert_eq!(window.used_tokens(), 0);
|
|
349
|
+
assert!(window.remaining_tokens() > 0);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#[test]
|
|
353
|
+
fn test_add_segment() {
|
|
354
|
+
let budget = ContextBudget::preset_default(test_execution_id());
|
|
355
|
+
let mut window = ContextWindow::new(budget).unwrap();
|
|
356
|
+
|
|
357
|
+
let segment = ContextSegment::system("You are a helpful assistant.", 10);
|
|
358
|
+
window.add_segment(segment).unwrap();
|
|
359
|
+
|
|
360
|
+
assert_eq!(window.segments().len(), 1);
|
|
361
|
+
assert_eq!(window.used_tokens(), 10);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#[test]
|
|
365
|
+
fn test_health_tracking() {
|
|
366
|
+
let budget = ContextBudget::preset_default(test_execution_id());
|
|
367
|
+
let window = ContextWindow::new(budget).unwrap();
|
|
368
|
+
|
|
369
|
+
assert_eq!(window.health(), BudgetHealth::Healthy);
|
|
370
|
+
assert!(!window.needs_compaction());
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "enact-core"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
description = "Core agent runtime for Enact - Graph-Native AI agents"
|
|
7
|
+
repository.workspace = true
|
|
8
|
+
homepage.workspace = true
|
|
9
|
+
keywords = ["agent", "runtime", "ai", "llm", "graph"]
|
|
10
|
+
categories.workspace = true
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
tokio.workspace = true
|
|
14
|
+
async-trait.workspace = true
|
|
15
|
+
async-stream.workspace = true
|
|
16
|
+
futures.workspace = true
|
|
17
|
+
serde.workspace = true
|
|
18
|
+
serde_json.workspace = true
|
|
19
|
+
tracing.workspace = true
|
|
20
|
+
uuid.workspace = true
|
|
21
|
+
svix-ksuid.workspace = true
|
|
22
|
+
chrono.workspace = true
|
|
23
|
+
thiserror.workspace = true
|
|
24
|
+
anyhow.workspace = true
|
|
25
|
+
reqwest.workspace = true
|
|
26
|
+
tokio-util.workspace = true
|
|
27
|
+
|
|
28
|
+
# Compression & Hashing (feat-04: Artifact Lifecycle)
|
|
29
|
+
miniz_oxide.workspace = true
|
|
30
|
+
sha2.workspace = true
|
|
31
|
+
hex.workspace = true
|
|
32
|
+
aes-gcm.workspace = true
|
|
33
|
+
rand.workspace = true
|
|
34
|
+
|
|
35
|
+
serde_yaml = "0.9.34"
|
|
36
|
+
urlencoding = { workspace = true }
|
|
37
|
+
html-escape = { workspace = true }
|
|
38
|
+
regex = { workspace = true }
|
|
39
|
+
|
|
40
|
+
[dev-dependencies]
|
|
41
|
+
tokio = { workspace = true, features = ["test-util"] }
|
|
42
|
+
tempfile.workspace = true
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# enact-core
|
|
2
|
+
|
|
3
|
+
`enact-core` is the core graph-first agent runtime crate for the Enact
|
|
4
|
+
platform. It provides the execution kernel, graph engine, and streaming
|
|
5
|
+
infrastructure for deterministic, auditable agent execution.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
### Execution Kernel
|
|
10
|
+
- **Reducer-based state machine** - Pure function state transitions for determinism
|
|
11
|
+
- **Tenant isolation** - `TenantContext` required for all executions
|
|
12
|
+
- **Agentic DAG** - Dynamic step discovery with policy-bounded execution
|
|
13
|
+
- **Parallel step execution** - Independent steps execute concurrently via `tokio::join!`
|
|
14
|
+
- **Circular graph detection** - Topological sort validation at compile time
|
|
15
|
+
|
|
16
|
+
### Mid-Execution Control
|
|
17
|
+
- **Inbox integration** - Inject guidance, evidence, and control signals mid-execution
|
|
18
|
+
- **Async cancellation** - Cooperative cancellation via `CancellationToken`
|
|
19
|
+
- **Checkpointing** - Save and restore execution state
|
|
20
|
+
|
|
21
|
+
### Content Protection
|
|
22
|
+
- **ProtectedEventEmitter** - Optional content protection pipeline
|
|
23
|
+
- **PII detection** - Via `enact-guardrails` integration
|
|
24
|
+
- **Context-aware masking** - Different protection based on destination
|
|
25
|
+
|
|
26
|
+
### Tool Execution
|
|
27
|
+
- **ToolExecutor** - Policy-aware tool execution
|
|
28
|
+
- **Policy decision events** - All tool policy decisions emitted for audit trail
|
|
29
|
+
- **Trust levels** - Untrusted, Low, Medium, High, System
|
|
30
|
+
|
|
31
|
+
### Observability
|
|
32
|
+
- **StreamEvent** - AI SDK compatible wire format with `data-*` prefix
|
|
33
|
+
- **EventStore** - Append-only event persistence
|
|
34
|
+
- **Artifact lifecycle** - First-class artifacts with deterministic IDs
|
|
35
|
+
|
|
36
|
+
## Module Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/
|
|
40
|
+
├── kernel/ # Execution kernel (state machine, reducer, events)
|
|
41
|
+
│ ├── kernel.rs # ExecutionKernel entry point
|
|
42
|
+
│ ├── reducer.rs # Pure state transitions
|
|
43
|
+
│ ├── event.rs # ExecutionEvent types
|
|
44
|
+
│ ├── error.rs # Error taxonomy with retry policies
|
|
45
|
+
│ ├── execution_state.rs # ExecutionState, WaitReason
|
|
46
|
+
│ ├── interrupt.rs # Interrupt handling for HITL
|
|
47
|
+
│ └── artifact/ # Artifact lifecycle (feat-04)
|
|
48
|
+
├── context/ # TenantContext, RuntimeContext
|
|
49
|
+
├── flow/ # Execution semantics (sequential, parallel, conditional)
|
|
50
|
+
├── callable/ # Callable trait, LlmCallable
|
|
51
|
+
├── policy/ # ExecutionPolicy, ToolPolicy, TenantPolicy
|
|
52
|
+
├── streaming/ # SSE streaming, EventStore, ProtectedEventEmitter
|
|
53
|
+
├── graph/ # StateGraph, CompiledGraph, Node
|
|
54
|
+
├── tool/ # Tool trait, ToolExecutor, policy enforcement
|
|
55
|
+
├── inbox/ # Mid-execution guidance (INV-INBOX-*)
|
|
56
|
+
├── providers/ # ModelProvider trait
|
|
57
|
+
├── telemetry/ # OpenTelemetry integration
|
|
58
|
+
└── runner/ # Thin wiring shell
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```rust
|
|
64
|
+
use enact_core::prelude::*;
|
|
65
|
+
use enact_core::context::TenantContext;
|
|
66
|
+
|
|
67
|
+
// Create kernel with required tenant context
|
|
68
|
+
let tenant = TenantContext::new(TenantId::from("tenant_123"));
|
|
69
|
+
let kernel = ExecutionKernel::new(tenant)
|
|
70
|
+
.with_inbox(Arc::new(InMemoryInboxStore::new()))
|
|
71
|
+
.with_artifact_store(Arc::new(InMemoryArtifactStore::new()));
|
|
72
|
+
|
|
73
|
+
// Compile and execute graph
|
|
74
|
+
let graph = StateGraph::new("my_agent")
|
|
75
|
+
.add_node("step1", my_callable)
|
|
76
|
+
.add_edge("start", "step1");
|
|
77
|
+
|
|
78
|
+
let compiled = graph.compile()?;
|
|
79
|
+
let result = kernel.execute_graph(&compiled, input).await?;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Invariants
|
|
83
|
+
|
|
84
|
+
See [04-KERNEL_INVARIANTS.md](../../docs/TECHNICAL/04-KERNEL_INVARIANTS.md) for:
|
|
85
|
+
- Execution invariants (determinism, replay, tenant isolation)
|
|
86
|
+
- Graph execution invariants (parallel execution, cycle detection)
|
|
87
|
+
- Inbox invariants (INV-INBOX-001 through INV-INBOX-004)
|
|
88
|
+
- Cancellation invariants (cooperative, propagating, observable)
|
|
89
|
+
- Artifact lifecycle invariants (first-class, deterministic IDs, immutable)
|
|
90
|
+
- Content protection invariants (protected emission, bypass for control)
|
|
91
|
+
- Tool policy invariants (auditable decisions, ToolExecutor enforcement)
|
|
92
|
+
|
|
93
|
+
## Related Documentation
|
|
94
|
+
|
|
95
|
+
- [02-ENACT-CORE-ARCHITECTURE.md](../../docs/TECHNICAL/02-ENACT-CORE-ARCHITECTURE.md) - Full architecture
|
|
96
|
+
- [07-EXECUTION_LIFECYCLE.md](../../docs/TECHNICAL/07-EXECUTION_LIFECYCLE.md) - Execution lifecycle
|
|
97
|
+
- [31-MID-EXECUTION-GUIDANCE.md](../../docs/TECHNICAL/31-MID-EXECUTION-GUIDANCE.md) - Inbox system
|
|
98
|
+
- [17-GUARDRAILS-PROTECTION.md](../../docs/TECHNICAL/17-GUARDRAILS-PROTECTION.md) - PII protection
|