@openpond/evals 0.12.3 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LEARNING.md +22 -0
- package/dist/tool-learning/actions.js +62 -0
- package/dist/tool-learning/consultation.js +198 -0
- package/dist/tool-learning/episodes.js +284 -0
- package/dist/tool-learning/index.js +6 -0
- package/dist/tool-learning/installations.js +151 -0
- package/dist/tool-learning/policy.js +38 -0
- package/dist/tool-learning/primitives.js +87 -0
- package/dist/types/tool-learning/actions.d.ts +54 -0
- package/dist/types/tool-learning/actions.d.ts.map +1 -0
- package/dist/types/tool-learning/consultation.d.ts +229 -0
- package/dist/types/tool-learning/consultation.d.ts.map +1 -0
- package/dist/types/tool-learning/episodes.d.ts +1058 -0
- package/dist/types/tool-learning/episodes.d.ts.map +1 -0
- package/dist/types/tool-learning/index.d.ts +7 -0
- package/dist/types/tool-learning/index.d.ts.map +1 -0
- package/dist/types/tool-learning/installations.d.ts +296 -0
- package/dist/types/tool-learning/installations.d.ts.map +1 -0
- package/dist/types/tool-learning/policy.d.ts +3 -0
- package/dist/types/tool-learning/policy.d.ts.map +1 -0
- package/dist/types/tool-learning/primitives.d.ts +73 -0
- package/dist/types/tool-learning/primitives.d.ts.map +1 -0
- package/package.json +5 -1
package/LEARNING.md
CHANGED
|
@@ -394,3 +394,25 @@ revision changes and budget reservations, resolve authoritative execution/gradin
|
|
|
394
394
|
receipts, isolate holdouts from training, bind the exact candidate at runtime,
|
|
395
395
|
verify cleanup, and enforce the verdict inside the candidate-decision transaction.
|
|
396
396
|
No customer serving is activated by an acceptance group.
|
|
397
|
+
|
|
398
|
+
### Bounded tool episodes
|
|
399
|
+
|
|
400
|
+
The explicit `@openpond/evals/tool-learning` entry describes bounded attempt,
|
|
401
|
+
observation, recovery-link, verification, installation, and recovery-consultation
|
|
402
|
+
contracts. An event is sealed over validated sanitized bytes; the observation
|
|
403
|
+
references the exact attempted action. Execution failure, validation failure,
|
|
404
|
+
incomplete observation, uncertain effects, and verified outcome stay distinct.
|
|
405
|
+
A producer-provided verifier reference is a claim that intake must resolve against
|
|
406
|
+
its authentic execution owner.
|
|
407
|
+
|
|
408
|
+
Collection acknowledgements explicitly have `learningEnrollment: false`. The
|
|
409
|
+
capture policy requires separate review for model updates and disables synthetic
|
|
410
|
+
variants. Retaining a successful command is not evidence of the requested outcome,
|
|
411
|
+
a qualified training target, or permission to spend. Reuse the existing task
|
|
412
|
+
review, immutable supervised batch, and publication owners for actual admission.
|
|
413
|
+
|
|
414
|
+
Recovery consultation binds its action/context, active manifest, selected model,
|
|
415
|
+
and usage receipt. Integration qualification advertises an existing-model route;
|
|
416
|
+
learned qualification additionally requires retained training/evaluation/activation
|
|
417
|
+
lineage. Neither installation nor receipt of evidence establishes learned efficacy.
|
|
418
|
+
These protocols do not install client hooks or start background services.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TOOL_LEARNING_LIMITS, ToolLearningHashSchema, boundedToolLearningSchema, toolLearningText, } from "./primitives.js";
|
|
3
|
+
const Text = toolLearningText(TOOL_LEARNING_LIMITS.actionBytes);
|
|
4
|
+
const RelativePath = z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.max(1_000)
|
|
8
|
+
.refine((value) => !value.startsWith("/") &&
|
|
9
|
+
!value.includes("\\") &&
|
|
10
|
+
!/^[A-Za-z]:/u.test(value) &&
|
|
11
|
+
value.split("/").every((part) => part !== "" && part !== "." && part !== ".."), "Use a workspace-relative artifact path.");
|
|
12
|
+
export const ToolLearningActionSchema = boundedToolLearningSchema(z.discriminatedUnion("kind", [
|
|
13
|
+
z.object({ kind: z.literal("python"), code: Text }).strict(),
|
|
14
|
+
z
|
|
15
|
+
.object({ kind: z.literal("shell"), command: Text, shell: z.string().min(1).max(120) })
|
|
16
|
+
.strict(),
|
|
17
|
+
z
|
|
18
|
+
.object({
|
|
19
|
+
kind: z.literal("tool_arguments"),
|
|
20
|
+
arguments: z.record(z.string().min(1).max(200), z.json()),
|
|
21
|
+
})
|
|
22
|
+
.strict(),
|
|
23
|
+
z
|
|
24
|
+
.object({
|
|
25
|
+
kind: z.literal("edit"),
|
|
26
|
+
patch: Text,
|
|
27
|
+
baseFiles: z
|
|
28
|
+
.array(z
|
|
29
|
+
.object({ path: RelativePath, contentHash: ToolLearningHashSchema.nullable() })
|
|
30
|
+
.strict())
|
|
31
|
+
.min(1)
|
|
32
|
+
.max(32),
|
|
33
|
+
})
|
|
34
|
+
.strict(),
|
|
35
|
+
]), TOOL_LEARNING_LIMITS.actionBytes);
|
|
36
|
+
export const ToolLearningRepairSchema = z.discriminatedUnion("kind", [
|
|
37
|
+
z
|
|
38
|
+
.object({
|
|
39
|
+
kind: z.literal("replacement"),
|
|
40
|
+
expectedActionHash: ToolLearningHashSchema,
|
|
41
|
+
environmentFingerprint: ToolLearningHashSchema,
|
|
42
|
+
action: ToolLearningActionSchema,
|
|
43
|
+
})
|
|
44
|
+
.strict(),
|
|
45
|
+
z
|
|
46
|
+
.object({
|
|
47
|
+
kind: z.literal("patch"),
|
|
48
|
+
expectedActionHash: ToolLearningHashSchema,
|
|
49
|
+
environmentFingerprint: ToolLearningHashSchema,
|
|
50
|
+
files: z
|
|
51
|
+
.array(z
|
|
52
|
+
.object({
|
|
53
|
+
path: RelativePath,
|
|
54
|
+
expectedContentHash: ToolLearningHashSchema,
|
|
55
|
+
patch: Text,
|
|
56
|
+
})
|
|
57
|
+
.strict())
|
|
58
|
+
.min(1)
|
|
59
|
+
.max(16),
|
|
60
|
+
})
|
|
61
|
+
.strict(),
|
|
62
|
+
]);
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ToolLearningActionSchema } from "./actions.js";
|
|
3
|
+
import { TOOL_LEARNING_LIMITS, ToolLearningEnvironmentSchema, ToolLearningHashSchema as Hash, ToolLearningIdSchema as Id, ToolLearningRefSchema as Ref, ToolLearningScopeSchema, ToolLearningTimestampSchema as Timestamp, boundedToolLearningSchema, toolLearningContentHash, toolLearningText, } from "./primitives.js";
|
|
4
|
+
export const ToolLearningCapabilitySchema = z
|
|
5
|
+
.object({
|
|
6
|
+
id: Id,
|
|
7
|
+
application: z.string().min(1).max(200),
|
|
8
|
+
versions: z.array(z.string().min(1).max(200)).min(1).max(32),
|
|
9
|
+
tools: z.array(z.string().min(1).max(200)).min(1).max(32),
|
|
10
|
+
mode: z.literal("recovery"),
|
|
11
|
+
routingInstruction: toolLearningText(2000),
|
|
12
|
+
qualification: z.discriminatedUnion("kind", [
|
|
13
|
+
z.object({ kind: z.literal("integration"), receipt: Ref }).strict(),
|
|
14
|
+
z
|
|
15
|
+
.object({
|
|
16
|
+
kind: z.literal("learned"),
|
|
17
|
+
receipt: Ref,
|
|
18
|
+
trainingRun: Ref,
|
|
19
|
+
adapter: Ref,
|
|
20
|
+
evaluation: Ref,
|
|
21
|
+
activation: Ref,
|
|
22
|
+
})
|
|
23
|
+
.strict(),
|
|
24
|
+
]),
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
const Manifest = z
|
|
28
|
+
.object({
|
|
29
|
+
schemaVersion: z.literal("openpond.toolLearningManifest.v1"),
|
|
30
|
+
installationId: Id,
|
|
31
|
+
scope: ToolLearningScopeSchema,
|
|
32
|
+
revision: z.number().int().positive(),
|
|
33
|
+
contentHash: Hash,
|
|
34
|
+
issuedAt: Timestamp,
|
|
35
|
+
expiresAt: Timestamp,
|
|
36
|
+
modelRelease: Ref,
|
|
37
|
+
responseSchema: z.literal("openpond.toolLearningDecision.v1"),
|
|
38
|
+
capabilities: z.array(ToolLearningCapabilitySchema).max(TOOL_LEARNING_LIMITS.capabilities),
|
|
39
|
+
})
|
|
40
|
+
.strict()
|
|
41
|
+
.superRefine((manifest, context) => {
|
|
42
|
+
const { contentHash, ...body } = manifest;
|
|
43
|
+
if (toolLearningContentHash(body) !== contentHash)
|
|
44
|
+
context.addIssue({
|
|
45
|
+
code: "custom",
|
|
46
|
+
path: ["contentHash"],
|
|
47
|
+
message: "Manifest content hash mismatch.",
|
|
48
|
+
});
|
|
49
|
+
const lifetime = Date.parse(manifest.expiresAt) - Date.parse(manifest.issuedAt);
|
|
50
|
+
if (lifetime <= 0 || lifetime > TOOL_LEARNING_LIMITS.manifestSeconds * 1000)
|
|
51
|
+
context.addIssue({
|
|
52
|
+
code: "custom",
|
|
53
|
+
path: ["expiresAt"],
|
|
54
|
+
message: "Manifest expiry exceeds the supported lifetime.",
|
|
55
|
+
});
|
|
56
|
+
if (new Set(manifest.capabilities.map((entry) => entry.id)).size !== manifest.capabilities.length)
|
|
57
|
+
context.addIssue({
|
|
58
|
+
code: "custom",
|
|
59
|
+
path: ["capabilities"],
|
|
60
|
+
message: "Capability IDs must be unique.",
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
export const ToolLearningManifestSchema = boundedToolLearningSchema(Manifest, TOOL_LEARNING_LIMITS.consultationBytes);
|
|
64
|
+
export const ToolLearningConsultationRequestSchema = boundedToolLearningSchema(z
|
|
65
|
+
.object({
|
|
66
|
+
schemaVersion: z.literal("openpond.toolLearningConsultation.v1"),
|
|
67
|
+
operationId: Id,
|
|
68
|
+
installationId: Id,
|
|
69
|
+
scope: ToolLearningScopeSchema,
|
|
70
|
+
sessionId: Id,
|
|
71
|
+
callId: Id,
|
|
72
|
+
capabilityId: Id,
|
|
73
|
+
manifestHash: Hash,
|
|
74
|
+
modelRelease: Ref,
|
|
75
|
+
mode: z.literal("recovery"),
|
|
76
|
+
intent: toolLearningText(2000),
|
|
77
|
+
action: ToolLearningActionSchema,
|
|
78
|
+
actionHash: Hash,
|
|
79
|
+
environment: ToolLearningEnvironmentSchema,
|
|
80
|
+
contextHash: Hash,
|
|
81
|
+
failure: z
|
|
82
|
+
.object({
|
|
83
|
+
message: toolLearningText(TOOL_LEARNING_LIMITS.resultBytes),
|
|
84
|
+
observation: Ref,
|
|
85
|
+
})
|
|
86
|
+
.strict(),
|
|
87
|
+
round: z.number().int().min(1).max(TOOL_LEARNING_LIMITS.consultationRounds),
|
|
88
|
+
})
|
|
89
|
+
.strict()
|
|
90
|
+
.superRefine((request, context) => {
|
|
91
|
+
if (toolLearningContentHash(request.action) !== request.actionHash)
|
|
92
|
+
context.addIssue({
|
|
93
|
+
code: "custom",
|
|
94
|
+
path: ["actionHash"],
|
|
95
|
+
message: "Consultation action hash mismatch.",
|
|
96
|
+
});
|
|
97
|
+
if (toolLearningContentHash({
|
|
98
|
+
intent: request.intent,
|
|
99
|
+
environment: request.environment,
|
|
100
|
+
failure: request.failure,
|
|
101
|
+
}) !== request.contextHash)
|
|
102
|
+
context.addIssue({
|
|
103
|
+
code: "custom",
|
|
104
|
+
path: ["contextHash"],
|
|
105
|
+
message: "Consultation context hash mismatch.",
|
|
106
|
+
});
|
|
107
|
+
}), TOOL_LEARNING_LIMITS.consultationBytes);
|
|
108
|
+
/** Only model-authored task content; transport failures never become proceed. */
|
|
109
|
+
export const ToolLearningDecisionSchema = z.discriminatedUnion("decision", [
|
|
110
|
+
z.object({ decision: z.literal("proceed") }).strict(),
|
|
111
|
+
z
|
|
112
|
+
.object({
|
|
113
|
+
decision: z.literal("revise"),
|
|
114
|
+
issues: z
|
|
115
|
+
.array(z
|
|
116
|
+
.object({
|
|
117
|
+
category: z.string().min(1).max(120),
|
|
118
|
+
feedback: toolLearningText(2000),
|
|
119
|
+
})
|
|
120
|
+
.strict())
|
|
121
|
+
.min(1)
|
|
122
|
+
.max(8),
|
|
123
|
+
})
|
|
124
|
+
.strict(),
|
|
125
|
+
z
|
|
126
|
+
.object({
|
|
127
|
+
decision: z.literal("needs_context"),
|
|
128
|
+
missing: z
|
|
129
|
+
.array(z
|
|
130
|
+
.object({
|
|
131
|
+
field: z.string().min(1).max(120),
|
|
132
|
+
reason: toolLearningText(500),
|
|
133
|
+
})
|
|
134
|
+
.strict())
|
|
135
|
+
.min(1)
|
|
136
|
+
.max(8),
|
|
137
|
+
})
|
|
138
|
+
.strict(),
|
|
139
|
+
]);
|
|
140
|
+
const ResponseIdentity = {
|
|
141
|
+
operationId: Id,
|
|
142
|
+
installationId: Id,
|
|
143
|
+
capabilityId: Id,
|
|
144
|
+
manifestHash: Hash,
|
|
145
|
+
modelRelease: Ref,
|
|
146
|
+
actionHash: Hash,
|
|
147
|
+
contextHash: Hash,
|
|
148
|
+
};
|
|
149
|
+
export const ToolLearningConsultationResponseSchema = boundedToolLearningSchema(z.discriminatedUnion("state", [
|
|
150
|
+
z
|
|
151
|
+
.object({
|
|
152
|
+
schemaVersion: z.literal("openpond.toolLearningDecision.v1"),
|
|
153
|
+
...ResponseIdentity,
|
|
154
|
+
state: z.literal("completed"),
|
|
155
|
+
decision: ToolLearningDecisionSchema,
|
|
156
|
+
usageReceipt: Ref,
|
|
157
|
+
completedAt: Timestamp,
|
|
158
|
+
})
|
|
159
|
+
.strict(),
|
|
160
|
+
z
|
|
161
|
+
.object({
|
|
162
|
+
schemaVersion: z.literal("openpond.toolLearningDecision.v1"),
|
|
163
|
+
...ResponseIdentity,
|
|
164
|
+
state: z.literal("unavailable"),
|
|
165
|
+
reason: z.enum([
|
|
166
|
+
"disabled",
|
|
167
|
+
"revoked",
|
|
168
|
+
"expired_manifest",
|
|
169
|
+
"unsupported_scope",
|
|
170
|
+
"budget_exhausted",
|
|
171
|
+
"timeout",
|
|
172
|
+
"invalid_output",
|
|
173
|
+
"cancelled",
|
|
174
|
+
"dispatch_uncertain",
|
|
175
|
+
]),
|
|
176
|
+
usageReceipt: Ref.nullable(),
|
|
177
|
+
})
|
|
178
|
+
.strict(),
|
|
179
|
+
]), TOOL_LEARNING_LIMITS.consultationBytes);
|
|
180
|
+
/** Invoke before exposing a result to the agent; even valid JSON may be stale. */
|
|
181
|
+
export function assertToolLearningConsultationResponse(requestValue, responseValue) {
|
|
182
|
+
const request = ToolLearningConsultationRequestSchema.parse(requestValue);
|
|
183
|
+
const response = ToolLearningConsultationResponseSchema.parse(responseValue);
|
|
184
|
+
for (const key of [
|
|
185
|
+
"operationId",
|
|
186
|
+
"installationId",
|
|
187
|
+
"capabilityId",
|
|
188
|
+
"manifestHash",
|
|
189
|
+
"actionHash",
|
|
190
|
+
"contextHash",
|
|
191
|
+
])
|
|
192
|
+
if (request[key] !== response[key])
|
|
193
|
+
throw new Error(`tool_learning_consultation_${key}_mismatch`);
|
|
194
|
+
if (request.modelRelease.id !== response.modelRelease.id ||
|
|
195
|
+
request.modelRelease.contentHash !== response.modelRelease.contentHash)
|
|
196
|
+
throw new Error("tool_learning_consultation_model_mismatch");
|
|
197
|
+
return response;
|
|
198
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ToolLearningActionSchema } from "./actions.js";
|
|
3
|
+
import { TOOL_LEARNING_LIMITS, ToolLearningAssetSchema, ToolLearningEnvironmentSchema, ToolLearningHashSchema as Hash, ToolLearningIdSchema as Id, ToolLearningRefSchema as Ref, ToolLearningSanitizationSchema, ToolLearningTimestampSchema as Timestamp, boundedToolLearningSchema, toolLearningContentHash, toolLearningText, } from "./primitives.js";
|
|
4
|
+
export const ToolEpisodeAttemptSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
intent: toolLearningText(2_000),
|
|
7
|
+
tool: z
|
|
8
|
+
.object({
|
|
9
|
+
name: z.string().min(1).max(200),
|
|
10
|
+
version: z.string().max(200).nullable(),
|
|
11
|
+
server: z.string().max(200).nullable(),
|
|
12
|
+
})
|
|
13
|
+
.strict(),
|
|
14
|
+
action: ToolLearningActionSchema,
|
|
15
|
+
actionHash: Hash,
|
|
16
|
+
environment: ToolLearningEnvironmentSchema,
|
|
17
|
+
contextAssets: z.array(ToolLearningAssetSchema).max(TOOL_LEARNING_LIMITS.assetsPerEpisode),
|
|
18
|
+
selection: z.discriminatedUnion("kind", [
|
|
19
|
+
z.object({ kind: z.literal("eligible_attempt") }).strict(),
|
|
20
|
+
z
|
|
21
|
+
.object({
|
|
22
|
+
kind: z.literal("unchanged_control"),
|
|
23
|
+
numerator: z.number().int().positive(),
|
|
24
|
+
denominator: z.number().int().positive(),
|
|
25
|
+
policyHash: Hash,
|
|
26
|
+
})
|
|
27
|
+
.strict(),
|
|
28
|
+
]),
|
|
29
|
+
})
|
|
30
|
+
.strict()
|
|
31
|
+
.superRefine((attempt, context) => {
|
|
32
|
+
if (toolLearningContentHash(attempt.action) !== attempt.actionHash)
|
|
33
|
+
context.addIssue({
|
|
34
|
+
code: "custom",
|
|
35
|
+
path: ["actionHash"],
|
|
36
|
+
message: "Action bytes differ from their hash.",
|
|
37
|
+
});
|
|
38
|
+
if (attempt.selection.kind === "unchanged_control" &&
|
|
39
|
+
attempt.selection.numerator > attempt.selection.denominator)
|
|
40
|
+
context.addIssue({
|
|
41
|
+
code: "custom",
|
|
42
|
+
path: ["selection"],
|
|
43
|
+
message: "Sampling probability cannot exceed one.",
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
export const ToolEpisodeFailureKindSchema = z.enum([
|
|
47
|
+
"validation_failure",
|
|
48
|
+
"execution_failure",
|
|
49
|
+
"process_failure",
|
|
50
|
+
"timeout",
|
|
51
|
+
"cancelled",
|
|
52
|
+
"infrastructure_failure",
|
|
53
|
+
"authorization_denied",
|
|
54
|
+
"outcome_failure",
|
|
55
|
+
"unknown_failure",
|
|
56
|
+
]);
|
|
57
|
+
const ObservationBase = {
|
|
58
|
+
attempt: Ref,
|
|
59
|
+
actionHash: Hash,
|
|
60
|
+
execution: z.enum(["not_executed", "executed", "unknown"]),
|
|
61
|
+
sideEffects: z.enum(["none", "possible", "observed", "unknown"]),
|
|
62
|
+
resultExcerpt: toolLearningText(TOOL_LEARNING_LIMITS.resultBytes),
|
|
63
|
+
resultHash: Hash.nullable(),
|
|
64
|
+
assets: z.array(ToolLearningAssetSchema).max(TOOL_LEARNING_LIMITS.assetsPerEpisode),
|
|
65
|
+
};
|
|
66
|
+
export const ToolEpisodeObservationSchema = z
|
|
67
|
+
.discriminatedUnion("outcome", [
|
|
68
|
+
z
|
|
69
|
+
.object({
|
|
70
|
+
...ObservationBase,
|
|
71
|
+
outcome: z.literal("succeeded"),
|
|
72
|
+
exitCode: z.literal(0).nullable(),
|
|
73
|
+
})
|
|
74
|
+
.strict(),
|
|
75
|
+
z
|
|
76
|
+
.object({
|
|
77
|
+
...ObservationBase,
|
|
78
|
+
outcome: z.literal("failed"),
|
|
79
|
+
classification: ToolEpisodeFailureKindSchema,
|
|
80
|
+
errorCode: z.string().min(1).max(200),
|
|
81
|
+
message: toolLearningText(2_000),
|
|
82
|
+
exitCode: z.number().int().min(-255).max(255).nullable(),
|
|
83
|
+
})
|
|
84
|
+
.strict(),
|
|
85
|
+
z
|
|
86
|
+
.object({
|
|
87
|
+
...ObservationBase,
|
|
88
|
+
outcome: z.literal("incomplete"),
|
|
89
|
+
reason: z.enum([
|
|
90
|
+
"completion_not_observed",
|
|
91
|
+
"observer_restarted",
|
|
92
|
+
"transport_interrupted",
|
|
93
|
+
"coverage_gap",
|
|
94
|
+
]),
|
|
95
|
+
})
|
|
96
|
+
.strict(),
|
|
97
|
+
])
|
|
98
|
+
.superRefine((observation, context) => {
|
|
99
|
+
if (observation.execution === "not_executed" && observation.sideEffects !== "none")
|
|
100
|
+
context.addIssue({
|
|
101
|
+
code: "custom",
|
|
102
|
+
path: ["sideEffects"],
|
|
103
|
+
message: "An unexecuted action cannot claim tool effects.",
|
|
104
|
+
});
|
|
105
|
+
if (observation.outcome === "succeeded" && observation.execution !== "executed")
|
|
106
|
+
context.addIssue({
|
|
107
|
+
code: "custom",
|
|
108
|
+
path: ["execution"],
|
|
109
|
+
message: "Successful execution requires an observed execution.",
|
|
110
|
+
});
|
|
111
|
+
if (observation.outcome !== "failed")
|
|
112
|
+
return;
|
|
113
|
+
if (observation.classification === "validation_failure" &&
|
|
114
|
+
observation.execution !== "not_executed")
|
|
115
|
+
context.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
path: ["execution"],
|
|
118
|
+
message: "Pre-execution validation failure must be unexecuted.",
|
|
119
|
+
});
|
|
120
|
+
if (observation.classification === "process_failure" &&
|
|
121
|
+
(observation.execution !== "executed" ||
|
|
122
|
+
observation.exitCode === null ||
|
|
123
|
+
observation.exitCode === 0))
|
|
124
|
+
context.addIssue({
|
|
125
|
+
code: "custom",
|
|
126
|
+
path: ["exitCode"],
|
|
127
|
+
message: "Process failure requires a nonzero observed exit.",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
export const ToolEpisodeRecoveryLinkSchema = z
|
|
131
|
+
.object({
|
|
132
|
+
failedAttempt: Ref,
|
|
133
|
+
failedObservation: Ref,
|
|
134
|
+
replacementAttempt: Ref,
|
|
135
|
+
goalId: Id,
|
|
136
|
+
basis: z.enum(["explicit_relation", "artifact_lineage", "unverified"]),
|
|
137
|
+
attributionEvidence: z.array(Ref).max(16),
|
|
138
|
+
})
|
|
139
|
+
.strict()
|
|
140
|
+
.superRefine((link, context) => {
|
|
141
|
+
if (link.failedAttempt.id === link.replacementAttempt.id)
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: "custom",
|
|
144
|
+
message: "A recovery must reference a different attempt.",
|
|
145
|
+
});
|
|
146
|
+
if (link.basis !== "unverified" && link.attributionEvidence.length === 0)
|
|
147
|
+
context.addIssue({
|
|
148
|
+
code: "custom",
|
|
149
|
+
path: ["attributionEvidence"],
|
|
150
|
+
message: "Recovery attribution needs retained evidence.",
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
/** A producer supplies a claim/reference. Intake must resolve its authentic owner
|
|
154
|
+
* receipt; possession of an append credential never grants verifier authority. */
|
|
155
|
+
export const ToolEpisodeVerificationSchema = z
|
|
156
|
+
.object({
|
|
157
|
+
attempt: Ref,
|
|
158
|
+
actionHash: Hash,
|
|
159
|
+
outcome: z.enum(["passed", "failed", "unknown"]),
|
|
160
|
+
verifierRelease: Ref.nullable(),
|
|
161
|
+
verifierReceipt: Ref.nullable(),
|
|
162
|
+
environmentFingerprint: Hash,
|
|
163
|
+
resetStateHash: Hash.nullable(),
|
|
164
|
+
outputArtifacts: z.array(ToolLearningAssetSchema).max(TOOL_LEARNING_LIMITS.assetsPerEpisode),
|
|
165
|
+
reason: toolLearningText(2_000),
|
|
166
|
+
})
|
|
167
|
+
.strict()
|
|
168
|
+
.superRefine((verification, context) => {
|
|
169
|
+
if (verification.outcome !== "unknown" &&
|
|
170
|
+
(!verification.verifierRelease || !verification.verifierReceipt))
|
|
171
|
+
context.addIssue({
|
|
172
|
+
code: "custom",
|
|
173
|
+
message: "A verification outcome requires an exact verifier and execution receipt.",
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
const Event = z
|
|
177
|
+
.object({
|
|
178
|
+
schemaVersion: z.literal("openpond.toolEpisodeEvent.v1"),
|
|
179
|
+
operationId: Id,
|
|
180
|
+
installationId: Id,
|
|
181
|
+
episodeId: Id,
|
|
182
|
+
eventId: Id,
|
|
183
|
+
sessionId: Id,
|
|
184
|
+
parentSessionId: Id.nullable(),
|
|
185
|
+
turnId: Id.nullable(),
|
|
186
|
+
callId: Id,
|
|
187
|
+
occurredAt: Timestamp,
|
|
188
|
+
sequence: z.number().int().nonnegative(),
|
|
189
|
+
adapterVersion: z.string().min(1).max(200),
|
|
190
|
+
sanitization: ToolLearningSanitizationSchema,
|
|
191
|
+
contentHash: Hash,
|
|
192
|
+
})
|
|
193
|
+
.strict();
|
|
194
|
+
export const ToolEpisodeEventSchema = boundedToolLearningSchema(z
|
|
195
|
+
.discriminatedUnion("eventKind", [
|
|
196
|
+
Event.extend({ eventKind: z.literal("attempt"), payload: ToolEpisodeAttemptSchema }).strict(),
|
|
197
|
+
Event.extend({
|
|
198
|
+
eventKind: z.literal("observation"),
|
|
199
|
+
payload: ToolEpisodeObservationSchema,
|
|
200
|
+
}).strict(),
|
|
201
|
+
Event.extend({
|
|
202
|
+
eventKind: z.literal("recovery_link"),
|
|
203
|
+
payload: ToolEpisodeRecoveryLinkSchema,
|
|
204
|
+
}).strict(),
|
|
205
|
+
Event.extend({
|
|
206
|
+
eventKind: z.literal("verification"),
|
|
207
|
+
payload: ToolEpisodeVerificationSchema,
|
|
208
|
+
}).strict(),
|
|
209
|
+
Event.extend({
|
|
210
|
+
eventKind: z.literal("lineage"),
|
|
211
|
+
payload: z
|
|
212
|
+
.object({
|
|
213
|
+
relation: z.enum(["fork", "split", "reopen"]),
|
|
214
|
+
parentEpisode: Ref,
|
|
215
|
+
parentSessionId: Id,
|
|
216
|
+
reason: toolLearningText(1_000),
|
|
217
|
+
})
|
|
218
|
+
.strict(),
|
|
219
|
+
}).strict(),
|
|
220
|
+
])
|
|
221
|
+
.superRefine((event, context) => {
|
|
222
|
+
const { contentHash, ...body } = event;
|
|
223
|
+
if (toolLearningContentHash(body) !== contentHash)
|
|
224
|
+
context.addIssue({
|
|
225
|
+
code: "custom",
|
|
226
|
+
path: ["contentHash"],
|
|
227
|
+
message: "Episode event content hash mismatch.",
|
|
228
|
+
});
|
|
229
|
+
}), TOOL_LEARNING_LIMITS.eventBytes);
|
|
230
|
+
export function sealToolEpisodeEvent(value) {
|
|
231
|
+
const hash = toolLearningContentHash(value);
|
|
232
|
+
if (!value ||
|
|
233
|
+
typeof value !== "object" ||
|
|
234
|
+
Array.isArray(value) ||
|
|
235
|
+
Object.hasOwn(value, "contentHash"))
|
|
236
|
+
throw new Error("tool_episode_unsigned_envelope_required");
|
|
237
|
+
return ToolEpisodeEventSchema.parse({ ...value, contentHash: hash });
|
|
238
|
+
}
|
|
239
|
+
export const ToolEpisodeAppendSchema = boundedToolLearningSchema(z
|
|
240
|
+
.object({
|
|
241
|
+
schemaVersion: z.literal("openpond.toolEpisodeAppend.v1"),
|
|
242
|
+
operationId: Id,
|
|
243
|
+
installationId: Id,
|
|
244
|
+
events: z.array(ToolEpisodeEventSchema).min(1).max(TOOL_LEARNING_LIMITS.eventsPerAppend),
|
|
245
|
+
})
|
|
246
|
+
.strict()
|
|
247
|
+
.superRefine((append, context) => {
|
|
248
|
+
if (append.events.some((event) => event.installationId !== append.installationId))
|
|
249
|
+
context.addIssue({
|
|
250
|
+
code: "custom",
|
|
251
|
+
path: ["events"],
|
|
252
|
+
message: "Append events must belong to the exact installation.",
|
|
253
|
+
});
|
|
254
|
+
if (new Set(append.events.map((event) => event.eventId)).size !== append.events.length)
|
|
255
|
+
context.addIssue({
|
|
256
|
+
code: "custom",
|
|
257
|
+
path: ["events"],
|
|
258
|
+
message: "Append event IDs must be unique.",
|
|
259
|
+
});
|
|
260
|
+
}), TOOL_LEARNING_LIMITS.eventBytes * TOOL_LEARNING_LIMITS.eventsPerAppend);
|
|
261
|
+
export const ToolEpisodeAppendReceiptSchema = z
|
|
262
|
+
.object({
|
|
263
|
+
schemaVersion: z.literal("openpond.toolEpisodeAppendReceipt.v1"),
|
|
264
|
+
operationId: Id,
|
|
265
|
+
requestHash: Hash,
|
|
266
|
+
installationId: Id,
|
|
267
|
+
teamId: Id,
|
|
268
|
+
projectId: Id,
|
|
269
|
+
receivedAt: Timestamp,
|
|
270
|
+
events: z
|
|
271
|
+
.array(z
|
|
272
|
+
.object({
|
|
273
|
+
eventId: Id,
|
|
274
|
+
contentHash: Hash,
|
|
275
|
+
state: z.enum(["retained", "already_retained", "privacy_rejected"]),
|
|
276
|
+
receiptId: Id,
|
|
277
|
+
reason: z.string().max(200).nullable(),
|
|
278
|
+
})
|
|
279
|
+
.strict())
|
|
280
|
+
.min(1)
|
|
281
|
+
.max(TOOL_LEARNING_LIMITS.eventsPerAppend),
|
|
282
|
+
learningEnrollment: z.literal(false),
|
|
283
|
+
})
|
|
284
|
+
.strict();
|