@openpond/harness 0.1.0

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/CONTRACT.md ADDED
@@ -0,0 +1,31 @@
1
+ # Harness package boundary
2
+
3
+ `@openpond/harness` owns portable, host-neutral Harness identities and state
4
+ transitions. Host applications own persistence, model streaming, provider
5
+ sessions, authorization, credential leases, local processes, and artifact
6
+ bytes.
7
+
8
+ ## Dependency direction
9
+
10
+ `@openpond/evals` may depend on and re-export `@openpond/harness`.
11
+ `@openpond/harness` must never import `@openpond/evals` or an application
12
+ package. This keeps Harness releases usable without installing an evaluation
13
+ runner.
14
+
15
+ ## Compatibility
16
+
17
+ - Package semver and schema-version literals are independent.
18
+ - Immutable content never contains secrets, mutable database identifiers,
19
+ provider handles, UI state, or process handles.
20
+ - Changing a required field or content-hash identity requires a new schema
21
+ literal or an explicit normalizer.
22
+ - The initial support target is Node.js ESM on Node 22.14 through Node 24.
23
+ - Portable paths are at most 2,000 characters and individual portable assets
24
+ are at most 250 MB.
25
+
26
+ ## Runtime ownership
27
+
28
+ The package describes Agent snapshots, releases, workspaces, overlays,
29
+ improvement evidence, tools, model identities, and traces. Evaluation execution
30
+ interfaces that bind a Harness to a Taskset and emit attempt receipts belong to
31
+ `@openpond/evals`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenPond
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # `@openpond/harness`
2
+
3
+ Portable contracts and pure helpers for OpenPond's mutable Harness:
4
+
5
+ - immutable Agent snapshots and Harness releases;
6
+ - content-addressed assets, artifacts, releases, and hashes;
7
+ - tool declarations and model identities;
8
+ - Harness workspaces, pinned run overlays, proposals, validation, advancement,
9
+ rollback, and merge receipts;
10
+ - improvement observations, Refiner outcomes, and apply receipts;
11
+ - model actions, tool observations, lifecycle events, and Harness traces.
12
+
13
+ ```ts
14
+ import {
15
+ HarnessReleaseSchema,
16
+ HarnessRunOverlaySchema,
17
+ ImprovementObservationSchema,
18
+ contentHash,
19
+ } from "@openpond/harness";
20
+ ```
21
+
22
+ Subpath exports are available at `/harness`, `/harness-improvements`,
23
+ `/harness-workspaces`, `/models`, and `/tools`.
24
+
25
+ This package does not run evaluations, grade outputs, persist product state,
26
+ execute a desktop or hosted session, or resolve credentials. Evaluation
27
+ Tasksets, runners, receipts, graders, and Work-evidence eligibility live in
28
+ `@openpond/evals`, which depends on this package.
29
+
30
+ ## Verification
31
+
32
+ ```bash
33
+ pnpm --dir packages/harness run check
34
+ ```
35
+
36
+ The check typechecks, tests, builds, scans the public dependency boundary,
37
+ installs the packed tarball into a clean consumer, verifies runtime and
38
+ TypeScript imports, and dry-run packs the public artifact.
package/RELEASING.md ADDED
@@ -0,0 +1,44 @@
1
+ # Releasing `@openpond/harness`
2
+
3
+ The package version is independent from OpenPond application and schema
4
+ versions. Version `0.1.0` is the initial public Harness release and supports
5
+ these initial schema literals:
6
+
7
+ - `openpond.agentSnapshot.v2`
8
+ - `openpond.harnessRelease.v2`
9
+ - `openpond.harnessTrace.v1`
10
+
11
+ ## Trusted publishing
12
+
13
+ This package name is not published yet. Its first publication is a one-time npm
14
+ bootstrap after the Local branch is accepted and merged. The automated workflow
15
+ intentionally refuses to publish until the npm package exists. Complete the
16
+ organization's bootstrap procedure, configure `release-harness.yml` as
17
+ the trusted publisher, and verify package integrity and provenance before using
18
+ the normal release helper below. Do not bootstrap from a local feature branch.
19
+
20
+ To inspect an already-published version:
21
+
22
+ ```bash
23
+ pnpm harness:check
24
+ npm trust list @openpond/harness
25
+ npm view @openpond/harness version dist.integrity dist.attestations
26
+ ```
27
+
28
+ ## Later releases
29
+
30
+ For releases that do not already carry an intentional version bump, merge
31
+ feature work without changing the package version. When the intended package
32
+ changes are on `master`, prepare a separate release from a clean current
33
+ `master` checkout:
34
+
35
+ ```bash
36
+ pnpm release:harness:patch
37
+ # or release:harness:minor / release:harness:major
38
+ ```
39
+
40
+ The release helper creates a Harness-only release PR and updates the package
41
+ version and lockfile. Merging it triggers trusted publishing with provenance,
42
+ registry verification, and a package-specific tag. Record the registry
43
+ integrity/provenance evidence and merge commit in the release notes after
44
+ publication.
package/dist/common.js ADDED
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ import { sha256Hex } from "./sha256.js";
3
+ export const MAX_PORTABLE_PATH_BYTES = 2_000;
4
+ export const MAX_PORTABLE_ASSET_BYTES = 250_000_000;
5
+ export const ReleaseIdSchema = z.string().trim().min(1).max(240);
6
+ export const ReleaseHashSchema = z.string().regex(/^[a-f0-9]{64}$/);
7
+ export const ReleaseTimestampSchema = z.string().datetime({ offset: true });
8
+ export const MetadataSchema = z.record(z.string(), z.unknown()).default({});
9
+ export const ImmutableReleaseRefSchema = z.object({
10
+ id: ReleaseIdSchema,
11
+ contentHash: ReleaseHashSchema,
12
+ }).strict();
13
+ export const ImmutableAssetRefSchema = z.object({
14
+ id: ReleaseIdSchema,
15
+ path: z.string().trim().min(1).max(MAX_PORTABLE_PATH_BYTES).refine(safeRelativePath),
16
+ contentHash: ReleaseHashSchema,
17
+ sizeBytes: z.number().int().nonnegative().max(MAX_PORTABLE_ASSET_BYTES),
18
+ mediaType: z.string().trim().min(1).max(200),
19
+ visibility: z.enum(["policy", "verifier", "host_private"]),
20
+ }).strict();
21
+ export const ImmutableArtifactRefSchema = z.object({
22
+ id: ReleaseIdSchema,
23
+ contentHash: ReleaseHashSchema,
24
+ mediaType: z.string().trim().min(1).max(200).nullable().default(null),
25
+ sizeBytes: z.number().int().nonnegative().max(MAX_PORTABLE_ASSET_BYTES).nullable().default(null),
26
+ }).strict();
27
+ export const FailureClassSchema = z.enum([
28
+ "policy_failure",
29
+ "grader_failure",
30
+ "environment_failure",
31
+ "infrastructure_failure",
32
+ "timeout",
33
+ "cancelled",
34
+ ]);
35
+ export function canonicalJson(value) {
36
+ return `${JSON.stringify(sortValue(value), null, 2)}\n`;
37
+ }
38
+ export function sha256(value) {
39
+ return sha256Hex(value);
40
+ }
41
+ export function contentHash(value) {
42
+ return sha256(canonicalJson(value));
43
+ }
44
+ export function withContentHash(value) {
45
+ return { ...value, contentHash: contentHash(value) };
46
+ }
47
+ export function assertContentHash(value, label) {
48
+ const { contentHash: actual, ...hashable } = value;
49
+ const expected = contentHash(hashable);
50
+ if (actual !== expected)
51
+ throw new Error(`${label} contentHash is ${actual}; expected ${expected}.`);
52
+ }
53
+ function safeRelativePath(value) {
54
+ const normalized = value.replaceAll("\\", "/");
55
+ if (!normalized || normalized.startsWith("/") || normalized.includes("\0"))
56
+ return false;
57
+ return !normalized.split("/").some((part) => !part || part === "." || part === "..");
58
+ }
59
+ function sortValue(value) {
60
+ if (Array.isArray(value))
61
+ return value.map(sortValue);
62
+ if (!value || typeof value !== "object")
63
+ return value;
64
+ return Object.fromEntries(Object.entries(value)
65
+ .sort(([left], [right]) => left.localeCompare(right))
66
+ .map(([key, child]) => [key, sortValue(child)]));
67
+ }
@@ -0,0 +1,331 @@
1
+ import { z } from "zod";
2
+ import { HarnessImprovementRouteSchema, HarnessOverlaySnapshotRefSchema, } from "./harness-workspaces.js";
3
+ import { contentHash, ImmutableReleaseRefSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, } from "./common.js";
4
+ const BoundedTextSchema = z.string().trim().min(1).max(100_000);
5
+ const MetadataSchema = z.record(z.string(), z.unknown()).default({});
6
+ export const ImprovementSafeBoundaryKindSchema = z.enum([
7
+ "completed_tool_batch",
8
+ "before_model_step",
9
+ "turn_completed",
10
+ "turn_paused",
11
+ ]);
12
+ export const ImprovementSafeBoundarySchema = z
13
+ .object({
14
+ kind: ImprovementSafeBoundaryKindSchema,
15
+ eventSequence: z.number().int().nonnegative(),
16
+ occurredAt: ReleaseTimestampSchema,
17
+ })
18
+ .strict();
19
+ export const ImprovementEventRefSchema = z
20
+ .object({
21
+ id: ReleaseIdSchema,
22
+ sequence: z.number().int().nonnegative().nullable(),
23
+ contentHash: ReleaseHashSchema,
24
+ })
25
+ .strict();
26
+ export const ImprovementObservationKindSchema = z.enum([
27
+ "tool_failure",
28
+ "retry",
29
+ "recovery",
30
+ "validation",
31
+ "user_turn",
32
+ "reusable_success",
33
+ "completion_detour",
34
+ ]);
35
+ export const ImprovementObservationStateSchema = z.enum([
36
+ "open",
37
+ "recovered",
38
+ "terminal",
39
+ ]);
40
+ export const ImprovementToolIdentitySchema = z
41
+ .object({
42
+ name: z.string().trim().min(1).max(500),
43
+ invocationKey: ReleaseHashSchema,
44
+ })
45
+ .strict();
46
+ export const ImprovementObservationContentSchema = z
47
+ .object({
48
+ schemaVersion: z.literal("openpond.improvementObservation.v1"),
49
+ id: ReleaseIdSchema,
50
+ runRef: ReleaseIdSchema,
51
+ turnId: ReleaseIdSchema,
52
+ harnessRelease: ImmutableReleaseRefSchema,
53
+ overlay: HarnessOverlaySnapshotRefSchema.nullable(),
54
+ eventRefs: z.array(ImprovementEventRefSchema).min(1).max(100),
55
+ kind: ImprovementObservationKindSchema,
56
+ state: ImprovementObservationStateSchema,
57
+ tool: ImprovementToolIdentitySchema.nullable(),
58
+ deterministicClass: z.string().trim().min(1).max(500).nullable(),
59
+ summary: BoundedTextSchema,
60
+ createdAt: ReleaseTimestampSchema,
61
+ metadata: MetadataSchema,
62
+ })
63
+ .strict()
64
+ .superRefine((observation, context) => {
65
+ if (new Set(observation.eventRefs.map((reference) => reference.id)).size !==
66
+ observation.eventRefs.length) {
67
+ context.addIssue({
68
+ code: "custom",
69
+ message: "observation event refs must be unique",
70
+ path: ["eventRefs"],
71
+ });
72
+ }
73
+ if (["tool_failure", "retry", "recovery", "completion_detour"].includes(observation.kind) &&
74
+ observation.tool === null) {
75
+ context.addIssue({
76
+ code: "custom",
77
+ message: `${observation.kind} observations require a tool identity`,
78
+ path: ["tool"],
79
+ });
80
+ }
81
+ });
82
+ export const ImprovementObservationSchema = ImprovementObservationContentSchema.extend({
83
+ contentHash: ReleaseHashSchema,
84
+ }).strict();
85
+ export const RefinementTriggerPolicySchema = z
86
+ .object({
87
+ schemaVersion: z.literal("openpond.refinementTriggerPolicy.v1"),
88
+ maxEstimatedCostUsd: z.number().finite().nonnegative(),
89
+ cooldownMs: z.number().int().nonnegative(),
90
+ maxPendingPlans: z.number().int().min(1).max(100),
91
+ maxEvidenceEvents: z.number().int().min(1).max(1_000),
92
+ maxProposalEdits: z.number().int().min(1).max(1_000),
93
+ maxProposalBytes: z.number().int().min(1).max(10_000_000),
94
+ })
95
+ .strict();
96
+ export const RefinementTriggerDecisionKindSchema = z.enum([
97
+ "no_action",
98
+ "route_deterministically",
99
+ "queue_refiner",
100
+ ]);
101
+ export const RefinementTriggerDecisionContentSchema = z
102
+ .object({
103
+ schemaVersion: z.literal("openpond.refinementTriggerDecision.v1"),
104
+ id: ReleaseIdSchema,
105
+ runRef: ReleaseIdSchema,
106
+ turnId: ReleaseIdSchema,
107
+ harnessRelease: ImmutableReleaseRefSchema,
108
+ overlay: HarnessOverlaySnapshotRefSchema.nullable(),
109
+ observations: z.array(ImmutableReleaseRefSchema).max(100),
110
+ decision: RefinementTriggerDecisionKindSchema,
111
+ deterministicRoute: HarnessImprovementRouteSchema.nullable(),
112
+ suggestedRoutes: z.array(HarnessImprovementRouteSchema).max(8),
113
+ reason: BoundedTextSchema,
114
+ deduplicationKey: ReleaseHashSchema,
115
+ policy: RefinementTriggerPolicySchema,
116
+ estimatedMaxCostUsd: z.number().finite().nonnegative(),
117
+ pendingPlanCount: z.number().int().nonnegative(),
118
+ boundary: ImprovementSafeBoundarySchema,
119
+ cooldownUntil: ReleaseTimestampSchema.nullable(),
120
+ createdAt: ReleaseTimestampSchema,
121
+ metadata: MetadataSchema,
122
+ })
123
+ .strict()
124
+ .superRefine((trigger, context) => {
125
+ if (trigger.estimatedMaxCostUsd > trigger.policy.maxEstimatedCostUsd) {
126
+ context.addIssue({
127
+ code: "custom",
128
+ message: "estimated Refiner cost exceeds the trigger policy budget",
129
+ path: ["estimatedMaxCostUsd"],
130
+ });
131
+ }
132
+ if (trigger.pendingPlanCount > trigger.policy.maxPendingPlans) {
133
+ context.addIssue({
134
+ code: "custom",
135
+ message: "pending plan count exceeds the trigger policy limit",
136
+ path: ["pendingPlanCount"],
137
+ });
138
+ }
139
+ if (trigger.observations.length > trigger.policy.maxEvidenceEvents) {
140
+ context.addIssue({
141
+ code: "custom",
142
+ message: "trigger observations exceed the evidence budget",
143
+ path: ["observations"],
144
+ });
145
+ }
146
+ if (trigger.decision === "route_deterministically" &&
147
+ trigger.deterministicRoute === null) {
148
+ context.addIssue({
149
+ code: "custom",
150
+ message: "deterministic decisions require a route",
151
+ path: ["deterministicRoute"],
152
+ });
153
+ }
154
+ if (trigger.decision !== "route_deterministically" &&
155
+ trigger.deterministicRoute !== null) {
156
+ context.addIssue({
157
+ code: "custom",
158
+ message: "only deterministic decisions may declare a deterministic route",
159
+ path: ["deterministicRoute"],
160
+ });
161
+ }
162
+ if (trigger.decision === "no_action" && trigger.suggestedRoutes.length > 0) {
163
+ context.addIssue({
164
+ code: "custom",
165
+ message: "no-action decisions cannot suggest routes",
166
+ path: ["suggestedRoutes"],
167
+ });
168
+ }
169
+ if (trigger.decision !== "no_action" && trigger.observations.length === 0) {
170
+ context.addIssue({
171
+ code: "custom",
172
+ message: "actionable trigger decisions require observations",
173
+ path: ["observations"],
174
+ });
175
+ }
176
+ });
177
+ export const RefinementTriggerDecisionSchema = RefinementTriggerDecisionContentSchema.extend({
178
+ contentHash: ReleaseHashSchema,
179
+ }).strict();
180
+ export const ImprovementRouteAuthoritySchema = z.enum([
181
+ "runtime_service",
182
+ "refiner_model",
183
+ "human_review",
184
+ "evaluation_system",
185
+ "training_system",
186
+ ]);
187
+ export const ImprovementRouteDecisionContentSchema = z
188
+ .object({
189
+ schemaVersion: z.literal("openpond.improvementRouteDecision.v1"),
190
+ id: ReleaseIdSchema,
191
+ trigger: ImmutableReleaseRefSchema,
192
+ route: HarnessImprovementRouteSchema,
193
+ authority: ImprovementRouteAuthoritySchema,
194
+ automatic: z.boolean(),
195
+ reason: BoundedTextSchema,
196
+ createdAt: ReleaseTimestampSchema,
197
+ metadata: MetadataSchema,
198
+ })
199
+ .strict()
200
+ .superRefine((decision, context) => {
201
+ if (decision.automatic &&
202
+ ["human_review", "evaluation_system", "training_system"].includes(decision.authority)) {
203
+ context.addIssue({
204
+ code: "custom",
205
+ message: `${decision.authority} routes cannot be marked automatic`,
206
+ path: ["automatic"],
207
+ });
208
+ }
209
+ if (decision.route === "training" &&
210
+ decision.automatic &&
211
+ decision.authority !== "training_system") {
212
+ context.addIssue({
213
+ code: "custom",
214
+ message: "automatic training routes require training-system authority",
215
+ path: ["authority"],
216
+ });
217
+ }
218
+ });
219
+ export const ImprovementRouteDecisionSchema = ImprovementRouteDecisionContentSchema.extend({
220
+ contentHash: ReleaseHashSchema,
221
+ }).strict();
222
+ export const HarnessRefinerOutcomeContentSchema = z
223
+ .object({
224
+ schemaVersion: z.literal("openpond.harnessRefinerOutcome.v1"),
225
+ id: ReleaseIdSchema,
226
+ trigger: ImmutableReleaseRefSchema,
227
+ decision: z.enum(["no_action", "proposed"]),
228
+ proposal: ImmutableReleaseRefSchema.nullable(),
229
+ reason: BoundedTextSchema,
230
+ evidenceRefs: z.array(ImmutableReleaseRefSchema).max(100),
231
+ estimatedCostUsd: z.number().finite().nonnegative(),
232
+ createdAt: ReleaseTimestampSchema,
233
+ metadata: MetadataSchema,
234
+ })
235
+ .strict()
236
+ .superRefine((outcome, context) => {
237
+ if ((outcome.decision === "proposed") !== (outcome.proposal !== null)) {
238
+ context.addIssue({
239
+ code: "custom",
240
+ message: "proposed Refiner outcomes require a proposal; no-action outcomes cannot include one",
241
+ path: ["proposal"],
242
+ });
243
+ }
244
+ });
245
+ export const HarnessRefinerOutcomeSchema = HarnessRefinerOutcomeContentSchema.extend({
246
+ contentHash: ReleaseHashSchema,
247
+ }).strict();
248
+ export const ImprovementApplyDecisionSchema = z.enum([
249
+ "applied",
250
+ "retained",
251
+ "declined",
252
+ "conflict",
253
+ "rolled_back",
254
+ ]);
255
+ export const ImprovementApplyReceiptContentSchema = z
256
+ .object({
257
+ schemaVersion: z.literal("openpond.improvementApplyReceipt.v1"),
258
+ id: ReleaseIdSchema,
259
+ proposal: ImmutableReleaseRefSchema,
260
+ beforeOverlay: HarnessOverlaySnapshotRefSchema,
261
+ afterOverlay: HarnessOverlaySnapshotRefSchema.nullable(),
262
+ decision: ImprovementApplyDecisionSchema,
263
+ boundary: ImprovementSafeBoundarySchema,
264
+ validationRefs: z.array(ImmutableReleaseRefSchema).max(100),
265
+ outcomeEvidenceRefs: z.array(ImprovementEventRefSchema).max(1_000),
266
+ rollbackOf: ImmutableReleaseRefSchema.nullable(),
267
+ createdAt: ReleaseTimestampSchema,
268
+ metadata: MetadataSchema,
269
+ })
270
+ .strict()
271
+ .superRefine((receipt, context) => {
272
+ if ((receipt.decision === "applied") !== (receipt.afterOverlay !== null)) {
273
+ context.addIssue({
274
+ code: "custom",
275
+ message: "applied receipts require an after-overlay; other decisions cannot include one",
276
+ path: ["afterOverlay"],
277
+ });
278
+ }
279
+ if ((receipt.decision === "rolled_back") !== (receipt.rollbackOf !== null)) {
280
+ context.addIssue({
281
+ code: "custom",
282
+ message: "only rollback receipts require rollbackOf",
283
+ path: ["rollbackOf"],
284
+ });
285
+ }
286
+ });
287
+ export const ImprovementApplyReceiptSchema = ImprovementApplyReceiptContentSchema.extend({
288
+ contentHash: ReleaseHashSchema,
289
+ }).strict();
290
+ function createHashedContract(input) {
291
+ const parsed = input.contentSchema.parse(input.content);
292
+ return input.resultSchema.parse({
293
+ ...parsed,
294
+ contentHash: contentHash(parsed),
295
+ });
296
+ }
297
+ export function createImprovementObservation(content) {
298
+ return createHashedContract({
299
+ content,
300
+ contentSchema: ImprovementObservationContentSchema,
301
+ resultSchema: ImprovementObservationSchema,
302
+ });
303
+ }
304
+ export function createRefinementTriggerDecision(content) {
305
+ return createHashedContract({
306
+ content,
307
+ contentSchema: RefinementTriggerDecisionContentSchema,
308
+ resultSchema: RefinementTriggerDecisionSchema,
309
+ });
310
+ }
311
+ export function createImprovementRouteDecision(content) {
312
+ return createHashedContract({
313
+ content,
314
+ contentSchema: ImprovementRouteDecisionContentSchema,
315
+ resultSchema: ImprovementRouteDecisionSchema,
316
+ });
317
+ }
318
+ export function createHarnessRefinerOutcome(content) {
319
+ return createHashedContract({
320
+ content,
321
+ contentSchema: HarnessRefinerOutcomeContentSchema,
322
+ resultSchema: HarnessRefinerOutcomeSchema,
323
+ });
324
+ }
325
+ export function createImprovementApplyReceipt(content) {
326
+ return createHashedContract({
327
+ content,
328
+ contentSchema: ImprovementApplyReceiptContentSchema,
329
+ resultSchema: ImprovementApplyReceiptSchema,
330
+ });
331
+ }