@vellumai/assistant 0.8.12 → 0.9.0-staging.2
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/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# Workflows — Authoring Guide
|
|
2
|
+
|
|
3
|
+
The workflow engine lets the assistant author a short JS/TS script that runs in a
|
|
4
|
+
sandbox and fans work out across many parallel, ephemeral **leaf agents**. A
|
|
5
|
+
workflow is the right tool when a task decomposes into a lot of similar small
|
|
6
|
+
sub-tasks that can run concurrently — score every item in a list, extract a field
|
|
7
|
+
from each of a hundred documents, draft-then-verify a batch — and you want the
|
|
8
|
+
results orchestrated deterministically and reported back when the whole run
|
|
9
|
+
finishes.
|
|
10
|
+
|
|
11
|
+
Workflows are gated behind the `workflows` feature flag (default **off**). The
|
|
12
|
+
`run_workflow` / `manage_workflows` tools are served by the flag-gated `workflows`
|
|
13
|
+
bundled skill rather than as always-on tools — load it with `skill_load` and invoke
|
|
14
|
+
its tools via `skill_execute`. When the flag is off, the skill is absent, the
|
|
15
|
+
management routes 404, and the scheduler rejects `workflow`-mode jobs.
|
|
16
|
+
|
|
17
|
+
- Engine code: `assistant/src/workflows/`
|
|
18
|
+
- Skill (tool surface): `assistant/src/config/bundled-skills/workflows/`
|
|
19
|
+
- Architecture overview: [`ARCHITECTURE.md` § Workflow Orchestration Engine](../../ARCHITECTURE.md#workflow-orchestration-engine)
|
|
20
|
+
- Manual e2e runbook: [`workflows-testing.md`](./workflows-testing.md)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why it works this way (design rationale)
|
|
25
|
+
|
|
26
|
+
### The sandbox is hooks-only because scripts may be authored from untrusted input
|
|
27
|
+
|
|
28
|
+
A workflow script can be written by the assistant **after** it has read untrusted
|
|
29
|
+
content — a hostile email, a web page, a shared document. The script must
|
|
30
|
+
therefore be unable to do anything on its own. It runs in a fresh QuickJS-WASM VM
|
|
31
|
+
per run with **no** `fetch`, `XMLHttpRequest`, `WebSocket`, `process`, `Bun`,
|
|
32
|
+
`require`, no dynamic `import()`, no timers, no filesystem, and no network. The
|
|
33
|
+
only way a script affects the outside world is through the host functions the
|
|
34
|
+
engine injects (`agent`, `parallel`, …). There are no ambient capabilities to
|
|
35
|
+
escalate, and the sandbox stops the script from reaching around its declared
|
|
36
|
+
capabilities.
|
|
37
|
+
|
|
38
|
+
### The capability declaration is the single consent point
|
|
39
|
+
|
|
40
|
+
A run declares **once**, up front, which side-effecting tools and host functions
|
|
41
|
+
its leaves may use and whether they may speak in the assistant's persona. That
|
|
42
|
+
declaration is the only place consent is given for the whole run — there are **no
|
|
43
|
+
per-call permission prompts inside a running workflow**. This is deliberate: a
|
|
44
|
+
run may spawn hundreds of leaves, and prompting per call would be unworkable and
|
|
45
|
+
would defeat the point of unattended fan-out. Leaves always get a curated
|
|
46
|
+
read-only baseline for free; anything that writes, sends, or executes must be
|
|
47
|
+
named in the manifest.
|
|
48
|
+
|
|
49
|
+
### The runaway guard is the agent cap — by design, no dollar kill-switch
|
|
50
|
+
|
|
51
|
+
The only structural limit on a run is the **agent cap** (`maxAgentsPerRun`,
|
|
52
|
+
default 500): the total number of leaves a single run may spawn. There is
|
|
53
|
+
intentionally **no spend/dollar kill-switch**. The cap bounds the blast radius in
|
|
54
|
+
a way that is deterministic and resume-safe (it counts agents, not wall-clock or
|
|
55
|
+
cost), and leaves default to a cost-optimized model. Concurrency is separately
|
|
56
|
+
bounded by `maxConcurrentLeaves` (default 6).
|
|
57
|
+
|
|
58
|
+
### Scripts must be deterministic so runs can resume
|
|
59
|
+
|
|
60
|
+
Every leaf call is journaled by a deterministic sequence number and an input
|
|
61
|
+
hash. If the assistant restarts mid-run, resuming the same `runId` **replays
|
|
62
|
+
the unchanged prefix from the journal** instead of re-spawning agents — so a
|
|
63
|
+
long run survives a deploy or crash without redoing (or re-paying for) completed
|
|
64
|
+
work. (Resume is explicit, not automatic — see
|
|
65
|
+
[Recovering a crashed run](#recovering-a-crashed-run).) That guarantee only
|
|
66
|
+
holds if the script is deterministic, so `Date.now()`, `Math.random()`, and
|
|
67
|
+
argless `new Date()` **throw**. Pass any timestamps or seeds in through `args`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## The script model
|
|
72
|
+
|
|
73
|
+
### Scripts are SYNCHRONOUS — never use `await`
|
|
74
|
+
|
|
75
|
+
This is the single most important authoring fact. Host functions are _asyncified_:
|
|
76
|
+
calling one suspends the entire VM until the host-side promise settles, then
|
|
77
|
+
resumes the VM with the value. From the script's perspective every host call is
|
|
78
|
+
**synchronous** — you call it and the result comes back directly:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const r = agent("Summarize this thread."); // r is the result, right here
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Do **not** write `await agent(...)`, and do **not** make the script `async`.
|
|
85
|
+
Asyncify can only suspend the main evaluation stack, never a promise
|
|
86
|
+
continuation, so an `async`/`await` script would deadlock on its second host
|
|
87
|
+
call. Write plain straight-line code.
|
|
88
|
+
|
|
89
|
+
### Every script starts with a literal `meta`
|
|
90
|
+
|
|
91
|
+
The first thing in a script must be a pure-literal export — no computed values,
|
|
92
|
+
template strings, or concatenation:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
export const meta = {
|
|
96
|
+
name: "triage-inbox",
|
|
97
|
+
description: "Triage and label inbox messages",
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`meta` is extracted **statically**, without executing the script (the source is
|
|
102
|
+
untrusted), so it must be a plain object literal with string `name` and
|
|
103
|
+
`description`. The `name` is how a saved workflow is referenced by `workflow(name)`
|
|
104
|
+
and the scheduler.
|
|
105
|
+
|
|
106
|
+
The script's result is whatever it `return`s at the top level. End with
|
|
107
|
+
`return <result>;` — the script body runs as a function, so a bare trailing
|
|
108
|
+
expression (e.g. `result;`) is **discarded** and the run finishes with no
|
|
109
|
+
result. Always `return` the value you want surfaced:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const result = agent(`Write the final summary: ${JSON.stringify(parts)}`);
|
|
113
|
+
return result; // returned as the run result
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Host API reference
|
|
119
|
+
|
|
120
|
+
All functions are synchronous from the script's perspective.
|
|
121
|
+
|
|
122
|
+
| Function | Returns | Notes |
|
|
123
|
+
| ---------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `agent(prompt, opts?)` | the leaf's result | Runs ONE leaf. **Throws** on leaf failure. |
|
|
125
|
+
| `leaf(prompt, opts?)` | a leaf descriptor | Runs nothing on its own; used inside `parallel`/`map`/`pipeline`. |
|
|
126
|
+
| `parallel(specs)` | `results[]` | Runs an array of `leaf(...)` descriptors concurrently (capped at `maxConcurrentLeaves`), results in input order. A failed leaf becomes `null` (never throws). |
|
|
127
|
+
| `map(items, build)` | `results[]` | `build(item, i)` returns a `leaf(...)` descriptor (or a bare prompt string) per item; runs them like `parallel`. |
|
|
128
|
+
| `pipeline(items, ...stages)` | `results[]` | Each `stage(prev, i)` returns a `leaf(...)` descriptor or a plain value, where `prev` is the prior stage's result at index `i`. Per-stage barrier (see below). |
|
|
129
|
+
| `phase(title)` | — | Marks a named phase; surfaced as a progress event. |
|
|
130
|
+
| `log(msg)` | — | Emits a progress log line. |
|
|
131
|
+
| `usage()` | `{ agentsSpawned, inputTokens, outputTokens }` | Live snapshot so a script can self-moderate. |
|
|
132
|
+
| `workflow(name, args?)` | the child's result | Runs a SAVED workflow inline, depth 1 only (see Nesting). |
|
|
133
|
+
| `args` | the run input | The `args` object passed to `run_workflow`. |
|
|
134
|
+
|
|
135
|
+
### `agent` vs `parallel` failure semantics
|
|
136
|
+
|
|
137
|
+
`agent(...)` is for a single sequential leaf and **throws** if that leaf fails —
|
|
138
|
+
an unhandled throw fails the whole run. `parallel(...)` is the fan-out primitive
|
|
139
|
+
and **never throws on a single leaf**: a failed leaf is `null` in the results
|
|
140
|
+
array, so a batch survives a few bad items. `map` and `pipeline` are built on
|
|
141
|
+
`parallel` and share that null-on-failure behavior.
|
|
142
|
+
|
|
143
|
+
### `pipeline` has a per-stage barrier
|
|
144
|
+
|
|
145
|
+
`pipeline(items, stageA, stageB)` runs `stageA` across all items in parallel,
|
|
146
|
+
**waits for all of stage A to finish**, then runs `stageB` across stage A's
|
|
147
|
+
results. There is no cross-stage streaming in v1 — item _n_ does not advance to
|
|
148
|
+
stage B early just because its stage A finished first. Each `stage(prev, i)`
|
|
149
|
+
callback receives the prior stage's result for index `i`. This barrier is a
|
|
150
|
+
consequence of the single-threaded VM and is honest about its cost: a pipeline is
|
|
151
|
+
only as fast as the slowest leaf in each stage.
|
|
152
|
+
|
|
153
|
+
### Leaf options (`opts` for `agent` / `leaf`)
|
|
154
|
+
|
|
155
|
+
| Option | Type | Effect |
|
|
156
|
+
| --------- | -------------------------- | --------------------------------------------------------------------------------------- |
|
|
157
|
+
| `schema` | JSON Schema object literal | Forces structured output via a tool. A schema leaf runs with **no tools**. |
|
|
158
|
+
| `label` | string | Short display/diagnostic label for the leaf. |
|
|
159
|
+
| `profile` | string | Overrides the model profile. Must exist in `llm.profiles` or the leaf throws. |
|
|
160
|
+
| `persona` | boolean | `true` makes the leaf speak as the assistant (identity + memory). Default is anonymous. |
|
|
161
|
+
|
|
162
|
+
#### `schema` is a JSON Schema literal, not Zod
|
|
163
|
+
|
|
164
|
+
A script runs in the sandbox and cannot hold a host-side Zod object, so a leaf's
|
|
165
|
+
`schema` is a plain **JSON Schema object literal**. The engine builds a forced
|
|
166
|
+
`tool_choice` call whose synthetic tool input is that schema, validates the
|
|
167
|
+
model's output against it, and returns the structured object. A leaf with a
|
|
168
|
+
`schema` is a pure judge/extractor — it gets **no tools**:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
leaf(`Score this option 0-10 for fit: ${opt}`, {
|
|
172
|
+
schema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: { score: { type: "number" } },
|
|
175
|
+
required: ["score"],
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### `persona` vs anonymous leaves, and profile resolution
|
|
181
|
+
|
|
182
|
+
By default a leaf is **anonymous**: a minimal task-scoped system prompt, no
|
|
183
|
+
assistant identity, no memory pipeline. Use anonymous leaves for impartial
|
|
184
|
+
judging, scoring, and extraction of input — the bulk of fan-out work.
|
|
185
|
+
|
|
186
|
+
`persona: true` opts the leaf into **persona mode**: it carries the assistant's
|
|
187
|
+
identity system prompt and runs the same memory-injection pipeline a normal turn
|
|
188
|
+
uses, so its output is authentically the assistant's voice (e.g. drafting a reply
|
|
189
|
+
to be sent). This is the costly path — use it for the small number of leaves whose
|
|
190
|
+
output is meant to be _in the assistant's voice_, not for bulk judging.
|
|
191
|
+
|
|
192
|
+
Model profile resolution:
|
|
193
|
+
|
|
194
|
+
- An explicit `profile` always wins and is validated up front — an unknown
|
|
195
|
+
profile throws (a deliberate, loud failure rather than a silent downgrade).
|
|
196
|
+
- With no explicit `profile`, a **persona** leaf mirrors the main agent: the
|
|
197
|
+
workspace `activeProfile` floats above the call-site default (a deleted/stale
|
|
198
|
+
active profile degrades gracefully to the default).
|
|
199
|
+
- With no explicit `profile`, an **anonymous** leaf uses the shipped
|
|
200
|
+
`workflowLeaf` call-site default (cost-optimized).
|
|
201
|
+
|
|
202
|
+
No leaf — anonymous or persona — ever creates a conversation row, jsonl mirror,
|
|
203
|
+
title job, or turn broadcast. Leaves are ephemeral.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Capability manifest semantics
|
|
208
|
+
|
|
209
|
+
The `capabilities` argument to `run_workflow` is the single consent point:
|
|
210
|
+
|
|
211
|
+
```jsonc
|
|
212
|
+
{
|
|
213
|
+
"tools": ["file_write", "gmail_send"], // side-effecting tools granted to leaves
|
|
214
|
+
"hostFunctions": [], // host-function names the run may invoke
|
|
215
|
+
"persona": true, // grant leaves persona (identity + memory) access
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Resolution: the leaf tool set is the **read-only baseline ∪ declared `tools`**,
|
|
220
|
+
minus a forbidden set.
|
|
221
|
+
|
|
222
|
+
- **Read-only baseline** (always available, no declaration needed): `file_read`,
|
|
223
|
+
`file_list`, `recall`, `web_search`. The baseline is auto-granted with no
|
|
224
|
+
launch approval, so it carries only read-only tools. `web_fetch` is **not**
|
|
225
|
+
here — it is classified as a side-effect tool (its URL can exfiltrate read data
|
|
226
|
+
or trigger external actions), so a run that needs it must declare it (which
|
|
227
|
+
arms the threshold-aware launch approval gate).
|
|
228
|
+
- **Declared tools** must exist in the tool registry — an unknown name is a hard
|
|
229
|
+
authoring error, not a silent drop.
|
|
230
|
+
- **Forbidden tools** can never be granted, even if declared (declaring one is a
|
|
231
|
+
hard error): `subagent_spawn`, `run_workflow`, `manage_workflows`,
|
|
232
|
+
`manage_secure_command_tool`, `run_authenticated_command`,
|
|
233
|
+
`make_authenticated_request`. The first four are recursion vectors or
|
|
234
|
+
human-in-the-loop install paths that must not be delegated to an unattended
|
|
235
|
+
leaf. The two CES tools can return `cesApprovalRequired`, which `ToolExecutor`
|
|
236
|
+
resolves by bridging an interactive approval and retrying with a grant; a leaf
|
|
237
|
+
executes `tool.execute()` directly (bypassing that post-processing), so it
|
|
238
|
+
would see the raw approval-required result as an error. They stay forbidden
|
|
239
|
+
until leaf invocations run the executor's post-processing.
|
|
240
|
+
|
|
241
|
+
Side-effecting tools (`file_write`, sends, shell, …) and `persona` are **not** in
|
|
242
|
+
the baseline; a run must declare them. Once declared, every leaf may use them with
|
|
243
|
+
no further prompting.
|
|
244
|
+
|
|
245
|
+
### Launch and resume approval are threshold-aware
|
|
246
|
+
|
|
247
|
+
Declaring any side-effecting tool or host function arms a **launch approval**: the
|
|
248
|
+
single point at which the user consents to the whole run. It is threshold-aware —
|
|
249
|
+
at the full-access posture (auto-approve threshold `high`) it does **not** prompt;
|
|
250
|
+
in normal posture it prompts once. A read-only run (no declared side effects)
|
|
251
|
+
never prompts.
|
|
252
|
+
|
|
253
|
+
The same posture gates **resume** of a run whose stored manifest granted side
|
|
254
|
+
effects, since resuming restarts the unfinished side-effecting leaves:
|
|
255
|
+
|
|
256
|
+
- **Conversationally** (`manage_workflows` action `resume`): full access bypasses;
|
|
257
|
+
normal posture re-prompts for fresh approval.
|
|
258
|
+
- **Over HTTP** (`POST /v1/workflows/runs/:id/resume`, and the
|
|
259
|
+
`vellum workflows resume` CLI on top of it): full access proceeds; normal
|
|
260
|
+
posture is **refused** (403) and the caller is directed to resume through the
|
|
261
|
+
assistant, since the route has no prompt channel.
|
|
262
|
+
|
|
263
|
+
A read-only run resumes freely regardless of posture. The check is
|
|
264
|
+
`isFullAccessThreshold` in `assistant/src/permissions/threshold.ts`, applied in
|
|
265
|
+
`permission-checker.ts` and `workflow-routes.ts`.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Worked examples
|
|
270
|
+
|
|
271
|
+
### Triage a list with `map`
|
|
272
|
+
|
|
273
|
+
Score and label each inbox item in parallel (anonymous schema leaves), then write
|
|
274
|
+
one summary in the assistant's voice (a single persona leaf). The item list is
|
|
275
|
+
passed in via `args` — never fetched inside the script.
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
export const meta = {
|
|
279
|
+
name: "triage-inbox",
|
|
280
|
+
description: "Score and summarize inbox items",
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
phase("score");
|
|
284
|
+
const scored = map(args.items, (item) =>
|
|
285
|
+
leaf(
|
|
286
|
+
`Rate this message's urgency 0-10 and give a one-line reason:\n${item.subject}\n${item.body}`,
|
|
287
|
+
{
|
|
288
|
+
label: `score:${item.id}`,
|
|
289
|
+
schema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
urgency: { type: "number" },
|
|
293
|
+
reason: { type: "string" },
|
|
294
|
+
},
|
|
295
|
+
required: ["urgency", "reason"],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
phase("summarize");
|
|
302
|
+
const summary = agent(
|
|
303
|
+
`Here are scored inbox items. Write a short triage summary for the user, ` +
|
|
304
|
+
`highlighting anything urgent:\n${JSON.stringify(scored)}`,
|
|
305
|
+
{ persona: true },
|
|
306
|
+
);
|
|
307
|
+
return summary;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
A failed scoring leaf shows up as `null` in `scored`; the run continues.
|
|
311
|
+
|
|
312
|
+
### Find, then verify, with `pipeline`
|
|
313
|
+
|
|
314
|
+
A two-stage pipeline with a barrier: stage 1 extracts a candidate answer from each
|
|
315
|
+
document; stage 2 verifies each candidate. Stage 2 only starts once all of stage 1
|
|
316
|
+
has finished.
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
export const meta = {
|
|
320
|
+
name: "find-and-verify",
|
|
321
|
+
description: "Extract then verify a fact per document",
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const verified = pipeline(
|
|
325
|
+
args.documents,
|
|
326
|
+
|
|
327
|
+
// Stage 1: extract a candidate from each document.
|
|
328
|
+
(doc) =>
|
|
329
|
+
leaf(
|
|
330
|
+
`Extract the contract end date from this document, or "unknown":\n${doc.text}`,
|
|
331
|
+
{
|
|
332
|
+
label: `extract:${doc.id}`,
|
|
333
|
+
schema: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: { endDate: { type: "string" } },
|
|
336
|
+
required: ["endDate"],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
),
|
|
340
|
+
|
|
341
|
+
// Stage 2: `prev` is stage 1's result for this index.
|
|
342
|
+
(prev, i) =>
|
|
343
|
+
leaf(
|
|
344
|
+
`A prior pass extracted end date "${prev?.endDate}" from document ` +
|
|
345
|
+
`"${args.documents[i].id}". Confirm or correct it, and rate your confidence 0-1.`,
|
|
346
|
+
{
|
|
347
|
+
label: `verify:${args.documents[i].id}`,
|
|
348
|
+
schema: {
|
|
349
|
+
type: "object",
|
|
350
|
+
properties: {
|
|
351
|
+
endDate: { type: "string" },
|
|
352
|
+
confidence: { type: "number" },
|
|
353
|
+
},
|
|
354
|
+
required: ["endDate", "confidence"],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
return verified;
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Granting a side-effecting tool
|
|
364
|
+
|
|
365
|
+
To let leaves write files, declare the tool in the manifest passed to
|
|
366
|
+
`run_workflow`:
|
|
367
|
+
|
|
368
|
+
```jsonc
|
|
369
|
+
{
|
|
370
|
+
"script": "...",
|
|
371
|
+
"args": {
|
|
372
|
+
"items": [
|
|
373
|
+
/* ... */
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
"capabilities": { "tools": ["file_write"] },
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Inside the script, a leaf that needs to write gets `file_write` automatically (no
|
|
381
|
+
schema, so it runs the tool path):
|
|
382
|
+
|
|
383
|
+
```js
|
|
384
|
+
agent(`Write a per-item report file for: ${JSON.stringify(item)}`, {
|
|
385
|
+
label: `report:${item.id}`,
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Saved workflows (library) and the scheduler
|
|
392
|
+
|
|
393
|
+
### Saving and invoking by name
|
|
394
|
+
|
|
395
|
+
A saved workflow is a normal script at `<workspace>/workflows/<name>.workflow.ts`.
|
|
396
|
+
It is resolved by its `meta.name` first, then by filename base. Invoke it by name
|
|
397
|
+
instead of an inline script:
|
|
398
|
+
|
|
399
|
+
- From the tool: `run_workflow({ name: "triage-inbox", args: { … } })`.
|
|
400
|
+
- Inline from another script: `workflow("triage-inbox", { … })`.
|
|
401
|
+
|
|
402
|
+
### Nesting is depth-1 only
|
|
403
|
+
|
|
404
|
+
A top-level script may call `workflow(name, args)` to run a saved workflow inline;
|
|
405
|
+
the child draws from the **same** seq counter, agent cap, journal, and signal, so
|
|
406
|
+
determinism and resume carry across the boundary. A child workflow may **not**
|
|
407
|
+
call `workflow()` — nesting deeper than one level throws.
|
|
408
|
+
|
|
409
|
+
### Scheduler `workflow` mode
|
|
410
|
+
|
|
411
|
+
A scheduled job can trigger a saved workflow by name (e.g. "triage the inbox every
|
|
412
|
+
morning"). Each `workflow`-mode schedule carries a **persisted capability manifest**
|
|
413
|
+
(`capabilities_json` on `cron_jobs`), consented to **once at schedule creation**:
|
|
414
|
+
`schedule_create` accepts a `capabilities` manifest, validates it against the same
|
|
415
|
+
forbidden/unknown checks `run_workflow` applies, and — if it grants side effects —
|
|
416
|
+
arms the threshold-aware approval at creation time. Both firing paths (the
|
|
417
|
+
scheduler's auto-fire and the run-now `POST /v1/schedules/:id/run` route) execute
|
|
418
|
+
the run under that stored manifest. Legacy or null-manifest schedules fall back to
|
|
419
|
+
the read-only baseline. The trigger records success once the run starts;
|
|
420
|
+
completion/failure is surfaced out-of-band via workflow events and the completion
|
|
421
|
+
wake.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Tools, routes, and CLI
|
|
426
|
+
|
|
427
|
+
### Tools (served by the `workflows` skill)
|
|
428
|
+
|
|
429
|
+
Reached via the skill (`skill_load` then `skill_execute`), not as always-on tools.
|
|
430
|
+
|
|
431
|
+
- **`run_workflow`** — `{ script?, name?, args?, capabilities?, label? }` (exactly
|
|
432
|
+
one of `script`/`name`). Returns `{ runId }` immediately; the run is
|
|
433
|
+
asynchronous and you are notified in the conversation when it completes. **Do
|
|
434
|
+
not poll.**
|
|
435
|
+
- **`manage_workflows`** — `{ action: "status" | "abort" | "resume" | "list_runs"
|
|
436
|
+
| "list_profiles", run_id? }`. `status`/`abort`/`resume` require `run_id`.
|
|
437
|
+
`list_profiles` returns `{ profiles, activeProfile }` — the defined LLM profile
|
|
438
|
+
names plus the workspace active profile, used to pick a valid leaf `profile`.
|
|
439
|
+
|
|
440
|
+
### Routes (read/abort/resume, all 404 when the flag is off)
|
|
441
|
+
|
|
442
|
+
| Method | Path | Purpose |
|
|
443
|
+
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
444
|
+
| `GET` | `/v1/workflows` | List saved (named) workflows. |
|
|
445
|
+
| `GET` | `/v1/workflows/runs` | List recent runs (newest first); `?limit`, `?status`. |
|
|
446
|
+
| `GET` | `/v1/workflows/runs/:id` | Get one run. |
|
|
447
|
+
| `POST` | `/v1/workflows/runs/:id/abort` | Signal an in-flight run to abort. |
|
|
448
|
+
| `POST` | `/v1/workflows/runs/:id/resume` | Resume an interrupted run (refuses a side-effecting run in normal posture; proceeds at full access). |
|
|
449
|
+
|
|
450
|
+
### CLI
|
|
451
|
+
|
|
452
|
+
```
|
|
453
|
+
vellum workflows list # saved (named) workflows
|
|
454
|
+
vellum workflows runs # recent runs (--limit, --status)
|
|
455
|
+
vellum workflows show <run-id> # one run's status + counts
|
|
456
|
+
vellum workflows abort <run-id> # abort an in-flight run
|
|
457
|
+
vellum workflows resume <run-id> # resume an interrupted run
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
All subcommands accept `--assistant <name>` to target a specific instance.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Configuration
|
|
465
|
+
|
|
466
|
+
Engine caps live under `workflows.*` in assistant config:
|
|
467
|
+
|
|
468
|
+
| Key | Default | Meaning |
|
|
469
|
+
| ---------------------- | ------- | ------------------------------------------------------------- |
|
|
470
|
+
| `maxAgentsPerRun` | 500 | Total leaves a single run may spawn (the runaway guard). |
|
|
471
|
+
| `maxConcurrentLeaves` | 6 | Max leaves in flight within one run. |
|
|
472
|
+
| `maxConcurrentRuns` | 3 | Max workflow runs in flight at once. |
|
|
473
|
+
| `journalRetentionDays` | 30 | How long finished runs' journals are retained before pruning. |
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Persistence and resume
|
|
478
|
+
|
|
479
|
+
Run state lives in two tables (migration 284):
|
|
480
|
+
|
|
481
|
+
- **`workflow_runs`** — one row per run: status (`running` / `completed` /
|
|
482
|
+
`failed` / `aborted` / `cap_exceeded` / `interrupted`), agent/token counts,
|
|
483
|
+
script source + hash, capability manifest, and the originating conversation.
|
|
484
|
+
- **`workflow_journal`** — append-only `(run_id, seq)` log of every leaf call.
|
|
485
|
+
|
|
486
|
+
Leaf cost is attributed in `llm_usage_events` under `call_site = 'workflowLeaf'`,
|
|
487
|
+
so a run's spend is queryable after the fact.
|
|
488
|
+
|
|
489
|
+
On resume (re-invoking the same `runId`), a journal entry whose `(run_id, seq)`
|
|
490
|
+
and input hash match a completed prior call is replayed from cache without
|
|
491
|
+
re-spawning the leaf — the longest-unchanged-prefix replays, and only changed or
|
|
492
|
+
not-yet-run leaves execute.
|
|
493
|
+
|
|
494
|
+
### Recovering a crashed run
|
|
495
|
+
|
|
496
|
+
Resume is **not automatic**. If the assistant restarts mid-run, the run row is
|
|
497
|
+
left `running`; at startup the assistant reconciles every such orphaned row to
|
|
498
|
+
`interrupted` (status only — the agent/token accounting is preserved so the agent
|
|
499
|
+
cap still carries across the restart). An `interrupted` run sits there until you
|
|
500
|
+
explicitly resume it:
|
|
501
|
+
|
|
502
|
+
- **From the assistant**: `manage_workflows` with `action: "resume"` and the
|
|
503
|
+
`run_id`.
|
|
504
|
+
- **From the CLI**: `vellum workflows resume <run-id>`.
|
|
505
|
+
|
|
506
|
+
Resuming re-invokes the engine with the same `runId`: it replays the completed
|
|
507
|
+
prefix from the journal and continues from the first unfinished leaf, under the
|
|
508
|
+
run's originally-declared capabilities and the same structural agent cap. Only
|
|
509
|
+
`interrupted` runs are resumable; a `completed` / `failed` / `aborted` run is
|
|
510
|
+
terminal.
|
|
@@ -51,7 +51,7 @@ echo/
|
|
|
51
51
|
## Install locally
|
|
52
52
|
|
|
53
53
|
The assistant scans `<workspaceDir>/plugins/*` (e.g.
|
|
54
|
-
|
|
54
|
+
`$VELLUM_WORKSPACE_DIR/plugins/`) for subdirectories containing a `package.json`
|
|
55
55
|
and loads each one during assistant startup. Dropping (or symlinking) this
|
|
56
56
|
directory in place is enough to enable it.
|
|
57
57
|
|
|
@@ -60,8 +60,8 @@ directory in place is enough to enable it.
|
|
|
60
60
|
From the repo root:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
mkdir -p
|
|
64
|
-
ln -s "$(pwd)/assistant/examples/plugins/echo"
|
|
63
|
+
mkdir -p "$VELLUM_WORKSPACE_DIR"/plugins
|
|
64
|
+
ln -s "$(pwd)/assistant/examples/plugins/echo" "$VELLUM_WORKSPACE_DIR"/plugins/echo
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Symlinks let you edit the plugin in-place and restart the assistant to
|
|
@@ -69,7 +69,7 @@ pick up changes.
|
|
|
69
69
|
|
|
70
70
|
### Option 2 — standalone copy
|
|
71
71
|
|
|
72
|
-
A plain `cp -R` of this directory into
|
|
72
|
+
A plain `cp -R` of this directory into `$VELLUM_WORKSPACE_DIR/plugins/echo/`
|
|
73
73
|
works as-is. The hooks import their types from the public `@vellumai/plugin-api`
|
|
74
74
|
specifier, which the daemon materializes as a workspace-level shim before it
|
|
75
75
|
loads any plugin — so the copied directory resolves it without any path
|
|
@@ -109,7 +109,7 @@ You should see one line per hook invocation, similar to:
|
|
|
109
109
|
Remove the symlink (or the copied directory) and restart the assistant:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
rm
|
|
112
|
+
rm "$VELLUM_WORKSPACE_DIR"/plugins/echo
|
|
113
113
|
vellum restart
|
|
114
114
|
```
|
|
115
115
|
|
package/knip.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"src/api/index.ts!"
|
|
9
9
|
],
|
|
10
10
|
"project": ["src/**/*.ts!", "src/**/*.tsx!", "scripts/**/*.ts"],
|
|
11
|
+
"ignore": ["src/config/preloaded-apps/**"],
|
|
11
12
|
"ignoreDependencies": [
|
|
12
13
|
"@microsoft/api-extractor",
|
|
13
14
|
"@vellumai/ces-client",
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"@resvg/resvg-js-darwin-arm64",
|
|
23
24
|
"@resvg/resvg-js-darwin-x64",
|
|
24
25
|
"@vellumai/skill-host-contracts",
|
|
26
|
+
"ajv",
|
|
25
27
|
"madge"
|
|
26
28
|
]
|
|
27
29
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway → daemon inbound payload contract.
|
|
3
|
+
*
|
|
4
|
+
* Zod schema defining the wire format for messages forwarded from the
|
|
5
|
+
* gateway to the daemon via `POST /v1/channels/inbound`. Both services
|
|
6
|
+
* import from here so the contract is enforced at compile time.
|
|
7
|
+
*
|
|
8
|
+
* The gateway constructs this payload in `forwardToRuntime()` from the
|
|
9
|
+
* normalized `GatewayInboundEvent`; the daemon validates and consumes
|
|
10
|
+
* it in `handleChannelInbound()`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Command intent (channel-initiated commands, e.g. Telegram /start)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export const CommandIntentSchema = z.object({
|
|
20
|
+
type: z.string(),
|
|
21
|
+
payload: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type CommandIntent = z.infer<typeof CommandIntentSchema>;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Source metadata — structured fields forwarded from the gateway's
|
|
28
|
+
// normalized inbound event. Replaces the untyped Record<string, unknown>.
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export const SourceMetadataSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
/** Provider-assigned update/event ID. */
|
|
34
|
+
updateId: z.string().optional(),
|
|
35
|
+
/** Provider message ID (e.g. Slack message `ts`). */
|
|
36
|
+
messageId: z.string().optional(),
|
|
37
|
+
/** Provider chat type (e.g. Telegram "private", "group"). */
|
|
38
|
+
chatType: z.string().optional(),
|
|
39
|
+
/** Thread/conversation-group ID (e.g. Slack `thread_ts`). */
|
|
40
|
+
threadId: z.string().optional(),
|
|
41
|
+
/** Channel name (e.g. Slack channel display name). */
|
|
42
|
+
channelName: z.string().optional(),
|
|
43
|
+
/** Actor's language code (e.g. "en", "es"). */
|
|
44
|
+
languageCode: z.string().optional(),
|
|
45
|
+
/** Whether the actor is a bot. */
|
|
46
|
+
isBot: z.boolean().optional(),
|
|
47
|
+
/** Actor's IANA timezone (e.g. "America/Los_Angeles"). */
|
|
48
|
+
timezone: z.string().optional(),
|
|
49
|
+
/** Human-readable timezone label (e.g. "Pacific Daylight Time"). */
|
|
50
|
+
timezoneLabel: z.string().optional(),
|
|
51
|
+
/** UTC offset in seconds. */
|
|
52
|
+
timezoneOffsetSeconds: z.number().optional(),
|
|
53
|
+
/** Slack-specific: actor is from an external workspace (Slack Connect). */
|
|
54
|
+
isStranger: z.boolean().optional(),
|
|
55
|
+
/** Slack-specific: actor is a guest / restricted account. */
|
|
56
|
+
isRestricted: z.boolean().optional(),
|
|
57
|
+
/** Transport-layer hints forwarded from the channel adapter. */
|
|
58
|
+
hints: z.array(z.string()).optional(),
|
|
59
|
+
/** Transport-layer UX brief. */
|
|
60
|
+
uxBrief: z.string().optional(),
|
|
61
|
+
/** Client-provided timezone for date formatting. */
|
|
62
|
+
clientTimezone: z.string().optional(),
|
|
63
|
+
/** Channel command intent (e.g. Telegram /start). */
|
|
64
|
+
commandIntent: CommandIntentSchema.optional(),
|
|
65
|
+
/** Slack-specific: whether the bot was @-mentioned. */
|
|
66
|
+
slackBotMentioned: z.boolean().optional(),
|
|
67
|
+
/** Slack workspace/team ID. */
|
|
68
|
+
account: z.string().optional(),
|
|
69
|
+
|
|
70
|
+
// Email-specific fields
|
|
71
|
+
/** Email subject line. */
|
|
72
|
+
emailSubject: z.string().optional(),
|
|
73
|
+
/** Email recipient address. */
|
|
74
|
+
emailRecipient: z.string().optional(),
|
|
75
|
+
/** Email In-Reply-To header. */
|
|
76
|
+
emailInReplyTo: z.string().optional(),
|
|
77
|
+
/** Email References header. */
|
|
78
|
+
emailReferences: z.string().optional(),
|
|
79
|
+
})
|
|
80
|
+
.passthrough();
|
|
81
|
+
|
|
82
|
+
export type SourceMetadata = z.infer<typeof SourceMetadataSchema>;
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Runtime inbound payload — the full wire format
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
export const RuntimeInboundPayloadSchema = z.object({
|
|
89
|
+
sourceChannel: z.string(),
|
|
90
|
+
interface: z.string(),
|
|
91
|
+
conversationExternalId: z.string(),
|
|
92
|
+
externalMessageId: z.string(),
|
|
93
|
+
content: z.string(),
|
|
94
|
+
isEdit: z.boolean().optional(),
|
|
95
|
+
callbackQueryId: z.string().optional(),
|
|
96
|
+
callbackData: z.string().optional(),
|
|
97
|
+
actorDisplayName: z.string().optional(),
|
|
98
|
+
actorExternalId: z.string(),
|
|
99
|
+
actorUsername: z.string().optional(),
|
|
100
|
+
sourceMetadata: SourceMetadataSchema.optional(),
|
|
101
|
+
attachmentIds: z.array(z.string()).optional(),
|
|
102
|
+
replyCallbackUrl: z.string().optional(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export type RuntimeInboundPayload = z.infer<typeof RuntimeInboundPayloadSchema>;
|