workflow 5.0.0-beta.47 → 5.0.0-beta.49
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/dist/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1 -1
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- package/docs/ai/defining-tools.mdx +1 -1
- package/docs/ai/index.mdx +1 -1
- package/docs/ai/sleep-and-delays.mdx +1 -1
- package/docs/ai/streaming-updates-from-tools.mdx +2 -2
- package/docs/api-reference/workflow-api/get-run.mdx +10 -0
- package/docs/api-reference/workflow-api/start.mdx +1 -0
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +185 -12
- package/docs/configuration/cli-and-web-ui.mdx +62 -3
- package/docs/configuration/runtime-tuning.mdx +19 -4
- package/docs/configuration/worlds.mdx +8 -0
- package/docs/errors/run-expired.mdx +85 -0
- package/docs/foundations/streaming.mdx +28 -0
- package/docs/getting-started/express.mdx +1 -1
- package/docs/getting-started/fastify.mdx +1 -1
- package/docs/getting-started/hono.mdx +1 -1
- package/docs/getting-started/nuxt.mdx +1 -1
- package/docs/getting-started/python.mdx +113 -24
- package/docs/getting-started/vite.mdx +1 -1
- package/docs/how-it-works/understanding-directives.mdx +1 -1
- package/docs/observability/attributes.mdx +23 -1
- package/docs/observability/index.mdx +1 -2
- package/docs/observability/meta.json +1 -1
- package/docs/observability/retention.mdx +93 -0
- package/docs/observability/tracing.mdx +1 -1
- package/docs/whats-new.mdx +2 -2
- package/package.json +14 -14
package/dist/api-workflow.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { CancelRunOptions, Event, StartOptions, StopSleepOptions, StopSleepResult, WorkflowReadableStreamOptions, WorkflowRun, } from '@workflow/core/runtime';
|
|
1
|
+
export type { CancelRunOptions, Event, StartOptions, StopSleepOptions, StopSleepResult, WorkflowReadableStreamOptions, WorkflowRun, WorkflowRunWritableStreamOptions, } from '@workflow/core/runtime';
|
|
2
2
|
export { Run } from '@workflow/core/runtime/run';
|
|
3
3
|
export { start } from '@workflow/core/runtime/start';
|
|
4
4
|
export declare const getRun: () => never;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-workflow.d.ts","sourceRoot":"","sources":["../src/api-workflow.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,6BAA6B,EAC7B,WAAW,
|
|
1
|
+
{"version":3,"file":"api-workflow.d.ts","sourceRoot":"","sources":["../src/api-workflow.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,6BAA6B,EAC7B,WAAW,EACX,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAQrD,eAAO,MAAM,MAAM,aAA+B,CAAC;AACnD,eAAO,MAAM,cAAc,aAAuC,CAAC;AACnE,eAAO,MAAM,UAAU,aAAmC,CAAC;AAC3D,eAAO,MAAM,aAAa,aAAsC,CAAC;AACjE,eAAO,MAAM,OAAO,aAAgC,CAAC"}
|
package/dist/api-workflow.js
CHANGED
|
@@ -8,4 +8,4 @@ export const getHookByToken = () => workflowStub('getHookByToken');
|
|
|
8
8
|
export const resumeHook = () => workflowStub('resumeHook');
|
|
9
9
|
export const resumeWebhook = () => workflowStub('resumeWebhook');
|
|
10
10
|
export const runStep = () => workflowStub('runStep');
|
|
11
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLXdvcmtmbG93LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS13b3JrZmxvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFXQSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBRXJELE1BQU0sWUFBWSxHQUFHLENBQUMsSUFBWSxFQUFFLEVBQUU7SUFDcEMsTUFBTSxJQUFJLEtBQUssQ0FDYixnRUFBZ0UsSUFBSSwyRkFBMkYsQ0FDaEssQ0FBQztBQUNKLENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDbkQsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDM0QsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUNqRSxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHR5cGUge1xuICBDYW5jZWxSdW5PcHRpb25zLFxuICBFdmVudCxcbiAgU3RhcnRPcHRpb25zLFxuICBTdG9wU2xlZXBPcHRpb25zLFxuICBTdG9wU2xlZXBSZXN1bHQsXG4gIFdvcmtmbG93UmVhZGFibGVTdHJlYW1PcHRpb25zLFxuICBXb3JrZmxvd1J1bixcbiAgV29ya2Zsb3dSdW5Xcml0YWJsZVN0cmVhbU9wdGlvbnMsXG59IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUnO1xuXG5leHBvcnQgeyBSdW4gfSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3J1bic7XG5leHBvcnQgeyBzdGFydCB9IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUvc3RhcnQnO1xuXG5jb25zdCB3b3JrZmxvd1N0dWIgPSAoaXRlbTogc3RyaW5nKSA9PiB7XG4gIHRocm93IG5ldyBFcnJvcihcbiAgICBgVGhlIHdvcmtmbG93IGVudmlyb25tZW50IGRvZXNuJ3QgYWxsb3cgdGhpcyBydW50aW1lIHVzYWdlIG9mICR7aXRlbX0uIE1vdmUgdGhpcyBjYWxsIHRvIGEgc3RlcCBmdW5jdGlvbiAoXCJ1c2Ugc3RlcFwiKSBvciBjYWxsIGl0IG91dHNpZGUgdGhlIHdvcmtmbG93IGNvbnRleHQuYFxuICApO1xufTtcblxuZXhwb3J0IGNvbnN0IGdldFJ1biA9ICgpID0+IHdvcmtmbG93U3R1YignZ2V0UnVuJyk7XG5leHBvcnQgY29uc3QgZ2V0SG9va0J5VG9rZW4gPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ2dldEhvb2tCeVRva2VuJyk7XG5leHBvcnQgY29uc3QgcmVzdW1lSG9vayA9ICgpID0+IHdvcmtmbG93U3R1YigncmVzdW1lSG9vaycpO1xuZXhwb3J0IGNvbnN0IHJlc3VtZVdlYmhvb2sgPSAoKSA9PiB3b3JrZmxvd1N0dWIoJ3Jlc3VtZVdlYmhvb2snKTtcbmV4cG9ydCBjb25zdCBydW5TdGVwID0gKCkgPT4gd29ya2Zsb3dTdHViKCdydW5TdGVwJyk7XG4iXX0=
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '@workflow/core/runtime/world-init';
|
|
2
2
|
export type { CancelRunOptions, Event, StopSleepOptions, StopSleepResult, WorkflowRun, } from '@workflow/core/runtime';
|
|
3
3
|
export { getHookByToken, type ResumedHook, resumeHook, resumeWebhook, } from '@workflow/core/runtime/resume-hook';
|
|
4
|
-
export { getRun, Run, type WorkflowReadableStream, type WorkflowReadableStreamOptions, } from '@workflow/core/runtime/run';
|
|
4
|
+
export { getRun, Run, type WorkflowReadableStream, type WorkflowReadableStreamOptions, type WorkflowRunWritableStreamOptions, } from '@workflow/core/runtime/run';
|
|
5
5
|
export { type StartOptions, start, } from '@workflow/core/runtime/start';
|
|
6
6
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,mCAAmC,CAAC;AAE3C,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,UAAU,EACV,aAAa,GACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,MAAM,EACN,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAOA,OAAO,mCAAmC,CAAC;AAE3C,YAAY,EACV,gBAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,KAAK,WAAW,EAChB,UAAU,EACV,aAAa,GACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,MAAM,EACN,GAAG,EACH,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,gCAAgC,GACtC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,GACN,MAAM,8BAA8B,CAAC"}
|
package/dist/api.js
CHANGED
|
@@ -9,4 +9,4 @@ import '@workflow/core/runtime/world-init';
|
|
|
9
9
|
export { getHookByToken, resumeHook, resumeWebhook, } from '@workflow/core/runtime/resume-hook';
|
|
10
10
|
export { getRun, Run, } from '@workflow/core/runtime/run';
|
|
11
11
|
export { start, } from '@workflow/core/runtime/start';
|
|
12
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2FwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxRUFBcUU7QUFDckUseUVBQXlFO0FBQ3pFLDJFQUEyRTtBQUMzRSw0RUFBNEU7QUFDNUUsNkVBQTZFO0FBQzdFLG1CQUFtQjtBQUNuQix5RUFBeUU7QUFDekUsT0FBTyxtQ0FBbUMsQ0FBQztBQVMzQyxPQUFPLEVBQ0wsY0FBYyxFQUVkLFVBQVUsRUFDVixhQUFhLEdBQ2QsTUFBTSxvQ0FBb0MsQ0FBQztBQUM1QyxPQUFPLEVBQ0wsTUFBTSxFQUNOLEdBQUcsR0FJSixNQUFNLDRCQUE0QixDQUFDO0FBQ3BDLE9BQU8sRUFFTCxLQUFLLEdBQ04sTUFBTSw4QkFBOEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIFNpZGUtZWZmZWN0IGltcG9ydDogZW5zdXJlIGB3b3JsZC50c2AgaXMgbG9hZGVkIHNvIGl0cyBtb2R1bGUtbG9hZFxuLy8gYGdsb2JhbFRoaXNbR2V0V29ybGRGbktleV0gPz89IGdldFdvcmxkYCByZWdpc3RyYXRpb24gZmlyZXMgYmVmb3JlIGFueVxuLy8gaG9zdCByb3V0ZSByZWFjaGVzIGBnZXRXb3JsZExhenkoKWAuIFdpdGhvdXQgdGhpcywgd2VicGFjay90dXJib3BhY2sgY2FuXG4vLyB0cmVlLXNoYWtlIGB3b3JsZC50c2Agb3V0IG9mIHJvdXRlcyB0aGF0IG9ubHkgdXNlIGBzdGFydGAuIFJlc29sdmVkIHRvIGFuXG4vLyBlbXB0eSBzdHViIHZpYSB0aGUgYHdvcmtmbG93YCBleHBvcnQgY29uZGl0aW9uIGluIFZNL3N0ZXAgYnVuZGxlcywgc28gdGhpc1xuLy8gc3RheXMgaG9zdC1vbmx5LlxuLy8gU2VlIGBAd29ya2Zsb3cvY29yZS9zcmMvcnVudGltZS93b3JsZC1pbml0LnRzYCBmb3IgdGhlIGZ1bGwgcmF0aW9uYWxlLlxuaW1wb3J0ICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3dvcmxkLWluaXQnO1xuXG5leHBvcnQgdHlwZSB7XG4gIENhbmNlbFJ1bk9wdGlvbnMsXG4gIEV2ZW50LFxuICBTdG9wU2xlZXBPcHRpb25zLFxuICBTdG9wU2xlZXBSZXN1bHQsXG4gIFdvcmtmbG93UnVuLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lJztcbmV4cG9ydCB7XG4gIGdldEhvb2tCeVRva2VuLFxuICB0eXBlIFJlc3VtZWRIb29rLFxuICByZXN1bWVIb29rLFxuICByZXN1bWVXZWJob29rLFxufSBmcm9tICdAd29ya2Zsb3cvY29yZS9ydW50aW1lL3Jlc3VtZS1ob29rJztcbmV4cG9ydCB7XG4gIGdldFJ1bixcbiAgUnVuLFxuICB0eXBlIFdvcmtmbG93UmVhZGFibGVTdHJlYW0sXG4gIHR5cGUgV29ya2Zsb3dSZWFkYWJsZVN0cmVhbU9wdGlvbnMsXG4gIHR5cGUgV29ya2Zsb3dSdW5Xcml0YWJsZVN0cmVhbU9wdGlvbnMsXG59IGZyb20gJ0B3b3JrZmxvdy9jb3JlL3J1bnRpbWUvcnVuJztcbmV4cG9ydCB7XG4gIHR5cGUgU3RhcnRPcHRpb25zLFxuICBzdGFydCxcbn0gZnJvbSAnQHdvcmtmbG93L2NvcmUvcnVudGltZS9zdGFydCc7XG4iXX0=
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
1
|
+
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, StreamError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
2
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,WAAW,EACX,aAAa,EACb,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,yBAAyB,EACzB,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC"}
|
package/dist/internal/errors.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
1
|
+
export { EntityConflictError, HookConflictError, HookNotFoundError, PreconditionFailedError, RunExpiredError, RunNotSupportedError, StepNotRegisteredError, StreamError, ThrottleError, TooEarlyError, WorkflowError, WorkflowNotRegisteredError, WorkflowRunCancelledError, WorkflowRunFailedError, WorkflowRunNotCompletedError, WorkflowRunNotFoundError, WorkflowRuntimeError, WorkflowWorldError, } from '@workflow/errors';
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludGVybmFsL2Vycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixpQkFBaUIsRUFDakIsdUJBQXVCLEVBQ3ZCLGVBQWUsRUFDZixvQkFBb0IsRUFDcEIsc0JBQXNCLEVBQ3RCLFdBQVcsRUFDWCxhQUFhLEVBQ2IsYUFBYSxFQUNiLGFBQWEsRUFDYiwwQkFBMEIsRUFDMUIseUJBQXlCLEVBQ3pCLHNCQUFzQixFQUN0Qiw0QkFBNEIsRUFDNUIsd0JBQXdCLEVBQ3hCLG9CQUFvQixFQUNwQixrQkFBa0IsR0FDbkIsTUFBTSxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCB7XG4gIEVudGl0eUNvbmZsaWN0RXJyb3IsXG4gIEhvb2tDb25mbGljdEVycm9yLFxuICBIb29rTm90Rm91bmRFcnJvcixcbiAgUHJlY29uZGl0aW9uRmFpbGVkRXJyb3IsXG4gIFJ1bkV4cGlyZWRFcnJvcixcbiAgUnVuTm90U3VwcG9ydGVkRXJyb3IsXG4gIFN0ZXBOb3RSZWdpc3RlcmVkRXJyb3IsXG4gIFN0cmVhbUVycm9yLFxuICBUaHJvdHRsZUVycm9yLFxuICBUb29FYXJseUVycm9yLFxuICBXb3JrZmxvd0Vycm9yLFxuICBXb3JrZmxvd05vdFJlZ2lzdGVyZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5DYW5jZWxsZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5GYWlsZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5Ob3RDb21wbGV0ZWRFcnJvcixcbiAgV29ya2Zsb3dSdW5Ob3RGb3VuZEVycm9yLFxuICBXb3JrZmxvd1J1bnRpbWVFcnJvcixcbiAgV29ya2Zsb3dXb3JsZEVycm9yLFxufSBmcm9tICdAd29ya2Zsb3cvZXJyb3JzJztcbiJdfQ==
|
|
@@ -20,7 +20,7 @@ Using WorkflowAgent, we model most tools as steps. These can range from a single
|
|
|
20
20
|
|
|
21
21
|
As with regular AI SDK tool definitions, tools in WorkflowAgent receive the tool's input parameters as the first argument and the tool call context as the second.
|
|
22
22
|
|
|
23
|
-
When
|
|
23
|
+
When your tool needs access to the full message history, you can access it via the `messages` property of the tool call context:
|
|
24
24
|
|
|
25
25
|
```typescript title="tools.ts" lineNumbers
|
|
26
26
|
import type { ModelMessage } from "ai";
|
package/docs/ai/index.mdx
CHANGED
|
@@ -403,7 +403,7 @@ This opens a local dashboard showing all workflow runs and their status, as well
|
|
|
403
403
|
|
|
404
404
|
## Next steps
|
|
405
405
|
|
|
406
|
-
Now that you have a basic durable agent, it's
|
|
406
|
+
Now that you have a basic durable agent, it's only a short step to add these additional features:
|
|
407
407
|
|
|
408
408
|
<Cards>
|
|
409
409
|
<Card title="Streaming Updates from Tools" href="/docs/ai/streaming-updates-from-tools">
|
|
@@ -14,7 +14,7 @@ related:
|
|
|
14
14
|
|
|
15
15
|
AI agents sometimes need to pause execution to schedule recurring or future actions, wait before retrying an operation (e.g. for rate limiting), or wait for external state to be available.
|
|
16
16
|
|
|
17
|
-
Workflow SDK's `sleep` function enables Agents to pause execution without consuming resources, and resume at a specified time, after a specified duration, or in response to an external event. Workflow
|
|
17
|
+
Workflow SDK's `sleep` function enables Agents to pause execution without consuming resources, and resume at a specified time, after a specified duration, or in response to an external event. Workflow operations that suspend will survive restarts, new deploys, and infrastructure changes, independent of whether the suspense takes seconds or months.
|
|
18
18
|
|
|
19
19
|
<Callout type="info">
|
|
20
20
|
See the [`sleep()` API Reference](/docs/api-reference/workflow/sleep) for the full list of supported duration formats and detailed API documentation, and see the [hooks](/docs/foundations/hooks) documentation for more information on how to resume in response to external events.
|
|
@@ -16,7 +16,7 @@ After [building a durable AI agent](/docs/ai), we already get UI message chunks
|
|
|
16
16
|
|
|
17
17
|
Workflow SDK enables this by letting step functions write custom chunks to the same stream the agent uses. These chunks appear as data parts in your messages, which you can render however you like.
|
|
18
18
|
|
|
19
|
-
As an example, we'll extend
|
|
19
|
+
As an example, we'll extend our Flight Booking Agent to emit more granular progress updates while searching for flights.
|
|
20
20
|
|
|
21
21
|
<Steps>
|
|
22
22
|
|
|
@@ -46,7 +46,7 @@ The `type` field must be a string starting with `data-` followed by your custom
|
|
|
46
46
|
|
|
47
47
|
### Emit updates from your tool
|
|
48
48
|
|
|
49
|
-
Use [`getWritable()`](/docs/api-reference/workflow/get-writable) inside a step function to get a handle to the stream. This is the same stream that the LLM and other
|
|
49
|
+
Use [`getWritable()`](/docs/api-reference/workflow/get-writable) inside a step function to get a handle to the stream. This is the same stream that the LLM and other tool calls are writing to, so we can inject our own data packets directly.
|
|
50
50
|
|
|
51
51
|
{/* @skip-typecheck: incomplete code sample */}
|
|
52
52
|
```typescript title="workflows/chat/steps/tools.ts" lineNumbers
|
|
@@ -65,6 +65,16 @@ import type { WorkflowReadableStreamOptions } from "workflow/api";
|
|
|
65
65
|
export default WorkflowReadableStreamOptions;`}
|
|
66
66
|
/>
|
|
67
67
|
|
|
68
|
+
#### WorkflowRunWritableStreamOptions
|
|
69
|
+
|
|
70
|
+
<TSDoc
|
|
71
|
+
definition={`
|
|
72
|
+
import type { WorkflowRunWritableStreamOptions } from "workflow/api";
|
|
73
|
+
export default WorkflowRunWritableStreamOptions;`}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
Use `run.writable` for the default stream or `run.getWritable(options)` to configure it. See [Writing to another run's stream](/docs/foundations/streaming#writing-to-another-runs-stream) for lifecycle details.
|
|
77
|
+
|
|
68
78
|
#### StopSleepOptions
|
|
69
79
|
|
|
70
80
|
<TSDoc
|
|
@@ -61,6 +61,7 @@ Learn more about [`WorkflowReadableStreamOptions`](/docs/api-reference/workflow-
|
|
|
61
61
|
* When you provide `deploymentId`, the argument types and return type become `unknown` because the workflow function's types may differ across deployments.
|
|
62
62
|
* `attributes` seeds plaintext run metadata as part of creation and requires a World implementing spec version 4 or later. Keys that start with `$` are reserved for framework and library code; framework-level callers can pass `allowReservedAttributes: true` to seed reserved keys, with the same semantics as the [`setAttributes`](/docs/api-reference/workflow/set-attributes) option of the same name.
|
|
63
63
|
* `region` pins the new run to a specific region on Worlds with a regional dimension. The [Vercel World](/worlds/vercel#explicit-region-selection) then serves the run's storage, queue dispatch, and streams from that region. When you omit `region`, the run is pinned to the region where it was created. Worlds without regions ignore the option.
|
|
64
|
+
* `experimental_retention` asks the World to delete the run's user data as soon as the run completes or fails, instead of keeping it for the World's default window. `0` requests immediate deletion; `'default'` is identical to omitting the option. These are the only two values accepted — the value is a duration and zero is the only one implemented, and its unit is not yet decided. Recorded as the reserved `$retention` attribute, so it needs a World implementing spec version 4 or later. Retention is enforced by the World, not the SDK: the first-party Worlds implement it and a World that does not keeps the data. Note that `await run.returnValue` on a run started with `experimental_retention: 0` usually throws [`RunExpiredError`](/docs/errors/run-expired) rather than resolving, because the deletion races the read. See [Data retention](/docs/observability/retention).
|
|
64
65
|
|
|
65
66
|
<Callout type="info">
|
|
66
67
|
If `start()` throws `'start' received an invalid workflow function. Ensure the Workflow SDK is configured correctly and the function includes a 'use workflow' directive.`, the compiler did not transform the passed function as a workflow. The two most common causes are a missing `"use workflow"` directive or missing framework integration. See [start-invalid-workflow-function](/docs/errors/start-invalid-workflow-function).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: Analytics
|
|
3
3
|
description: Metadata-only read APIs for runs, steps, events, hooks, waits, and attributes, backed by the observability pipeline.
|
|
4
4
|
type: reference
|
|
5
|
-
summary: "Interfaces: world.analytics.runs, .attributes, .steps, .events, .hooks, .waits. Metadata-only listings with plan-based lookback windows; filter runs by attribute key=value."
|
|
5
|
+
summary: "Interfaces: world.analytics.runs, .attributes, .steps, .events, .hooks, .waits. Metadata-only listings with plan-based lookback windows; filter runs by attribute key=value. Page limits are 1000 run-scoped, 100 cross-run."
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/api-reference/workflow-runtime/get-world
|
|
8
8
|
related:
|
|
@@ -13,7 +13,8 @@ keywords:
|
|
|
13
13
|
- analytics.runs
|
|
14
14
|
- analytics.attributes
|
|
15
15
|
- attribute filter
|
|
16
|
-
-
|
|
16
|
+
- getMany
|
|
17
|
+
- pagination limit
|
|
17
18
|
- lookback window
|
|
18
19
|
- observability-upgrade-required
|
|
19
20
|
- pageInfo
|
|
@@ -22,9 +23,10 @@ keywords:
|
|
|
22
23
|
|
|
23
24
|
`world.analytics` is an optional, read-only namespace for observability surfaces: dashboards, command-line interface (CLI) tools, and admin tools that list large numbers of runs without touching payload data.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
[
|
|
27
|
-
|
|
26
|
+
Prefer this namespace for observability: listing, filtering, and inspecting
|
|
27
|
+
workflow state. Use the [Storage](/docs/api-reference/workflow-runtime/world/storage)
|
|
28
|
+
API for payload-bearing reads, and for anything operational that has to see the
|
|
29
|
+
canonical, up-to-the-moment record.
|
|
28
30
|
|
|
29
31
|
It differs from [Storage](/docs/api-reference/workflow-runtime/world/storage) in two ways:
|
|
30
32
|
|
|
@@ -108,19 +110,190 @@ for (const { key, runCount, lastSeenAt } of page.data) {
|
|
|
108
110
|
|
|
109
111
|
---
|
|
110
112
|
|
|
111
|
-
## analytics.steps
|
|
113
|
+
## analytics.steps
|
|
112
114
|
|
|
113
|
-
Run-scoped listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data
|
|
115
|
+
Run-scoped step listings mirroring their [Storage](/docs/api-reference/workflow-runtime/world/storage) counterparts, minus payload data.
|
|
116
|
+
|
|
117
|
+
### steps.list()
|
|
118
|
+
|
|
119
|
+
```typescript lineNumbers
|
|
120
|
+
const steps = await world.analytics.steps.list({
|
|
121
|
+
runId,
|
|
122
|
+
pagination: { limit: 200, sortOrder: "asc" },
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Parameter | Type | Description |
|
|
127
|
+
|-----------|------|-------------|
|
|
128
|
+
| `params.runId` | `string` | Required. The run to list steps for |
|
|
129
|
+
| `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
|
|
130
|
+
|
|
131
|
+
**Returns:** `PaginatedResponse<AnalyticsStep>`. Each step includes `stepId`, `stepName`, `status`, `attempt`, lifecycle timestamps, `errorCode`, and the `computeInstanceId` of the latest attempt.
|
|
132
|
+
|
|
133
|
+
### steps.get()
|
|
134
|
+
|
|
135
|
+
```typescript lineNumbers
|
|
136
|
+
const step = await world.analytics.steps.get(runId, stepId);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Returns:** `AnalyticsStep`. A step id is only unique within its run, so both arguments are required.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## analytics.events
|
|
144
|
+
|
|
145
|
+
### events.list()
|
|
146
|
+
|
|
147
|
+
```typescript lineNumbers
|
|
148
|
+
const events = await world.analytics.events.list({
|
|
149
|
+
runId,
|
|
150
|
+
eventType: "step_failed", // [!code highlight]
|
|
151
|
+
pagination: { limit: 1000 },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Description |
|
|
156
|
+
|-----------|------|-------------|
|
|
157
|
+
| `params.runId` | `string` | Required. The run to list events for |
|
|
158
|
+
| `params.eventType` | `string` | One event type, for example `run_failed` or `step_retrying` |
|
|
159
|
+
| `params.correlationId` | `string` | Narrow to one entity: a step, hook, wait, or attribute id |
|
|
160
|
+
| `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
|
|
161
|
+
|
|
162
|
+
**Returns:** `PaginatedResponse<AnalyticsEvent>`. Each event includes `eventId`, `eventType`, `correlationId`, `stepName`, `createdAt`, and provenance fields (`region`, `requestId`, `computeInstanceId`).
|
|
163
|
+
|
|
164
|
+
Pass a step id as `correlationId` to build that step's timeline: `step_created` through `step_completed`, `step_failed`, or `step_retrying`.
|
|
165
|
+
|
|
166
|
+
### events.get()
|
|
167
|
+
|
|
168
|
+
```typescript lineNumbers
|
|
169
|
+
const event = await world.analytics.events.get(runId, eventId);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Returns:** `AnalyticsEvent`.
|
|
173
|
+
|
|
174
|
+
### events.getMany()
|
|
175
|
+
|
|
176
|
+
Look up a bounded set of event ids in one run with a single request.
|
|
177
|
+
|
|
178
|
+
```typescript lineNumbers
|
|
179
|
+
const events = await world.analytics.events.getMany(runId, eventIds); // [!code highlight]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Returns:** `AnalyticsEvent[]` — not paginated, and no `pageInfo`. Duplicate ids are looked up once, and ids with no analytics row yet are **omitted rather than erroring**, since ingestion can trail canonical storage. Compare the returned length against your input to detect that.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## analytics.hooks
|
|
187
|
+
|
|
188
|
+
### hooks.list()
|
|
114
189
|
|
|
115
190
|
```typescript lineNumbers
|
|
116
|
-
const steps = await world.analytics.steps.list({ runId });
|
|
117
|
-
const events = await world.analytics.events.list({ runId, eventType: "step_failed" });
|
|
118
|
-
const related = await world.analytics.events.listByCorrelationId({ runId, correlationId });
|
|
119
191
|
const hooks = await world.analytics.hooks.list({ runId });
|
|
120
|
-
const waits = await world.analytics.waits.list({ runId, status: "waiting" });
|
|
121
192
|
```
|
|
122
193
|
|
|
123
|
-
|
|
194
|
+
| Parameter | Type | Description |
|
|
195
|
+
|-----------|------|-------------|
|
|
196
|
+
| `params.runId` | `string` | Required. The run to list hooks for |
|
|
197
|
+
| `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 100 |
|
|
198
|
+
|
|
199
|
+
**Returns:** `PaginatedResponse<AnalyticsHook>`: `hookId`, `status` (`created`, `received`, `disposed`, or `conflict`), `receivedAt`, `disposedAt`, `isWebhook`, `isSystem`.
|
|
200
|
+
|
|
201
|
+
### hooks.get()
|
|
202
|
+
|
|
203
|
+
```typescript lineNumbers
|
|
204
|
+
const hook = await world.analytics.hooks.get(hookId);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Unlike steps and waits, a hook id identifies one hook on its own, so no `runId` is needed. Pass `{ runId }` to scope the lookup when you already know it.
|
|
208
|
+
|
|
209
|
+
<Callout>
|
|
210
|
+
Hook listings never include the hook token. Resolve it separately through the
|
|
211
|
+
runtime APIs if you need to deliver a payload.
|
|
212
|
+
</Callout>
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## analytics.waits
|
|
217
|
+
|
|
218
|
+
### waits.list()
|
|
219
|
+
|
|
220
|
+
```typescript lineNumbers
|
|
221
|
+
const waits = await world.analytics.waits.list({
|
|
222
|
+
runId,
|
|
223
|
+
status: "waiting", // [!code highlight]
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Parameter | Type | Description |
|
|
228
|
+
|-----------|------|-------------|
|
|
229
|
+
| `params.runId` | `string` | Required. The run to list waits for |
|
|
230
|
+
| `params.status` | `string` | `waiting` or `completed` |
|
|
231
|
+
| `params.pagination` | `PaginationOptions` | Cursor pagination, `limit` up to 1000 |
|
|
232
|
+
|
|
233
|
+
**Returns:** `PaginatedResponse<AnalyticsWait>`: `waitId`, `status`, `resumeAt`, `completedAt`.
|
|
234
|
+
|
|
235
|
+
### waits.get()
|
|
236
|
+
|
|
237
|
+
```typescript lineNumbers
|
|
238
|
+
const wait = await world.analytics.waits.get(runId, waitId);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Returns:** `AnalyticsWait`. A wait id is only unique within its run, so both arguments are required.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Limits and validation
|
|
246
|
+
|
|
247
|
+
Arguments are validated in your process before a request goes out. An
|
|
248
|
+
out-of-range or malformed argument throws a `RangeError` naming the bound it
|
|
249
|
+
broke, rather than reaching the backend and coming back as a 400 — which
|
|
250
|
+
matters because analytics is optional and callers commonly wrap it in a
|
|
251
|
+
`try`/`catch`, where a rejected request is easy to mistake for "no data".
|
|
252
|
+
|
|
253
|
+
### Page limits
|
|
254
|
+
|
|
255
|
+
`pagination.limit` defaults to 40 everywhere. The maximum depends on whether
|
|
256
|
+
the listing scans within one run or across runs:
|
|
257
|
+
|
|
258
|
+
| Method | Max `limit` |
|
|
259
|
+
|--------|-------------|
|
|
260
|
+
| `steps.list()`, `events.list()`, `waits.list()` | 1000 |
|
|
261
|
+
| `runs.list()`, `attributes.list()`, `hooks.list()` | 100 |
|
|
262
|
+
|
|
263
|
+
`events.getMany()` is not paginated; it accepts 1 to 100 event ids per call.
|
|
264
|
+
|
|
265
|
+
<Callout type="warn">
|
|
266
|
+
The two page caps differ by a factor of ten, and `hooks.list()` takes the
|
|
267
|
+
lower one despite being run-scoped. Reusing one page size across listings is
|
|
268
|
+
the most common way to trip this.
|
|
269
|
+
</Callout>
|
|
270
|
+
|
|
271
|
+
### Identifiers
|
|
272
|
+
|
|
273
|
+
Every id is a prefix plus a ULID, and each is checked before the request:
|
|
274
|
+
|
|
275
|
+
| Parameter | Shape |
|
|
276
|
+
|-----------|-------|
|
|
277
|
+
| `runId` | `wrun_` |
|
|
278
|
+
| `stepId` | `step_` |
|
|
279
|
+
| `eventId` | `evnt_` |
|
|
280
|
+
| `hookId` | `hook_` |
|
|
281
|
+
| `waitId` | `wait_` |
|
|
282
|
+
| `correlationId` | `step_`, `hook_`, `wait_`, or `attr_` |
|
|
283
|
+
|
|
284
|
+
Step, event, and wait ids are only unique **within** their run, so the methods that take them require a `runId` too. A hook id stands alone.
|
|
285
|
+
|
|
286
|
+
### Time windows
|
|
287
|
+
|
|
288
|
+
`startTime` and `endTime` must be supplied **together** and be parseable ISO 8601 timestamps with `startTime` no later than `endTime`. Passing one without the other throws: it used to be dropped silently, which turned a listing you meant to bound into a scan of the whole retention window that looked like a successful answer.
|
|
289
|
+
|
|
290
|
+
### Attribute filters
|
|
291
|
+
|
|
292
|
+
`runs.list({ attributes })` accepts 1 to 8 pairs. Keys are 1 to 256 characters; values are at most 256 UTF-8 bytes. Reserved `$`-prefixed keys are valid in a filter even though user code cannot write them.
|
|
293
|
+
|
|
294
|
+
### Pagination
|
|
295
|
+
|
|
296
|
+
`cursor` is an opaque token from the previous response; do not construct or parse one. Branch on `hasMore`, not on `cursor` being non-null, and do not change `sortOrder` mid-walk — the cursor encodes the sort position, so reversing it can skip or repeat rows.
|
|
124
297
|
|
|
125
298
|
---
|
|
126
299
|
|
|
@@ -94,14 +94,14 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
|
|
|
94
94
|
### `--sort`
|
|
95
95
|
|
|
96
96
|
- Environment variable: none
|
|
97
|
-
- Default: `desc`
|
|
97
|
+
- Default: `desc` for time-ordered listings; `workflow inspect attributes` orders keys alphabetically unless you pass this flag
|
|
98
98
|
- Sort order for list commands. Accepts `asc` or `desc`.
|
|
99
99
|
|
|
100
100
|
### `--limit`
|
|
101
101
|
|
|
102
102
|
- Environment variable: none
|
|
103
103
|
- Default: `20`
|
|
104
|
-
- Number of items returned per page for list commands.
|
|
104
|
+
- Number of items returned per page for list commands. `workflow cancel` bounds it separately; see [`--limit` (cancel)](#--limit-cancel).
|
|
105
105
|
|
|
106
106
|
### `--cursor`
|
|
107
107
|
|
|
@@ -115,6 +115,63 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
|
|
|
115
115
|
- Default: disabled
|
|
116
116
|
- Enables keyboard-controlled pagination for supported list commands.
|
|
117
117
|
|
|
118
|
+
## Inspect filtering
|
|
119
|
+
|
|
120
|
+
Flags for `workflow inspect`. Each list view accepts a different subset, noted
|
|
121
|
+
per flag.
|
|
122
|
+
|
|
123
|
+
### `--runId` / `-r`
|
|
124
|
+
|
|
125
|
+
- Command: `workflow inspect`
|
|
126
|
+
- Default: unset
|
|
127
|
+
- Scopes the listing to one run. Required for `steps`, `events`, and `sleeps`; optional for `hooks`. Must be a run ID: `wrun_` followed by a 26-character ULID.
|
|
128
|
+
|
|
129
|
+
### `--limit` (inspect)
|
|
130
|
+
|
|
131
|
+
- Command: `workflow inspect`
|
|
132
|
+
- Default: `20`
|
|
133
|
+
- Must be an integer between 1 and 100, the smallest page any inspect listing accepts. Larger pages are reachable by paging: pass `--cursor`, or `--interactive` to walk them.
|
|
134
|
+
|
|
135
|
+
### `--stepId` / `-s`
|
|
136
|
+
|
|
137
|
+
- Command: `workflow inspect events`
|
|
138
|
+
- Default: unset
|
|
139
|
+
- Filters events to one step.
|
|
140
|
+
|
|
141
|
+
### `--hookId`
|
|
142
|
+
|
|
143
|
+
- Command: `workflow inspect events`
|
|
144
|
+
- Default: unset
|
|
145
|
+
- Filters events to one hook.
|
|
146
|
+
|
|
147
|
+
### `--attribute`
|
|
148
|
+
|
|
149
|
+
- Command: `workflow inspect runs`
|
|
150
|
+
- Default: unset
|
|
151
|
+
- Filters runs to those whose [attributes](/docs/observability/attributes) match every `key=value` pair given. Repeatable up to 8 times, and splits on the first `=` so a value may contain one.
|
|
152
|
+
- Requires a backend with the analytics read path; ignored with a warning otherwise.
|
|
153
|
+
- Cannot be combined with `--url` or `--web`, which hand off to the dashboard, or with `--withData`, which reads payloads from storage. Storage carries no attribute index.
|
|
154
|
+
- Use `workflow inspect attributes` to discover which keys exist.
|
|
155
|
+
|
|
156
|
+
### `--since` / `--until`
|
|
157
|
+
|
|
158
|
+
- Command: `workflow inspect runs`, `workflow inspect attributes`
|
|
159
|
+
- Default: the backend's own window
|
|
160
|
+
- Bounds the listing to a window. `--since` opens the window and accepts a relative duration (`30m`, `12h`, `7d`, `2w`) or a timestamp. `--until` is optional and defaults to now, so `--until` on its own is rejected.
|
|
161
|
+
- Requires a backend with the analytics read path; ignored with a warning otherwise.
|
|
162
|
+
|
|
163
|
+
### `--withData` / `-d`
|
|
164
|
+
|
|
165
|
+
- Command: `workflow inspect`
|
|
166
|
+
- Default: disabled
|
|
167
|
+
- Includes full input and output payloads in list views. Deprecated for list views — use `workflow inspect <resource> <id>` to read one item's payloads. Setting it also moves the read off the analytics path, which carries metadata only.
|
|
168
|
+
|
|
169
|
+
### `--decrypt`
|
|
170
|
+
|
|
171
|
+
- Command: `workflow inspect`
|
|
172
|
+
- Default: disabled
|
|
173
|
+
- Decrypts encrypted values. Triggers an audit-logged key retrieval.
|
|
174
|
+
|
|
118
175
|
## Bulk cancel
|
|
119
176
|
|
|
120
177
|
`workflow cancel <run-id>` cancels one run. Given a filter instead, it bulk-cancels a batch; bulk mode requires `--status` or `--workflowName`.
|
|
@@ -124,18 +181,20 @@ Vercel project and auth settings can often be inferred from `.vercel/project.jso
|
|
|
124
181
|
- Command: `workflow cancel`
|
|
125
182
|
- Default: unset
|
|
126
183
|
- Restricts the batch to this status. Only `pending` and `running` are accepted; terminal runs cannot be canceled.
|
|
184
|
+
- Also filters `workflow inspect runs`, which accepts any run status. It does not narrow `workflow inspect attributes`, which indexes keys per tenant rather than per run; passing it there warns and lists every key.
|
|
127
185
|
|
|
128
186
|
### `--workflowName` / `-n`
|
|
129
187
|
|
|
130
188
|
- Command: `workflow cancel`
|
|
131
189
|
- Default: unset
|
|
132
190
|
- Restricts the batch to one workflow. Expects the generated workflow ID from `workflow inspect runs`, not the short function name.
|
|
191
|
+
- Also filters `workflow inspect runs` and `workflow inspect attributes`.
|
|
133
192
|
|
|
134
193
|
### `--limit` (cancel)
|
|
135
194
|
|
|
136
195
|
- Command: `workflow cancel`
|
|
137
196
|
- Default: `50`
|
|
138
|
-
- Maximum runs to cancel in one batch (1–
|
|
197
|
+
- Maximum runs to cancel in one batch (1–100), the largest page the run listing serves. Only one batch is canceled per invocation; run the command again to cancel the next batch.
|
|
139
198
|
|
|
140
199
|
### `--confirm` / `-y`
|
|
141
200
|
|
|
@@ -185,10 +185,10 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
185
185
|
### `WORKFLOW_RETAINED_VM`
|
|
186
186
|
|
|
187
187
|
- Default: enabled
|
|
188
|
-
- Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM.
|
|
189
|
-
- A step-driven suspension can keep the VM retained even when hooks are open or created at the same boundary. Hook-only suspensions park the invocation
|
|
188
|
+
- Node.js VM engine only. Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM. QuickJS manages its own retained inline loop independently of this setting.
|
|
189
|
+
- A step- or attribute-driven suspension can keep the VM retained even when hooks or waits are open or created at the same boundary. Hook- or wait-only suspensions park the invocation because nothing in the current delivery can advance them, with one exception: when the hook's own create is what the workflow is waiting on (a `hook.getConflict()` awaiter, or a create whose token is already claimed), the invocation resumes the retained VM over the committed `hook_created` or `hook_conflict` instead of re-invoking through the queue. Any replay divergence falls back to a full replay.
|
|
190
190
|
- Step inputs made of plain data (objects, arrays, primitives) and standard built-ins (`Map`, `Set`, `Date`, `RegExp`, typed arrays, `ArrayBuffer`, `URL`, `Headers`) keep the VM retained. Patching or polyfilling built-in prototypes doesn't change that because serialization never calls them. A boundary falls back to a full replay only when serializing its arguments runs code the workflow controls, such as a getter, a proxy, or a custom class serializer, or computes an `Error`'s stack trace.
|
|
191
|
-
- Set `0` or `false` to replay from scratch in a fresh VM on every iteration.
|
|
191
|
+
- Set `0` or `false` to replay the Node.js workflow from scratch in a fresh VM on every iteration.
|
|
192
192
|
|
|
193
193
|
### `WORKFLOW_INLINE_OWNERSHIP`
|
|
194
194
|
|
|
@@ -276,10 +276,25 @@ Node's own modules do less than the client they replace, so enabling this drops
|
|
|
276
276
|
- Requests lose their transport-level retry. Failures still surface to the layers above, which retry event writes and redeliver queue messages, so nothing is silently dropped, but a failure that a same-connection retry would have hidden now costs a full redelivery.
|
|
277
277
|
- Stream close loses its retry of retriable server errors. A transient failure at close can leave a stream marked closing until the run expires, where it would previously have resolved on the retry.
|
|
278
278
|
|
|
279
|
-
Connection pooling, keep-alive, and the request, header, and body deadlines are preserved. Node's agents configure pooling and keep-alive, and each request receives the deadlines from the Local World's two queue timeouts or the same defaults that the Vercel World's HTTP client currently applies. Queue sends are the exception
|
|
279
|
+
Connection pooling, keep-alive, and the request, header, and body deadlines are preserved. Node's agents configure pooling and keep-alive, and each request receives the deadlines from the Local World's two queue timeouts or the same defaults that the Vercel World's HTTP client currently applies. Queue sends are the partial exception: that client takes no transport override, so it cannot move to Node's modules. It does honor this variable, by dispatching through the runtime's own copy of the library rather than the copy the World bundles, which is the distinction that matters when the bundled copy is the thing that does not work.
|
|
280
280
|
|
|
281
281
|
A `dispatcher` passed to `createVercelWorld()` still wins over this variable. The variable chooses which transport the World builds when you have not supplied one.
|
|
282
282
|
|
|
283
|
+
### `WORKFLOW_VERCEL_QUEUE_TIMEOUT_MS`
|
|
284
|
+
|
|
285
|
+
- Default: `30000`
|
|
286
|
+
- Total deadline for one request the Vercel World's queue client makes, measured from the moment it is handed to the transport, so it also covers time the request spends waiting for a free connection.
|
|
287
|
+
- Clamped to `[5000, 120000]`.
|
|
288
|
+
- This is the only request path the World cannot bound with `WORKFLOW_REQUEST_TIMEOUT_MS`, because the queue client makes its own calls and accepts no override for them. Without a deadline here, a queue acknowledgement that never completes holds the invocation until the platform kills it, and a killed invocation never acknowledges, so the message is redelivered.
|
|
289
|
+
- Keep it below the queue's visibility-renewal interval (60 seconds) so a failed call has room to surface and be retried before the message lease lapses.
|
|
290
|
+
|
|
291
|
+
### `WORKFLOW_VERCEL_QUEUE_CONNECTIONS`
|
|
292
|
+
|
|
293
|
+
- Default: `64`
|
|
294
|
+
- Connections the Vercel World's queue client may open to the queue service.
|
|
295
|
+
- Clamped to `[1, 1024]`.
|
|
296
|
+
- Sized much higher than the World's other connection pools on purpose. The queue client makes roughly two small requests per invocation, so its concurrency tracks how many invocations a compute instance is serving at once rather than any per-request fan-out. Lower it only if you have a reason to cap sockets; too low turns invocation concurrency into the limit on how fast messages can be acknowledged.
|
|
297
|
+
|
|
283
298
|
## Queue namespace
|
|
284
299
|
|
|
285
300
|
### `WORKFLOW_QUEUE_NAMESPACE`
|
|
@@ -281,6 +281,14 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
|
|
|
281
281
|
- Default: `1000`
|
|
282
282
|
- Maximum stream chunks written in one Vercel World request. Larger batches are split.
|
|
283
283
|
|
|
284
|
+
### `WORKFLOW_STREAMS_TRANSPORT`
|
|
285
|
+
|
|
286
|
+
- Factory option: none
|
|
287
|
+
- CLI flag: none
|
|
288
|
+
- Default: `http`
|
|
289
|
+
- Experimental stream-write transport capability. Set to exactly `ws` to advertise support for `workflow-stream-ws/v1` when it becomes available. The server authoritatively accepts or declines an upgrade; a decline uses HTTP directly. Stream reads remain HTTP and demand-driven.
|
|
290
|
+
- This is not tenant rollout policy or a package-version check. HTTP remains the compatibility path. `/websockets/v1` is independent of REST v2/v4 and persisted workflow `specVersion` values.
|
|
291
|
+
|
|
284
292
|
### `WORKFLOW_DISABLE_ANALYTICS_READS`
|
|
285
293
|
|
|
286
294
|
- Factory option: none
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: run-expired
|
|
3
|
+
description: A run's data passed its retention boundary, so its result can no longer be read.
|
|
4
|
+
type: troubleshooting
|
|
5
|
+
summary: Read a run's result before it expires, or return it through a channel you control.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability/retention
|
|
10
|
+
- /docs/api-reference/workflow-api/start
|
|
11
|
+
- /docs/foundations/hooks
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Error
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Run "wrun_..." completed, but its data expired at 2026-08-28T05:02:35.009Z
|
|
18
|
+
and is no longer readable.
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Thrown as a `RunExpiredError` from `await run.returnValue`.
|
|
22
|
+
|
|
23
|
+
## Why this happens
|
|
24
|
+
|
|
25
|
+
A run's payloads — its input, output and error, and those of its steps — are
|
|
26
|
+
kept only for as long as the World's retention policy says. Its *metadata* —
|
|
27
|
+
id, status, timestamps — usually outlives them. So a run can be readable as a
|
|
28
|
+
record while its result is already gone.
|
|
29
|
+
|
|
30
|
+
Rather than hand back a placeholder that is indistinguishable from a value the
|
|
31
|
+
workflow genuinely returned, `returnValue` throws.
|
|
32
|
+
|
|
33
|
+
Two ways to reach it:
|
|
34
|
+
|
|
35
|
+
- **The run was started with `experimental_retention: 0`.** Its data is
|
|
36
|
+
deleted the moment it reaches a terminal state, and that deletion races your
|
|
37
|
+
own read of the result — and generally wins. On these runs, expect this
|
|
38
|
+
error rather than treating it as an edge case. See
|
|
39
|
+
[Data retention](/docs/observability/retention).
|
|
40
|
+
- **The run simply aged out.** It finished long enough ago that the World's
|
|
41
|
+
default retention window has passed.
|
|
42
|
+
|
|
43
|
+
## How to respond
|
|
44
|
+
|
|
45
|
+
`RunExpiredError` is terminal. Retrying will not bring the data back, so catch
|
|
46
|
+
Catch the error and use the run's metadata to decide what you want to do.
|
|
47
|
+
|
|
48
|
+
```typescript lineNumbers
|
|
49
|
+
import { getRun } from "workflow/api"
|
|
50
|
+
import { RunExpiredError } from "workflow/errors"
|
|
51
|
+
|
|
52
|
+
export async function readResult(runId: string) {
|
|
53
|
+
try {
|
|
54
|
+
return await getRun(runId).returnValue
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (RunExpiredError.is(error)) { // [!code highlight]
|
|
57
|
+
// `runStatus` is the run's terminal status when the World still has
|
|
58
|
+
// it, so you can tell a successful run whose result is gone from a
|
|
59
|
+
// failed one whose error is gone.
|
|
60
|
+
if (error.runStatus === "completed") {
|
|
61
|
+
// The run succeeded; its result is simply no longer stored.
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
throw error
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The error carries `runId`, `runStatus` and `expiredAt` when the World reports
|
|
71
|
+
them.
|
|
72
|
+
|
|
73
|
+
### If you need the result of a zero-retention run
|
|
74
|
+
|
|
75
|
+
Do not read it back off the run. Send it somewhere you control while the run
|
|
76
|
+
is still executing — a step that writes it to your own store. That is the intended pattern
|
|
77
|
+
for `experimental_retention: 0`: the point of the option is that the platform
|
|
78
|
+
does not keep your data, so the platform cannot also be where you fetch it
|
|
79
|
+
from afterwards.
|
|
80
|
+
|
|
81
|
+
## Related
|
|
82
|
+
|
|
83
|
+
If the run is gone entirely — metadata included — the World reports it as
|
|
84
|
+
missing and you get a `WorkflowRunNotFoundError` instead. That means the
|
|
85
|
+
record itself has been cleaned up, not just its payloads.
|
|
@@ -314,6 +314,34 @@ export async function POST(request: Request) {
|
|
|
314
314
|
}
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
+
## Writing to another run's stream
|
|
318
|
+
|
|
319
|
+
`getRun(runId).getWritable()` appends to a stream owned by another run. This lets short-lived runs contribute to a long-lived holder run's stream using only its ID.
|
|
320
|
+
|
|
321
|
+
```typescript title="workflows/turn.ts" lineNumbers
|
|
322
|
+
import { getRun } from "workflow/api";
|
|
323
|
+
|
|
324
|
+
type SessionEvent = { turn: number; text: string };
|
|
325
|
+
|
|
326
|
+
async function runTurn(holderRunId: string, turn: number) {
|
|
327
|
+
"use step";
|
|
328
|
+
|
|
329
|
+
const writable = getRun(holderRunId).getWritable<SessionEvent>(); // [!code highlight]
|
|
330
|
+
const writer = writable.getWriter();
|
|
331
|
+
|
|
332
|
+
await writer.write({ turn, text: "done" });
|
|
333
|
+
writer.releaseLock(); // [!code highlight]
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Pass `{ namespace: "name" }` to target a [namespaced stream](#namespaced-streams). The writable can also be forwarded through `start()` and into steps.
|
|
338
|
+
|
|
339
|
+
<Callout type="warn">
|
|
340
|
+
Contributors should call `releaseLock()`, which flushes pending writes. Calling `close()` closes the shared stream for every writer.
|
|
341
|
+
</Callout>
|
|
342
|
+
|
|
343
|
+
The API grants append access, not read access or additional authorization. The owning run controls the stream's lifecycle, and writes to an unknown run fail.
|
|
344
|
+
|
|
317
345
|
## Common patterns
|
|
318
346
|
|
|
319
347
|
### Progress updates for long-running tasks
|
|
@@ -260,7 +260,7 @@ npx workflow inspect runs
|
|
|
260
260
|
|
|
261
261
|
## Deploying to production
|
|
262
262
|
|
|
263
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
263
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
264
264
|
|
|
265
265
|
<FluidComputeCallout />
|
|
266
266
|
|
|
@@ -247,7 +247,7 @@ npx workflow inspect runs # add '--web' for an interactive Web based UI
|
|
|
247
247
|
|
|
248
248
|
## Deploying to production
|
|
249
249
|
|
|
250
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
250
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
251
251
|
|
|
252
252
|
<FluidComputeCallout />
|
|
253
253
|
|
|
@@ -242,7 +242,7 @@ npx workflow inspect runs
|
|
|
242
242
|
|
|
243
243
|
## Deploying to production
|
|
244
244
|
|
|
245
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
245
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
246
246
|
|
|
247
247
|
<FluidComputeCallout />
|
|
248
248
|
|
|
@@ -227,7 +227,7 @@ npx workflow inspect runs
|
|
|
227
227
|
|
|
228
228
|
## Deploying to production
|
|
229
229
|
|
|
230
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
230
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
231
231
|
|
|
232
232
|
<FluidComputeCallout />
|
|
233
233
|
|
|
@@ -11,23 +11,23 @@ related:
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
<CopyPrompt
|
|
14
|
-
text="
|
|
14
|
+
text="Set up Workflow in this Python project. In `pyproject.toml`, add `requires-python = ">=3.12"` and `dependencies = ["vercel-workflow"]` under `[project]`, then add `[[tool.vercel.workflows]]` with `entrypoint = "app.workflows:wf"`. Create `app/workflow.py` with `from vercel import workflow` and `wf = workflow.Workflows()`. Create `app/steps/generate_draft.py`, import `wf`, and define async step functions such as `generate_draft` and `summarize_draft`, decorating each with `@wf.step`. Then create `app/workflows/ai_content_workflow.py`, import `wf` and those step functions, and define `@wf.workflow async def ai_content_workflow(*, topic: str)` to orchestrate them and return the result. In `app/workflows/__init__.py`, export `wf` and import the workflow module so its definitions are registered. From server-side code, start it with `await workflow.start(ai_content_workflow, topic=...)`; use the returned `Run` to access its ID, check its status, or await its return value. Where the workflow needs a durable delay, use `await workflow.sleep(timedelta(days=7))` after importing `timedelta` from `datetime`. Where it needs an external approval event, define a Pydantic model that also extends `workflow.BaseHook`, wait with `.wait(token=...)`, and resume it from server-side code with `.resume(token)`."
|
|
15
15
|
/>
|
|
16
16
|
|
|
17
17
|
<Callout type="warn">
|
|
18
|
-
The Python SDK is currently in **beta**. APIs and behavior may change.
|
|
18
|
+
The Python SDK is currently in **beta**. APIs and behavior may change.
|
|
19
19
|
</Callout>
|
|
20
20
|
|
|
21
|
-
You can build durable workflows in Python using the [`vercel`
|
|
21
|
+
You can build durable workflows in Python using the [`vercel-workflow` SDK](https://pypi.org/project/vercel-workflow/). Your workflow code can pause, resume, and maintain state, just like the JavaScript and TypeScript Workflow SDK.
|
|
22
22
|
|
|
23
23
|
## Getting started
|
|
24
24
|
|
|
25
|
-
Add the `vercel` package and workflow entrypoint to `pyproject.toml`:
|
|
25
|
+
Add the `vercel-workflow` package and workflow entrypoint to `pyproject.toml`:
|
|
26
26
|
|
|
27
27
|
```toml filename="pyproject.toml"
|
|
28
28
|
[project]
|
|
29
29
|
requires-python = ">=3.12"
|
|
30
|
-
dependencies = ["vercel"]
|
|
30
|
+
dependencies = ["vercel-workflow"]
|
|
31
31
|
|
|
32
32
|
[[tool.vercel.workflows]]
|
|
33
33
|
entrypoint = "app.workflows:wf"
|
|
@@ -39,16 +39,17 @@ The workflow `entrypoint` uses the `module:object` format and points to the expo
|
|
|
39
39
|
|
|
40
40
|
A workflow is a stateful function that coordinates multi-step logic over time. Create a `Workflows` instance and use the `@wf.workflow` decorator to mark a function as durable:
|
|
41
41
|
|
|
42
|
-
```python filename="app/workflow.py"
|
|
42
|
+
```python filename="app/workflow.py"
|
|
43
43
|
from vercel import workflow
|
|
44
44
|
|
|
45
|
-
wf = workflow.Workflows()
|
|
45
|
+
wf = workflow.Workflows() # [!code highlight]
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
```python filename="app/workflows/ai_content_workflow.py"
|
|
48
|
+
```python filename="app/workflows/ai_content_workflow.py"
|
|
49
49
|
from app.workflow import wf
|
|
50
|
+
from app.steps.generate_draft import generate_draft, summarize_draft
|
|
50
51
|
|
|
51
|
-
@wf.workflow
|
|
52
|
+
@wf.workflow # [!code highlight]
|
|
52
53
|
async def ai_content_workflow(*, topic: str):
|
|
53
54
|
draft = await generate_draft(topic=topic)
|
|
54
55
|
summary = await summarize_draft(draft=draft)
|
|
@@ -74,15 +75,15 @@ Under the hood, the workflow compiles into a route that orchestrates execution.
|
|
|
74
75
|
|
|
75
76
|
A step is a stateless function that runs a unit of durable work inside a workflow. Use `@wf.step` to mark a function as a step:
|
|
76
77
|
|
|
77
|
-
```python filename="app/steps/generate_draft.py"
|
|
78
|
+
```python filename="app/steps/generate_draft.py"
|
|
78
79
|
import random
|
|
79
80
|
from app.workflow import wf
|
|
80
81
|
|
|
81
|
-
@wf.step
|
|
82
|
+
@wf.step # [!code highlight]
|
|
82
83
|
async def generate_draft(*, topic: str):
|
|
83
84
|
return await ai_generate(prompt=f"Write a blog post about {topic}")
|
|
84
85
|
|
|
85
|
-
@wf.step
|
|
86
|
+
@wf.step # [!code highlight]
|
|
86
87
|
async def summarize_draft(*, draft: str):
|
|
87
88
|
summary = await ai_summarize(text=draft)
|
|
88
89
|
|
|
@@ -95,18 +96,42 @@ async def summarize_draft(*, draft: str):
|
|
|
95
96
|
|
|
96
97
|
Each step executes separately from the workflow orchestrator. While the step executes, the workflow suspends without consuming resources. When the step completes, the workflow resumes automatically where it left off.
|
|
97
98
|
|
|
99
|
+
## Starting a workflow
|
|
100
|
+
|
|
101
|
+
Call `workflow.start()` from server-side code to start a workflow. It returns a `Run` that you can use to identify the run, check its status, and wait for its result:
|
|
102
|
+
|
|
103
|
+
```python filename="app/api/generate.py"
|
|
104
|
+
from app.workflows.ai_content_workflow import ai_content_workflow
|
|
105
|
+
from vercel import workflow
|
|
106
|
+
|
|
107
|
+
@app.post("/api/generate")
|
|
108
|
+
async def generate_content(*, topic: str):
|
|
109
|
+
run = await workflow.start(ai_content_workflow, topic=topic) # [!code highlight]
|
|
110
|
+
|
|
111
|
+
print(run.run_id)
|
|
112
|
+
print(await run.status()) # [!code highlight]
|
|
113
|
+
|
|
114
|
+
# Wait until the workflow completes and return its result.
|
|
115
|
+
return await run.return_value() # [!code highlight]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Starting a workflow only waits until the run has been created and queued. Await `return_value()` to wait for the workflow to finish, or save its `run_id` and recreate the handle later with `workflow.Run(run_id)`.
|
|
119
|
+
|
|
98
120
|
## Sleep
|
|
99
121
|
|
|
100
122
|
Sleep pauses a workflow for a specified duration without consuming compute resources:
|
|
101
123
|
|
|
102
|
-
```python filename="app/workflows/ai_refine.py"
|
|
124
|
+
```python filename="app/workflows/ai_refine.py"
|
|
125
|
+
from datetime import timedelta
|
|
126
|
+
|
|
127
|
+
from app.workflow import wf
|
|
103
128
|
from vercel import workflow
|
|
104
129
|
|
|
105
130
|
@wf.workflow
|
|
106
131
|
async def ai_refine_workflow(*, draft_id: str):
|
|
107
132
|
draft = await fetch_draft(draft_id)
|
|
108
133
|
|
|
109
|
-
await workflow.sleep(
|
|
134
|
+
await workflow.sleep(timedelta(days=7)) # Wait 7 days to gather more signals. # [!code highlight]
|
|
110
135
|
|
|
111
136
|
refined = await refine_draft(draft)
|
|
112
137
|
|
|
@@ -116,7 +141,31 @@ async def ai_refine_workflow(*, draft_id: str):
|
|
|
116
141
|
}
|
|
117
142
|
```
|
|
118
143
|
|
|
119
|
-
The
|
|
144
|
+
The parameter accepts four forms:
|
|
145
|
+
|
|
146
|
+
| Form | Description | Example |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `str` | Human-readable duration string | `"2 days"`, `"1w"`, `"1h 30m"` |
|
|
149
|
+
| `int` or `float` | Seconds from now | `5` (5 seconds) |
|
|
150
|
+
| `datetime.timedelta` | Duration from now | `timedelta(days=7)` |
|
|
151
|
+
| `datetime.datetime` | Absolute wake-up time (must be timezone-aware) | `datetime(2025, 1, 1, tzinfo=UTC)` |
|
|
152
|
+
|
|
153
|
+
The string form accepts one or more `<value><unit>` pairs. Supported units:
|
|
154
|
+
|
|
155
|
+
| Duration | Unit |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| Milliseconds | `ms` |
|
|
158
|
+
| Seconds | `s`, `second`, `seconds` |
|
|
159
|
+
| Minutes | `m`, `minute`, `minutes` |
|
|
160
|
+
| Hours | `h`, `hour`, `hours` |
|
|
161
|
+
| Days | `d`, `day`, `days` |
|
|
162
|
+
| Weeks | `w`, `week`, `weeks` |
|
|
163
|
+
|
|
164
|
+
<Callout>
|
|
165
|
+
`sleep()` must be called from the workflow body, not from inside a step. Calling it from a step raises a `RuntimeError`.
|
|
166
|
+
</Callout>
|
|
167
|
+
|
|
168
|
+
The sleep consumes no resources. The workflow resumes automatically when the time expires.
|
|
120
169
|
|
|
121
170
|
## Hooks
|
|
122
171
|
|
|
@@ -124,13 +173,17 @@ A hook lets a workflow wait for external events such as user actions, webhooks,
|
|
|
124
173
|
|
|
125
174
|
Define a hook model with Pydantic and `workflow.BaseHook`:
|
|
126
175
|
|
|
127
|
-
```python filename="app/workflows/approval.py"
|
|
176
|
+
```python filename="app/workflows/approval.py"
|
|
177
|
+
import typing
|
|
178
|
+
|
|
179
|
+
import pydantic
|
|
180
|
+
from app.workflow import wf
|
|
128
181
|
from vercel import workflow
|
|
129
182
|
|
|
130
|
-
class Approval(BaseModel, workflow.BaseHook):
|
|
183
|
+
class Approval(pydantic.BaseModel, workflow.BaseHook): # [!code highlight]
|
|
131
184
|
"""Human approval for AI-generated drafts"""
|
|
132
185
|
|
|
133
|
-
decision: Literal["approved", "changes"]
|
|
186
|
+
decision: typing.Literal["approved", "changes"]
|
|
134
187
|
notes: str | None = None
|
|
135
188
|
|
|
136
189
|
@wf.workflow
|
|
@@ -138,7 +191,7 @@ async def ai_approval_workflow(*, topic: str):
|
|
|
138
191
|
draft = await generate_draft(topic=topic)
|
|
139
192
|
|
|
140
193
|
# Wait for human approval events
|
|
141
|
-
async for event in Approval.wait(token="draft-123"):
|
|
194
|
+
async for event in Approval.wait(token="draft-123"): # [!code highlight]
|
|
142
195
|
if event.decision == "approved":
|
|
143
196
|
await publish_draft(draft)
|
|
144
197
|
break
|
|
@@ -149,20 +202,56 @@ async def ai_approval_workflow(*, topic: str):
|
|
|
149
202
|
|
|
150
203
|
Resume the workflow when data arrives:
|
|
151
204
|
|
|
152
|
-
```python filename="app/api/resume.py"
|
|
205
|
+
```python filename="app/api/resume.py"
|
|
206
|
+
from app.workflows.approval import Approval
|
|
207
|
+
|
|
153
208
|
@app.post("/api/resume")
|
|
154
|
-
async def resume(approval: Approval):
|
|
209
|
+
async def resume(approval: Approval): # [!code highlight]
|
|
155
210
|
"""Resume the workflow when an approval is received"""
|
|
156
211
|
|
|
157
|
-
await approval.resume("draft-123")
|
|
212
|
+
await approval.resume("draft-123") # [!code highlight]
|
|
158
213
|
return {"ok": True}
|
|
159
214
|
```
|
|
160
215
|
|
|
161
216
|
When a hook receives data, the workflow resumes automatically. You don't need polling, message queues, or manual state management.
|
|
162
217
|
|
|
163
|
-
##
|
|
218
|
+
## Streaming
|
|
219
|
+
|
|
220
|
+
Steps can stream progress while a workflow is running. Get the run's writable stream inside a step, write values to it, and close it when no more values will be sent:
|
|
221
|
+
|
|
222
|
+
```python filename="app/workflows/streaming.py"
|
|
223
|
+
from app.workflow import wf
|
|
224
|
+
from vercel import workflow
|
|
225
|
+
|
|
226
|
+
@wf.step
|
|
227
|
+
async def write_progress():
|
|
228
|
+
writable = workflow.get_writable() # [!code highlight]
|
|
229
|
+
|
|
230
|
+
for message in ["Drafting", "Reviewing", "Complete"]:
|
|
231
|
+
await writable.write(message) # [!code highlight]
|
|
232
|
+
|
|
233
|
+
await writable.close()
|
|
234
|
+
|
|
235
|
+
@wf.workflow
|
|
236
|
+
async def streaming_workflow():
|
|
237
|
+
await write_progress()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Read the values from the returned `Run` as they arrive:
|
|
241
|
+
|
|
242
|
+
```python filename="app/api/stream.py"
|
|
243
|
+
from app.workflows.streaming import streaming_workflow
|
|
244
|
+
from vercel import workflow
|
|
245
|
+
|
|
246
|
+
@app.post("/api/stream")
|
|
247
|
+
async def stream_progress():
|
|
248
|
+
run = await workflow.start(streaming_workflow)
|
|
249
|
+
|
|
250
|
+
async for message in run.readable(): # [!code highlight]
|
|
251
|
+
print(message)
|
|
252
|
+
```
|
|
164
253
|
|
|
165
|
-
|
|
254
|
+
Streams are not closed automatically. Close the writable in the last step that writes to it so readers know when the stream is complete.
|
|
166
255
|
|
|
167
256
|
## Next steps
|
|
168
257
|
|
|
@@ -232,7 +232,7 @@ npx workflow inspect runs
|
|
|
232
232
|
|
|
233
233
|
## Deploying to production
|
|
234
234
|
|
|
235
|
-
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and
|
|
235
|
+
Workflow SDK apps currently work best when deployed to [Vercel](https://vercel.com/home) and need no special configuration.
|
|
236
236
|
|
|
237
237
|
<FluidComputeCallout />
|
|
238
238
|
|
|
@@ -310,7 +310,7 @@ The directive approach solved all these issues: it works in any project structur
|
|
|
310
310
|
|
|
311
311
|
We considered decorators, but they presented technical and ergonomic challenges.
|
|
312
312
|
|
|
313
|
-
**Decorators are
|
|
313
|
+
**Decorators are not-yet-standard and class-focused**
|
|
314
314
|
|
|
315
315
|
Decorators are not yet a standard syntax ([TC39 proposal](https://github.com/tc39/proposal-decorators)) and they currently only work with classes. A class decorator approach could look like this:
|
|
316
316
|
|
|
@@ -11,7 +11,7 @@ related:
|
|
|
11
11
|
- /docs/api-reference/workflow-errors/workflow-world-error
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
[`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes appear in the Workflow CLI and web UI, and you can
|
|
14
|
+
[`setAttributes`](/docs/api-reference/workflow/set-attributes) attaches plaintext string metadata to the current workflow run. These attributes appear in the Workflow CLI and web UI, and you can search and filter runs by them from either the [CLI](#from-the-cli) or the [Analytics API](/docs/api-reference/workflow-runtime/world/analytics).
|
|
15
15
|
|
|
16
16
|
You can also seed any attributes directly when starting a run:
|
|
17
17
|
|
|
@@ -82,6 +82,28 @@ Expanding an `attr_set` event (in the run sidebar or the Events tab) shows the c
|
|
|
82
82
|
|
|
83
83
|
## Searching and filtering by attributes
|
|
84
84
|
|
|
85
|
+
### From the CLI
|
|
86
|
+
|
|
87
|
+
`workflow inspect attributes` lists the keys recorded on this project's runs,
|
|
88
|
+
with how many runs carry each and when it was first and last seen:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
workflow inspect attributes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Pass one or more `--attribute key=value` pairs to `inspect runs` to list the
|
|
95
|
+
runs carrying them. Repeatable up to 8 times:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
workflow inspect runs --attribute phase=received --status running
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Both require a backend with the analytics read path. `--attribute` is ignored
|
|
102
|
+
with a warning on backends without one, and `inspect attributes` reports that
|
|
103
|
+
it is unavailable.
|
|
104
|
+
|
|
105
|
+
### From the Analytics API
|
|
106
|
+
|
|
85
107
|
The [Analytics API](/docs/api-reference/workflow-runtime/world/analytics) can discover which attribute keys exist and filter run listings by them. The `analytics` namespace is optional on `World`, so feature-detect it before use; it is absent on local, Postgres, and other custom Worlds:
|
|
86
108
|
|
|
87
109
|
```typescript lineNumbers
|
|
@@ -18,7 +18,7 @@ Workflow SDK provides a Workflow CLI and web UI to inspect, monitor, and debug w
|
|
|
18
18
|
npx workflow
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
The CLI comes pre-installed with the Workflow SDK and registers the `workflow` command. If the `workflow` package is not already installed, `npx workflow` will download and run the CLI temporarily, or use the
|
|
21
|
+
The CLI comes pre-installed with the Workflow SDK and registers the `workflow` command. If the `workflow` package is not already installed, `npx workflow` will download and run the CLI temporarily, or use the locally installed version if available.
|
|
22
22
|
|
|
23
23
|
Get started inspecting your local workflows:
|
|
24
24
|
|
|
@@ -44,7 +44,6 @@ npx workflow inspect runs --web
|
|
|
44
44
|
|
|
45
45
|
On [Nitro](/docs/getting-started/nitro), the dev server has the web UI built
|
|
46
46
|
in: open `/_workflow` while `nitro dev` is running. No separate command is required.
|
|
47
|
-
needed.
|
|
48
47
|
|
|
49
48
|
In the runs table, select one or more runs and choose **Cancel** to cancel the batch in a single request. Runs that fail with a retryable error stay selected so you can retry them.
|
|
50
49
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data retention
|
|
3
|
+
description: Control how long a run's data is kept after it finishes.
|
|
4
|
+
type: reference
|
|
5
|
+
summary: Control how long a run's data is kept after the run ends.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/api-reference/workflow-api/start
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A finished run leaves data behind: the inputs and outputs of the workflow and
|
|
14
|
+
each of its steps, the payloads on its event log, and anything written to its
|
|
15
|
+
streams. How long that data is kept is decided by the World you are running
|
|
16
|
+
on, not by the SDK.
|
|
17
|
+
|
|
18
|
+
`experimental_retention` on [`start()`](/docs/api-reference/workflow-api/start)
|
|
19
|
+
lets a run ask for a specific retention period, rather than the World's default.
|
|
20
|
+
|
|
21
|
+
## Deleting a run's data as soon as it ends
|
|
22
|
+
|
|
23
|
+
{/* @skip-typecheck: abbreviated usage; processDocumentWorkflow is the reader's own workflow */}
|
|
24
|
+
```typescript lineNumbers
|
|
25
|
+
const run = await start(processDocumentWorkflow, [documentId], {
|
|
26
|
+
experimental_retention: 0, // [!code highlight]
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`0` asks the World to delete the run's **user data** the moment the run
|
|
31
|
+
completes or fails, rather than keeping it for the World's default window.
|
|
32
|
+
|
|
33
|
+
Two values are accepted today:
|
|
34
|
+
|
|
35
|
+
| Value | Meaning |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `0` | Delete user data as soon as the run reaches a terminal state. |
|
|
38
|
+
| `'default'` | Use the World's default. Identical to omitting the option. |
|
|
39
|
+
|
|
40
|
+
<Callout type="warn">
|
|
41
|
+
The option is prefixed `experimental_` because both its name and the set of
|
|
42
|
+
values it accepts are expected to change.
|
|
43
|
+
</Callout>
|
|
44
|
+
|
|
45
|
+
## What is deleted, and what is not
|
|
46
|
+
|
|
47
|
+
**Deleted:** the run's input, output and error; every step's input, output and
|
|
48
|
+
error; the payloads on the event log; and stream contents.
|
|
49
|
+
|
|
50
|
+
**Kept:** the run, step and event records themselves — their ids, timestamps,
|
|
51
|
+
status, step names, and any [attributes](/docs/observability/attributes) you
|
|
52
|
+
set. They are kept for the World's default period so the run stays visible in
|
|
53
|
+
the CLI and web UI. A purged run is still listed and still traceable; its
|
|
54
|
+
payloads simply read back as expired.
|
|
55
|
+
|
|
56
|
+
Inspecting a purged run shows it as expired rather than failing. The Workflow
|
|
57
|
+
CLI renders the run's own input, output and error as `<data expired>`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
workflow inspect runs wrun_...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Step, hook and event payloads read back empty. On the Vercel World they also
|
|
64
|
+
render as `<data expired>`; on Worlds that clear the stored value outright
|
|
65
|
+
they simply show as empty. Either way the data is gone — the difference is
|
|
66
|
+
only in how the absence is labelled.
|
|
67
|
+
|
|
68
|
+
<Callout type="warn">
|
|
69
|
+
**You cannot read the return value of a run started with
|
|
70
|
+
`experimental_retention: 0`.** The deletion races your own read of the
|
|
71
|
+
result and generally wins, so `await run.returnValue` throws
|
|
72
|
+
[`RunExpiredError`](/docs/errors/run-expired) instead of resolving.
|
|
73
|
+
|
|
74
|
+
This is a known limitation. If you need the result, send it somewhere you
|
|
75
|
+
control, e.g. a step that writes it to your own store, rather than reading it back off the run.
|
|
76
|
+
</Callout>
|
|
77
|
+
|
|
78
|
+
`RunExpiredError` is not specific to `experimental_retention: 0`. Any run read
|
|
79
|
+
after its retention window has passed throws it, and the error carries
|
|
80
|
+
`runId`, `runStatus` and `expiredAt` when the World still has them — so a
|
|
81
|
+
caller can tell a successful run whose result is gone from a failed one whose
|
|
82
|
+
error is gone. If the run's metadata is gone too, the World reports the run as
|
|
83
|
+
missing and you get `WorkflowRunNotFoundError` instead.
|
|
84
|
+
|
|
85
|
+
## Retention is implemented by the World
|
|
86
|
+
|
|
87
|
+
The SDK records your preference; it does not enforce it. `start()` writes the
|
|
88
|
+
value onto the run as the reserved `$retention` attribute, and the World
|
|
89
|
+
decides what to do when the run ends.
|
|
90
|
+
A World that does not implement retention
|
|
91
|
+
**keeps the data**. If you need certainty that a specific World deletes your data,
|
|
92
|
+
confirm it against that World's own documentation rather than the presence of this
|
|
93
|
+
option.
|
|
@@ -63,7 +63,7 @@ Stream spans are emitted by the SDK's world backend on the client that writes or
|
|
|
63
63
|
| `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting: queue dwell plus any cold start. |
|
|
64
64
|
| `workflow.stream.name` | The stream name, on stream write/read spans. |
|
|
65
65
|
| `workflow.stream.operation` | The stream operation: `write`, `write_multi`, `close`, `read`, or `flush`. |
|
|
66
|
-
| `workflow.stream.write.chunk_rtt` | Time between
|
|
66
|
+
| `workflow.stream.write.chunk_rtt` | Time between emission of a chunk to the wire, and receiving the `ack` message for that chunk. Also stamped on `workflow.stream.flush` (the batch's write RPC duration, network included). |
|
|
67
67
|
| `workflow.stream.flush.buffer_dwell_ms` | On `workflow.stream.flush`: time the batch's first chunk waited in the client-side write buffer (flush timer, run-ready barrier) before the request was dispatched. `workflow.stream.flush.chunks` / `.bytes` carry the batch shape. |
|
|
68
68
|
| `workflow.stream.read.ttfc_ms` | Time between opening a read connection and observing and receiving the first chunk back. |
|
|
69
69
|
| `workflow.stream.read.connect_ms` | On `workflow.stream.read`: the connect portion (read dispatch → stream handle/response headers), network included. |
|
package/docs/whats-new.mdx
CHANGED
|
@@ -31,9 +31,9 @@ The largest change in v5 has no API surface: the runtime does far less work per
|
|
|
31
31
|
|
|
32
32
|
**A workflow invocation now does as much as it can in a single pass.** In 4.x, progress was largely deferred to the queue: an invocation would execute a step, hand back to the queue, and let a fresh invocation pick up the next one. v5 creates and executes several steps inline per suspension, in parallel, and only uses the queue for a wait, a hook, or when the function approaches its timeout.
|
|
33
33
|
|
|
34
|
-
**The runtime avoids waiting on the persistence layer where it can determine that is safe for your workload.** The runtime skips many API calls when they aren't needed, such as requesting the event log on a run's first invocation. Step creation is folded into step execution rather than being its own round trip. The inline loop consumes the event-log delta from the previous step's write instead of re-listing events. Each optimization is gated on specific runtime conditions and can be turned off individually. See [Runtime tuning](/docs/configuration/runtime-tuning).
|
|
34
|
+
**The runtime avoids waiting on the persistence layer where it can determine that it is safe for your workload.** The runtime skips many API calls when they aren't needed, such as requesting the event log on a run's first invocation. Step creation is folded into step execution rather than being its own round trip. The inline loop consumes the event-log delta from the previous step's write instead of re-listing events. Each optimization is gated on specific runtime conditions and can be turned off individually. See [Runtime tuning](/docs/configuration/runtime-tuning).
|
|
35
35
|
|
|
36
|
-
**The workflow VM is kept alive across inline steps.** Within one invocation, a step-driven suspension keeps the live VM and hydrated state, including when hooks are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
|
|
36
|
+
**The workflow VM is kept alive across inline steps.** Within one invocation, a step- or attribute-driven suspension keeps the live VM and hydrated state, including when hooks or waits are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
|
|
37
37
|
|
|
38
38
|
**Resuming a hook takes one round trip instead of two.** `resumeHook()` writes the `hook_received` event and dispatches the queue message concurrently, with a `(runId, resumeId)` dedup constraint keeping the two writers converging on exactly one event. See [Resilient hook resumption](/docs/changelog/resilient-resume).
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.49",
|
|
4
4
|
"description": "Workflow SDK - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -58,25 +58,25 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"
|
|
62
|
-
"@workflow/
|
|
63
|
-
"@workflow/
|
|
64
|
-
"@workflow/
|
|
65
|
-
"@workflow/errors": "5.0.0-beta.19",
|
|
61
|
+
"@workflow/astro": "5.0.0-beta.49",
|
|
62
|
+
"@workflow/cli": "5.0.0-beta.49",
|
|
63
|
+
"@workflow/core": "5.0.0-beta.49",
|
|
64
|
+
"@workflow/errors": "5.0.0-beta.21",
|
|
66
65
|
"@workflow/typescript-plugin": "5.0.0-beta.5",
|
|
67
66
|
"@workflow/utils": "5.0.0-beta.10",
|
|
68
|
-
"
|
|
69
|
-
"@workflow/
|
|
70
|
-
"@workflow/
|
|
71
|
-
"@workflow/
|
|
72
|
-
"@workflow/
|
|
73
|
-
"@workflow/
|
|
67
|
+
"ms": "2.1.3",
|
|
68
|
+
"@workflow/next": "5.0.0-beta.49",
|
|
69
|
+
"@workflow/nest": "5.0.0-beta.49",
|
|
70
|
+
"@workflow/nitro": "5.0.0-beta.49",
|
|
71
|
+
"@workflow/nuxt": "5.0.0-beta.49",
|
|
72
|
+
"@workflow/sveltekit": "5.0.0-beta.49",
|
|
73
|
+
"@workflow/rollup": "5.0.0-beta.49"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@types/ms": "2.1.0",
|
|
77
77
|
"@types/node": "22.19.0",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
78
|
+
"@workflow/tsconfig": "5.0.0-beta.0",
|
|
79
|
+
"typescript": "^6.0.3"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"@opentelemetry/api": "1"
|