@yolk-sdk/harness 0.1.0-canary.100
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/LICENSE +21 -0
- package/README.md +206 -0
- package/dist/coordinator.d.mts +72 -0
- package/dist/coordinator.d.mts.map +1 -0
- package/dist/coordinator.mjs +154 -0
- package/dist/coordinator.mjs.map +1 -0
- package/dist/driver/durable-object.d.mts +20 -0
- package/dist/driver/durable-object.d.mts.map +1 -0
- package/dist/driver/durable-object.mjs +12 -0
- package/dist/driver/durable-object.mjs.map +1 -0
- package/dist/driver/memory.d.mts +21 -0
- package/dist/driver/memory.d.mts.map +1 -0
- package/dist/driver/memory.mjs +15 -0
- package/dist/driver/memory.mjs.map +1 -0
- package/dist/driver.d.mts +126 -0
- package/dist/driver.d.mts.map +1 -0
- package/dist/driver.mjs +96 -0
- package/dist/driver.mjs.map +1 -0
- package/dist/inbox.d.mts +368 -0
- package/dist/inbox.d.mts.map +1 -0
- package/dist/inbox.mjs +235 -0
- package/dist/inbox.mjs.map +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/outcome-constructors-internal.d.mts +403 -0
- package/dist/outcome-constructors-internal.d.mts.map +1 -0
- package/dist/outcome-constructors-internal.mjs +11 -0
- package/dist/outcome-constructors-internal.mjs.map +1 -0
- package/dist/outcome.d.mts +361 -0
- package/dist/outcome.d.mts.map +1 -0
- package/dist/outcome.mjs +136 -0
- package/dist/outcome.mjs.map +1 -0
- package/dist/store.d.mts +39 -0
- package/dist/store.d.mts.map +1 -0
- package/dist/store.mjs +98 -0
- package/dist/store.mjs.map +1 -0
- package/package.json +90 -0
- package/src/coordinator.ts +355 -0
- package/src/driver/durable-object.ts +28 -0
- package/src/driver/memory.ts +39 -0
- package/src/driver.ts +270 -0
- package/src/inbox.ts +528 -0
- package/src/index.ts +2 -0
- package/src/outcome-constructors-internal.ts +18 -0
- package/src/outcome.ts +377 -0
- package/src/store.ts +161 -0
package/dist/store.mjs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Ref, Semaphore } from "effect";
|
|
2
|
+
//#region src/store.ts
|
|
3
|
+
const emptySnapshot = {
|
|
4
|
+
claimed: [],
|
|
5
|
+
resumes: []
|
|
6
|
+
};
|
|
7
|
+
const snapshotFromMemory = (memory) => ({
|
|
8
|
+
claimed: [...memory.claimed],
|
|
9
|
+
resumes: [...memory.resumes.entries()]
|
|
10
|
+
});
|
|
11
|
+
const memoryFromSnapshot = (snapshot) => ({
|
|
12
|
+
claimed: new Set(snapshot.claimed),
|
|
13
|
+
resumes: new Map(snapshot.resumes)
|
|
14
|
+
});
|
|
15
|
+
var RunStore = class RunStore extends Context.Service()("@yolk-sdk/harness/RunStore") {
|
|
16
|
+
/**
|
|
17
|
+
* Canonical owning layer for snapshot-backed claims. Each call builds a fresh layer so
|
|
18
|
+
* composed harnesses never share `Ref` state or the snapshot lock across factory calls.
|
|
19
|
+
*/
|
|
20
|
+
static snapshotLayer = (options) => Layer.effect(this, Effect.gen(function* () {
|
|
21
|
+
const loaded = yield* options.load;
|
|
22
|
+
const memory = yield* Ref.make(memoryFromSnapshot(loaded ?? emptySnapshot));
|
|
23
|
+
const lock = yield* Semaphore.make(1);
|
|
24
|
+
const mutate = (effect) => Effect.uninterruptibleMask((restore) => restore(lock.take(1)).pipe(Effect.flatMap(() => effect.pipe(Effect.ensuring(lock.release(1))))));
|
|
25
|
+
const commit = (next) => options.save(snapshotFromMemory(next)).pipe(Effect.flatMap(() => Ref.set(memory, next)));
|
|
26
|
+
return RunStore.of({
|
|
27
|
+
claim: (runId) => mutate(Effect.gen(function* () {
|
|
28
|
+
const current = yield* Ref.get(memory);
|
|
29
|
+
yield* commit({
|
|
30
|
+
claimed: new Set(current.claimed).add(runId),
|
|
31
|
+
resumes: current.resumes
|
|
32
|
+
});
|
|
33
|
+
})),
|
|
34
|
+
release: (runId) => mutate(Effect.gen(function* () {
|
|
35
|
+
const current = yield* Ref.get(memory);
|
|
36
|
+
const claimed = new Set(current.claimed);
|
|
37
|
+
claimed.delete(runId);
|
|
38
|
+
const resumes = new Map(current.resumes);
|
|
39
|
+
resumes.delete(runId);
|
|
40
|
+
yield* commit({
|
|
41
|
+
claimed,
|
|
42
|
+
resumes
|
|
43
|
+
});
|
|
44
|
+
})),
|
|
45
|
+
isClaimed: (runId) => Ref.get(memory).pipe(Effect.map((current) => current.claimed.has(runId))),
|
|
46
|
+
claimed: Ref.get(memory).pipe(Effect.map((current) => new Set(current.claimed))),
|
|
47
|
+
incrementResumeCount: (runId) => mutate(Effect.gen(function* () {
|
|
48
|
+
const current = yield* Ref.get(memory);
|
|
49
|
+
const nextCount = (current.resumes.get(runId) ?? 0) + 1;
|
|
50
|
+
const resumes = new Map(current.resumes);
|
|
51
|
+
resumes.set(runId, nextCount);
|
|
52
|
+
yield* commit({
|
|
53
|
+
claimed: current.claimed,
|
|
54
|
+
resumes
|
|
55
|
+
});
|
|
56
|
+
return nextCount;
|
|
57
|
+
})),
|
|
58
|
+
resumeCount: (runId) => Ref.get(memory).pipe(Effect.map((current) => current.resumes.get(runId) ?? 0))
|
|
59
|
+
});
|
|
60
|
+
}));
|
|
61
|
+
/**
|
|
62
|
+
* Canonical owning layer for process-local claims. Each call builds a fresh layer so
|
|
63
|
+
* composed harnesses never share `Ref` state across factory calls.
|
|
64
|
+
*/
|
|
65
|
+
static inMemoryLayer = () => Layer.effect(this, Effect.gen(function* () {
|
|
66
|
+
const claimed = yield* Ref.make(/* @__PURE__ */ new Set());
|
|
67
|
+
const resumes = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
68
|
+
return RunStore.of({
|
|
69
|
+
claim: (runId) => Ref.update(claimed, (current) => new Set(current).add(runId)),
|
|
70
|
+
release: (runId) => Effect.zip(Ref.update(claimed, (current) => {
|
|
71
|
+
const next = new Set(current);
|
|
72
|
+
next.delete(runId);
|
|
73
|
+
return next;
|
|
74
|
+
}), Ref.update(resumes, (current) => {
|
|
75
|
+
const next = new Map(current);
|
|
76
|
+
next.delete(runId);
|
|
77
|
+
return next;
|
|
78
|
+
})).pipe(Effect.asVoid),
|
|
79
|
+
isClaimed: (runId) => Ref.get(claimed).pipe(Effect.map((current) => current.has(runId))),
|
|
80
|
+
claimed: Ref.get(claimed).pipe(Effect.map((current) => new Set(current))),
|
|
81
|
+
incrementResumeCount: (runId) => Ref.modify(resumes, (current) => {
|
|
82
|
+
const nextCount = (current.get(runId) ?? 0) + 1;
|
|
83
|
+
const next = new Map(current);
|
|
84
|
+
next.set(runId, nextCount);
|
|
85
|
+
return [nextCount, next];
|
|
86
|
+
}),
|
|
87
|
+
resumeCount: (runId) => Ref.get(resumes).pipe(Effect.map((current) => current.get(runId) ?? 0))
|
|
88
|
+
});
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
/** Backward-compatible delegation to {@link RunStore.snapshotLayer}. */
|
|
92
|
+
const makeSnapshotRunStoreLayer = (options) => RunStore.snapshotLayer(options);
|
|
93
|
+
/** Backward-compatible delegation to {@link RunStore.inMemoryLayer}. */
|
|
94
|
+
const makeInMemoryRunStoreLayer = () => RunStore.inMemoryLayer();
|
|
95
|
+
//#endregion
|
|
96
|
+
export { RunStore, makeInMemoryRunStoreLayer, makeSnapshotRunStoreLayer };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { Context, Effect, Layer, Ref, Semaphore } from 'effect'\n\nexport type RunStoreApi = {\n readonly claim: (runId: string) => Effect.Effect<void>\n readonly release: (runId: string) => Effect.Effect<void>\n readonly isClaimed: (runId: string) => Effect.Effect<boolean>\n readonly claimed: Effect.Effect<ReadonlySet<string>>\n readonly incrementResumeCount: (runId: string) => Effect.Effect<number>\n readonly resumeCount: (runId: string) => Effect.Effect<number>\n}\n\nexport type SnapshotRunStoreOptions = {\n readonly load: Effect.Effect<DurableRunStoreSnapshot | undefined>\n readonly save: (snapshot: DurableRunStoreSnapshot) => Effect.Effect<void>\n}\n\nexport type DurableRunStoreSnapshot = {\n readonly claimed: ReadonlyArray<string>\n readonly resumes: ReadonlyArray<readonly [string, number]>\n}\n\ntype MemorySnapshot = {\n readonly claimed: ReadonlySet<string>\n readonly resumes: ReadonlyMap<string, number>\n}\n\nconst emptySnapshot: DurableRunStoreSnapshot = {\n claimed: [],\n resumes: []\n}\n\nconst snapshotFromMemory = (memory: MemorySnapshot): DurableRunStoreSnapshot => ({\n claimed: [...memory.claimed],\n resumes: [...memory.resumes.entries()]\n})\n\nconst memoryFromSnapshot = (snapshot: DurableRunStoreSnapshot): MemorySnapshot => ({\n claimed: new Set(snapshot.claimed),\n resumes: new Map(snapshot.resumes)\n})\n\nexport class RunStore extends Context.Service<RunStore, RunStoreApi>()(\n '@yolk-sdk/harness/RunStore'\n) {\n /**\n * Canonical owning layer for snapshot-backed claims. Each call builds a fresh layer so\n * composed harnesses never share `Ref` state or the snapshot lock across factory calls.\n */\n static snapshotLayer = (options: SnapshotRunStoreOptions): Layer.Layer<RunStore> =>\n Layer.effect(\n this,\n Effect.gen(function* () {\n const loaded = yield* options.load\n const memory = yield* Ref.make(memoryFromSnapshot(loaded ?? emptySnapshot))\n const lock = yield* Semaphore.make(1)\n\n const mutate = <A, E, R>(effect: Effect.Effect<A, E, R>) =>\n Effect.uninterruptibleMask(restore =>\n restore(lock.take(1)).pipe(\n Effect.flatMap(() => effect.pipe(Effect.ensuring(lock.release(1))))\n )\n )\n\n const commit = (next: MemorySnapshot) =>\n options.save(snapshotFromMemory(next)).pipe(Effect.flatMap(() => Ref.set(memory, next)))\n\n return RunStore.of({\n claim: runId =>\n mutate(\n Effect.gen(function* () {\n const current = yield* Ref.get(memory)\n yield* commit({\n claimed: new Set(current.claimed).add(runId),\n resumes: current.resumes\n })\n })\n ),\n release: runId =>\n mutate(\n Effect.gen(function* () {\n const current = yield* Ref.get(memory)\n const claimed = new Set(current.claimed)\n claimed.delete(runId)\n const resumes = new Map(current.resumes)\n resumes.delete(runId)\n yield* commit({ claimed, resumes })\n })\n ),\n isClaimed: runId =>\n Ref.get(memory).pipe(Effect.map(current => current.claimed.has(runId))),\n claimed: Ref.get(memory).pipe(Effect.map(current => new Set(current.claimed))),\n incrementResumeCount: runId =>\n mutate(\n Effect.gen(function* () {\n const current = yield* Ref.get(memory)\n const nextCount = (current.resumes.get(runId) ?? 0) + 1\n const resumes = new Map(current.resumes)\n resumes.set(runId, nextCount)\n yield* commit({ claimed: current.claimed, resumes })\n\n return nextCount\n })\n ),\n resumeCount: runId =>\n Ref.get(memory).pipe(Effect.map(current => current.resumes.get(runId) ?? 0))\n })\n })\n )\n\n /**\n * Canonical owning layer for process-local claims. Each call builds a fresh layer so\n * composed harnesses never share `Ref` state across factory calls.\n */\n static inMemoryLayer = (): Layer.Layer<RunStore> =>\n Layer.effect(\n this,\n Effect.gen(function* () {\n const claimed = yield* Ref.make(new Set<string>())\n const resumes = yield* Ref.make(new Map<string, number>())\n\n return RunStore.of({\n claim: runId => Ref.update(claimed, current => new Set(current).add(runId)),\n release: runId =>\n Effect.zip(\n Ref.update(claimed, current => {\n const next = new Set(current)\n next.delete(runId)\n\n return next\n }),\n Ref.update(resumes, current => {\n const next = new Map(current)\n next.delete(runId)\n\n return next\n })\n ).pipe(Effect.asVoid),\n isClaimed: runId => Ref.get(claimed).pipe(Effect.map(current => current.has(runId))),\n claimed: Ref.get(claimed).pipe(Effect.map(current => new Set(current))),\n incrementResumeCount: runId =>\n Ref.modify(resumes, current => {\n const nextCount = (current.get(runId) ?? 0) + 1\n const next = new Map(current)\n next.set(runId, nextCount)\n\n return [nextCount, next] as const\n }),\n resumeCount: runId =>\n Ref.get(resumes).pipe(Effect.map(current => current.get(runId) ?? 0))\n })\n })\n )\n}\n\n/** Backward-compatible delegation to {@link RunStore.snapshotLayer}. */\nexport const makeSnapshotRunStoreLayer = (\n options: SnapshotRunStoreOptions\n): Layer.Layer<RunStore> => RunStore.snapshotLayer(options)\n\n/** Backward-compatible delegation to {@link RunStore.inMemoryLayer}. */\nexport const makeInMemoryRunStoreLayer = (): Layer.Layer<RunStore> => RunStore.inMemoryLayer()\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yolk-sdk/harness",
|
|
3
|
+
"version": "0.1.0-canary.100",
|
|
4
|
+
"description": "Domain-free agent run lifecycle: coordinator, store, inbox, and pluggable drivers.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/magoz/yolk-sdk.git",
|
|
11
|
+
"directory": "packages/harness"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/magoz/yolk-sdk/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/magoz/yolk-sdk#readme",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"agent",
|
|
19
|
+
"harness",
|
|
20
|
+
"lifecycle",
|
|
21
|
+
"effect"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"import": "./dist/index.mjs",
|
|
31
|
+
"default": "./dist/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./coordinator": {
|
|
34
|
+
"types": "./dist/coordinator.d.mts",
|
|
35
|
+
"import": "./dist/coordinator.mjs",
|
|
36
|
+
"default": "./dist/coordinator.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./store": {
|
|
39
|
+
"types": "./dist/store.d.mts",
|
|
40
|
+
"import": "./dist/store.mjs",
|
|
41
|
+
"default": "./dist/store.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./inbox": {
|
|
44
|
+
"types": "./dist/inbox.d.mts",
|
|
45
|
+
"import": "./dist/inbox.mjs",
|
|
46
|
+
"default": "./dist/inbox.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./driver": {
|
|
49
|
+
"types": "./dist/driver.d.mts",
|
|
50
|
+
"import": "./dist/driver.mjs",
|
|
51
|
+
"default": "./dist/driver.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./driver/memory": {
|
|
54
|
+
"types": "./dist/driver/memory.d.mts",
|
|
55
|
+
"import": "./dist/driver/memory.mjs",
|
|
56
|
+
"default": "./dist/driver/memory.mjs"
|
|
57
|
+
},
|
|
58
|
+
"./driver/durable-object": {
|
|
59
|
+
"types": "./dist/driver/durable-object.d.mts",
|
|
60
|
+
"import": "./dist/driver/durable-object.mjs",
|
|
61
|
+
"default": "./dist/driver/durable-object.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./outcome": {
|
|
64
|
+
"types": "./dist/outcome.d.mts",
|
|
65
|
+
"import": "./dist/outcome.mjs",
|
|
66
|
+
"default": "./dist/outcome.mjs"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"files": [
|
|
70
|
+
"src/**/*.ts",
|
|
71
|
+
"!src/**/*.test.ts",
|
|
72
|
+
"!src/**/*.test.tsx",
|
|
73
|
+
"dist/**/*",
|
|
74
|
+
"README.md"
|
|
75
|
+
],
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public",
|
|
78
|
+
"provenance": true
|
|
79
|
+
},
|
|
80
|
+
"dependencies": {
|
|
81
|
+
"effect": "4.0.0",
|
|
82
|
+
"@yolk-sdk/agent": "^0.1.0-canary.100"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "tsdown",
|
|
86
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
87
|
+
"test": "vitest run --passWithNoTests",
|
|
88
|
+
"test:run": "vitest run --passWithNoTests"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-local run coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Port of OpenCode v2 `SessionRunCoordinator` (MIT): one busy period per key,
|
|
5
|
+
* a doorbell that coalesces wakes, and interrupt that claims pending wakes so a
|
|
6
|
+
* dead intent cannot restart. Steering lands at the next drain boundary.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/sst/opencode (packages/core/src/session/run-coordinator.ts)
|
|
9
|
+
*/
|
|
10
|
+
import { Cause, Context, Deferred, Effect, Exit, Fiber, FiberSet, Layer } from 'effect'
|
|
11
|
+
import type { Scope } from 'effect'
|
|
12
|
+
import { StopReceipt as stopReceipt } from './outcome-constructors-internal.ts'
|
|
13
|
+
import { RunStore } from './store.ts'
|
|
14
|
+
|
|
15
|
+
// Private settlement receipt: succeed the Deferred with Exit as a value so
|
|
16
|
+
// interrupt fan-out does not skip Effect 4.0.0-beta.80 Deferred listeners.
|
|
17
|
+
// Public waiters flatten that Exit; this is not a global Deferred fix.
|
|
18
|
+
const awaitDone = <E>(done: Deferred.Deferred<Exit.Exit<void, E>>): Effect.Effect<void, E> =>
|
|
19
|
+
Deferred.await(done).pipe(Effect.flatMap(exit => exit))
|
|
20
|
+
|
|
21
|
+
/** `"input"` subsumes `"steer"` when coalescing wakes. */
|
|
22
|
+
export type Promotable = 'input' | 'steer'
|
|
23
|
+
|
|
24
|
+
/** Shutdown retains the claim; explicit user stop releases it after settlement. */
|
|
25
|
+
export type InterruptReason = 'user' | 'shutdown'
|
|
26
|
+
|
|
27
|
+
export type CapturedRun<E> =
|
|
28
|
+
| { readonly _tag: 'Started'; readonly join: Effect.Effect<void, E> }
|
|
29
|
+
| { readonly _tag: 'Joined'; readonly join: Effect.Effect<void, E> }
|
|
30
|
+
| { readonly _tag: 'Stopping'; readonly awaitSettlement: Effect.Effect<void> }
|
|
31
|
+
|
|
32
|
+
export type StopReceipt =
|
|
33
|
+
| { readonly _tag: 'Idle' }
|
|
34
|
+
| { readonly _tag: 'Interrupted' }
|
|
35
|
+
| { readonly _tag: 'LiveStopping' }
|
|
36
|
+
| { readonly _tag: 'Settling' }
|
|
37
|
+
|
|
38
|
+
export type Coordinator<Key, E, Reason = never> = {
|
|
39
|
+
readonly active: Effect.Effect<ReadonlySet<Key>>
|
|
40
|
+
readonly isActive: (key: Key) => Effect.Effect<boolean>
|
|
41
|
+
/** Starts an execution while idle, or joins the active execution. */
|
|
42
|
+
readonly run: (key: Key) => Effect.Effect<void, E>
|
|
43
|
+
/**
|
|
44
|
+
* Captures a run waiter without awaiting settlement.
|
|
45
|
+
* Idle starts force=true (`Started`); an active owner is joined (`Joined`),
|
|
46
|
+
* including during natural settlement. A stopping owner yields `Stopping` with a
|
|
47
|
+
* settlement waiter and no start.
|
|
48
|
+
*/
|
|
49
|
+
readonly captureRun: (key: Key) => Effect.Effect<CapturedRun<E>>
|
|
50
|
+
/** Rings the doorbell: idle starts; active drains again before settling. */
|
|
51
|
+
readonly wake: (key: Key, scope?: Promotable) => Effect.Effect<void>
|
|
52
|
+
/**
|
|
53
|
+
* Stops the active execution and clears its doorbell. No-op when idle.
|
|
54
|
+
* Resolves once the interruption request is delivered, not when cleanup settles.
|
|
55
|
+
* First accepted `reason` is kept; use `terminalStop` to escalate.
|
|
56
|
+
*/
|
|
57
|
+
readonly interrupt: (
|
|
58
|
+
key: Key,
|
|
59
|
+
reason?: Reason,
|
|
60
|
+
options?: { readonly awaitSettlement?: boolean }
|
|
61
|
+
) => Effect.Effect<boolean>
|
|
62
|
+
/**
|
|
63
|
+
* Terminal stop receipt. Always clears pendingWake. Settling means the owner
|
|
64
|
+
* is already gone and the settled callback is chosen; Driver may release the
|
|
65
|
+
* claim under the Inbox gate. LiveStopping/Interrupted leave release to that
|
|
66
|
+
* owner's future settled callback. LiveStopping joins the same outstanding
|
|
67
|
+
* interruption request rather than forking a second one.
|
|
68
|
+
*/
|
|
69
|
+
readonly terminalStop: (key: Key, reason: Reason) => Effect.Effect<StopReceipt>
|
|
70
|
+
/** Resolves once no execution is active. Never starts work. */
|
|
71
|
+
readonly awaitIdle: (key: Key) => Effect.Effect<void>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type Execution<E, Reason> = {
|
|
75
|
+
readonly done: Deferred.Deferred<Exit.Exit<void, E>>
|
|
76
|
+
owner?: Fiber.Fiber<void>
|
|
77
|
+
request?: Fiber.Fiber<void>
|
|
78
|
+
scope: Promotable
|
|
79
|
+
pendingWake?: Promotable
|
|
80
|
+
stopping: boolean
|
|
81
|
+
interruptionReason?: Reason
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const widerScope = (current: Promotable | undefined, next: Promotable): Promotable =>
|
|
85
|
+
current === 'input' || next === 'input' ? 'input' : 'steer'
|
|
86
|
+
|
|
87
|
+
export const makeCoordinator = <Key, E, Reason = never>(options: {
|
|
88
|
+
readonly drain: (key: Key, force: boolean, scope: Promotable) => Effect.Effect<void, E>
|
|
89
|
+
readonly started?: (key: Key) => Effect.Effect<void>
|
|
90
|
+
readonly settled?: (key: Key, exit: Exit.Exit<void, E>, reason?: Reason) => Effect.Effect<void>
|
|
91
|
+
}): Effect.Effect<Coordinator<Key, E, Reason>, never, Scope.Scope> =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
const executions = new Map<Key, Execution<E, Reason>>()
|
|
94
|
+
const fork = yield* FiberSet.makeRuntime<never, void, never>()
|
|
95
|
+
|
|
96
|
+
const loop = (
|
|
97
|
+
key: Key,
|
|
98
|
+
execution: Execution<E, Reason>,
|
|
99
|
+
force: boolean
|
|
100
|
+
): Effect.Effect<void, E> =>
|
|
101
|
+
Effect.suspend(() => {
|
|
102
|
+
if (execution.stopping) return Effect.void
|
|
103
|
+
|
|
104
|
+
return options.drain(key, force, execution.scope)
|
|
105
|
+
}).pipe(
|
|
106
|
+
Effect.andThen(
|
|
107
|
+
Effect.suspend(() => {
|
|
108
|
+
if (execution.stopping || execution.pendingWake === undefined) return Effect.void
|
|
109
|
+
execution.scope = execution.pendingWake
|
|
110
|
+
execution.pendingWake = undefined
|
|
111
|
+
|
|
112
|
+
return Effect.yieldNow.pipe(Effect.andThen(loop(key, execution, false)))
|
|
113
|
+
})
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
const settle = (key: Key, execution: Execution<E, Reason>, exit: Exit.Exit<void, E>) => {
|
|
118
|
+
if (execution.pendingWake !== undefined) start(key, false, execution.pendingWake)
|
|
119
|
+
else executions.delete(key)
|
|
120
|
+
|
|
121
|
+
return Deferred.succeed(execution.done, exit).pipe(Effect.asVoid)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const start = (key: Key, force: boolean, scope: Promotable) => {
|
|
125
|
+
const execution: Execution<E, Reason> = {
|
|
126
|
+
done: Deferred.makeUnsafe<Exit.Exit<void, E>>(),
|
|
127
|
+
scope,
|
|
128
|
+
stopping: false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
executions.set(key, execution)
|
|
132
|
+
execution.owner = fork(
|
|
133
|
+
Effect.yieldNow.pipe(
|
|
134
|
+
Effect.andThen(
|
|
135
|
+
Effect.uninterruptible(Effect.suspend(() => options.started?.(key) ?? Effect.void))
|
|
136
|
+
),
|
|
137
|
+
Effect.andThen(loop(key, execution, force)),
|
|
138
|
+
Effect.onExit(exit =>
|
|
139
|
+
Effect.suspend(() => {
|
|
140
|
+
execution.owner = undefined
|
|
141
|
+
|
|
142
|
+
return options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void
|
|
143
|
+
})
|
|
144
|
+
),
|
|
145
|
+
Effect.onExit(exit => settle(key, execution, exit)),
|
|
146
|
+
Effect.exit,
|
|
147
|
+
Effect.asVoid
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return execution
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const forkInterruptionRequest = (owner: Fiber.Fiber<void>): Fiber.Fiber<void> =>
|
|
155
|
+
fork(Effect.yieldNow.pipe(Effect.andThen(Effect.sync(() => owner.interruptUnsafe()))))
|
|
156
|
+
|
|
157
|
+
const interruptNow = (key: Key, reason?: Reason): Effect.Effect<boolean> =>
|
|
158
|
+
Effect.suspend(() => {
|
|
159
|
+
const execution = executions.get(key)
|
|
160
|
+
|
|
161
|
+
if (execution === undefined || execution.stopping) return Effect.succeed(false)
|
|
162
|
+
|
|
163
|
+
if (execution.owner === undefined) {
|
|
164
|
+
execution.pendingWake = undefined
|
|
165
|
+
|
|
166
|
+
return Effect.succeed(false)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const owner = execution.owner
|
|
170
|
+
execution.stopping = true
|
|
171
|
+
execution.pendingWake = undefined
|
|
172
|
+
execution.interruptionReason = reason
|
|
173
|
+
// Capture the exact old owner and fork the request in this same lazy
|
|
174
|
+
// transition so cancellation cannot leave stopping=true with no request.
|
|
175
|
+
// Initial yield breaks owner -> resumed caller -> owner reentrancy.
|
|
176
|
+
// Join the request fiber only; interruptUnsafe may run interruptible
|
|
177
|
+
// finalizers inline until they suspend. Do not await owner settlement.
|
|
178
|
+
const request = forkInterruptionRequest(owner)
|
|
179
|
+
execution.request = request
|
|
180
|
+
|
|
181
|
+
return Fiber.join(request).pipe(Effect.as(true))
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const awaitIdle = (key: Key): Effect.Effect<void> =>
|
|
185
|
+
Effect.suspend(() => {
|
|
186
|
+
const execution = executions.get(key)
|
|
187
|
+
|
|
188
|
+
if (execution === undefined) return Effect.void
|
|
189
|
+
|
|
190
|
+
return awaitDone(execution.done).pipe(Effect.ignoreCause, Effect.andThen(awaitIdle(key)))
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
active: Effect.sync(() => new Set(executions.keys())),
|
|
195
|
+
isActive: key => Effect.sync(() => executions.has(key)),
|
|
196
|
+
run: key =>
|
|
197
|
+
Effect.suspend(() => {
|
|
198
|
+
const execution = executions.get(key)
|
|
199
|
+
|
|
200
|
+
if (execution === undefined) return awaitDone(start(key, true, 'input').done)
|
|
201
|
+
|
|
202
|
+
if (!execution.stopping) return awaitDone(execution.done)
|
|
203
|
+
|
|
204
|
+
return awaitDone(execution.done).pipe(
|
|
205
|
+
Effect.ignoreCause,
|
|
206
|
+
Effect.andThen(
|
|
207
|
+
Effect.suspend(() => {
|
|
208
|
+
const next = executions.get(key)
|
|
209
|
+
|
|
210
|
+
return next === undefined
|
|
211
|
+
? awaitDone(start(key, true, 'input').done)
|
|
212
|
+
: awaitDone(next.done)
|
|
213
|
+
})
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
}),
|
|
217
|
+
captureRun: key =>
|
|
218
|
+
Effect.sync(() => {
|
|
219
|
+
const execution = executions.get(key)
|
|
220
|
+
|
|
221
|
+
if (execution === undefined) {
|
|
222
|
+
const started = start(key, true, 'input')
|
|
223
|
+
|
|
224
|
+
return { _tag: 'Started' as const, join: awaitDone(started.done) }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!execution.stopping) {
|
|
228
|
+
return { _tag: 'Joined' as const, join: awaitDone(execution.done) }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
_tag: 'Stopping' as const,
|
|
233
|
+
awaitSettlement: awaitDone(execution.done).pipe(Effect.ignoreCause)
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
wake: (key, scope = 'input') =>
|
|
237
|
+
Effect.sync(() => {
|
|
238
|
+
const execution = executions.get(key)
|
|
239
|
+
|
|
240
|
+
if (execution !== undefined) {
|
|
241
|
+
execution.pendingWake = widerScope(execution.pendingWake, scope)
|
|
242
|
+
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
start(key, false, scope)
|
|
247
|
+
}),
|
|
248
|
+
interrupt: (key, reason, options) =>
|
|
249
|
+
Effect.suspend(() => {
|
|
250
|
+
const execution = executions.get(key)
|
|
251
|
+
|
|
252
|
+
return interruptNow(key, reason).pipe(
|
|
253
|
+
Effect.tap(() =>
|
|
254
|
+
options?.awaitSettlement === true && execution !== undefined
|
|
255
|
+
? awaitDone(execution.done).pipe(Effect.ignoreCause)
|
|
256
|
+
: Effect.void
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
}),
|
|
260
|
+
terminalStop: (key, reason) =>
|
|
261
|
+
Effect.suspend((): Effect.Effect<StopReceipt> => {
|
|
262
|
+
const execution = executions.get(key)
|
|
263
|
+
|
|
264
|
+
if (execution === undefined) return Effect.succeed(stopReceipt.Idle())
|
|
265
|
+
execution.pendingWake = undefined
|
|
266
|
+
|
|
267
|
+
if (execution.owner === undefined) {
|
|
268
|
+
execution.stopping = true
|
|
269
|
+
|
|
270
|
+
return Effect.succeed(stopReceipt.Settling())
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
execution.interruptionReason = reason
|
|
274
|
+
|
|
275
|
+
if (execution.stopping) {
|
|
276
|
+
const request = execution.request
|
|
277
|
+
|
|
278
|
+
return request === undefined
|
|
279
|
+
? Effect.succeed(stopReceipt.LiveStopping())
|
|
280
|
+
: Fiber.join(request).pipe(Effect.as(stopReceipt.LiveStopping()))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const owner = execution.owner
|
|
284
|
+
execution.stopping = true
|
|
285
|
+
const request = forkInterruptionRequest(owner)
|
|
286
|
+
execution.request = request
|
|
287
|
+
|
|
288
|
+
return Fiber.join(request).pipe(Effect.as(stopReceipt.Interrupted()))
|
|
289
|
+
}),
|
|
290
|
+
awaitIdle
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
/** Owning coordinator layer, with per-acquisition claim receipts and settlement policy. */
|
|
295
|
+
export class RunCoordinator extends Context.Service<
|
|
296
|
+
RunCoordinator,
|
|
297
|
+
Coordinator<string, never, InterruptReason>
|
|
298
|
+
>()('@yolk-sdk/harness/RunCoordinator') {
|
|
299
|
+
static layer = (options?: {
|
|
300
|
+
readonly drain?: (runId: string, force: boolean, scope: Promotable) => Effect.Effect<void>
|
|
301
|
+
}): Layer.Layer<RunCoordinator, never, RunStore> =>
|
|
302
|
+
Layer.effect(
|
|
303
|
+
this,
|
|
304
|
+
Effect.gen(function* () {
|
|
305
|
+
const store = yield* RunStore
|
|
306
|
+
const drain = options?.drain ?? ((_runId, _force, _scope) => Effect.void)
|
|
307
|
+
const acquired = new Set<string>()
|
|
308
|
+
|
|
309
|
+
return yield* makeCoordinator<string, never, InterruptReason>({
|
|
310
|
+
drain,
|
|
311
|
+
started: runId =>
|
|
312
|
+
store.claim(runId).pipe(
|
|
313
|
+
Effect.tap(() =>
|
|
314
|
+
Effect.sync(() => {
|
|
315
|
+
acquired.add(runId)
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
),
|
|
319
|
+
settled: (runId, exit, reason) =>
|
|
320
|
+
Effect.suspend(() => {
|
|
321
|
+
const owned = acquired.delete(runId)
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
owned &&
|
|
325
|
+
(reason === 'user' || (reason === undefined && !Exit.hasInterrupts(exit)))
|
|
326
|
+
) {
|
|
327
|
+
return store.release(runId)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Acquisition receipt guards automatic failed-start settlement only.
|
|
331
|
+
// Explicit user-terminal authority still releases a leftover claim
|
|
332
|
+
// after this owner has actually settled, matching Idle/Settling stop.
|
|
333
|
+
if (reason === 'user' && !owned) {
|
|
334
|
+
return store
|
|
335
|
+
.isClaimed(runId)
|
|
336
|
+
.pipe(Effect.flatMap(claimed => (claimed ? store.release(runId) : Effect.void)))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return Effect.void
|
|
340
|
+
}).pipe(
|
|
341
|
+
Effect.exit,
|
|
342
|
+
Effect.flatMap(settledExit => {
|
|
343
|
+
if (Exit.isSuccess(settledExit)) return Effect.void
|
|
344
|
+
|
|
345
|
+
return Effect.failCause(
|
|
346
|
+
Exit.isFailure(exit)
|
|
347
|
+
? Cause.combine(exit.cause, settledExit.cause)
|
|
348
|
+
: settledExit.cause
|
|
349
|
+
)
|
|
350
|
+
})
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
)
|
|
355
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Layer } from 'effect'
|
|
2
|
+
import type { Effect } from 'effect'
|
|
3
|
+
import { Driver, type Drain, type InvalidMaxResumeAttempts } from '../driver.ts'
|
|
4
|
+
import { Inbox } from '../inbox.ts'
|
|
5
|
+
import { RunStore, type DurableRunStoreSnapshot } from '../store.ts'
|
|
6
|
+
|
|
7
|
+
export type { DurableRunStoreSnapshot }
|
|
8
|
+
|
|
9
|
+
type DurableObjectDriverBase = {
|
|
10
|
+
readonly load: Effect.Effect<DurableRunStoreSnapshot | undefined>
|
|
11
|
+
readonly save: (snapshot: DurableRunStoreSnapshot) => Effect.Effect<void>
|
|
12
|
+
readonly drain?: Drain
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function makeDurableObjectDriverLayer(
|
|
16
|
+
options: DurableObjectDriverBase & { readonly maxResumeAttempts?: undefined }
|
|
17
|
+
): Layer.Layer<Driver | RunStore | Inbox>
|
|
18
|
+
export function makeDurableObjectDriverLayer(
|
|
19
|
+
options: DurableObjectDriverBase & { readonly maxResumeAttempts?: number }
|
|
20
|
+
): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>
|
|
21
|
+
export function makeDurableObjectDriverLayer(
|
|
22
|
+
options: DurableObjectDriverBase & { readonly maxResumeAttempts?: number }
|
|
23
|
+
) {
|
|
24
|
+
return Driver.coordinatedLayer(options).pipe(
|
|
25
|
+
Layer.provideMerge(RunStore.snapshotLayer(options)),
|
|
26
|
+
Layer.provideMerge(Inbox.layer())
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Layer } from 'effect'
|
|
2
|
+
import {
|
|
3
|
+
Driver,
|
|
4
|
+
type Drain,
|
|
5
|
+
type DriverLayerOptions,
|
|
6
|
+
type InvalidMaxResumeAttempts
|
|
7
|
+
} from '../driver.ts'
|
|
8
|
+
import { Inbox } from '../inbox.ts'
|
|
9
|
+
import { RunStore } from '../store.ts'
|
|
10
|
+
|
|
11
|
+
export function makeInMemoryDriverLayer(): Layer.Layer<Driver, never, RunStore | Inbox>
|
|
12
|
+
export function makeInMemoryDriverLayer(options?: {
|
|
13
|
+
readonly drain?: Drain
|
|
14
|
+
readonly maxResumeAttempts?: undefined
|
|
15
|
+
}): Layer.Layer<Driver, never, RunStore | Inbox>
|
|
16
|
+
export function makeInMemoryDriverLayer(
|
|
17
|
+
options?: DriverLayerOptions
|
|
18
|
+
): Layer.Layer<Driver, InvalidMaxResumeAttempts, RunStore | Inbox>
|
|
19
|
+
export function makeInMemoryDriverLayer(options?: DriverLayerOptions) {
|
|
20
|
+
return options === undefined ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function makeInMemoryHarnessLayer(): Layer.Layer<Driver | RunStore | Inbox>
|
|
24
|
+
export function makeInMemoryHarnessLayer(options?: {
|
|
25
|
+
readonly drain?: Drain
|
|
26
|
+
readonly maxResumeAttempts?: undefined
|
|
27
|
+
}): Layer.Layer<Driver | RunStore | Inbox>
|
|
28
|
+
export function makeInMemoryHarnessLayer(
|
|
29
|
+
options?: DriverLayerOptions
|
|
30
|
+
): Layer.Layer<Driver | RunStore | Inbox, InvalidMaxResumeAttempts>
|
|
31
|
+
export function makeInMemoryHarnessLayer(options?: DriverLayerOptions) {
|
|
32
|
+
const driver =
|
|
33
|
+
options === undefined ? Driver.coordinatedLayer() : Driver.coordinatedLayer(options)
|
|
34
|
+
|
|
35
|
+
return driver.pipe(
|
|
36
|
+
Layer.provideMerge(RunStore.inMemoryLayer()),
|
|
37
|
+
Layer.provideMerge(Inbox.layer())
|
|
38
|
+
)
|
|
39
|
+
}
|