effect-agent 0.1.0-beta.144 → 0.1.0-beta.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Admin-CQMppCcy.d.mts → Admin-B8ISZyYf.d.mts} +37 -22
- package/dist/Agent-1DMSO_Fl.mjs.map +1 -1
- package/dist/{Agent-BWhD4Dvu.d.mts → Agent-Cjd_ZveM.d.mts} +7 -3
- package/dist/{AgentRuntime-xeCUXutN.mjs → AgentRuntime-D8U9gl9Q.mjs} +23 -18
- package/dist/AgentRuntime-D8U9gl9Q.mjs.map +1 -0
- package/dist/{AgentRuntime-B4kIzmSF.d.mts → AgentRuntime-sXpKcyxu.d.mts} +5 -5
- package/dist/{DurableAgentRuntime-D8GI66mP.mjs → DurableAgentRuntime-DLa4IwFi.mjs} +87 -49
- package/dist/DurableAgentRuntime-DLa4IwFi.mjs.map +1 -0
- package/dist/{EventSource-w9PonN9X.d.mts → EventSource-CkN05NT3.d.mts} +2 -2
- package/dist/{Messaging-Dnmk31yq.d.mts → Messaging-ccQURKCf.d.mts} +3 -3
- package/dist/{MessagingHost-CajpPDPG.d.mts → MessagingHost-D04wE-7W.d.mts} +2 -2
- package/dist/{Output-DhtWbBcw.d.mts → Output-CFD1CPfU.d.mts} +2 -2
- package/dist/{PreparedInputAdmission-BfcLy_k4.d.mts → PreparedInputAdmission-fctZOTnn.d.mts} +3 -3
- package/dist/{RunOptions-IlEXLG5F.d.mts → RunOptions-Bv9NpkPZ.d.mts} +10 -2
- package/dist/{Subagent-Cl8D1mFM.d.mts → Subagent-VHrANXFy.d.mts} +5 -5
- package/dist/{Subagent-8RzJkqJ9.mjs → Subagent-hG9lJ7tJ.mjs} +2 -2
- package/dist/{Subagent-8RzJkqJ9.mjs.map → Subagent-hG9lJ7tJ.mjs.map} +1 -1
- package/dist/{SubagentHost-B6f4PBC2.d.mts → SubagentHost-C-4A7tA2.d.mts} +2 -2
- package/dist/{Subscription-ClkC4_T0.d.mts → Subscription-B16A-48F.d.mts} +2 -2
- package/dist/{SubscriptionInput-CMjEKL1z.d.mts → SubscriptionInput-DIU7CiXE.d.mts} +2 -2
- package/dist/capabilities/Commands.d.mts +1 -1
- package/dist/capabilities/Messaging.d.mts +1 -1
- package/dist/capabilities/RunHooks.d.mts +1 -1
- package/dist/capabilities/Subagent.d.mts +1 -1
- package/dist/capabilities/Subagent.mjs +1 -1
- package/dist/core/Agent.d.mts +1 -1
- package/dist/durable/Admin.d.mts +1 -1
- package/dist/durable/AgentRegistration.d.mts +1 -1
- package/dist/durable/Certification.d.mts +1 -1
- package/dist/durable/CommittedActivity.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.mjs +1 -1
- package/dist/durable/EventSource.d.mts +1 -1
- package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
- package/dist/durable/LifecyclePublication.d.mts +2 -2
- package/dist/durable/LifecyclePublication.mjs +32 -12
- package/dist/durable/LifecyclePublication.mjs.map +1 -1
- package/dist/durable/MessageDelivery.d.mts +3 -3
- package/dist/durable/MessageDeliveryStoreConformance.d.mts +1 -1
- package/dist/durable/PersistentHistory.d.mts +1 -1
- package/dist/durable/PreparedInputAdmission.d.mts +1 -1
- package/dist/durable/Recovery.d.mts +1 -1
- package/dist/durable/RunJournal.d.mts +1 -1
- package/dist/durable/Scheduling.d.mts +1 -1
- package/dist/durable/SubmissionLedger.d.mts +1 -1
- package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
- package/dist/durable/SubmissionStatus.d.mts +1 -1
- package/dist/durable/Subscription.d.mts +1 -1
- package/dist/durable/SubscriptionInput.d.mts +1 -1
- package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
- package/dist/durable/SubscriptionTools.d.mts +2 -2
- package/dist/durable/SubscriptionTransition.d.mts +1 -1
- package/dist/durable/Subscriptions.d.mts +5 -5
- package/dist/durable/ThreadContextHistory.d.mts +1 -1
- package/dist/durable/ThreadInvariants.d.mts +1 -1
- package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
- package/dist/durable/ThreadStore.d.mts +1 -1
- package/dist/durable/ThreadStoreConformance.d.mts +1 -1
- package/dist/durable/WorkerHost.d.mts +25 -5
- package/dist/durable/WorkerHost.mjs.map +1 -1
- package/dist/engine/AgentRuntime.d.mts +1 -1
- package/dist/engine/AgentRuntime.mjs +1 -1
- package/dist/engine/MessagingHost.d.mts +1 -1
- package/dist/engine/Output.d.mts +1 -1
- package/dist/engine/Output.mjs +1 -1
- package/dist/engine/RunOptions.d.mts +1 -1
- package/dist/engine/RunOptions.mjs.map +1 -1
- package/dist/engine/SubagentHost.d.mts +1 -1
- package/dist/index.d.mts +13 -13
- package/dist/index.mjs +3 -3
- package/dist/{output-contract-CANJJGA5.mjs → output-contract-DS-S8mTv.mjs} +46 -10
- package/dist/{output-contract-CANJJGA5.mjs.map → output-contract-DS-S8mTv.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/core/Agent.ts +6 -2
- package/src/durable/DurableAgentRuntime.ts +86 -78
- package/src/durable/LifecyclePublication.ts +79 -25
- package/src/durable/WorkerHost.ts +23 -1
- package/src/durable/internal/worker-host.ts +169 -17
- package/src/engine/RunOptions.ts +8 -0
- package/src/engine/internal/agent-runtime.ts +80 -18
- package/src/engine/internal/output-contract.ts +67 -8
- package/dist/AgentRuntime-xeCUXutN.mjs.map +0 -1
- package/dist/DurableAgentRuntime-D8GI66mP.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Context, Crypto, Effect, Layer, Option, Schema, type Scope } from "effect";
|
|
1
|
+
import { Cause, Clock, Context, Crypto, Effect, Layer, Option, Schema, type Scope } from "effect";
|
|
2
2
|
|
|
3
3
|
import { SubmissionId, ThreadId } from "../core/Identifiers.ts";
|
|
4
4
|
import { Receipt } from "../core/Receipt.ts";
|
|
@@ -122,37 +122,70 @@ export class LifecyclePublicationError extends Schema.TaggedError<LifecyclePubli
|
|
|
122
122
|
},
|
|
123
123
|
) {}
|
|
124
124
|
|
|
125
|
-
/**
|
|
125
|
+
/** Bound both retained facts and the immutable admission evidence resolved before dispatch. */
|
|
126
|
+
export const lifecyclePublicationBatchMaxFacts = 8;
|
|
127
|
+
|
|
128
|
+
/** One owner's bounded pending prefix, strictly ordered by its durable ordinals. */
|
|
129
|
+
export const LifecyclePublicationBatch = Schema.NonEmptyArray(LifecyclePublication).check(
|
|
130
|
+
Schema.isMaxLength(lifecyclePublicationBatchMaxFacts),
|
|
131
|
+
Schema.makeFilter(
|
|
132
|
+
(facts) =>
|
|
133
|
+
facts.every((fact, index) => {
|
|
134
|
+
const previous = facts[index - 1];
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
fact.ownerThreadId === facts[0].ownerThreadId &&
|
|
138
|
+
(previous === undefined || fact.ordinal > previous.ordinal)
|
|
139
|
+
);
|
|
140
|
+
}),
|
|
141
|
+
{ expected: "One owner with strictly increasing publication ordinals" },
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
export type LifecyclePublicationBatch = typeof LifecyclePublicationBatch.Type;
|
|
146
|
+
|
|
147
|
+
/** Native recovery state, independent of Attempts. Hosts serialize drains per storage owner. */
|
|
126
148
|
export interface LifecyclePublicationStorage {
|
|
149
|
+
/** Select up to `limit` due owners, with a bounded pending prefix for each selected owner. */
|
|
127
150
|
readonly pending: (
|
|
128
151
|
nowMillis: number,
|
|
129
152
|
limit: number,
|
|
130
|
-
) => Effect.Effect<ReadonlyArray<
|
|
153
|
+
) => Effect.Effect<ReadonlyArray<LifecyclePublicationBatch>, LifecyclePublicationError>;
|
|
154
|
+
/** Atomically acknowledge the exact batch, retaining its identities and fingerprints. */
|
|
131
155
|
readonly acknowledge: (
|
|
132
|
-
|
|
133
|
-
) => Effect.Effect<void, LifecyclePublicationError>;
|
|
134
|
-
readonly defer: (
|
|
135
|
-
publication: LifecyclePublication,
|
|
136
|
-
untilMillis: number,
|
|
156
|
+
publications: LifecyclePublicationBatch,
|
|
137
157
|
) => Effect.Effect<void, LifecyclePublicationError>;
|
|
158
|
+
/**
|
|
159
|
+
* Claim a due owner batch and persist its retry before dispatch. Eight automatic attempts;
|
|
160
|
+
* 1s exponential backoff capped at 60s, after the dispatch timeout. The final attempt parks
|
|
161
|
+
* the payload before dispatch so process loss cannot renew its budget. False means not due.
|
|
162
|
+
*/
|
|
163
|
+
readonly claim: (
|
|
164
|
+
publications: LifecyclePublicationBatch,
|
|
165
|
+
nowMillis: number,
|
|
166
|
+
timeoutMillis: number,
|
|
167
|
+
) => Effect.Effect<boolean, LifecyclePublicationError>;
|
|
138
168
|
readonly pendingDeadline: Effect.Effect<Option.Option<number>, LifecyclePublicationError>;
|
|
139
|
-
/**
|
|
140
|
-
readonly
|
|
169
|
+
/** Explicit operator retry after repairing a parked owner's destination. */
|
|
170
|
+
readonly retryParked: (
|
|
141
171
|
ownerThreadId: ThreadId,
|
|
142
|
-
|
|
172
|
+
nowMillis: number,
|
|
173
|
+
) => Effect.Effect<void, LifecyclePublicationError>;
|
|
143
174
|
}
|
|
144
175
|
|
|
145
176
|
/**
|
|
146
|
-
* Publish
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
177
|
+
* Publish one owner's ordered batch in one idempotent host transaction, including authorization,
|
|
178
|
+
* records, receipts and delivery intents. Larger backlogs continue in later batches.
|
|
179
|
+
* Return only after the entire batch commits. Retries
|
|
180
|
+
* may include already committed identities plus later facts; deduplicate each fact's `id`.
|
|
181
|
+
* Private input/update/result fields remain private; select declared public fields explicitly.
|
|
182
|
+
* Revocation/deletion is an acknowledged domain decision, not an infrastructure retry.
|
|
150
183
|
*/
|
|
151
184
|
export class LifecyclePublicationHandler extends Context.Service<
|
|
152
185
|
LifecyclePublicationHandler,
|
|
153
186
|
{
|
|
154
187
|
readonly publish: (
|
|
155
|
-
|
|
188
|
+
publications: LifecyclePublicationBatch,
|
|
156
189
|
) => Effect.Effect<void, LifecyclePublicationError, Scope.Scope>;
|
|
157
190
|
}
|
|
158
191
|
>()("@effect-agent/thread/LifecyclePublicationHandler") {}
|
|
@@ -218,7 +251,8 @@ const withSource = Effect.fn("LifecyclePublication.withSource")(
|
|
|
218
251
|
|
|
219
252
|
/**
|
|
220
253
|
* One finite wave for the host's existing maintenance coordinator. Persist the next deadline
|
|
221
|
-
* before dispatch, so interruption and process loss retain the same
|
|
254
|
+
* before dispatch, so interruption and process loss retain the same facts without another timer.
|
|
255
|
+
* Finish independent owner batches before surfacing failures; interruption stops the wave.
|
|
222
256
|
* Acknowledgements are exact and idempotent. No producer or external Tool is re-executed here.
|
|
223
257
|
*/
|
|
224
258
|
export const drainLifecyclePublications = Effect.fn("LifecyclePublication.drain")(function* (
|
|
@@ -229,16 +263,36 @@ export const drainLifecyclePublications = Effect.fn("LifecyclePublication.drain"
|
|
|
229
263
|
const handler = yield* LifecyclePublicationHandler;
|
|
230
264
|
const pending = yield* storage.pending(yield* Clock.currentTimeMillis, limit);
|
|
231
265
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
266
|
+
let failures: Cause.Cause<LifecyclePublicationError> = Cause.empty;
|
|
267
|
+
|
|
268
|
+
for (const batch of pending) {
|
|
269
|
+
yield* Effect.gen(function* () {
|
|
270
|
+
if (!(yield* storage.claim(batch, yield* Clock.currentTimeMillis, timeoutMillis))) return;
|
|
271
|
+
yield* Effect.scoped(
|
|
272
|
+
Effect.gen(function* () {
|
|
273
|
+
const publications = yield* Effect.forEach(batch, withSource);
|
|
274
|
+
|
|
275
|
+
yield* handler.publish(publications);
|
|
276
|
+
}),
|
|
277
|
+
).pipe(
|
|
278
|
+
Effect.timeoutOrElse({
|
|
279
|
+
duration: timeoutMillis,
|
|
280
|
+
orElse: () => LifecyclePublicationError.make({ reason: "unavailable" }),
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
yield* storage.acknowledge(batch);
|
|
284
|
+
}).pipe(
|
|
285
|
+
Effect.catchCauseIf(
|
|
286
|
+
(cause) => !Cause.hasInterrupts(cause),
|
|
287
|
+
(cause) => {
|
|
288
|
+
failures = Cause.combine(failures, cause);
|
|
289
|
+
|
|
290
|
+
return Effect.void;
|
|
291
|
+
},
|
|
292
|
+
),
|
|
239
293
|
);
|
|
240
|
-
yield* storage.acknowledge(publication);
|
|
241
294
|
}
|
|
295
|
+
if (failures.reasons.length > 0) return yield* Effect.failCause(failures);
|
|
242
296
|
|
|
243
297
|
return pending.length;
|
|
244
298
|
});
|
|
@@ -2,10 +2,11 @@ import { Context, Effect, Option, Schema } from "effect";
|
|
|
2
2
|
|
|
3
3
|
import type * as Agent from "../core/Agent.ts";
|
|
4
4
|
import type { AgentPolicy } from "../core/AgentPolicy.ts";
|
|
5
|
-
import type { SubmissionId, ThreadId } from "../core/Identifiers.ts";
|
|
5
|
+
import type { AgentId, SubmissionId, ThreadId } from "../core/Identifiers.ts";
|
|
6
6
|
import type { SubagentBudgetReservation } from "../core/SubagentContract.ts";
|
|
7
7
|
import { WorkerError, type WorkerRef, type WorkerSource } from "../core/Worker.ts";
|
|
8
8
|
import type {
|
|
9
|
+
CanonicalSequence,
|
|
9
10
|
DefinitionDigests,
|
|
10
11
|
Digest,
|
|
11
12
|
PersistedJson,
|
|
@@ -14,6 +15,24 @@ import type {
|
|
|
14
15
|
} from "./Records.ts";
|
|
15
16
|
import type { Principal, SubmissionSnapshot } from "./SubmissionLedger.ts";
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* One successful start authorization at an exact source tail. This host-created hint is
|
|
20
|
+
* available only during that start's preparation and synchronous admission; it is never
|
|
21
|
+
* persisted or transported. Hooks may reuse their admission decision, but must retain
|
|
22
|
+
* current access checks at commit boundaries. A retry or changed tail gets no old grant.
|
|
23
|
+
*/
|
|
24
|
+
export interface WorkerStartAdmission {
|
|
25
|
+
readonly sourceThreadId: ThreadId;
|
|
26
|
+
readonly sourceSubmissionId?: SubmissionId;
|
|
27
|
+
readonly requestedPrincipal: Principal;
|
|
28
|
+
readonly principal: Principal;
|
|
29
|
+
readonly targetAgentId: AgentId;
|
|
30
|
+
readonly continuationOf?: WorkerContinuation;
|
|
31
|
+
readonly access: "send";
|
|
32
|
+
readonly tailSequence: CanonicalSequence;
|
|
33
|
+
readonly tailDigest: Digest;
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
/** Host-verified source input; absence never means the latest input in the Thread. */
|
|
18
37
|
export interface WorkerPolicySource {
|
|
19
38
|
readonly threadId: ThreadId;
|
|
@@ -30,6 +49,7 @@ export type WorkerPolicyTarget = {
|
|
|
30
49
|
} & (
|
|
31
50
|
| {
|
|
32
51
|
readonly _tag: "InitialInput";
|
|
52
|
+
readonly admission?: WorkerStartAdmission;
|
|
33
53
|
readonly continuationOf?: WorkerContinuation;
|
|
34
54
|
readonly sourceSubmission?: SubmissionSnapshot;
|
|
35
55
|
readonly input: PersistedJson;
|
|
@@ -136,6 +156,7 @@ export const WorkerHostConfig = Context.Reference<WorkerHostLimits>(
|
|
|
136
156
|
* identifiers alone never confer any of them.
|
|
137
157
|
*/
|
|
138
158
|
export interface WorkerHostAuthorizationRequest {
|
|
159
|
+
readonly admission?: WorkerStartAdmission;
|
|
139
160
|
readonly sourceThreadId: ThreadId;
|
|
140
161
|
/** An explicitly selected owner input; authorize this locator as well as the Thread. */
|
|
141
162
|
readonly sourceSubmissionId?: SubmissionId;
|
|
@@ -170,6 +191,7 @@ export const WorkerHostAuthorizer = Context.Reference<{
|
|
|
170
191
|
export const WorkerBudgetAuthorizer = Context.Reference<{
|
|
171
192
|
readonly authorize: (request: {
|
|
172
193
|
readonly source: WorkerSource;
|
|
194
|
+
readonly admission?: WorkerStartAdmission;
|
|
173
195
|
readonly principal: Principal;
|
|
174
196
|
readonly worker: WorkerRef;
|
|
175
197
|
readonly continuationOf?: WorkerContinuation;
|
|
@@ -128,6 +128,7 @@ import {
|
|
|
128
128
|
WorkerHostConfig,
|
|
129
129
|
WorkerPolicyResolver,
|
|
130
130
|
type WorkerPolicyTarget,
|
|
131
|
+
type WorkerStartAdmission,
|
|
131
132
|
} from "../WorkerHost.ts";
|
|
132
133
|
import {
|
|
133
134
|
definitionDigestsEqual,
|
|
@@ -263,6 +264,20 @@ export class WorkerInputControl extends Context.Service<
|
|
|
263
264
|
}
|
|
264
265
|
>()("@effect-agent/thread/internal/WorkerInputControl") {}
|
|
265
266
|
|
|
267
|
+
const CurrentStartPreparation = Context.Reference<WorkerStartAdmission | undefined>(
|
|
268
|
+
"@effect-agent/thread/internal/CurrentStartPreparation",
|
|
269
|
+
{ defaultValue: () => undefined },
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const CurrentStartDelivery = Context.Reference<
|
|
273
|
+
| {
|
|
274
|
+
readonly authorization: WorkerStartAdmission;
|
|
275
|
+
readonly admission: WorkerAdmission;
|
|
276
|
+
readonly inputDigest: Digest;
|
|
277
|
+
}
|
|
278
|
+
| undefined
|
|
279
|
+
>("@effect-agent/thread/internal/CurrentStartDelivery", { defaultValue: () => undefined });
|
|
280
|
+
|
|
266
281
|
/** No in-memory ownership: every mutation is reserved in source canonical history with CAS. */
|
|
267
282
|
export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
268
283
|
options: WorkerRuntimeOptions,
|
|
@@ -290,9 +305,40 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
290
305
|
const withCrypto = <A, E>(effect: Effect.Effect<A, E, Crypto.Crypto>) =>
|
|
291
306
|
Effect.provideService(effect, Crypto.Crypto, deps.crypto);
|
|
292
307
|
|
|
308
|
+
const currentStart = Effect.fnUntraced(function* (
|
|
309
|
+
admission: WorkerStartAdmission | undefined,
|
|
310
|
+
sourceThreadId: ThreadId,
|
|
311
|
+
sourceSubmissionId: SubmissionId | undefined,
|
|
312
|
+
principal: Principal,
|
|
313
|
+
targetAgentId: AgentId,
|
|
314
|
+
continuationOf: WorkerContinuation | undefined,
|
|
315
|
+
) {
|
|
316
|
+
if (
|
|
317
|
+
admission === undefined ||
|
|
318
|
+
admission.sourceThreadId !== sourceThreadId ||
|
|
319
|
+
admission.sourceSubmissionId !== sourceSubmissionId ||
|
|
320
|
+
(admission.principal !== principal && admission.requestedPrincipal !== principal) ||
|
|
321
|
+
admission.targetAgentId !== targetAgentId ||
|
|
322
|
+
!Schema.toEquivalence(Schema.UndefinedOr(WorkerContinuation))(
|
|
323
|
+
admission.continuationOf,
|
|
324
|
+
continuationOf,
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
return undefined;
|
|
328
|
+
|
|
329
|
+
const tail = yield* deps.store
|
|
330
|
+
.inspectTail(ThreadTailRequest.make({ threadId: sourceThreadId }))
|
|
331
|
+
.pipe(Effect.mapError(storageFailure("start")));
|
|
332
|
+
|
|
333
|
+
return tail.tailSequence === admission.tailSequence && tail.tailDigest === admission.tailDigest
|
|
334
|
+
? admission
|
|
335
|
+
: undefined;
|
|
336
|
+
});
|
|
337
|
+
|
|
293
338
|
const authorizeBudget = Effect.fn("WorkerHost.authorizeBudget")(function* (
|
|
294
339
|
origin: WorkerOrigin,
|
|
295
340
|
principal: Principal,
|
|
341
|
+
admission?: WorkerStartAdmission,
|
|
296
342
|
) {
|
|
297
343
|
yield* verifyContinuation(origin);
|
|
298
344
|
if (origin.budgetScope !== "worker-run") return;
|
|
@@ -300,6 +346,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
300
346
|
if (origin.depth !== 1) return yield* failure("start", "denied");
|
|
301
347
|
|
|
302
348
|
yield* deps.budgetAuthorizer.authorize({
|
|
349
|
+
...(admission === undefined ? {} : { admission }),
|
|
303
350
|
source: origin.source,
|
|
304
351
|
principal,
|
|
305
352
|
worker: origin.worker,
|
|
@@ -1049,6 +1096,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1049
1096
|
input: PersistedJson,
|
|
1050
1097
|
principal: Principal,
|
|
1051
1098
|
retainedFrameworkInput: boolean,
|
|
1099
|
+
authorization?: WorkerStartAdmission,
|
|
1052
1100
|
): Effect.fn.Return<void, WorkerError> {
|
|
1053
1101
|
const origin = admission.origin;
|
|
1054
1102
|
|
|
@@ -1133,6 +1181,11 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1133
1181
|
? {
|
|
1134
1182
|
...targetRequest,
|
|
1135
1183
|
_tag: "InitialInput",
|
|
1184
|
+
...(authorization === undefined ||
|
|
1185
|
+
authorization.tailSequence !== current.tailSequence ||
|
|
1186
|
+
authorization.tailDigest !== current.tailDigest
|
|
1187
|
+
? {}
|
|
1188
|
+
: { admission: authorization }),
|
|
1136
1189
|
...(origin.continuationOf === undefined
|
|
1137
1190
|
? {}
|
|
1138
1191
|
: { continuationOf: origin.continuationOf }),
|
|
@@ -1351,7 +1404,24 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1351
1404
|
}
|
|
1352
1405
|
|
|
1353
1406
|
yield* verifyContinuation(admission.origin);
|
|
1407
|
+
const delivery = yield* CurrentStartDelivery;
|
|
1408
|
+
|
|
1409
|
+
const authorization =
|
|
1410
|
+
delivery !== undefined &&
|
|
1411
|
+
sameAdmission(delivery.admission, admission) &&
|
|
1412
|
+
delivery.inputDigest === inputDigest
|
|
1413
|
+
? yield* currentStart(
|
|
1414
|
+
delivery.authorization,
|
|
1415
|
+
admission.origin.source.threadId,
|
|
1416
|
+
admission.sourceSubmissionId,
|
|
1417
|
+
options.principal,
|
|
1418
|
+
agentId,
|
|
1419
|
+
admission.origin.continuationOf,
|
|
1420
|
+
)
|
|
1421
|
+
: undefined;
|
|
1422
|
+
|
|
1354
1423
|
yield* deps.authorizer.authorize({
|
|
1424
|
+
...(authorization === undefined ? {} : { admission: authorization }),
|
|
1355
1425
|
sourceThreadId: admission.origin.source.threadId,
|
|
1356
1426
|
...(admission.sourceSubmissionId === undefined
|
|
1357
1427
|
? {}
|
|
@@ -1364,7 +1434,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1364
1434
|
? {}
|
|
1365
1435
|
: { continuationOf: admission.origin.continuationOf }),
|
|
1366
1436
|
});
|
|
1367
|
-
yield* authorizeBudget(admission.origin, options.principal);
|
|
1437
|
+
yield* authorizeBudget(admission.origin, options.principal, authorization);
|
|
1368
1438
|
if (
|
|
1369
1439
|
admission.origin.worker.threadId !== options.threadId ||
|
|
1370
1440
|
admission.origin.worker.targetAgentId !== agentId ||
|
|
@@ -1378,7 +1448,14 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1378
1448
|
|
|
1379
1449
|
if (retainedFrameworkInput && admission.reportKind !== "update")
|
|
1380
1450
|
yield* validateCompletion(options.messageAdmission, options, agentId, inputDigest);
|
|
1381
|
-
yield* reservation(
|
|
1451
|
+
yield* reservation(
|
|
1452
|
+
admission,
|
|
1453
|
+
inputDigest,
|
|
1454
|
+
input,
|
|
1455
|
+
options.principal,
|
|
1456
|
+
retainedFrameworkInput,
|
|
1457
|
+
authorization,
|
|
1458
|
+
);
|
|
1382
1459
|
|
|
1383
1460
|
return admission;
|
|
1384
1461
|
});
|
|
@@ -1866,13 +1943,17 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1866
1943
|
principal: Principal,
|
|
1867
1944
|
sourceSubmissionId?: SubmissionId,
|
|
1868
1945
|
): SubagentHost["Service"] => {
|
|
1946
|
+
const requestedPrincipal = principal;
|
|
1947
|
+
|
|
1869
1948
|
const authorize = (
|
|
1870
1949
|
operation: WorkerError["operation"],
|
|
1871
1950
|
access: "context" | "read" | "send" | "control",
|
|
1872
1951
|
worker?: WorkerRef,
|
|
1873
1952
|
continuationOf?: WorkerContinuation,
|
|
1953
|
+
admission?: WorkerStartAdmission,
|
|
1874
1954
|
) =>
|
|
1875
1955
|
deps.authorizer.authorize({
|
|
1956
|
+
...(admission === undefined ? {} : { admission }),
|
|
1876
1957
|
sourceThreadId: context.source.threadId,
|
|
1877
1958
|
...(sourceSubmissionId === undefined ? {} : { sourceSubmissionId }),
|
|
1878
1959
|
principal,
|
|
@@ -1894,6 +1975,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1894
1975
|
target: Agent.AnyDefinition,
|
|
1895
1976
|
encodedInput: unknown,
|
|
1896
1977
|
continuationOf?: WorkerContinuation,
|
|
1978
|
+
authorization?: WorkerStartAdmission,
|
|
1897
1979
|
) {
|
|
1898
1980
|
const resolved = yield* binding(target, "start");
|
|
1899
1981
|
|
|
@@ -1908,8 +1990,18 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
1908
1990
|
|
|
1909
1991
|
const source = yield* sourceAuthority(context.source.threadId, sourceSubmissionId);
|
|
1910
1992
|
|
|
1993
|
+
const admission = yield* currentStart(
|
|
1994
|
+
authorization,
|
|
1995
|
+
context.source.threadId,
|
|
1996
|
+
sourceSubmissionId,
|
|
1997
|
+
principal,
|
|
1998
|
+
target.id,
|
|
1999
|
+
continuationOf,
|
|
2000
|
+
);
|
|
2001
|
+
|
|
1911
2002
|
const policy = yield* resolveTargetPolicy({
|
|
1912
2003
|
_tag: "InitialInput",
|
|
2004
|
+
...(admission === undefined ? {} : { admission }),
|
|
1913
2005
|
...(continuationOf === undefined ? {} : { continuationOf }),
|
|
1914
2006
|
definition: resolved.definition,
|
|
1915
2007
|
definitions: resolved.digests,
|
|
@@ -2064,6 +2156,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2064
2156
|
encodedParameters: unknown,
|
|
2065
2157
|
principal: Principal,
|
|
2066
2158
|
operation: "start" | "followUp",
|
|
2159
|
+
authorization?: WorkerStartAdmission,
|
|
2067
2160
|
) {
|
|
2068
2161
|
if (Option.isNone(deps.deliveries)) return yield* failure(operation, "unavailable");
|
|
2069
2162
|
const deliveries = deps.deliveries.value;
|
|
@@ -2146,17 +2239,25 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2146
2239
|
Effect.provideService(MessageDeliveryStore, deliveries),
|
|
2147
2240
|
Effect.provideService(PreparedInputAdmission, {
|
|
2148
2241
|
submit: (prepared) =>
|
|
2149
|
-
deps
|
|
2150
|
-
.
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2242
|
+
deps.submit(prepared).pipe(
|
|
2243
|
+
Effect.provideService(
|
|
2244
|
+
CurrentStartDelivery,
|
|
2245
|
+
authorization !== undefined &&
|
|
2246
|
+
prepared.workerAdmission !== undefined &&
|
|
2247
|
+
prepared.admissionKey === messageId &&
|
|
2248
|
+
prepared.inputDigest === inputDigest &&
|
|
2249
|
+
sameOrigin(prepared.workerAdmission.origin, origin)
|
|
2250
|
+
? { authorization, admission: prepared.workerAdmission, inputDigest }
|
|
2251
|
+
: undefined,
|
|
2159
2252
|
),
|
|
2253
|
+
Effect.mapError((error) =>
|
|
2254
|
+
error._tag === "AdmissionConflict" || error._tag === "AgentInputError"
|
|
2255
|
+
? ScheduledInputRefused.make({ code: error._tag, cause: error })
|
|
2256
|
+
: error._tag === "AdmissionPolicyError" && error.reason === "refused"
|
|
2257
|
+
? ScheduledInputRefused.make({ code: error.code, cause: error })
|
|
2258
|
+
: ScheduledInputRetryable.make({ reason: "storage", cause: error }),
|
|
2259
|
+
),
|
|
2260
|
+
),
|
|
2160
2261
|
submissionStatus: (receipt) =>
|
|
2161
2262
|
deps
|
|
2162
2263
|
.status(receipt)
|
|
@@ -2393,10 +2494,23 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2393
2494
|
request.continuationOf.delegationId,
|
|
2394
2495
|
);
|
|
2395
2496
|
|
|
2396
|
-
yield*
|
|
2497
|
+
const admission = yield* currentStart(
|
|
2498
|
+
yield* CurrentStartPreparation,
|
|
2499
|
+
context.source.threadId,
|
|
2500
|
+
sourceSubmissionId,
|
|
2501
|
+
principal,
|
|
2502
|
+
request.target.id,
|
|
2503
|
+
previous?.evidence,
|
|
2504
|
+
);
|
|
2505
|
+
|
|
2506
|
+
yield* authorize("start", "send", undefined, previous?.evidence, admission);
|
|
2397
2507
|
|
|
2398
|
-
return (yield* preparedTarget(
|
|
2399
|
-
.
|
|
2508
|
+
return (yield* preparedTarget(
|
|
2509
|
+
request.target,
|
|
2510
|
+
request.encodedInput,
|
|
2511
|
+
previous?.evidence,
|
|
2512
|
+
admission,
|
|
2513
|
+
)).policy;
|
|
2400
2514
|
}),
|
|
2401
2515
|
start: Effect.fn("WorkerHost.start")(function* <E = never, R = never>(
|
|
2402
2516
|
command: StartWorkerRequest | DeferredStartWorkerRequest<E, R>,
|
|
@@ -2411,8 +2525,26 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2411
2525
|
command.delegationId,
|
|
2412
2526
|
);
|
|
2413
2527
|
|
|
2528
|
+
// Bind the decision to the tail before authorization. If it changes while the
|
|
2529
|
+
// authorizer reads current state, currentStart rejects reuse of this decision.
|
|
2530
|
+
const tail = yield* deps.store
|
|
2531
|
+
.inspectTail(ThreadTailRequest.make({ threadId: context.source.threadId }))
|
|
2532
|
+
.pipe(Effect.mapError(storageFailure("start")));
|
|
2533
|
+
|
|
2414
2534
|
const principal = yield* authorize("start", "send", undefined, previous?.evidence);
|
|
2415
2535
|
|
|
2536
|
+
const startAdmission: WorkerStartAdmission = {
|
|
2537
|
+
sourceThreadId: context.source.threadId,
|
|
2538
|
+
...(sourceSubmissionId === undefined ? {} : { sourceSubmissionId }),
|
|
2539
|
+
requestedPrincipal,
|
|
2540
|
+
principal,
|
|
2541
|
+
targetAgentId: command.target.id,
|
|
2542
|
+
...(previous === undefined ? {} : { continuationOf: previous.evidence }),
|
|
2543
|
+
access: "send",
|
|
2544
|
+
tailSequence: tail.tailSequence,
|
|
2545
|
+
tailDigest: tail.tailDigest,
|
|
2546
|
+
};
|
|
2547
|
+
|
|
2416
2548
|
if (context.depth !== 0 && sourceSubmissionId === undefined)
|
|
2417
2549
|
return yield* failure("start", "denied");
|
|
2418
2550
|
|
|
@@ -2507,7 +2639,14 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2507
2639
|
|
|
2508
2640
|
return yield* Effect.gen(function* () {
|
|
2509
2641
|
const request =
|
|
2510
|
-
"prepare" in command
|
|
2642
|
+
"prepare" in command
|
|
2643
|
+
? {
|
|
2644
|
+
...(yield* command.prepare.pipe(
|
|
2645
|
+
Effect.provideService(CurrentStartPreparation, startAdmission),
|
|
2646
|
+
)),
|
|
2647
|
+
...command,
|
|
2648
|
+
}
|
|
2649
|
+
: command;
|
|
2511
2650
|
|
|
2512
2651
|
const currentPrevious =
|
|
2513
2652
|
command.continuationOf === undefined
|
|
@@ -2535,6 +2674,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2535
2674
|
request.target,
|
|
2536
2675
|
request.encodedInput,
|
|
2537
2676
|
currentPrevious?.evidence,
|
|
2677
|
+
startAdmission,
|
|
2538
2678
|
);
|
|
2539
2679
|
|
|
2540
2680
|
const resolved = prepared.resolved;
|
|
@@ -2596,7 +2736,18 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2596
2736
|
"start",
|
|
2597
2737
|
);
|
|
2598
2738
|
|
|
2599
|
-
yield* authorizeBudget(
|
|
2739
|
+
yield* authorizeBudget(
|
|
2740
|
+
origin,
|
|
2741
|
+
principal,
|
|
2742
|
+
yield* currentStart(
|
|
2743
|
+
startAdmission,
|
|
2744
|
+
context.source.threadId,
|
|
2745
|
+
sourceSubmissionId,
|
|
2746
|
+
principal,
|
|
2747
|
+
request.target.id,
|
|
2748
|
+
currentPrevious?.evidence,
|
|
2749
|
+
),
|
|
2750
|
+
);
|
|
2600
2751
|
|
|
2601
2752
|
const targetPolicy = Option.isSome(prepared.policy)
|
|
2602
2753
|
? prepared.policy.value
|
|
@@ -2623,6 +2774,7 @@ export const makeWorkerRuntime = Effect.fn("WorkerHost.make")(function* (
|
|
|
2623
2774
|
request.encodedParameters,
|
|
2624
2775
|
principal,
|
|
2625
2776
|
"start",
|
|
2777
|
+
startAdmission,
|
|
2626
2778
|
);
|
|
2627
2779
|
|
|
2628
2780
|
return { worker: origin.worker, delivery };
|
package/src/engine/RunOptions.ts
CHANGED
|
@@ -210,6 +210,14 @@ export interface RunContextRequest {
|
|
|
210
210
|
* identities rather than infer durable state from this Prompt alone.
|
|
211
211
|
*/
|
|
212
212
|
readonly source: Prompt.Prompt;
|
|
213
|
+
/**
|
|
214
|
+
* Durable coordinator's exact prior-Run prefix length in `source`. A preparation hook
|
|
215
|
+
* can replace `source.content.slice(0, priorRunPrefixLength)` with application context
|
|
216
|
+
* while preserving this Run's instructions, input, updates and recovered Tool exchanges.
|
|
217
|
+
* This is a model-only transformation: canonical records and retry receipts are retained.
|
|
218
|
+
* Absent for non-durable Runs; never infer this boundary from message roles or text.
|
|
219
|
+
*/
|
|
220
|
+
readonly priorRunPrefixLength?: number | undefined;
|
|
213
221
|
/**
|
|
214
222
|
* The exact model-visible final-output contract the engine appends to the
|
|
215
223
|
* prepared prompt after this hook returns (RUN-028), or
|