@orkestrel/workflow 0.0.3 → 0.0.4

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.
@@ -435,6 +435,39 @@ export declare function createWorkflow(definition: WorkflowDefinition, options?:
435
435
  */
436
436
  export declare function createWorkflowContract(): ContractInterface<WorkflowDefinition>;
437
437
 
438
+ /**
439
+ * Create a {@link WorkflowManagerInterface} — the store-backed registry of
440
+ * {@link WorkflowInterface}s, the additive manager tier mirroring the `@orkestrel/agent`
441
+ * line's `createConversationManager` / `createWorkspaceManager`.
442
+ *
443
+ * @remarks
444
+ * `options.functions` flows into every workflow the manager mints (`add`, via
445
+ * {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
446
+ * {@link restoreWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
447
+ * mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
448
+ * manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
449
+ * is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
450
+ * {@link restoreWorkflow} remain valid — the manager is one more caller-driven persistence
451
+ * seam, not a replacement.
452
+ *
453
+ * @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
454
+ * @returns A working {@link WorkflowManagerInterface}
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * import { createMemoryWorkflowStore, createWorkflowManager } from '@src/core'
459
+ *
460
+ * const manager = createWorkflowManager({
461
+ * store: createMemoryWorkflowStore(),
462
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
463
+ * })
464
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
465
+ * await manager.save(workflow.id) // persisted to the store
466
+ * const reopened = await manager.open(workflow.id) // already registered — no store hit
467
+ * ```
468
+ */
469
+ export declare function createWorkflowManager(options?: WorkflowManagerOptions): WorkflowManagerInterface;
470
+
438
471
  /**
439
472
  * Create a workflow runner — a {@link WorkflowRunnerInterface} that EXECUTES a live W-b
440
473
  * workflow tree by COMPOSING the shipped substrate: phases sequential, tasks concurrent,
@@ -3102,6 +3135,190 @@ export declare interface WorkflowInterface {
3102
3135
  snapshot(): WorkflowSnapshot;
3103
3136
  }
3104
3137
 
3138
+ /**
3139
+ * The store-backed registry of {@link WorkflowInterface}s keyed by `id`, in insertion order —
3140
+ * the additive manager tier mirroring the `@orkestrel/agent` line's `ConversationManager` /
3141
+ * `WorkspaceManager`. Event-free (a registry, like its twins); the observability lives on each
3142
+ * {@link WorkflowInterface}.
3143
+ *
3144
+ * @remarks
3145
+ * - **Registry.** Workflows live in an insertion-ordered `Map` keyed by `id`. `add(definition)`
3146
+ * mints a live {@link WorkflowInterface} through {@link createWorkflow} (flowing the manager's
3147
+ * `functions` registry in) and stores it under `definition.id` — an already-present id
3148
+ * OVERWRITES (last write wins). `count` is the map size, `workflow(id)` looks one up,
3149
+ * `workflows()` lists them in insertion order.
3150
+ * - **Durable open / save.** `open(id)` returns an already-registered workflow directly; on a
3151
+ * registry MISS with a `store` set it rehydrates through {@link restoreWorkflow} (flowing the
3152
+ * manager's `functions` registry in so the rehydrated tree is RUNNABLE), registers it, and
3153
+ * returns it — lenient (`undefined`) with no store or a store miss. `save(id)` persists a
3154
+ * registered workflow's `snapshot()` to the `store` — lenient (`false`) with no store or an
3155
+ * unknown id.
3156
+ * - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true` when
3157
+ * any was removed. `clear` empties the registry.
3158
+ * - **No active pointer.** Unlike its `ConversationManager` / `WorkspaceManager` twins, there is
3159
+ * no `active` / `switch` — nothing in the workflow domain renders "the current workflow".
3160
+ *
3161
+ * @example
3162
+ * ```ts
3163
+ * const manager = new WorkflowManager({
3164
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
3165
+ * })
3166
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
3167
+ * manager.workflow(workflow.id) // the same workflow
3168
+ * manager.count // 1
3169
+ * ```
3170
+ */
3171
+ export declare class WorkflowManager implements WorkflowManagerInterface {
3172
+ #private;
3173
+ constructor(options?: WorkflowManagerOptions);
3174
+ get count(): number;
3175
+ workflow(id: string): WorkflowInterface | undefined;
3176
+ workflows(): readonly WorkflowInterface[];
3177
+ add(definition: WorkflowDefinition): WorkflowInterface;
3178
+ open(id: string): Promise<WorkflowInterface | undefined>;
3179
+ save(id: string): Promise<boolean>;
3180
+ remove(ids: readonly string[]): boolean;
3181
+ remove(id: string): boolean;
3182
+ clear(): void;
3183
+ }
3184
+
3185
+ /**
3186
+ * A store-backed registry of {@link WorkflowInterface}s keyed by their `id`, in insertion
3187
+ * order — the additive manager tier mirroring `ConversationManagerInterface` /
3188
+ * `WorkspaceManagerInterface` from the `@orkestrel/agent` line, adapted for the workflow
3189
+ * domain: `add` mints from a {@link WorkflowDefinition} (not an empty `Input`, since a
3190
+ * workflow only exists relative to a definition), and the optional `store` seam's `open`
3191
+ * threads the manager's {@link WorkflowFunctions} registry so a HYDRATED workflow is
3192
+ * immediately RUNNABLE, not merely a restored state mirror. NO `active` / `switch` pointer
3193
+ * (AGENTS §21) — the workflow domain has no consumer that renders "the current workflow" the
3194
+ * way an agent context renders the active conversation/workspace.
3195
+ *
3196
+ * @remarks
3197
+ * - **Registry.** `count` is how many are stored. `add(definition)` mints a live
3198
+ * {@link WorkflowInterface} via {@link import('./factories.js').createWorkflow} (flowing
3199
+ * this manager's `functions` registry in) and registers it under `definition.id` — an
3200
+ * already-present id OVERWRITES (last write wins, since `createWorkflow` keys the tree by
3201
+ * the definition's own id). `workflow(id)` looks one up (`undefined` when absent);
3202
+ * `workflows()` lists them in insertion order.
3203
+ * - **Durable open / save (the optional `store` seam).** When a {@link WorkflowStoreInterface}
3204
+ * is supplied (the `store` option), `open(id)` resolves an already-registered workflow
3205
+ * directly (no store hit); on a registry MISS it HYDRATES one from `store.get(id)` through
3206
+ * {@link import('./factories.js').restoreWorkflow} — flowing this manager's `functions`
3207
+ * registry in so the rehydrated tree is RUNNABLE — registers it, and returns it. `save(id)`
3208
+ * PERSISTS a registered workflow's {@link WorkflowInterface.snapshot} to the store. Both are
3209
+ * LENIENT without a store — `open` resolves only registered ids, `save` is a no-op
3210
+ * (`false`) — never a throw. The EXACT analogue of
3211
+ * `ConversationManagerInterface.open` / `.save` and `WorkspaceManagerInterface.open` /
3212
+ * `.save` — this is the workflow line's caller-driven persistence gaining the standard
3213
+ * open/save seam, ADDITIVE alongside direct {@link WorkflowStoreInterface} use and
3214
+ * {@link import('./factories.js').restoreWorkflow} (both remain valid).
3215
+ * - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true`
3216
+ * when any was removed. `clear` empties the registry.
3217
+ * - **Event-free.** A purely registry store — no `Emitter`, no events (each
3218
+ * {@link WorkflowInterface} owns its own {@link WorkflowEventMap} emitter).
3219
+ *
3220
+ * @example
3221
+ * ```ts
3222
+ * import { createWorkflowManager } from '@src/core'
3223
+ *
3224
+ * const manager = createWorkflowManager({
3225
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
3226
+ * })
3227
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE (functions flow in)
3228
+ * manager.count // 1
3229
+ * ```
3230
+ */
3231
+ export declare interface WorkflowManagerInterface {
3232
+ readonly count: number;
3233
+ workflow(id: string): WorkflowInterface | undefined;
3234
+ workflows(): readonly WorkflowInterface[];
3235
+ /**
3236
+ * MINT a live {@link WorkflowInterface} from `definition` (via
3237
+ * {@link import('./factories.js').createWorkflow}, flowing this manager's `functions`
3238
+ * registry in) and register it under `definition.id`.
3239
+ *
3240
+ * @remarks
3241
+ * An already-registered `definition.id` OVERWRITES (last write wins) — `createWorkflow`
3242
+ * keys the live tree by the definition's own id, so a re-`add` under the same id is
3243
+ * indistinguishable from a fresh mint at the registry level.
3244
+ *
3245
+ * @param definition - The {@link WorkflowDefinition} to build the live tree from
3246
+ * @returns The minted, registered {@link WorkflowInterface}
3247
+ */
3248
+ add(definition: WorkflowDefinition): WorkflowInterface;
3249
+ /**
3250
+ * Resolve a workflow by id — from the registry if present, else HYDRATED from the
3251
+ * optional {@link WorkflowStoreInterface} (`store`), RUNNABLE (this manager's `functions`
3252
+ * registry is threaded into the rehydration).
3253
+ *
3254
+ * @remarks
3255
+ * - If `id` is ALREADY registered, it is returned directly — no store hit.
3256
+ * - Else if a `store` is set, `store.get(id)` is awaited; on a HIT the snapshot is
3257
+ * rehydrated into a fresh {@link WorkflowInterface} via
3258
+ * {@link import('./factories.js').restoreWorkflow}, flowing this manager's `functions`
3259
+ * registry in (so the rehydrated tree carries real resolved `handler`s and can RESUME
3260
+ * real work), registers it, and returns it.
3261
+ * - Else (no store, or a store MISS) ⇒ `undefined` (lenient — no throw).
3262
+ *
3263
+ * @param id - The workflow id to open
3264
+ * @returns The resolved, RUNNABLE {@link WorkflowInterface}, or `undefined` when neither registered nor stored
3265
+ */
3266
+ open(id: string): Promise<WorkflowInterface | undefined>;
3267
+ /**
3268
+ * Persist a REGISTERED workflow's {@link WorkflowInterface.snapshot} to the optional
3269
+ * {@link WorkflowStoreInterface} (`store`).
3270
+ *
3271
+ * @remarks
3272
+ * Lenient: when a `store` is set AND `id` is registered, `store.set(workflow.snapshot())`
3273
+ * is awaited and `true` is returned; otherwise (no store, OR an unknown id) it is a NO-OP
3274
+ * returning `false` — never a throw.
3275
+ *
3276
+ * @param id - The id of the registered workflow to persist
3277
+ * @returns `true` when the snapshot was persisted; `false` when no store / unknown id
3278
+ */
3279
+ save(id: string): Promise<boolean>;
3280
+ remove(ids: readonly string[]): boolean;
3281
+ remove(id: string): boolean;
3282
+ clear(): void;
3283
+ }
3284
+
3285
+ /**
3286
+ * Options for `createWorkflowManager` — the optional durable {@link WorkflowStoreInterface}
3287
+ * seam plus the {@link WorkflowFunctions} registry every workflow the manager mints or
3288
+ * hydrates resolves its tasks' handlers against.
3289
+ *
3290
+ * @remarks
3291
+ * `store` is the EXACT analogue of `ConversationManagerOptions.store` /
3292
+ * `WorkspaceManagerOptions.store` (the `@orkestrel/agent` line's store standard) — omitted ⇒
3293
+ * the manager is registry-only: {@link WorkflowManagerInterface.open} resolves only what is
3294
+ * already registered, and {@link WorkflowManagerInterface.save} is a no-op (`false`). `functions`
3295
+ * is the workflow-specific addition: the SAME {@link WorkflowFunctions} registry threaded into
3296
+ * every {@link import('./factories.js').createWorkflow} ({@link WorkflowManagerInterface.add})
3297
+ * and every {@link import('./factories.js').restoreWorkflow}
3298
+ * ({@link WorkflowManagerInterface.open}'s hydration path) the manager performs — so a
3299
+ * hydrated workflow carries real resolved `handler`s and is RUNNABLE, not merely a restored
3300
+ * state mirror. Omitted ⇒ every minted/hydrated task resolves no `handler` (the no-handler
3301
+ * rule — it auto-completes if driven).
3302
+ */
3303
+ export declare interface WorkflowManagerOptions {
3304
+ /**
3305
+ * The optional durable {@link WorkflowStoreInterface} backing
3306
+ * {@link WorkflowManagerInterface.open} / {@link WorkflowManagerInterface.save} — a memory
3307
+ * / JSON / SQLite / IndexedDB store a workflow is HYDRATED from (`open` a registry miss)
3308
+ * and PERSISTED to (`save`). Omitted ⇒ the manager is registry-only: `open` resolves only
3309
+ * what is already registered, and `save` is a no-op (`false`).
3310
+ */
3311
+ readonly store?: WorkflowStoreInterface;
3312
+ /**
3313
+ * The {@link WorkflowFunctions} registry threaded into every workflow this manager mints
3314
+ * (`add`, via {@link import('./factories.js').createWorkflow}) or hydrates (`open`'s
3315
+ * registry-miss path, via {@link import('./factories.js').restoreWorkflow}) — so a
3316
+ * hydrated workflow is RUNNABLE, its tasks carrying real resolved `handler`s. Omitted ⇒
3317
+ * every task resolves no `handler` (the no-handler rule).
3318
+ */
3319
+ readonly functions?: WorkflowFunctions;
3320
+ }
3321
+
3105
3322
  /**
3106
3323
  * The runtime options for a {@link WorkflowInterface} — the construction bag the
3107
3324
  * live derived workflow state machine (W-b) carries, the root {@link createWorkflow}
@@ -435,6 +435,39 @@ export declare function createWorkflow(definition: WorkflowDefinition, options?:
435
435
  */
436
436
  export declare function createWorkflowContract(): ContractInterface<WorkflowDefinition>;
437
437
 
438
+ /**
439
+ * Create a {@link WorkflowManagerInterface} — the store-backed registry of
440
+ * {@link WorkflowInterface}s, the additive manager tier mirroring the `@orkestrel/agent`
441
+ * line's `createConversationManager` / `createWorkspaceManager`.
442
+ *
443
+ * @remarks
444
+ * `options.functions` flows into every workflow the manager mints (`add`, via
445
+ * {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
446
+ * {@link restoreWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
447
+ * mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
448
+ * manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
449
+ * is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
450
+ * {@link restoreWorkflow} remain valid — the manager is one more caller-driven persistence
451
+ * seam, not a replacement.
452
+ *
453
+ * @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
454
+ * @returns A working {@link WorkflowManagerInterface}
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * import { createMemoryWorkflowStore, createWorkflowManager } from '@src/core'
459
+ *
460
+ * const manager = createWorkflowManager({
461
+ * store: createMemoryWorkflowStore(),
462
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
463
+ * })
464
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
465
+ * await manager.save(workflow.id) // persisted to the store
466
+ * const reopened = await manager.open(workflow.id) // already registered — no store hit
467
+ * ```
468
+ */
469
+ export declare function createWorkflowManager(options?: WorkflowManagerOptions): WorkflowManagerInterface;
470
+
438
471
  /**
439
472
  * Create a workflow runner — a {@link WorkflowRunnerInterface} that EXECUTES a live W-b
440
473
  * workflow tree by COMPOSING the shipped substrate: phases sequential, tasks concurrent,
@@ -3102,6 +3135,190 @@ export declare interface WorkflowInterface {
3102
3135
  snapshot(): WorkflowSnapshot;
3103
3136
  }
3104
3137
 
3138
+ /**
3139
+ * The store-backed registry of {@link WorkflowInterface}s keyed by `id`, in insertion order —
3140
+ * the additive manager tier mirroring the `@orkestrel/agent` line's `ConversationManager` /
3141
+ * `WorkspaceManager`. Event-free (a registry, like its twins); the observability lives on each
3142
+ * {@link WorkflowInterface}.
3143
+ *
3144
+ * @remarks
3145
+ * - **Registry.** Workflows live in an insertion-ordered `Map` keyed by `id`. `add(definition)`
3146
+ * mints a live {@link WorkflowInterface} through {@link createWorkflow} (flowing the manager's
3147
+ * `functions` registry in) and stores it under `definition.id` — an already-present id
3148
+ * OVERWRITES (last write wins). `count` is the map size, `workflow(id)` looks one up,
3149
+ * `workflows()` lists them in insertion order.
3150
+ * - **Durable open / save.** `open(id)` returns an already-registered workflow directly; on a
3151
+ * registry MISS with a `store` set it rehydrates through {@link restoreWorkflow} (flowing the
3152
+ * manager's `functions` registry in so the rehydrated tree is RUNNABLE), registers it, and
3153
+ * returns it — lenient (`undefined`) with no store or a store miss. `save(id)` persists a
3154
+ * registered workflow's `snapshot()` to the `store` — lenient (`false`) with no store or an
3155
+ * unknown id.
3156
+ * - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true` when
3157
+ * any was removed. `clear` empties the registry.
3158
+ * - **No active pointer.** Unlike its `ConversationManager` / `WorkspaceManager` twins, there is
3159
+ * no `active` / `switch` — nothing in the workflow domain renders "the current workflow".
3160
+ *
3161
+ * @example
3162
+ * ```ts
3163
+ * const manager = new WorkflowManager({
3164
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
3165
+ * })
3166
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
3167
+ * manager.workflow(workflow.id) // the same workflow
3168
+ * manager.count // 1
3169
+ * ```
3170
+ */
3171
+ export declare class WorkflowManager implements WorkflowManagerInterface {
3172
+ #private;
3173
+ constructor(options?: WorkflowManagerOptions);
3174
+ get count(): number;
3175
+ workflow(id: string): WorkflowInterface | undefined;
3176
+ workflows(): readonly WorkflowInterface[];
3177
+ add(definition: WorkflowDefinition): WorkflowInterface;
3178
+ open(id: string): Promise<WorkflowInterface | undefined>;
3179
+ save(id: string): Promise<boolean>;
3180
+ remove(ids: readonly string[]): boolean;
3181
+ remove(id: string): boolean;
3182
+ clear(): void;
3183
+ }
3184
+
3185
+ /**
3186
+ * A store-backed registry of {@link WorkflowInterface}s keyed by their `id`, in insertion
3187
+ * order — the additive manager tier mirroring `ConversationManagerInterface` /
3188
+ * `WorkspaceManagerInterface` from the `@orkestrel/agent` line, adapted for the workflow
3189
+ * domain: `add` mints from a {@link WorkflowDefinition} (not an empty `Input`, since a
3190
+ * workflow only exists relative to a definition), and the optional `store` seam's `open`
3191
+ * threads the manager's {@link WorkflowFunctions} registry so a HYDRATED workflow is
3192
+ * immediately RUNNABLE, not merely a restored state mirror. NO `active` / `switch` pointer
3193
+ * (AGENTS §21) — the workflow domain has no consumer that renders "the current workflow" the
3194
+ * way an agent context renders the active conversation/workspace.
3195
+ *
3196
+ * @remarks
3197
+ * - **Registry.** `count` is how many are stored. `add(definition)` mints a live
3198
+ * {@link WorkflowInterface} via {@link import('./factories.js').createWorkflow} (flowing
3199
+ * this manager's `functions` registry in) and registers it under `definition.id` — an
3200
+ * already-present id OVERWRITES (last write wins, since `createWorkflow` keys the tree by
3201
+ * the definition's own id). `workflow(id)` looks one up (`undefined` when absent);
3202
+ * `workflows()` lists them in insertion order.
3203
+ * - **Durable open / save (the optional `store` seam).** When a {@link WorkflowStoreInterface}
3204
+ * is supplied (the `store` option), `open(id)` resolves an already-registered workflow
3205
+ * directly (no store hit); on a registry MISS it HYDRATES one from `store.get(id)` through
3206
+ * {@link import('./factories.js').restoreWorkflow} — flowing this manager's `functions`
3207
+ * registry in so the rehydrated tree is RUNNABLE — registers it, and returns it. `save(id)`
3208
+ * PERSISTS a registered workflow's {@link WorkflowInterface.snapshot} to the store. Both are
3209
+ * LENIENT without a store — `open` resolves only registered ids, `save` is a no-op
3210
+ * (`false`) — never a throw. The EXACT analogue of
3211
+ * `ConversationManagerInterface.open` / `.save` and `WorkspaceManagerInterface.open` /
3212
+ * `.save` — this is the workflow line's caller-driven persistence gaining the standard
3213
+ * open/save seam, ADDITIVE alongside direct {@link WorkflowStoreInterface} use and
3214
+ * {@link import('./factories.js').restoreWorkflow} (both remain valid).
3215
+ * - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true`
3216
+ * when any was removed. `clear` empties the registry.
3217
+ * - **Event-free.** A purely registry store — no `Emitter`, no events (each
3218
+ * {@link WorkflowInterface} owns its own {@link WorkflowEventMap} emitter).
3219
+ *
3220
+ * @example
3221
+ * ```ts
3222
+ * import { createWorkflowManager } from '@src/core'
3223
+ *
3224
+ * const manager = createWorkflowManager({
3225
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
3226
+ * })
3227
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE (functions flow in)
3228
+ * manager.count // 1
3229
+ * ```
3230
+ */
3231
+ export declare interface WorkflowManagerInterface {
3232
+ readonly count: number;
3233
+ workflow(id: string): WorkflowInterface | undefined;
3234
+ workflows(): readonly WorkflowInterface[];
3235
+ /**
3236
+ * MINT a live {@link WorkflowInterface} from `definition` (via
3237
+ * {@link import('./factories.js').createWorkflow}, flowing this manager's `functions`
3238
+ * registry in) and register it under `definition.id`.
3239
+ *
3240
+ * @remarks
3241
+ * An already-registered `definition.id` OVERWRITES (last write wins) — `createWorkflow`
3242
+ * keys the live tree by the definition's own id, so a re-`add` under the same id is
3243
+ * indistinguishable from a fresh mint at the registry level.
3244
+ *
3245
+ * @param definition - The {@link WorkflowDefinition} to build the live tree from
3246
+ * @returns The minted, registered {@link WorkflowInterface}
3247
+ */
3248
+ add(definition: WorkflowDefinition): WorkflowInterface;
3249
+ /**
3250
+ * Resolve a workflow by id — from the registry if present, else HYDRATED from the
3251
+ * optional {@link WorkflowStoreInterface} (`store`), RUNNABLE (this manager's `functions`
3252
+ * registry is threaded into the rehydration).
3253
+ *
3254
+ * @remarks
3255
+ * - If `id` is ALREADY registered, it is returned directly — no store hit.
3256
+ * - Else if a `store` is set, `store.get(id)` is awaited; on a HIT the snapshot is
3257
+ * rehydrated into a fresh {@link WorkflowInterface} via
3258
+ * {@link import('./factories.js').restoreWorkflow}, flowing this manager's `functions`
3259
+ * registry in (so the rehydrated tree carries real resolved `handler`s and can RESUME
3260
+ * real work), registers it, and returns it.
3261
+ * - Else (no store, or a store MISS) ⇒ `undefined` (lenient — no throw).
3262
+ *
3263
+ * @param id - The workflow id to open
3264
+ * @returns The resolved, RUNNABLE {@link WorkflowInterface}, or `undefined` when neither registered nor stored
3265
+ */
3266
+ open(id: string): Promise<WorkflowInterface | undefined>;
3267
+ /**
3268
+ * Persist a REGISTERED workflow's {@link WorkflowInterface.snapshot} to the optional
3269
+ * {@link WorkflowStoreInterface} (`store`).
3270
+ *
3271
+ * @remarks
3272
+ * Lenient: when a `store` is set AND `id` is registered, `store.set(workflow.snapshot())`
3273
+ * is awaited and `true` is returned; otherwise (no store, OR an unknown id) it is a NO-OP
3274
+ * returning `false` — never a throw.
3275
+ *
3276
+ * @param id - The id of the registered workflow to persist
3277
+ * @returns `true` when the snapshot was persisted; `false` when no store / unknown id
3278
+ */
3279
+ save(id: string): Promise<boolean>;
3280
+ remove(ids: readonly string[]): boolean;
3281
+ remove(id: string): boolean;
3282
+ clear(): void;
3283
+ }
3284
+
3285
+ /**
3286
+ * Options for `createWorkflowManager` — the optional durable {@link WorkflowStoreInterface}
3287
+ * seam plus the {@link WorkflowFunctions} registry every workflow the manager mints or
3288
+ * hydrates resolves its tasks' handlers against.
3289
+ *
3290
+ * @remarks
3291
+ * `store` is the EXACT analogue of `ConversationManagerOptions.store` /
3292
+ * `WorkspaceManagerOptions.store` (the `@orkestrel/agent` line's store standard) — omitted ⇒
3293
+ * the manager is registry-only: {@link WorkflowManagerInterface.open} resolves only what is
3294
+ * already registered, and {@link WorkflowManagerInterface.save} is a no-op (`false`). `functions`
3295
+ * is the workflow-specific addition: the SAME {@link WorkflowFunctions} registry threaded into
3296
+ * every {@link import('./factories.js').createWorkflow} ({@link WorkflowManagerInterface.add})
3297
+ * and every {@link import('./factories.js').restoreWorkflow}
3298
+ * ({@link WorkflowManagerInterface.open}'s hydration path) the manager performs — so a
3299
+ * hydrated workflow carries real resolved `handler`s and is RUNNABLE, not merely a restored
3300
+ * state mirror. Omitted ⇒ every minted/hydrated task resolves no `handler` (the no-handler
3301
+ * rule — it auto-completes if driven).
3302
+ */
3303
+ export declare interface WorkflowManagerOptions {
3304
+ /**
3305
+ * The optional durable {@link WorkflowStoreInterface} backing
3306
+ * {@link WorkflowManagerInterface.open} / {@link WorkflowManagerInterface.save} — a memory
3307
+ * / JSON / SQLite / IndexedDB store a workflow is HYDRATED from (`open` a registry miss)
3308
+ * and PERSISTED to (`save`). Omitted ⇒ the manager is registry-only: `open` resolves only
3309
+ * what is already registered, and `save` is a no-op (`false`).
3310
+ */
3311
+ readonly store?: WorkflowStoreInterface;
3312
+ /**
3313
+ * The {@link WorkflowFunctions} registry threaded into every workflow this manager mints
3314
+ * (`add`, via {@link import('./factories.js').createWorkflow}) or hydrates (`open`'s
3315
+ * registry-miss path, via {@link import('./factories.js').restoreWorkflow}) — so a
3316
+ * hydrated workflow is RUNNABLE, its tasks carrying real resolved `handler`s. Omitted ⇒
3317
+ * every task resolves no `handler` (the no-handler rule).
3318
+ */
3319
+ readonly functions?: WorkflowFunctions;
3320
+ }
3321
+
3105
3322
  /**
3106
3323
  * The runtime options for a {@link WorkflowInterface} — the construction bag the
3107
3324
  * live derived workflow state machine (W-b) carries, the root {@link createWorkflow}
@@ -1843,6 +1843,91 @@ var Workflow = class {
1843
1843
  }
1844
1844
  };
1845
1845
  //#endregion
1846
+ //#region src/core/WorkflowManager.ts
1847
+ /**
1848
+ * The store-backed registry of {@link WorkflowInterface}s keyed by `id`, in insertion order —
1849
+ * the additive manager tier mirroring the `@orkestrel/agent` line's `ConversationManager` /
1850
+ * `WorkspaceManager`. Event-free (a registry, like its twins); the observability lives on each
1851
+ * {@link WorkflowInterface}.
1852
+ *
1853
+ * @remarks
1854
+ * - **Registry.** Workflows live in an insertion-ordered `Map` keyed by `id`. `add(definition)`
1855
+ * mints a live {@link WorkflowInterface} through {@link createWorkflow} (flowing the manager's
1856
+ * `functions` registry in) and stores it under `definition.id` — an already-present id
1857
+ * OVERWRITES (last write wins). `count` is the map size, `workflow(id)` looks one up,
1858
+ * `workflows()` lists them in insertion order.
1859
+ * - **Durable open / save.** `open(id)` returns an already-registered workflow directly; on a
1860
+ * registry MISS with a `store` set it rehydrates through {@link restoreWorkflow} (flowing the
1861
+ * manager's `functions` registry in so the rehydrated tree is RUNNABLE), registers it, and
1862
+ * returns it — lenient (`undefined`) with no store or a store miss. `save(id)` persists a
1863
+ * registered workflow's `snapshot()` to the `store` — lenient (`false`) with no store or an
1864
+ * unknown id.
1865
+ * - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true` when
1866
+ * any was removed. `clear` empties the registry.
1867
+ * - **No active pointer.** Unlike its `ConversationManager` / `WorkspaceManager` twins, there is
1868
+ * no `active` / `switch` — nothing in the workflow domain renders "the current workflow".
1869
+ *
1870
+ * @example
1871
+ * ```ts
1872
+ * const manager = new WorkflowManager({
1873
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
1874
+ * })
1875
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
1876
+ * manager.workflow(workflow.id) // the same workflow
1877
+ * manager.count // 1
1878
+ * ```
1879
+ */
1880
+ var WorkflowManager = class {
1881
+ #workflows = /* @__PURE__ */ new Map();
1882
+ #functions;
1883
+ #store;
1884
+ constructor(options) {
1885
+ this.#functions = options?.functions;
1886
+ this.#store = options?.store;
1887
+ }
1888
+ get count() {
1889
+ return this.#workflows.size;
1890
+ }
1891
+ workflow(id) {
1892
+ return this.#workflows.get(id);
1893
+ }
1894
+ workflows() {
1895
+ return [...this.#workflows.values()];
1896
+ }
1897
+ add(definition) {
1898
+ const workflow = createWorkflow(definition, { functions: this.#functions });
1899
+ this.#workflows.set(workflow.id, workflow);
1900
+ return workflow;
1901
+ }
1902
+ async open(id) {
1903
+ const existing = this.#workflows.get(id);
1904
+ if (existing !== void 0) return existing;
1905
+ if (this.#store === void 0) return void 0;
1906
+ const snapshot = await this.#store.get(id);
1907
+ if (snapshot === void 0) return void 0;
1908
+ const workflow = restoreWorkflow(snapshot, { functions: this.#functions });
1909
+ this.#workflows.set(workflow.id, workflow);
1910
+ return workflow;
1911
+ }
1912
+ async save(id) {
1913
+ const workflow = this.#workflows.get(id);
1914
+ if (this.#store === void 0 || workflow === void 0) return false;
1915
+ await this.#store.set(workflow.snapshot());
1916
+ return true;
1917
+ }
1918
+ remove(ids) {
1919
+ if (isArray(ids)) {
1920
+ let removed = false;
1921
+ for (const id of ids) if (this.#workflows.delete(id)) removed = true;
1922
+ return removed;
1923
+ }
1924
+ return this.#workflows.delete(ids);
1925
+ }
1926
+ clear() {
1927
+ this.#workflows.clear();
1928
+ }
1929
+ };
1930
+ //#endregion
1846
1931
  //#region src/core/Controller.ts
1847
1932
  /**
1848
1933
  * The per-unit handle a runner handler receives — wraps the unit's identity,
@@ -2804,6 +2889,40 @@ function createWorkflowRunner(options) {
2804
2889
  return new WorkflowRunner(options?.scheduler ?? createScheduler());
2805
2890
  }
2806
2891
  /**
2892
+ * Create a {@link WorkflowManagerInterface} — the store-backed registry of
2893
+ * {@link WorkflowInterface}s, the additive manager tier mirroring the `@orkestrel/agent`
2894
+ * line's `createConversationManager` / `createWorkspaceManager`.
2895
+ *
2896
+ * @remarks
2897
+ * `options.functions` flows into every workflow the manager mints (`add`, via
2898
+ * {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
2899
+ * {@link restoreWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
2900
+ * mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
2901
+ * manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
2902
+ * is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
2903
+ * {@link restoreWorkflow} remain valid — the manager is one more caller-driven persistence
2904
+ * seam, not a replacement.
2905
+ *
2906
+ * @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
2907
+ * @returns A working {@link WorkflowManagerInterface}
2908
+ *
2909
+ * @example
2910
+ * ```ts
2911
+ * import { createMemoryWorkflowStore, createWorkflowManager } from '@src/core'
2912
+ *
2913
+ * const manager = createWorkflowManager({
2914
+ * store: createMemoryWorkflowStore(),
2915
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
2916
+ * })
2917
+ * const workflow = manager.add(definition) // minted, registered, RUNNABLE
2918
+ * await manager.save(workflow.id) // persisted to the store
2919
+ * const reopened = await manager.open(workflow.id) // already registered — no store hit
2920
+ * ```
2921
+ */
2922
+ function createWorkflowManager(options) {
2923
+ return new WorkflowManager(options);
2924
+ }
2925
+ /**
2807
2926
  * Create the safe cross-environment cooperative-yield default — a
2808
2927
  * {@link SchedulerInterface} built on `setTimeout` / `clearTimeout` alone, so it
2809
2928
  * runs unchanged in both the browser and Node.
@@ -2895,6 +3014,6 @@ function createRunner(options) {
2895
3014
  return new Runner(options);
2896
3015
  }
2897
3016
  //#endregion
2898
- export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, failure, findFailure, insertEntry, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, restoreWorkflow, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape };
3017
+ export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, failure, findFailure, insertEntry, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, restoreWorkflow, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape };
2899
3018
 
2900
3019
  //# sourceMappingURL=index.js.map