effect-agent 0.1.0-beta.95 → 0.1.0-beta.97
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/{AgentRuntime-CufqQ4JM.d.mts → AgentRuntime-B64362Id.d.mts} +2 -2
- package/dist/AgentRuntime-Cdvqw7l_.mjs.map +1 -1
- package/dist/{DurableAgentRuntime-BaGsWDHO.mjs → DurableAgentRuntime-DbUvZHDZ.mjs} +82 -66
- package/dist/DurableAgentRuntime-DbUvZHDZ.mjs.map +1 -0
- package/dist/DurableFailpoint-DQJet828.d.mts +41 -0
- package/dist/{EventSource-DVfX1GRH.d.mts → EventSource-D6PAAEyK.d.mts} +3 -3
- package/dist/{PreparedInputAdmission-DoWaMC2R.d.mts → PreparedInputAdmission-omMbwd--.d.mts} +4 -4
- package/dist/{Records-CsEsSW2o.mjs → Records-BAutSSnJ.mjs} +18 -5
- package/dist/Records-BAutSSnJ.mjs.map +1 -0
- package/dist/{Records-B84j3Jq_.d.mts → Records-CV49DgQK.d.mts} +90 -16
- package/dist/{RunJournal-yYpUlTOE.mjs → RunJournal-DRRRZmG7.mjs} +8 -3
- package/dist/RunJournal-DRRRZmG7.mjs.map +1 -0
- package/dist/{RunOptions-CE-G3niT.d.mts → RunOptions-Clj8cLsp.d.mts} +4 -4
- package/dist/{Schedule-DCYBfhgT.d.mts → Schedule-DePbGfgd.d.mts} +2 -2
- package/dist/{Scheduling-CUuYc3-D.mjs → Scheduling-C3QpYn0W.mjs} +2 -2
- package/dist/{Scheduling-CUuYc3-D.mjs.map → Scheduling-C3QpYn0W.mjs.map} +1 -1
- package/dist/{Subagent-DscbtbNY.d.mts → Subagent-Bm1dRecN.d.mts} +3 -3
- package/dist/{Subscription-C8IHdmFk.d.mts → Subscription-BR_8Cxh3.d.mts} +3 -3
- package/dist/{SubscriptionInput-DhM3jUW2.d.mts → SubscriptionInput-B95sQbuk.d.mts} +3 -3
- package/dist/agent-registration-CXGDwvJH.mjs +379 -0
- package/dist/agent-registration-CXGDwvJH.mjs.map +1 -0
- package/dist/capabilities/Commands.d.mts +1 -1
- package/dist/capabilities/RunHooks.d.mts +1 -1
- package/dist/capabilities/Subagent.d.mts +1 -1
- package/dist/durable/ActivityStore.d.mts +1 -1
- package/dist/durable/ActivityStore.mjs +1 -1
- package/dist/durable/Admin.d.mts +1 -1
- package/dist/durable/Admin.mjs +1 -1
- package/dist/durable/AgentRegistration.d.mts +2 -2
- package/dist/durable/AgentRegistration.mjs +7 -3
- package/dist/durable/AgentRegistration.mjs.map +1 -1
- package/dist/durable/Certification.d.mts +2 -2
- package/dist/durable/CommittedActivity.d.mts +1 -1
- package/dist/durable/CommittedActivity.mjs +1 -1
- package/dist/durable/Digest.d.mts +1 -1
- package/dist/durable/Digest.mjs +1 -1
- package/dist/durable/DurableAgentRuntime.d.mts +1 -1
- package/dist/durable/DurableAgentRuntime.mjs +1 -1
- package/dist/durable/DurableFailpoint.d.mts +1 -1
- package/dist/durable/DurableFailpoint.mjs +2 -0
- package/dist/durable/DurableFailpoint.mjs.map +1 -1
- package/dist/durable/DurableFailpointTestControl.d.mts +1 -1
- package/dist/durable/EventSource.d.mts +1 -1
- package/dist/durable/EventSource.mjs +1 -1
- package/dist/durable/GitHubWorkflowSource.d.mts +3 -3
- package/dist/durable/GitHubWorkflowSource.mjs +1 -1
- package/dist/durable/MessageDelivery.d.mts +3 -3
- package/dist/durable/MessageDelivery.mjs +1 -1
- package/dist/durable/MessageDeliveryStoreConformance.d.mts +39 -39
- package/dist/durable/MessageDeliveryStoreConformance.mjs +1 -1
- package/dist/durable/PersistentHistory.d.mts +1 -1
- package/dist/durable/PersistentHistory.mjs +2 -2
- package/dist/durable/PreparedInputAdmission.d.mts +1 -1
- package/dist/durable/Records.d.mts +2 -2
- package/dist/durable/Records.mjs +2 -2
- package/dist/durable/Recovery.d.mts +1 -1
- package/dist/durable/Recovery.mjs +1 -1
- package/dist/durable/RunJournal.d.mts +2 -2
- package/dist/durable/RunJournal.mjs +2 -2
- package/dist/durable/Schedule.d.mts +1 -1
- package/dist/durable/Schedule.mjs +1 -1
- package/dist/durable/ScheduleStoreConformance.d.mts +1 -1
- package/dist/durable/ScheduleStoreConformance.mjs +1 -1
- package/dist/durable/ScheduleTransition.d.mts +1 -1
- package/dist/durable/ScheduleTransition.mjs +1 -1
- package/dist/durable/Scheduling.d.mts +2 -2
- package/dist/durable/Scheduling.mjs +1 -1
- package/dist/durable/SqlStorageV2Upgrade.d.mts +2 -2
- package/dist/durable/SqlStorageV2Upgrade.mjs +1 -1
- package/dist/durable/SqlSubscriptionStore.d.mts +2 -2
- package/dist/durable/SqlSubscriptionStore.mjs +1 -1
- package/dist/durable/SubmissionLedger.d.mts +1 -1
- package/dist/durable/SubmissionLedger.mjs +1 -1
- package/dist/durable/SubmissionLedgerConformance.d.mts +1 -1
- package/dist/durable/SubmissionLedgerConformance.mjs +1 -1
- package/dist/durable/SubmissionStatus.d.mts +1 -1
- package/dist/durable/Subscription.d.mts +1 -1
- package/dist/durable/Subscription.mjs +1 -1
- package/dist/durable/SubscriptionInput.d.mts +1 -1
- package/dist/durable/SubscriptionInput.mjs +2 -2
- package/dist/durable/SubscriptionStoreConformance.d.mts +1 -1
- package/dist/durable/SubscriptionStoreConformance.mjs +1 -1
- package/dist/durable/SubscriptionTools.d.mts +3 -3
- package/dist/durable/SubscriptionTransition.d.mts +1 -1
- package/dist/durable/SubscriptionTransition.mjs +1 -1
- package/dist/durable/Subscriptions.d.mts +5 -5
- package/dist/durable/Subscriptions.mjs +1 -1
- package/dist/durable/ThreadContextHistory.d.mts +1 -1
- package/dist/durable/ThreadContextHistory.mjs +1 -1
- package/dist/durable/ThreadContextHistoryProjection.d.mts +1 -1
- package/dist/durable/ThreadContextHistoryProjection.mjs +1 -1
- package/dist/durable/ThreadInvariants.d.mts +1 -1
- package/dist/durable/ThreadInvariants.mjs +1 -1
- package/dist/durable/ThreadProjection.d.mts +1 -1
- package/dist/durable/ThreadProjection.mjs +1 -1
- package/dist/durable/ThreadProjectionMaintenance.d.mts +1 -1
- package/dist/durable/ThreadStore.d.mts +1 -1
- package/dist/durable/ThreadStore.mjs +1 -1
- package/dist/durable/ThreadStoreConformance.d.mts +1 -1
- package/dist/durable/ThreadStoreConformance.mjs +1 -1
- package/dist/durable/ToolReconciler.mjs +1 -1
- package/dist/durable/WorkerHost.d.mts +1 -1
- package/dist/engine/AgentRuntime.d.mts +1 -1
- package/dist/engine/RunOptions.d.mts +1 -1
- package/dist/engine/RunOptions.mjs.map +1 -1
- package/dist/index.d.mts +10 -10
- package/dist/index.mjs +4 -4
- package/package.json +1 -1
- package/src/durable/AgentRegistration.ts +5 -0
- package/src/durable/DurableAgentRuntime.ts +155 -84
- package/src/durable/DurableFailpoint.ts +2 -0
- package/src/durable/Records.ts +19 -2
- package/src/durable/RunJournal.ts +6 -0
- package/src/durable/internal/agent-registration.ts +427 -14
- package/src/durable/internal/messaging-host.ts +3 -6
- package/src/durable/internal/worker-host.ts +114 -88
- package/src/engine/RunOptions.ts +3 -3
- package/src/engine/internal/agent-runtime.ts +3 -4
- package/dist/DurableAgentRuntime-BaGsWDHO.mjs.map +0 -1
- package/dist/DurableFailpoint-Oinuuedm.d.mts +0 -41
- package/dist/Records-CsEsSW2o.mjs.map +0 -1
- package/dist/RunJournal-yYpUlTOE.mjs.map +0 -1
- package/dist/agent-registration-CWDMOAZA.mjs +0 -172
- package/dist/agent-registration-CWDMOAZA.mjs.map +0 -1
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Crypto,
|
|
3
|
+
type Option,
|
|
4
|
+
type Scope,
|
|
5
|
+
Array,
|
|
6
|
+
Context,
|
|
7
|
+
Effect,
|
|
8
|
+
Layer,
|
|
9
|
+
Schema,
|
|
10
|
+
} from "effect";
|
|
2
11
|
import { Tool } from "effect/unstable/ai";
|
|
3
12
|
|
|
4
13
|
import type * as Agent from "../../core/Agent.ts";
|
|
@@ -9,6 +18,7 @@ import {
|
|
|
9
18
|
type RunDispositionDeclaration,
|
|
10
19
|
} from "../../core/Agent.ts";
|
|
11
20
|
import { type ThreadId, AgentId } from "../../core/Identifiers.ts";
|
|
21
|
+
import { getToolExecutionKind } from "../../core/SubagentContract.ts";
|
|
12
22
|
import {
|
|
13
23
|
AdditionalToolCatalog,
|
|
14
24
|
DiscoveryTool,
|
|
@@ -17,14 +27,20 @@ import {
|
|
|
17
27
|
ToolNamespace,
|
|
18
28
|
} from "../../core/ToolExposure.ts";
|
|
19
29
|
import { type RuntimeBinding } from "../../engine/AgentRuntime.ts";
|
|
30
|
+
import { getToolExecutionClass } from "../../engine/DurableStep.ts";
|
|
20
31
|
import {
|
|
21
32
|
BackgroundReporting,
|
|
22
33
|
WorkerReportPreparationFailure,
|
|
23
34
|
type WorkerReporting,
|
|
24
35
|
} from "../../engine/SubagentHost.ts";
|
|
25
|
-
import { digestDefinitions, DigestError } from "../Digest.ts";
|
|
36
|
+
import { digestDefinitions, digestJson, DigestError } from "../Digest.ts";
|
|
26
37
|
import type { DurableWorkerFailure, DurableWorkerRequirements } from "../DurableAgentRuntime.ts";
|
|
27
|
-
import
|
|
38
|
+
import {
|
|
39
|
+
DefinitionDigestInput,
|
|
40
|
+
DefinitionDigests,
|
|
41
|
+
ReplayContract,
|
|
42
|
+
PersistedJson,
|
|
43
|
+
} from "../Records.ts";
|
|
28
44
|
import type { Claim, Settlement } from "../SubmissionLedger.ts";
|
|
29
45
|
|
|
30
46
|
/**
|
|
@@ -41,10 +57,9 @@ export class BindingUnavailable extends Schema.TaggedError<BindingUnavailable>()
|
|
|
41
57
|
) {}
|
|
42
58
|
|
|
43
59
|
/**
|
|
44
|
-
* A Binding is registered for the identity but
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* a typed compatibility failure, never a silent substitution.
|
|
60
|
+
* A Binding is registered for the identity but neither the exact admission fingerprints
|
|
61
|
+
* nor its declared replay contract support the retained pending operations. Refusal leaves
|
|
62
|
+
* the original Submission pending until compatible code is available.
|
|
48
63
|
*/
|
|
49
64
|
export class BindingDigestMismatch extends Schema.TaggedError<BindingDigestMismatch>()(
|
|
50
65
|
"BindingDigestMismatch",
|
|
@@ -62,7 +77,370 @@ export const definitionDigestsEqual = (
|
|
|
62
77
|
left: DefinitionDigests,
|
|
63
78
|
right: DefinitionDigests,
|
|
64
79
|
): boolean =>
|
|
65
|
-
left.agent === right.agent &&
|
|
80
|
+
left.agent === right.agent &&
|
|
81
|
+
left.model === right.model &&
|
|
82
|
+
left.tools === right.tools &&
|
|
83
|
+
Schema.toEquivalence(Schema.UndefinedOr(ReplayContract))(left.replay, right.replay);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Host-owned semantic versions, independent of deployment and tool inventory. Change a version
|
|
87
|
+
* when the meaning of an operation, its durable Step names or external idempotency keys changes.
|
|
88
|
+
* Codecs and execution class are included automatically; JSON Schema alone cannot prove semantics.
|
|
89
|
+
*/
|
|
90
|
+
export interface ReplayVersions {
|
|
91
|
+
readonly agent: PersistedJson;
|
|
92
|
+
readonly tools: Readonly<Record<string, PersistedJson>>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Data exported from a definition, never an old executable Binding. A host may supply an audited
|
|
97
|
+
* manifest from the original release for work admitted before replay contracts were retained.
|
|
98
|
+
* Its original declarations must hash to that admission; a bare old digest is not a manifest.
|
|
99
|
+
*/
|
|
100
|
+
const ToolReplayDeclaration = Schema.Struct({
|
|
101
|
+
version: PersistedJson,
|
|
102
|
+
parameters: PersistedJson,
|
|
103
|
+
success: PersistedJson,
|
|
104
|
+
failure: PersistedJson,
|
|
105
|
+
failureMode: Schema.String,
|
|
106
|
+
needsApproval: Schema.Union([Schema.Boolean, Schema.Literal("dynamic")]),
|
|
107
|
+
executionClass: Schema.String,
|
|
108
|
+
executionKind: Schema.String,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const AgentReplayDeclaration = Schema.Struct({
|
|
112
|
+
version: PersistedJson,
|
|
113
|
+
id: AgentId,
|
|
114
|
+
input: PersistedJson,
|
|
115
|
+
output: PersistedJson,
|
|
116
|
+
updates: Schema.NullOr(PersistedJson),
|
|
117
|
+
completion: Schema.NullOr(Schema.Struct({ tool: Schema.String, required: Schema.Boolean })),
|
|
118
|
+
completionFromTools: Schema.Array(Schema.String),
|
|
119
|
+
disposition: Schema.NullOr(PersistedJson),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export class BindingManifest extends Schema.Class<BindingManifest>("BindingManifest")({
|
|
123
|
+
agentId: AgentId,
|
|
124
|
+
definitions: DefinitionDigestInput,
|
|
125
|
+
replay: ReplayContract,
|
|
126
|
+
agentContract: Schema.optionalKey(AgentReplayDeclaration),
|
|
127
|
+
toolContracts: Schema.optionalKey(Schema.Record(Schema.String, ToolReplayDeclaration)),
|
|
128
|
+
}) {}
|
|
129
|
+
|
|
130
|
+
export const makeBindingManifest = Effect.fn("AgentRegistration.makeBindingManifest")(function* (
|
|
131
|
+
definition: Agent.AnyDefinition,
|
|
132
|
+
definitions: DefinitionDigestInput,
|
|
133
|
+
versions: ReplayVersions,
|
|
134
|
+
) {
|
|
135
|
+
const jsonSchema = (schema: Schema.Top) =>
|
|
136
|
+
Schema.decodeUnknownSync(Schema.Json)(Tool.getJsonSchemaFromSchema(schema));
|
|
137
|
+
|
|
138
|
+
const contracts = yield* Effect.try({
|
|
139
|
+
try: () => {
|
|
140
|
+
const tools = Object.values(definition.toolkit.tools);
|
|
141
|
+
|
|
142
|
+
if (tools.some((tool) => !Object.hasOwn(versions.tools, tool.name)))
|
|
143
|
+
throw new Error("Every Tool needs an explicit replay semantic version");
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
agent: {
|
|
147
|
+
version: versions.agent,
|
|
148
|
+
id: definition.id,
|
|
149
|
+
input: jsonSchema(definition.input),
|
|
150
|
+
output: jsonSchema(definition.output),
|
|
151
|
+
updates: definition.updates === undefined ? null : jsonSchema(definition.updates),
|
|
152
|
+
completion:
|
|
153
|
+
definition.completion === undefined
|
|
154
|
+
? null
|
|
155
|
+
: {
|
|
156
|
+
tool: definition.completion.tool,
|
|
157
|
+
required: definition.completion.required ?? false,
|
|
158
|
+
},
|
|
159
|
+
completionFromTools:
|
|
160
|
+
definition.completionFromTools?.map((entry) => entry.tool).sort() ?? [],
|
|
161
|
+
disposition:
|
|
162
|
+
definition.runDisposition === undefined
|
|
163
|
+
? null
|
|
164
|
+
: jsonSchema(definition.runDisposition.schema),
|
|
165
|
+
},
|
|
166
|
+
tools: tools.map((tool) => ({
|
|
167
|
+
name: tool.name,
|
|
168
|
+
contract: {
|
|
169
|
+
version: versions.tools[tool.name]!,
|
|
170
|
+
parameters: jsonSchema(tool.parametersSchema),
|
|
171
|
+
success: jsonSchema(tool.successSchema),
|
|
172
|
+
failure: jsonSchema(tool.failureSchema),
|
|
173
|
+
failureMode: tool.failureMode,
|
|
174
|
+
needsApproval:
|
|
175
|
+
typeof tool.needsApproval === "function" ? "dynamic" : (tool.needsApproval ?? false),
|
|
176
|
+
executionClass: getToolExecutionClass(tool),
|
|
177
|
+
executionKind: getToolExecutionKind(tool.annotations),
|
|
178
|
+
},
|
|
179
|
+
})),
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
catch: () =>
|
|
183
|
+
DigestError.make({
|
|
184
|
+
message:
|
|
185
|
+
"Replay contracts require serializable codecs and an explicit semantic version for every Tool",
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const agent = yield* digestJson(contracts.agent);
|
|
190
|
+
const agentBehavior = yield* digestJson({ ...contracts.agent, input: null });
|
|
191
|
+
|
|
192
|
+
const tools = yield* Effect.forEach(contracts.tools, ({ name, contract }) =>
|
|
193
|
+
digestJson(contract).pipe(Effect.map((digest) => [name, digest] as const)),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return BindingManifest.make({
|
|
197
|
+
agentId: definition.id,
|
|
198
|
+
definitions,
|
|
199
|
+
replay: ReplayContract.make({ agent, agentBehavior, tools: Object.fromEntries(tools) }),
|
|
200
|
+
agentContract: contracts.agent,
|
|
201
|
+
toolContracts: Object.fromEntries(
|
|
202
|
+
contracts.tools.map(({ name, contract }) => [name, contract]),
|
|
203
|
+
),
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const jsonEqual = Schema.toEquivalence(PersistedJson);
|
|
208
|
+
|
|
209
|
+
// JSON Schema generation can widen object openness without changing an Effect codec.
|
|
210
|
+
// Prove only this widening; all other codec changes still require an exact contract.
|
|
211
|
+
const schemasCompatible = (previous: PersistedJson, current: PersistedJson): boolean => {
|
|
212
|
+
if (jsonEqual(previous, current)) return true;
|
|
213
|
+
|
|
214
|
+
const hasNegativeSchema = (value: PersistedJson): boolean => {
|
|
215
|
+
if (Array.isArray<PersistedJson>(value)) return value.some(hasNegativeSchema);
|
|
216
|
+
if (value === null || typeof value !== "object") return false;
|
|
217
|
+
|
|
218
|
+
return Object.entries(value).some(
|
|
219
|
+
([key, child]) =>
|
|
220
|
+
key === "oneOf" ||
|
|
221
|
+
key === "maxContains" ||
|
|
222
|
+
key === "unevaluatedProperties" ||
|
|
223
|
+
key === "unevaluatedItems" ||
|
|
224
|
+
key === "if" ||
|
|
225
|
+
key === "then" ||
|
|
226
|
+
key === "else" ||
|
|
227
|
+
key === "$dynamicRef" ||
|
|
228
|
+
key === "$recursiveRef" ||
|
|
229
|
+
(key === "not" && !jsonEqual(child, {})) ||
|
|
230
|
+
hasNegativeSchema(child),
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (hasNegativeSchema(previous) || hasNegativeSchema(current)) return false;
|
|
235
|
+
|
|
236
|
+
const compare = (before: PersistedJson, after: PersistedJson): boolean => {
|
|
237
|
+
if (jsonEqual(before, after)) return true;
|
|
238
|
+
if (
|
|
239
|
+
before === null ||
|
|
240
|
+
typeof before !== "object" ||
|
|
241
|
+
Array.isArray<PersistedJson>(before) ||
|
|
242
|
+
after === null ||
|
|
243
|
+
typeof after !== "object" ||
|
|
244
|
+
Array.isArray<PersistedJson>(after)
|
|
245
|
+
)
|
|
246
|
+
return false;
|
|
247
|
+
if (Object.keys(before).length !== Object.keys(after).length) return false;
|
|
248
|
+
|
|
249
|
+
return Object.entries(before).every(([key, value]) => {
|
|
250
|
+
if (!Object.hasOwn(after, key)) return false;
|
|
251
|
+
const next = after[key]!;
|
|
252
|
+
|
|
253
|
+
if (jsonEqual(value, next)) return true;
|
|
254
|
+
if (key === "additionalProperties") return value === false && next === true;
|
|
255
|
+
if (key === "items") return compare(value, next);
|
|
256
|
+
if (
|
|
257
|
+
(key === "anyOf" || key === "allOf") &&
|
|
258
|
+
Array.isArray<PersistedJson>(value) &&
|
|
259
|
+
Array.isArray<PersistedJson>(next)
|
|
260
|
+
)
|
|
261
|
+
return (
|
|
262
|
+
value.length === next.length &&
|
|
263
|
+
value.every((entry, index) => compare(entry, next[index]!))
|
|
264
|
+
);
|
|
265
|
+
if (
|
|
266
|
+
(key === "properties" || key === "$defs" || key === "definitions") &&
|
|
267
|
+
value !== null &&
|
|
268
|
+
typeof value === "object" &&
|
|
269
|
+
!Array.isArray<PersistedJson>(value) &&
|
|
270
|
+
next !== null &&
|
|
271
|
+
typeof next === "object" &&
|
|
272
|
+
!Array.isArray<PersistedJson>(next)
|
|
273
|
+
)
|
|
274
|
+
return (
|
|
275
|
+
Object.keys(value).length === Object.keys(next).length &&
|
|
276
|
+
Object.entries(value).every(
|
|
277
|
+
([name, schema]) => Object.hasOwn(next, name) && compare(schema, next[name]!),
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
return false;
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return compare(previous, current);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/** Compile audited declaration data once, without acquiring an executable or rewriting receipts.
|
|
289
|
+
* Full input schemas stay in registration metadata, never in each admission's replay contract. */
|
|
290
|
+
export const compileBindingManifest = Effect.fn("AgentRegistration.compileBindingManifest")(
|
|
291
|
+
function* (
|
|
292
|
+
definition: Agent.AnyDefinition,
|
|
293
|
+
definitions: DefinitionDigestInput,
|
|
294
|
+
versions: ReplayVersions,
|
|
295
|
+
retained: ReadonlyArray<BindingManifest> = [],
|
|
296
|
+
): Effect.fn.Return<
|
|
297
|
+
Pick<ResolvedBinding, "digests" | "manifest" | "retainedManifests" | "acceptsInput">,
|
|
298
|
+
DigestError,
|
|
299
|
+
Crypto.Crypto
|
|
300
|
+
> {
|
|
301
|
+
const manifest = yield* makeBindingManifest(definition, definitions, versions);
|
|
302
|
+
|
|
303
|
+
const validContract = Effect.fnUntraced(function* (entry: BindingManifest) {
|
|
304
|
+
if (entry.agentContract === undefined) return undefined;
|
|
305
|
+
if (
|
|
306
|
+
entry.agentContract.id !== entry.agentId ||
|
|
307
|
+
(yield* digestJson(entry.agentContract)) !== entry.replay.agent ||
|
|
308
|
+
(entry.replay.agentBehavior !== undefined &&
|
|
309
|
+
(yield* digestJson({ ...entry.agentContract, input: null })) !==
|
|
310
|
+
entry.replay.agentBehavior)
|
|
311
|
+
)
|
|
312
|
+
return yield* DigestError.make({
|
|
313
|
+
message: "A manifest's Agent contract does not match its replay digest",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return entry.agentContract;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const current = manifest.agentContract!;
|
|
320
|
+
const acceptsInput = Schema.is(Schema.toEncoded(definition.input));
|
|
321
|
+
|
|
322
|
+
const validateTools = Effect.fnUntraced(function* (entry: BindingManifest) {
|
|
323
|
+
for (const [name, contract] of Object.entries(entry.toolContracts ?? {}))
|
|
324
|
+
if ((yield* digestJson(contract)) !== entry.replay.tools[name])
|
|
325
|
+
return yield* DigestError.make({
|
|
326
|
+
message: "A manifest's Tool contract does not match its replay digest",
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const digests = yield* digestDefinitions(manifest.definitions);
|
|
331
|
+
|
|
332
|
+
const retainedManifests = yield* Effect.forEach(
|
|
333
|
+
retained,
|
|
334
|
+
Effect.fnUntraced(function* (entry) {
|
|
335
|
+
if (entry.agentId !== manifest.agentId)
|
|
336
|
+
return yield* DigestError.make({
|
|
337
|
+
message: "A retained manifest belongs to another Agent",
|
|
338
|
+
});
|
|
339
|
+
const previous = yield* validContract(entry);
|
|
340
|
+
|
|
341
|
+
yield* validateTools(entry);
|
|
342
|
+
|
|
343
|
+
const compatible =
|
|
344
|
+
previous !== undefined &&
|
|
345
|
+
jsonEqual(
|
|
346
|
+
{ ...previous, input: null, output: null, updates: null, disposition: null },
|
|
347
|
+
{ ...current, input: null, output: null, updates: null, disposition: null },
|
|
348
|
+
) &&
|
|
349
|
+
schemasCompatible(previous.output, current.output) &&
|
|
350
|
+
schemasCompatible(previous.updates, current.updates) &&
|
|
351
|
+
schemasCompatible(previous.disposition, current.disposition);
|
|
352
|
+
|
|
353
|
+
const compatibleTools: Record<string, string> = {};
|
|
354
|
+
|
|
355
|
+
for (const [name, oldTool] of Object.entries(entry.toolContracts ?? {})) {
|
|
356
|
+
const newTool = manifest.toolContracts?.[name];
|
|
357
|
+
|
|
358
|
+
if (
|
|
359
|
+
newTool !== undefined &&
|
|
360
|
+
jsonEqual(
|
|
361
|
+
{ ...oldTool, parameters: null, success: null, failure: null },
|
|
362
|
+
{ ...newTool, parameters: null, success: null, failure: null },
|
|
363
|
+
) &&
|
|
364
|
+
schemasCompatible(oldTool.parameters, newTool.parameters) &&
|
|
365
|
+
schemasCompatible(oldTool.success, newTool.success) &&
|
|
366
|
+
schemasCompatible(oldTool.failure, newTool.failure)
|
|
367
|
+
)
|
|
368
|
+
compatibleTools[name] = manifest.replay.tools[name]!;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
digests: yield* digestDefinitions(entry.definitions),
|
|
373
|
+
replay: entry.replay,
|
|
374
|
+
compatibleTools,
|
|
375
|
+
...(compatible ? { compatibleAgent: manifest.replay.agent } : {}),
|
|
376
|
+
};
|
|
377
|
+
}),
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
digests: DefinitionDigests.make({ ...digests, replay: manifest.replay }),
|
|
382
|
+
manifest,
|
|
383
|
+
retainedManifests,
|
|
384
|
+
acceptsInput: (input: PersistedJson) => {
|
|
385
|
+
// Async checks and defective guards cannot establish synchronous replay proof.
|
|
386
|
+
try {
|
|
387
|
+
return acceptsInput(input);
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const replaySupports = (
|
|
397
|
+
current: ReplayContract,
|
|
398
|
+
retained: ReplayContract,
|
|
399
|
+
requiredTools?: ReadonlyArray<string>,
|
|
400
|
+
inputCompatible = false,
|
|
401
|
+
compatibleTools: Readonly<Record<string, string>> = {},
|
|
402
|
+
): boolean =>
|
|
403
|
+
(current.agent === retained.agent || inputCompatible) &&
|
|
404
|
+
(requiredTools ?? Object.keys(retained.tools)).every(
|
|
405
|
+
(name) =>
|
|
406
|
+
retained.tools[name] !== undefined &&
|
|
407
|
+
(current.tools[name] === retained.tools[name] ||
|
|
408
|
+
(current.tools[name] !== undefined && compatibleTools[name] === current.tools[name])),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
/** Resolve executable code only. Admission, delivery, lineage and receipt comparisons stay exact. */
|
|
412
|
+
export const bindingSupports = (
|
|
413
|
+
binding: Pick<ResolvedBinding, "digests" | "retainedManifests" | "acceptsInput">,
|
|
414
|
+
digests: DefinitionDigests,
|
|
415
|
+
requiredTools?: ReadonlyArray<string>,
|
|
416
|
+
input?: PersistedJson,
|
|
417
|
+
): boolean => {
|
|
418
|
+
if (definitionDigestsEqual(binding.digests, digests)) return true;
|
|
419
|
+
|
|
420
|
+
const manifest = binding.retainedManifests?.find(
|
|
421
|
+
(entry) =>
|
|
422
|
+
definitionDigestsEqual(entry.digests, digests) ||
|
|
423
|
+
definitionDigestsEqual({ ...entry.digests, replay: entry.replay }, digests),
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
const retained = digests.replay ?? manifest?.replay;
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
binding.digests.replay !== undefined &&
|
|
430
|
+
retained !== undefined &&
|
|
431
|
+
replaySupports(
|
|
432
|
+
binding.digests.replay,
|
|
433
|
+
retained,
|
|
434
|
+
requiredTools,
|
|
435
|
+
input !== undefined &&
|
|
436
|
+
binding.acceptsInput?.(input) === true &&
|
|
437
|
+
((binding.digests.replay.agentBehavior !== undefined &&
|
|
438
|
+
binding.digests.replay.agentBehavior === retained.agentBehavior) ||
|
|
439
|
+
manifest?.compatibleAgent === binding.digests.replay.agent),
|
|
440
|
+
manifest?.compatibleTools,
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
};
|
|
66
444
|
|
|
67
445
|
type ExecutableDefinition = Agent.AnyDefinition & {
|
|
68
446
|
readonly instructions: InstructionSource<never, unknown, unknown>;
|
|
@@ -194,6 +572,16 @@ const backgroundReports = (definition: Agent.AnyDefinition) => [
|
|
|
194
572
|
/** One exact executable registration used by durable claim-time resolution. */
|
|
195
573
|
export interface ResolvedBinding extends CapturedBinding {
|
|
196
574
|
readonly digests: DefinitionDigests;
|
|
575
|
+
readonly manifest?: BindingManifest;
|
|
576
|
+
/** Current encoded input guard, acquired without executable services. */
|
|
577
|
+
readonly acceptsInput?: (input: PersistedJson) => boolean;
|
|
578
|
+
readonly retainedManifests?: ReadonlyArray<{
|
|
579
|
+
readonly digests: DefinitionDigests;
|
|
580
|
+
readonly replay: ReplayContract;
|
|
581
|
+
readonly compatibleTools?: Readonly<Record<string, string>>;
|
|
582
|
+
/** Original declarations prove unchanged semantics and output/completion contracts. */
|
|
583
|
+
readonly compatibleAgent?: string;
|
|
584
|
+
}>;
|
|
197
585
|
}
|
|
198
586
|
|
|
199
587
|
/** Trusted claim identity for resolving per-Attempt application services. Not model input. */
|
|
@@ -291,6 +679,8 @@ export const resolveWorkerBinding = (
|
|
|
291
679
|
bindings: ReadonlyArray<ResolvedBinding>,
|
|
292
680
|
agentId: AgentId,
|
|
293
681
|
digests: DefinitionDigests,
|
|
682
|
+
requiredTools?: ReadonlyArray<string>,
|
|
683
|
+
input?: PersistedJson,
|
|
294
684
|
): Effect.Effect<ResolvedBinding, DurableBindingFailure> => {
|
|
295
685
|
const registered = bindings.filter((binding) => binding.agentId === agentId);
|
|
296
686
|
|
|
@@ -304,16 +694,22 @@ export const resolveWorkerBinding = (
|
|
|
304
694
|
}
|
|
305
695
|
const exact = registered.find((binding) => definitionDigestsEqual(binding.digests, digests));
|
|
306
696
|
|
|
307
|
-
|
|
697
|
+
const compatible =
|
|
698
|
+
exact ??
|
|
699
|
+
(registered.length === 1 && bindingSupports(registered[0]!, digests, requiredTools, input)
|
|
700
|
+
? registered[0]
|
|
701
|
+
: undefined);
|
|
702
|
+
|
|
703
|
+
if (compatible === undefined) {
|
|
308
704
|
return Effect.fail(
|
|
309
705
|
BindingDigestMismatch.make({
|
|
310
706
|
agentId,
|
|
311
|
-
message: `
|
|
707
|
+
message: `No registered Binding proves the retained replay contracts for ${agentId}; the original request and receipts remain pending`,
|
|
312
708
|
}),
|
|
313
709
|
);
|
|
314
710
|
}
|
|
315
711
|
|
|
316
|
-
return Effect.succeed(
|
|
712
|
+
return Effect.succeed(compatible);
|
|
317
713
|
};
|
|
318
714
|
|
|
319
715
|
/** Resolve authoring-time admission through the one exact host registration, never by identity alone. */
|
|
@@ -354,6 +750,11 @@ export type AgentRegistration<A extends ExecutableAgentBinding = ExecutableAgent
|
|
|
354
750
|
readonly definitions: DefinitionDigestInput;
|
|
355
751
|
}
|
|
356
752
|
) & {
|
|
753
|
+
/** Opt into contract-checked deployment continuity. Omit to require an exact registration. */
|
|
754
|
+
readonly continuity?: {
|
|
755
|
+
readonly versions: ReplayVersions;
|
|
756
|
+
readonly retainedManifests?: ReadonlyArray<BindingManifest>;
|
|
757
|
+
};
|
|
357
758
|
/**
|
|
358
759
|
* Source-owned reports capture services outside attemptLayer and open a fresh Scope per
|
|
359
760
|
* preparation. Version changes with this registration. Missing exact code and preparation
|
|
@@ -483,8 +884,8 @@ const compileRegistration = <Entry extends AgentRegistration>(
|
|
|
483
884
|
try: () => registrationDefinitions(entry),
|
|
484
885
|
catch: () =>
|
|
485
886
|
DigestError.make({ message: "Agent update schema has no serializable wire contract" }),
|
|
486
|
-
})
|
|
487
|
-
(
|
|
887
|
+
}),
|
|
888
|
+
(definitions) =>
|
|
488
889
|
Effect.gen(function* () {
|
|
489
890
|
const binding = yield* capture(
|
|
490
891
|
entry.model === undefined ? entry.agent : { definition: entry.agent, model: entry.model },
|
|
@@ -496,7 +897,19 @@ const compileRegistration = <Entry extends AgentRegistration>(
|
|
|
496
897
|
...backgroundReports(binding.definition),
|
|
497
898
|
]);
|
|
498
899
|
|
|
499
|
-
|
|
900
|
+
if (entry.continuity === undefined)
|
|
901
|
+
return { ...binding, digests: yield* digestDefinitions(definitions), reporting };
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
...binding,
|
|
905
|
+
...(yield* compileBindingManifest(
|
|
906
|
+
binding.definition,
|
|
907
|
+
definitions,
|
|
908
|
+
entry.continuity.versions,
|
|
909
|
+
entry.continuity.retainedManifests,
|
|
910
|
+
)),
|
|
911
|
+
reporting,
|
|
912
|
+
};
|
|
500
913
|
}),
|
|
501
914
|
// The erased descriptor accepts arbitrary provided services. Restore the concrete entry's
|
|
502
915
|
// Exclude<worker requirements, provided services> | layer requirements at this collection seam.
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
} from "../ThreadStore.ts";
|
|
53
53
|
import {
|
|
54
54
|
definitionDigestsEqual,
|
|
55
|
+
bindingSupports,
|
|
55
56
|
resolveDefinitionBinding,
|
|
56
57
|
type ResolvedBinding,
|
|
57
58
|
} from "./agent-registration.ts";
|
|
@@ -102,11 +103,7 @@ export const makeMessagingRuntime = Effect.fn("MessagingHost.make")(function* (
|
|
|
102
103
|
|
|
103
104
|
if (created?._tag !== "ThreadCreated") return yield* failure("context", "not-found");
|
|
104
105
|
|
|
105
|
-
const matching = deps.bindings.filter(
|
|
106
|
-
(binding) =>
|
|
107
|
-
binding.agentId === created.agentId &&
|
|
108
|
-
definitionDigestsEqual(binding.digests, created.definitions),
|
|
109
|
-
);
|
|
106
|
+
const matching = deps.bindings.filter((binding) => binding.agentId === created.agentId);
|
|
110
107
|
|
|
111
108
|
if (matching.length !== 1) return yield* failure("context", "binding-mismatch");
|
|
112
109
|
|
|
@@ -310,7 +307,7 @@ export const makeMessagingRuntime = Effect.fn("MessagingHost.make")(function* (
|
|
|
310
307
|
!Schema.is(MessageAdmission)(metadata) ||
|
|
311
308
|
metadata.peerName !== request.name ||
|
|
312
309
|
saved.envelope.agentId !== target.agentId ||
|
|
313
|
-
!
|
|
310
|
+
!bindingSupports(target, saved.envelope.definitions, undefined, saved.envelope.input) ||
|
|
314
311
|
!sameJson(saved.envelope.input, input) ||
|
|
315
312
|
(inReplyTo === undefined || metadata.inReplyTo === undefined
|
|
316
313
|
? inReplyTo !== metadata.inReplyTo
|