@vercel/factory 0.0.15 → 0.0.16

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.
Files changed (214) hide show
  1. package/CHANGELOG.md +356 -0
  2. package/README.md +49 -261
  3. package/dist/agent-routes.d.mts +47 -3
  4. package/dist/agent-routes.mjs +28 -1
  5. package/dist/agent-routes.mjs.map +1 -1
  6. package/dist/api-contracts.d.mts +20 -1
  7. package/dist/api-contracts.mjs +2 -1
  8. package/dist/api-contracts.mjs.map +1 -1
  9. package/dist/api.d.mts +45 -2
  10. package/dist/api.mjs +199 -10
  11. package/dist/api.mjs.map +1 -1
  12. package/dist/approval-contracts.d.mts +6 -0
  13. package/dist/blob/index.d.mts +51 -14
  14. package/dist/blob/index.mjs +26 -10
  15. package/dist/blob/index.mjs.map +1 -1
  16. package/dist/budget.d.mts +7 -0
  17. package/dist/budget.mjs +6 -0
  18. package/dist/budget.mjs.map +1 -1
  19. package/dist/build-factory.d.mts +1 -0
  20. package/dist/change-verification/dispatch.d.mts +16 -3
  21. package/dist/change-verification/dispatch.mjs +45 -7
  22. package/dist/change-verification/dispatch.mjs.map +1 -1
  23. package/dist/change-verification/eve-tool.d.mts +2 -1
  24. package/dist/change-verification/eve-tool.mjs +35 -47
  25. package/dist/change-verification/eve-tool.mjs.map +1 -1
  26. package/dist/change-verification/result.mjs +130 -0
  27. package/dist/change-verification/result.mjs.map +1 -0
  28. package/dist/changes/eve-record-change.d.mts +2 -2
  29. package/dist/changes/eve-record-change.mjs +43 -9
  30. package/dist/changes/eve-record-change.mjs.map +1 -1
  31. package/dist/changes.d.mts +4 -3
  32. package/dist/changes.mjs +2 -2
  33. package/dist/changes.mjs.map +1 -1
  34. package/dist/client-events.d.mts +10 -3
  35. package/dist/client-events.mjs +6 -2
  36. package/dist/client-events.mjs.map +1 -1
  37. package/dist/client-stream.mjs +8 -2
  38. package/dist/client-stream.mjs.map +1 -1
  39. package/dist/client-transcript.mjs +5 -1
  40. package/dist/client-transcript.mjs.map +1 -1
  41. package/dist/client.d.mts +121 -12
  42. package/dist/client.mjs +117 -9
  43. package/dist/client.mjs.map +1 -1
  44. package/dist/code-review/contracts.d.mts +1 -0
  45. package/dist/code-review/eve-post-review.d.mts +4 -4
  46. package/dist/code-review/eve-post-review.mjs +29 -17
  47. package/dist/code-review/eve-post-review.mjs.map +1 -1
  48. package/dist/code-review/eve-review-comments.d.mts +13 -2
  49. package/dist/code-review/eve-review-comments.mjs +45 -11
  50. package/dist/code-review/eve-review-comments.mjs.map +1 -1
  51. package/dist/code-review/github-reporter.d.mts +2 -0
  52. package/dist/code-review/github-reporter.mjs +7 -5
  53. package/dist/code-review/github-reporter.mjs.map +1 -1
  54. package/dist/code-review.d.mts +3 -2
  55. package/dist/deepsec/eve-tool.mjs +3 -1
  56. package/dist/deepsec/eve-tool.mjs.map +1 -1
  57. package/dist/dispatch.d.mts +79 -8
  58. package/dist/dispatch.mjs +68 -9
  59. package/dist/dispatch.mjs.map +1 -1
  60. package/dist/eve/index.d.mts +70 -10
  61. package/dist/eve/index.mjs +93 -22
  62. package/dist/eve/index.mjs.map +1 -1
  63. package/dist/eve/invoke.mjs +24 -11
  64. package/dist/eve/invoke.mjs.map +1 -1
  65. package/dist/eve/session-client.d.mts +122 -4
  66. package/dist/eve/session-client.mjs +127 -13
  67. package/dist/eve/session-client.mjs.map +1 -1
  68. package/dist/eve/task-execution.d.mts +380 -0
  69. package/dist/eve/task-execution.mjs +57 -2
  70. package/dist/eve/task-execution.mjs.map +1 -1
  71. package/dist/eve/task-session.d.mts +44 -2
  72. package/dist/eve/task-session.mjs +44 -2
  73. package/dist/eve/task-session.mjs.map +1 -1
  74. package/dist/eve/transcript.mjs +5 -1
  75. package/dist/eve/transcript.mjs.map +1 -1
  76. package/dist/execution.d.mts +117 -9
  77. package/dist/execution.mjs +76 -6
  78. package/dist/execution.mjs.map +1 -1
  79. package/dist/finding-remediation/admission.d.mts +2 -0
  80. package/dist/finding-remediation/admission.mjs +4 -1
  81. package/dist/finding-remediation/admission.mjs.map +1 -1
  82. package/dist/findings.d.mts +1 -0
  83. package/dist/github-publication.d.mts +1 -0
  84. package/dist/github-publication.mjs +97 -84
  85. package/dist/github-publication.mjs.map +1 -1
  86. package/dist/github-transfer.d.mts +15 -6
  87. package/dist/github-transfer.mjs +214 -66
  88. package/dist/github-transfer.mjs.map +1 -1
  89. package/dist/github.d.mts +51 -11
  90. package/dist/github.mjs +126 -24
  91. package/dist/github.mjs.map +1 -1
  92. package/dist/inbox-activity.d.mts +53 -0
  93. package/dist/inbox-activity.mjs +41 -0
  94. package/dist/inbox-activity.mjs.map +1 -0
  95. package/dist/index.d.mts +3 -1
  96. package/dist/index.mjs +3 -2
  97. package/dist/intake-contracts.d.mts +0 -1
  98. package/dist/integrations/deepsec.d.mts +1 -0
  99. package/dist/integrations/github.d.mts +2 -2
  100. package/dist/integrations/github.mjs +2 -2
  101. package/dist/integrations/slack.d.mts +3 -1
  102. package/dist/integrations/slack.mjs +3 -1
  103. package/dist/integrations/vercel.d.mts +4 -2
  104. package/dist/integrations/vercel.mjs +3 -2
  105. package/dist/merge-resolution/eve-tools.d.mts +1 -0
  106. package/dist/merge-resolution/eve-tools.mjs +7 -2
  107. package/dist/merge-resolution/eve-tools.mjs.map +1 -1
  108. package/dist/model-settings.d.mts +41 -0
  109. package/dist/model-settings.mjs +35 -0
  110. package/dist/model-settings.mjs.map +1 -0
  111. package/dist/planning/reconcile.mjs +6 -0
  112. package/dist/planning/reconcile.mjs.map +1 -1
  113. package/dist/postgres/index.d.mts +43 -2
  114. package/dist/postgres/index.mjs +40 -2
  115. package/dist/postgres/index.mjs.map +1 -1
  116. package/dist/presets/software-development/dispatch.d.mts +4 -1
  117. package/dist/presets/software-development/dispatch.mjs +2 -1
  118. package/dist/presets/software-development/dispatch.mjs.map +1 -1
  119. package/dist/presets/software-development/task-communication.d.mts +1 -0
  120. package/dist/presets/software-development/task-communication.mjs +48 -11
  121. package/dist/presets/software-development/task-communication.mjs.map +1 -1
  122. package/dist/presets/software-development.d.mts +1 -0
  123. package/dist/pull-requests/github-publisher.d.mts +15 -1
  124. package/dist/pull-requests/github-publisher.mjs +61 -1
  125. package/dist/pull-requests/github-publisher.mjs.map +1 -1
  126. package/dist/pull-requests.d.mts +1 -0
  127. package/dist/sandbox/index.d.mts +1 -0
  128. package/dist/schema/agent-route.d.mts +19 -1
  129. package/dist/schema/agent-route.mjs +19 -1
  130. package/dist/schema/agent-route.mjs.map +1 -1
  131. package/dist/schema/factory-config.d.mts +27 -0
  132. package/dist/schema/factory-config.mjs +33 -3
  133. package/dist/schema/factory-config.mjs.map +1 -1
  134. package/dist/schema/repository.d.mts +4 -0
  135. package/dist/schema/repository.mjs +5 -1
  136. package/dist/schema/repository.mjs.map +1 -1
  137. package/dist/schema/session.d.mts +1 -0
  138. package/dist/schema/session.mjs +1 -0
  139. package/dist/schema/session.mjs.map +1 -1
  140. package/dist/schema/slack-pr-notifications.d.mts +12 -0
  141. package/dist/schema/slack-pr-notifications.mjs +11 -0
  142. package/dist/schema/slack-pr-notifications.mjs.map +1 -0
  143. package/dist/schema/task-graph.d.mts +39 -0
  144. package/dist/schema/task.d.mts +1 -0
  145. package/dist/schema/task.mjs +2 -1
  146. package/dist/schema/task.mjs.map +1 -1
  147. package/dist/schema/transcript.d.mts +6 -0
  148. package/dist/schema/transcript.mjs +2 -1
  149. package/dist/schema/transcript.mjs.map +1 -1
  150. package/dist/schema/work.d.mts +52 -3
  151. package/dist/schema/work.mjs.map +1 -1
  152. package/dist/session-previews.d.mts +76 -0
  153. package/dist/session-previews.mjs +55 -0
  154. package/dist/session-previews.mjs.map +1 -0
  155. package/dist/session-review.d.mts +120 -0
  156. package/dist/session-review.mjs +79 -0
  157. package/dist/session-review.mjs.map +1 -0
  158. package/dist/signal-triage.mjs +1 -1
  159. package/dist/signals.d.mts +1 -0
  160. package/dist/stall.d.mts +4 -1
  161. package/dist/stall.mjs +6 -2
  162. package/dist/stall.mjs.map +1 -1
  163. package/dist/store/driver.d.mts +1 -1
  164. package/dist/store/driver.mjs.map +1 -1
  165. package/dist/store/engine.d.mts +206 -8
  166. package/dist/store/engine.mjs +147 -13
  167. package/dist/store/engine.mjs.map +1 -1
  168. package/dist/store/memory.d.mts +18 -1
  169. package/dist/store/memory.mjs +18 -1
  170. package/dist/store/memory.mjs.map +1 -1
  171. package/dist/store/slack-pr-notifications.d.mts +44 -0
  172. package/dist/store/slack-pr-notifications.mjs +121 -0
  173. package/dist/store/slack-pr-notifications.mjs.map +1 -0
  174. package/dist/store/task-work.d.mts +121 -6
  175. package/dist/store/task-work.mjs +7 -4
  176. package/dist/store/task-work.mjs.map +1 -1
  177. package/dist/sweep.d.mts +28 -6
  178. package/dist/sweep.mjs +34 -6
  179. package/dist/sweep.mjs.map +1 -1
  180. package/dist/task-graph-view.d.mts +3 -0
  181. package/dist/tasks.d.mts +3 -3
  182. package/dist/tasks.mjs +3 -3
  183. package/dist/vercel-git.d.mts +35 -3
  184. package/dist/vercel-git.mjs +265 -33
  185. package/dist/vercel-git.mjs.map +1 -1
  186. package/dist/vercel-github-api.d.mts +103 -0
  187. package/dist/vercel-github-api.mjs +363 -0
  188. package/dist/vercel-github-api.mjs.map +1 -0
  189. package/dist/vercel.d.mts +3 -2
  190. package/dist/vercel.mjs +3 -2
  191. package/dist/vercel.mjs.map +1 -1
  192. package/dist/work-triage.d.mts +1 -0
  193. package/dist/workflows.d.mts +102 -4
  194. package/dist/workflows.mjs +55 -2
  195. package/dist/workflows.mjs.map +1 -1
  196. package/dist/workspace-files-git.d.mts +15 -0
  197. package/dist/workspace-files-git.mjs +61 -0
  198. package/dist/workspace-files-git.mjs.map +1 -0
  199. package/dist/workspace-files.d.mts +107 -0
  200. package/dist/workspace-files.mjs +74 -0
  201. package/dist/workspace-files.mjs.map +1 -0
  202. package/docs/getting-started.md +104 -0
  203. package/docs/index.md +100 -0
  204. package/docs/recipes/cancellation.md +215 -0
  205. package/docs/recipes/custom-workflow.md +153 -0
  206. package/docs/recipes/dependent-tasks.md +207 -0
  207. package/docs/recipes/eve-agent.md +277 -0
  208. package/docs/recipes/human-input.md +204 -0
  209. package/docs/recipes/persistence-recovery.md +268 -0
  210. package/docs/recipes/retry-recovery.md +241 -0
  211. package/docs/recipes/task-messaging.md +215 -0
  212. package/docs/recipes/typed-eve-result.md +161 -0
  213. package/docs/runtime-integration.md +137 -0
  214. package/package.json +17 -6
@@ -0,0 +1,215 @@
1
+ # Process a typed Task message
2
+
3
+ Task messages are trusted application APIs with deny-by-default authorization. This recipe sends a
4
+ typed parent-to-child message, stores a decision before applying its effect, and acknowledges the
5
+ message only after the effect commits.
6
+
7
+ Task messaging coordinates durable work around Eve sessions; it is not another Eve transport. An
8
+ application exposes narrowly authorized message operations through its own tools or workflow code,
9
+ and the Eve agent remains bound to its current Task attempt. Start with
10
+ [Run an Eve agent as a Factory Task](./eve-agent.md) before adding inter-Task messages.
11
+
12
+ ## Run the recipe
13
+
14
+ Save this as `task-messaging.mts` in an ESM project with `@vercel/factory`, `eve`, and `zod`
15
+ installed.
16
+
17
+ <!-- runnable-example:start -->
18
+
19
+ ```ts
20
+ import type { ToolContext } from "eve/tools";
21
+ import { z } from "zod";
22
+ import {
23
+ factoryTaskAttemptAttribute,
24
+ factoryTaskAttribute,
25
+ requireTaskExecution,
26
+ } from "@vercel/factory";
27
+ import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
28
+ import {
29
+ defineTaskMessageProtocol,
30
+ defineTaskMessageProtocols,
31
+ processTaskMessage,
32
+ taskBoundMessagePolicy,
33
+ taskMessageInput,
34
+ } from "@vercel/factory/tasks";
35
+ import { defineWorkflow, defineWorkflows, taskWork } from "@vercel/factory/workflows";
36
+
37
+ const noteProtocol = defineTaskMessageProtocol({
38
+ id: "review-note",
39
+ version: 1,
40
+ payload: z.strictObject({ note: z.string().min(1) }),
41
+ });
42
+ const messageWorkflow = defineWorkflow({
43
+ id: "process-review-note",
44
+ version: 1,
45
+ input: z.null(),
46
+ output: z.strictObject({ handled: z.boolean() }),
47
+ });
48
+ const driver = createInMemoryDriver();
49
+ let nowMs = Date.parse("2026-09-10T12:00:00.000Z");
50
+ const openStores = () =>
51
+ createStores({
52
+ driver,
53
+ now: () => new Date(nowMs).toISOString(),
54
+ workflows: defineWorkflows([messageWorkflow]),
55
+ messages: {
56
+ protocols: defineTaskMessageProtocols([noteProtocol]),
57
+ authorize: taskBoundMessagePolicy,
58
+ },
59
+ });
60
+ const stores = openStores();
61
+ const base = {
62
+ repositoryIds: ["repo_recipe05"] as const,
63
+ kind: "example",
64
+ origin: { operator: "local:recipe" },
65
+ replyTo: { channel: "local", address: "task-messaging" },
66
+ work: taskWork({ title: "Process a review note", input: null, workflow: messageWorkflow }),
67
+ } as const;
68
+ const root = await stores.tasks.create({ ...base, dedupeKey: "message-root" });
69
+ const child = await stores.tasks.create({
70
+ ...base,
71
+ parentTaskId: root.id,
72
+ dedupeKey: "message-child",
73
+ });
74
+ await stores.tasks.transition(root.id, "running");
75
+ await stores.tasks.transition(child.id, "running");
76
+ const runningRoot = await stores.tasks.recordExecution(root.id, {
77
+ execution: { provider: "eve", sessionId: "ses_message_root" },
78
+ });
79
+ const runningChild = await stores.tasks.recordExecution(child.id, {
80
+ execution: { provider: "eve", sessionId: "ses_message_child" },
81
+ });
82
+
83
+ function toolContext(task: typeof runningRoot): ToolContext {
84
+ if (task.execution?.provider !== "eve") throw new Error("Expected an Eve execution");
85
+ return {
86
+ session: {
87
+ id: task.execution.sessionId,
88
+ auth: {
89
+ initiator: {
90
+ attributes: {
91
+ [factoryTaskAttribute]: task.id,
92
+ [factoryTaskAttemptAttribute]: String(task.attempt),
93
+ },
94
+ },
95
+ },
96
+ },
97
+ } as unknown as ToolContext;
98
+ }
99
+
100
+ const rootContext = toolContext(runningRoot);
101
+ const authenticatedRoot = await requireTaskExecution(stores, rootContext, runningRoot.id);
102
+ const rootPrincipal = {
103
+ id: `eve:${rootContext.session.id}`,
104
+ taskId: authenticatedRoot.id,
105
+ };
106
+ const childContext = toolContext(runningChild);
107
+ const authenticatedChild = await requireTaskExecution(stores, childContext, runningChild.id);
108
+ const childPrincipal = {
109
+ id: `eve:${childContext.session.id}`,
110
+ taskId: authenticatedChild.id,
111
+ };
112
+
113
+ const sendInput = taskMessageInput(noteProtocol, {
114
+ operationId: "review-note-1",
115
+ fromTaskId: root.id,
116
+ toTaskId: child.id,
117
+ expectAttempt: root.attempt,
118
+ payload: { note: "Check the retry boundary" },
119
+ });
120
+ const sent = await stores.messages.send({
121
+ principal: rootPrincipal,
122
+ message: sendInput,
123
+ });
124
+ const duplicate = await stores.messages.send({
125
+ principal: rootPrincipal,
126
+ message: sendInput,
127
+ });
128
+ if (duplicate.id !== sent.id) throw new Error("Expected idempotent message admission");
129
+
130
+ let unauthorizedReadRejected = false;
131
+ try {
132
+ await stores.messages.pending({
133
+ principal: rootPrincipal,
134
+ toTaskId: child.id,
135
+ });
136
+ } catch {
137
+ unauthorizedReadRejected = true;
138
+ }
139
+ const externalEffects = new Set<string>();
140
+ let decisionCalls = 0;
141
+ let applyCalls = 0;
142
+ let interruptAfterCommit = true;
143
+ const processNext = (currentStores: typeof stores) =>
144
+ processTaskMessage({
145
+ stores: currentStores,
146
+ principal: childPrincipal,
147
+ task: { taskId: authenticatedChild.id, attempt: authenticatedChild.attempt },
148
+ leaseMs: 1_000,
149
+ workflow: messageWorkflow.binding,
150
+ protocol: noteProtocol,
151
+ decisionSchema: z.strictObject({ handled: z.boolean() }),
152
+ now: () => new Date(nowMs).toISOString(),
153
+ async decide({ message }) {
154
+ decisionCalls += 1;
155
+ return { handled: message.payload.note === "Check the retry boundary" };
156
+ },
157
+ async apply({ decision, effectKey }) {
158
+ if (!decision.handled) throw new Error("Expected the note to be handled");
159
+ applyCalls += 1;
160
+ externalEffects.add(effectKey);
161
+ if (interruptAfterCommit) {
162
+ interruptAfterCommit = false;
163
+ throw new Error("process interrupted after the external effect committed");
164
+ }
165
+ },
166
+ });
167
+
168
+ let interruptionObserved = false;
169
+ try {
170
+ await processNext(stores);
171
+ } catch (error) {
172
+ interruptionObserved =
173
+ error instanceof Error && error.message.includes("interrupted after the external effect");
174
+ }
175
+ const restartedStores = openStores();
176
+ const stillLeased = await processNext(restartedStores);
177
+ nowMs += 1_001;
178
+ const processed = await processNext(restartedStores);
179
+ const replay = await processNext(restartedStores);
180
+ if (
181
+ !unauthorizedReadRejected ||
182
+ !interruptionObserved ||
183
+ stillLeased.status !== "idle" ||
184
+ processed.status !== "processed" ||
185
+ processed.decision.handled !== true ||
186
+ decisionCalls !== 1 ||
187
+ applyCalls !== 2 ||
188
+ externalEffects.size !== 1 ||
189
+ replay.status !== "idle"
190
+ ) {
191
+ throw new Error("Expected authorized, interruption-safe message processing");
192
+ }
193
+
194
+ console.log(JSON.stringify({ messageId: sent.id, status: processed.status }));
195
+ ```
196
+
197
+ <!-- runnable-example:end -->
198
+
199
+ ## What it proves
200
+
201
+ The supplied policy allows direct parent-child sends and recipient-local reads and processing. A
202
+ stable source operation ID makes retries return the same immutable message. The example interrupts
203
+ processing after the external effect commits, opens the stores again, waits for the claim lease to
204
+ expire, and recovers without recomputing the durable decision. `apply` repeats with the same effect
205
+ key, so the simulated external system records one effect.
206
+
207
+ Leases do not make external systems exactly-once: `apply` may repeat after interruption. Production
208
+ effects must remain idempotent by `effectKey`. An Eve-facing wrapper should call
209
+ `requireTaskExecution` and derive its message principal from that authenticated Task and session,
210
+ as the recipe does; never accept the principal from model input.
211
+
212
+ Message admission does not wake an idle recipient by itself. After a successful send, publish an
213
+ application-owned queue wakeup or run message processing on a schedule. If the recipient is
214
+ deliberately inactive, use `stores.work.waitForMessages` and `stores.work.resume` around that
215
+ workflow phase.
@@ -0,0 +1,161 @@
1
+ # Submit a typed workflow result from Eve
2
+
3
+ The generic `finish_task` tool records `{ summary }` for the default `task@1` workflow. When an
4
+ Eve agent must return a domain-specific result, define a narrow Eve tool that authenticates the
5
+ current execution with `requireTaskExecution` and completes the exact registered workflow.
6
+
7
+ ## Run the recipe
8
+
9
+ Save this as `typed-eve-result.mts` in an ESM project with `@vercel/factory`, `eve`, and `zod`
10
+ installed.
11
+
12
+ <!-- runnable-example:start -->
13
+
14
+ ```ts
15
+ import { defineTool, type ToolContext } from "eve/tools";
16
+ import { z } from "zod";
17
+ import {
18
+ factoryTaskAttemptAttribute,
19
+ factoryTaskAttribute,
20
+ requireTaskExecution,
21
+ startEveSession,
22
+ taskHeaders,
23
+ } from "@vercel/factory";
24
+ import { dispatchTask } from "@vercel/factory/execution";
25
+ import { createInMemoryDriver, createStores } from "@vercel/factory/storage";
26
+ import { taskIdSchema } from "@vercel/factory/tasks";
27
+ import {
28
+ defineWorkflow,
29
+ defineWorkflows,
30
+ parseAgentRouteBinding,
31
+ taskWork,
32
+ } from "@vercel/factory/workflows";
33
+
34
+ const greetingWorkflow = defineWorkflow({
35
+ id: "greeting",
36
+ version: 1,
37
+ input: z.strictObject({ name: z.string().min(1) }),
38
+ output: z.strictObject({
39
+ greeting: z.string().min(1),
40
+ language: z.enum(["en", "de"]),
41
+ }),
42
+ });
43
+ const route = parseAgentRouteBinding({ id: "greeter", version: 1 });
44
+ const stores = createStores({
45
+ driver: createInMemoryDriver(),
46
+ workflows: defineWorkflows([greetingWorkflow]),
47
+ });
48
+ const base = {
49
+ repositoryIds: ["repo_typed01"] as const,
50
+ kind: "example",
51
+ origin: { operator: "local:recipe" },
52
+ replyTo: { channel: "local", address: "typed-eve-result" },
53
+ } as const;
54
+ const task = await stores.tasks.create({
55
+ ...base,
56
+ work: {
57
+ ...taskWork({
58
+ title: "Create a German greeting",
59
+ input: { name: "Factory" },
60
+ workflow: greetingWorkflow,
61
+ }),
62
+ route,
63
+ },
64
+ });
65
+ const otherTask = await stores.tasks.create({
66
+ ...base,
67
+ work: {
68
+ ...taskWork({
69
+ title: "A different greeting",
70
+ input: { name: "Eve" },
71
+ workflow: greetingWorkflow,
72
+ }),
73
+ route,
74
+ },
75
+ });
76
+
77
+ const running = await dispatchTask(stores, {
78
+ taskId: task.id,
79
+ route,
80
+ launch: (claimed) =>
81
+ startEveSession({
82
+ agentUrl: "https://factory.example/eve/agents/greeter",
83
+ message: `Greet ${greetingWorkflow.input.parse(claimed.work.input).name}`,
84
+ operationId: `factory-task:${claimed.id}:${claimed.attempt}`,
85
+ headers: taskHeaders({ taskId: claimed.id, attempt: claimed.attempt }),
86
+ fetch: async () => Response.json({ sessionId: "ses_typed_greeting" }),
87
+ }),
88
+ });
89
+ if (running.execution?.provider !== "eve") throw new Error("Expected an Eve execution");
90
+ const context = {
91
+ session: {
92
+ id: running.execution.sessionId,
93
+ auth: {
94
+ initiator: {
95
+ attributes: {
96
+ [factoryTaskAttribute]: running.id,
97
+ [factoryTaskAttemptAttribute]: String(running.attempt),
98
+ },
99
+ },
100
+ },
101
+ },
102
+ } as unknown as ToolContext;
103
+
104
+ const submitGreeting = defineTool({
105
+ description: "Validate and submit the greeting produced for your current Factory Task.",
106
+ inputSchema: z.strictObject({
107
+ taskId: taskIdSchema,
108
+ greeting: greetingWorkflow.output.shape.greeting,
109
+ language: greetingWorkflow.output.shape.language,
110
+ }),
111
+ async execute({ taskId, greeting, language }, ctx) {
112
+ const authenticated = await requireTaskExecution(stores, ctx, taskId);
113
+ if (authenticated.execution?.provider !== "eve") {
114
+ throw new Error("Expected the authenticated Eve execution");
115
+ }
116
+ const completed = await stores.work.completeWorkflow({
117
+ task: { taskId: authenticated.id, attempt: authenticated.attempt },
118
+ workflow: greetingWorkflow.binding,
119
+ output: { greeting, language },
120
+ fence: { from: "running", execution: authenticated.execution },
121
+ });
122
+ return { submitted: true as const, taskId: completed.id };
123
+ },
124
+ });
125
+
126
+ let foreignTaskRejected = false;
127
+ try {
128
+ await submitGreeting.execute(
129
+ { taskId: otherTask.id, greeting: "Hallo, Eve!", language: "de" },
130
+ context,
131
+ );
132
+ } catch {
133
+ foreignTaskRejected = true;
134
+ }
135
+ await submitGreeting.execute(
136
+ { taskId: running.id, greeting: "Hallo, Factory!", language: "de" },
137
+ context,
138
+ );
139
+ const completed = await stores.tasks.get(running.id);
140
+ const output = greetingWorkflow.output.parse(completed?.workResult?.output);
141
+ if (
142
+ !foreignTaskRejected ||
143
+ completed?.state !== "succeeded" ||
144
+ output.greeting !== "Hallo, Factory!"
145
+ ) {
146
+ throw new Error("Expected authenticated, schema-validated workflow completion");
147
+ }
148
+
149
+ console.log(JSON.stringify({ state: completed.state, output }));
150
+ ```
151
+
152
+ <!-- runnable-example:end -->
153
+
154
+ Export the tool from the target agent's `tools/` directory, alongside `get_task`. Its input
155
+ schema tells the model exactly what result to return, while the workflow registry validates the
156
+ persisted output again. The completion fence checks the authenticated Task's running state and Eve
157
+ execution atomically with the success transition, so a concurrent pause, cancellation, retry, or
158
+ replacement execution cannot accept a stale result.
159
+
160
+ Keep each historical workflow version registered while stored Tasks can still reference it. Create
161
+ a new workflow and tool version when the result contract changes incompatibly.
@@ -0,0 +1,137 @@
1
+ # Runtime composition
2
+
3
+ `@vercel/factory` provides typed contracts and policy injection points. An application still owns
4
+ its configured repositories, credentials, agent routes, budgets, prompts, approval policy, and
5
+ provider accounts.
6
+
7
+ ## Kernel and supplied policy
8
+
9
+ The package root owns common configuration, repository admission, credential-redacted diagnostics,
10
+ generic Eve session integration, and declarations with no narrower capability owner. Kernel
11
+ capabilities live on their own paths, including Tasks, workflows, execution, planning, budgets,
12
+ signals, receipts, storage, and communication.
13
+
14
+ The supplied software-development behavior is an explicit composition under
15
+ `@vercel/factory/presets/software-development`. It combines approval, completion, communication,
16
+ recovery, dispatch, and delegation policy with capability-owned Task and Change tools. Applications
17
+ inject repository policy, planner behavior, launchers, credentials, budget, wakeup publication,
18
+ and retry and batch limits.
19
+
20
+ ## Durable work
21
+
22
+ Every Task contains explicit work: a title, bounded JSON input, completion criteria, an exact
23
+ workflow ID and version, and an optional exact agent route. `taskWork` selects the generic `task@1`
24
+ JSON contract. Custom input and output contracts require `defineWorkflow` and registration through
25
+ `defineWorkflows`.
26
+
27
+ Task kind is descriptive. It does not select execution or determine success. Canonical dependencies
28
+ belong to `stores.graphs`, and `stores.work.complete` records validated output. A route-less Task is
29
+ owned by local workflow code; agent work records an exact route and must recover that same binding.
30
+
31
+ Task messages are trusted application APIs rather than HTTP endpoints. Message access defaults to
32
+ deny. `taskBoundMessagePolicy` permits parent-child sends and recipient-local operations when an
33
+ application explicitly configures the supplied protocols. Durable decisions are stored before
34
+ idempotent effects, but leases cannot fence external systems or remove cancellation races.
35
+ The runnable [Task messaging recipe](recipes/task-messaging.md) demonstrates that boundary and its
36
+ effect-key requirement.
37
+
38
+ ## Where Eve fits
39
+
40
+ Eve is the normal agent runtime. Factory does not implement the model loop or replace an Eve
41
+ agent's `agent.ts`; it records the durable contract around each run and connects that contract to
42
+ the Eve session.
43
+
44
+ | Task execution mode | Use it for | What advances the Task |
45
+ | ------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
46
+ | Exact agent route | Normal Eve agent work | `dispatchTask` starts an Eve session; authenticated Factory tools and hooks complete or recover the attempt |
47
+ | Workflow owned | Deterministic application or orchestrator logic with no provider session | Application code transitions and completes the Task |
48
+ | Execution adapter | Coding harnesses, sandboxes, and other provider sessions | The adapter returns evidence; workflow policy interprets it and completes or retries the Task |
49
+
50
+ For an Eve route, the application calls `startEveSession` with `taskHeaders`. The agent's Eve
51
+ channel wraps its authentication with `withFactoryTask`, and its filesystem mounts tools from
52
+ `createTaskTools` plus the hook from `createFactoryHooks`. The session is thereby bound to one Task
53
+ ID and attempt. See the runnable [Eve agent integration recipe](recipes/eve-agent.md), followed by
54
+ the [typed Eve result](recipes/typed-eve-result.md), [human input](recipes/human-input.md), and
55
+ [dependent Eve agents](recipes/dependent-tasks.md) recipes.
56
+
57
+ ## Assemble the production loop
58
+
59
+ A deployable factory needs an application-owned loop around the package primitives. Keep each
60
+ boundary explicit so recovery can repeat one operation without guessing what happened elsewhere.
61
+
62
+ | Boundary | Production responsibility |
63
+ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
64
+ | Persistence | Construct one `createStores` instance with Blob or Postgres; register every workflow and message protocol version referenced by stored Tasks |
65
+ | Route launchers | Map each exact `routeKey` to a protected Eve agent URL; merge `taskHeaders` with deployment identity |
66
+ | Dispatch | Run `sweepQueued` from a durable schedule and after events that can unblock work; pass budget and approval policy before launch |
67
+ | Agent completion | Mount `withFactoryTask`, `createFactoryHooks`, and only the Task or domain tools that role needs |
68
+ | Questions | Deliver persisted QuestionTasks through Foreman, authenticate replies to the original `replyTo`, then wake dispatch |
69
+ | Recovery | Let hooks requeue bounded Eve failures; sweep the exact stored route and retain attempt-derived operation IDs |
70
+ | Cancellation | Persist Task cancellation first, request provider cancellation second, and retry ambiguous provider stops |
71
+ | Messages and effects | Derive principals from authenticated sessions, publish recipient wakeups, and make external effects idempotent by effect key |
72
+ | Operations | Monitor failed launches, exhausted Tasks, unanswered questions, queue age, budget refusal, and outbox delivery |
73
+
74
+ The dispatcher can run concurrently: its queued-to-running admission has one winner. Invoke it on
75
+ a short schedule for repair and also from queue wakeups for latency. A useful wakeup contains no
76
+ authority; the worker rereads canonical state, runs one bounded `sweepQueued` pass, and records or
77
+ alerts on every `failed` result. Treat `skipped` results as named gates such as dependencies,
78
+ approval, or workflow ownership rather than launch failures. Budget exhaustion fails the Task and
79
+ appears as a `launch_failed` result, so operations should surface its persisted refusal reason.
80
+
81
+ Budget reservation and approval happen before a route launcher creates an Eve session. Configure
82
+ the policy in trusted application code, pass the current budget snapshot to dispatch, and expose
83
+ approval actions only through an authenticated operator boundary. The agent should receive the
84
+ approved Task contract and role-specific tools, never provider credentials or the ability to widen
85
+ repository scope.
86
+
87
+ ## Storage and execution
88
+
89
+ `@vercel/factory/storage` exposes the validating store engine and a non-durable in-memory driver.
90
+ Persistent adapters are opt-in through `@vercel/factory/storage/blob` and
91
+ `@vercel/factory/storage/postgres`.
92
+
93
+ Recovery has three separate meanings. Reopening a persistent driver only restores access to the
94
+ records. `stores.work.recover(rootTaskId)` then reads the canonical graph and repairs Task snapshots
95
+ whose admitted graph nodes survived an interrupted creation write. It does not launch or resume a
96
+ provider. Execution recovery separately inspects the Task's recorded provider execution and either
97
+ reattaches when it is confirmed active or applies workflow-owned retry policy. The runnable
98
+ [persistence and restart recovery recipe](recipes/persistence-recovery.md) demonstrates the first
99
+ two boundaries with workflow-owned work; the retry recipe covers provider execution recovery.
100
+
101
+ `@vercel/factory/execution` owns provider-neutral dispatch and execution lifecycle contracts.
102
+ `@vercel/factory/sandbox` supplies retry-safe execution records, abort propagation, harness
103
+ continuation, workspace contracts, and scoped Vercel Sandbox lifecycle. The application injects
104
+ prompt construction, evidence interpretation, pricing, credentials, repository seeding, and its
105
+ model and harness registry.
106
+
107
+ The [custom workflow recipe](recipes/custom-workflow.md) demonstrates the adapter path for
108
+ non-Eve execution. The [cancellation](recipes/cancellation.md) recipe covers adapter and Eve
109
+ cancellation, while [retry recovery](recipes/retry-recovery.md) exercises Eve lifecycle hooks and
110
+ scheduled replacement attempts.
111
+
112
+ ## Provider adapters
113
+
114
+ External-provider APIs stay opt-in under `@vercel/factory/integrations/<provider>`:
115
+
116
+ - GitHub provides intake normalization, App-token scope validation, checkout and publication
117
+ primitives, communication delivery, review reporting, labels, and work triage.
118
+ - Slack delivers communication events through an injected client.
119
+ - DeepSec normalizes scanner output and provides its Eve tool.
120
+ - Vercel provides Queue wakeups and credential-isolated Git transport.
121
+
122
+ Applications remain responsible for selecting credentials and authorizing repositories. Connector
123
+ installation access never expands the application's repository allowlist.
124
+ The Vercel-managed transport uses fresh trusted Sandboxes for reads and signed writes. They execute
125
+ no repository code or model. Publication consumes credential-free bundles, applies exact remote-head
126
+ leases, rechecks application authority at the mutation boundary, and returns the signed remote SHA.
127
+ Do not attach managed Git fields to agent or harness Sandboxes. Applications still choose their
128
+ pull-request creation adapter. In Vercel mode, initial Git Data and pull-request publication use one
129
+ fresh repository-scoped managed Sandbox; Connect mode retains the installation-token publication path.
130
+
131
+ ## Operator boundary
132
+
133
+ `@vercel/factory/client` is browser-safe and exposes `FactoryClient`, request and response schemas,
134
+ Task states, transcripts, and session streaming. Server handlers and their dependencies live under
135
+ `@vercel/factory/api`; Next.js integration lives under `@vercel/factory/next`.
136
+
137
+ Return to the [consumer documentation index](index.md) to find exact import paths and declarations.
package/package.json CHANGED
@@ -1,9 +1,18 @@
1
1
  {
2
2
  "name": "@vercel/factory",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Schema, state machines, and kernel primitives for Agent Factory.",
5
+ "homepage": "https://github.com/vercel-labs/agent-factory/tree/main/packages/factory",
6
+ "bugs": "https://github.com/vercel-labs/agent-factory/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/vercel-labs/agent-factory.git",
10
+ "directory": "packages/factory"
11
+ },
5
12
  "files": [
6
- "dist"
13
+ "CHANGELOG.md",
14
+ "dist",
15
+ "docs"
7
16
  ],
8
17
  "type": "module",
9
18
  "sideEffects": false,
@@ -124,6 +133,8 @@
124
133
  "./package.json": "./package.json"
125
134
  },
126
135
  "dependencies": {
136
+ "@types/json-schema": "^7.0.15",
137
+ "@types/node": "24.x",
127
138
  "diff": "8.0.2"
128
139
  },
129
140
  "devDependencies": {
@@ -131,13 +142,13 @@
131
142
  "@ai-sdk/harness-claude-code": "1.0.112",
132
143
  "@ai-sdk/sandbox-vercel": "1.0.108",
133
144
  "@electric-sql/pglite": "^0.5.7",
134
- "@types/node": "24.x",
135
145
  "@vercel/blob": "^2.8.0",
136
146
  "@vercel/sandbox": "2.10.0-beta.0",
137
147
  "drizzle-orm": "^0.45.2",
138
- "eve": "^0.46.1",
148
+ "eve": "^0.57.0",
139
149
  "tsdown": "^0.22.14",
140
150
  "typescript": "5.9.3",
151
+ "typescript-current": "npm:typescript@7.0.2",
141
152
  "vitest": "4.1.9",
142
153
  "zod": "4.4.3"
143
154
  },
@@ -147,7 +158,7 @@
147
158
  "@vercel/blob": "^2.6.0",
148
159
  "@vercel/sandbox": ">=2.10.0-beta.0 <4",
149
160
  "drizzle-orm": "^0.45.0",
150
- "eve": "^0.46.0",
161
+ "eve": "^0.57.0",
151
162
  "zod": "^4.4.3"
152
163
  },
153
164
  "peerDependenciesMeta": {
@@ -177,6 +188,6 @@
177
188
  "lint": "oxlint --config ../../.oxlintrc.json --max-warnings 0 .",
178
189
  "lint:logs": "node ../../scripts/check-structured-logging.mjs src",
179
190
  "test": "vitest run",
180
- "typecheck": "tsc"
191
+ "typecheck": "node node_modules/typescript/bin/tsc"
181
192
  }
182
193
  }