openpond-sdk 0.12.8 → 0.12.9
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/TRAINING_WORKFLOWS.md +44 -10
- package/examples/training/continual.ts +71 -9
- package/package.json +1 -1
package/TRAINING_WORKFLOWS.md
CHANGED
|
@@ -76,15 +76,46 @@ publication, evidence and feedback, grade/review decisions, batch sealing and
|
|
|
76
76
|
iteration reserve/cancel/retry. Reuse released resources and exact revision refs;
|
|
77
77
|
the SDK does not execute Python-authored graders or invent another scheduler.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
The TS example `examples/training/continual.ts` and native Python example
|
|
80
|
+
`python/openpond-sdk/examples/continual.py` expose the same explicit steps:
|
|
81
|
+
|
|
82
|
+
1. `inspect policy-ref.json inspection.json` checks the exact released policy.
|
|
83
|
+
Use existing reviewed source/definition/reward/binding resources and approved
|
|
84
|
+
evidence; `command command.json response.json` may publish/review/seal only
|
|
85
|
+
the explicitly supplied typed command. These are writes, and grade/reward
|
|
86
|
+
checks can incur model cost. They are never inferred from opening a policy.
|
|
87
|
+
2. `reserve reserve-command.json reserved.json` sends one existing
|
|
88
|
+
`reserve_iteration` command with a stable operationId, pinned policy
|
|
89
|
+
`{id, revision, contentHash}` and manual trigger `{kind:"manual", identity}`.
|
|
90
|
+
This is the explicit paid-admission boundary: host workers may dispatch it
|
|
91
|
+
automatically. The policy must already contain the reviewed configuration,
|
|
92
|
+
batch/evidence eligibility and finite budget; the example never enables it,
|
|
93
|
+
changes its limits or accepts/promotes a candidate automatically.
|
|
94
|
+
3. If the reply is lost, `reconcile reserve-command.json reconciled.json` reads
|
|
95
|
+
that policy's chain and scans at most10 pages of100 reservations for the SAME
|
|
96
|
+
pinned policy/manual trigger. It never issues another reservation. A missing
|
|
97
|
+
result is UNKNOWN, not permission to submit a new operation/trigger.
|
|
98
|
+
4. `status reserved.json status.json` reloads the retained iteration, lists only
|
|
99
|
+
its dispatch, refreshes the iteration once and checks policy/execution identity
|
|
100
|
+
before reading its training job. Inconsistent snapshots require another read,
|
|
101
|
+
never a mutation. Reinvoke explicitly
|
|
102
|
+
with the latest receipt; there is no unbounded polling. `results status.json
|
|
103
|
+
results.json` requires settled dispatch plus authoritative terminal cleanup
|
|
104
|
+
receipt, then reads retained training outputs. Only cancellation before initial
|
|
105
|
+
submission may return a terminal record without a job. A candidate_ready result is not
|
|
106
|
+
accepted or served, and failed/cancelled terminal records are not success.
|
|
107
|
+
5. For `cancel` or `retry`, add a new stable `operationId` to a retained response.
|
|
108
|
+
Each reloads current iteration revision; retry additionally requires the SAME
|
|
109
|
+
blocked dispatch. The host handles cancellation by dispatch identity, including
|
|
110
|
+
uncertain initial submission. Read status/results until authoritative terminal
|
|
111
|
+
receipt is observed. Local request abort never claims remote cancellation.
|
|
112
|
+
|
|
113
|
+
Each invocation has a120-second bound, exclusive private output and retains exact
|
|
114
|
+
iteration/policy/dispatch/job references. Reservation reads cannot manufacture
|
|
115
|
+
proof of actual execution. This declares existing manual-trigger continual
|
|
116
|
+
iterations; schedule/approved-count automation and Python-authored graders are
|
|
117
|
+
not demonstrated by this example. One ordinary external consumer must still run
|
|
118
|
+
the reviewed hosted sequence after platform security qualification.
|
|
88
119
|
|
|
89
120
|
## Proof and release
|
|
90
121
|
|
|
@@ -95,4 +126,7 @@ lost-reply reconciliation, pagination and real cancellation/recovery remain
|
|
|
95
126
|
integration gates. Examples and source/package checks do not establish a real
|
|
96
127
|
GPU result. Actual hosted GRPO/reward-model execution uses a separately bounded
|
|
97
128
|
approved workflow and retains exact version/SHA/input/result/cleanup evidence.
|
|
98
|
-
|
|
129
|
+
The original `openpond-sdk@0.12.8` is published and independently verified.
|
|
130
|
+
The source follow-up prepares coordinated SDK/Ponder0.12.9; it does not claim
|
|
131
|
+
that version is published. Ponder npm and Python first-name publication remain separate publisher/bootstrap
|
|
132
|
+
gates; clean packed consumers do not establish registry ownership.
|
|
@@ -1,19 +1,81 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { LearningCommandSchema, LearningRevisionRefSchema, OpenPondLearningClient } from "openpond-sdk/learning";
|
|
2
|
+
import { LearningCommandSchema, LearningIterationSchema, LearningRevisionRefSchema, OpenPondLearningClient } from "openpond-sdk/learning";
|
|
3
|
+
import { createTrainingClient } from "openpond-sdk/training";
|
|
3
4
|
|
|
5
|
+
// Each invocation performs one explicit transition or one finite read snapshot.
|
|
4
6
|
const [action, inputPath, outputPath] = process.argv.slice(2);
|
|
5
|
-
if (!["inspect", "command"].includes(action ?? "") || !inputPath || !outputPath) throw new Error("Usage: continual.ts <inspect|command> <
|
|
7
|
+
if (!["inspect", "command", "reserve", "reconcile", "status", "results", "cancel", "retry"].includes(action ?? "") || !inputPath || !outputPath) throw new Error("Usage: continual.ts <inspect|command|reserve|reconcile|status|results|cancel|retry> <input.json> <new-response.json>");
|
|
6
8
|
const { OPENPOND_API_URL: baseUrl, OPENPOND_API_KEY: apiKey, OPENPOND_TEAM_ID: scope } = process.env;
|
|
7
9
|
if (!baseUrl || !apiKey || !scope) throw new Error("Set OPENPOND_API_URL, OPENPOND_API_KEY and OPENPOND_TEAM_ID.");
|
|
8
10
|
const bytes = await readFile(inputPath);
|
|
9
11
|
if (bytes.byteLength > 16_777_216) throw new Error("Learning input exceeds the protocol limit.");
|
|
10
12
|
const input: unknown = JSON.parse(bytes.toString("utf8"));
|
|
11
13
|
const client = new OpenPondLearningClient({ baseUrl, apiKey, scope });
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const signal = AbortSignal.timeout(120_000);
|
|
15
|
+
const options = { signal };
|
|
16
|
+
const training = createTrainingClient({ baseUrl, headers: { Authorization: `Bearer ${apiKey}`, "X-OpenPond-Team-Id": scope }, fetch: (url, init) => fetch(url, { ...init, signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal, redirect: "error" }) });
|
|
17
|
+
let result: unknown;
|
|
18
|
+
if (action === "inspect") result = await client.inspectPolicy(LearningRevisionRefSchema.parse(input), options);
|
|
19
|
+
else if (action === "reconcile") {
|
|
20
|
+
const command = LearningCommandSchema.parse(input);
|
|
21
|
+
if (command.action !== "reserve_iteration" || command.trigger.kind !== "manual") throw new Error("Reconcile the original manual reservation command.");
|
|
22
|
+
const triggerIdentity = command.trigger.identity;
|
|
23
|
+
const inspection = await client.inspectPolicy(command.policy, options);
|
|
24
|
+
if (!inspection.chain) throw new Error("No chain observed; do not create another reservation automatically.");
|
|
25
|
+
let afterId: string | undefined;
|
|
26
|
+
let resultFound = false;
|
|
27
|
+
const seen = new Set<string>();
|
|
28
|
+
for (let pageIndex = 0; pageIndex < 10; pageIndex++) {
|
|
29
|
+
const page = await client.list("reservation", { parentId: inspection.chain.id, limit: 100, ...(afterId ? { afterId } : {}) }, options);
|
|
30
|
+
const reservation = page.items.find((item) => item.policy.id === command.policy.id && item.policy.revision === command.policy.revision && item.policy.contentHash === command.policy.contentHash && item.trigger.kind === "manual" && item.trigger.identity === triggerIdentity);
|
|
31
|
+
if (reservation) {
|
|
32
|
+
const iteration = await client.get("iteration", reservation.iterationId, undefined, options);
|
|
33
|
+
result = { reservation, iteration }; resultFound = true; break;
|
|
34
|
+
}
|
|
35
|
+
if (!page.nextCursor) break;
|
|
36
|
+
if (seen.has(page.nextCursor)) throw new Error("Repeated reservation cursor.");
|
|
37
|
+
seen.add(page.nextCursor); afterId = page.nextCursor;
|
|
38
|
+
}
|
|
39
|
+
if (!resultFound) throw new Error("Reservation not observed within finite scan; preserve original command and reconcile with operator before any replay.");
|
|
40
|
+
} else if (action === "command" || action === "reserve") {
|
|
41
|
+
const command = LearningCommandSchema.parse(input);
|
|
42
|
+
if ((action === "reserve") !== (command.action === "reserve_iteration")) throw new Error("Use explicit reserve for reserve_iteration; command is only for reviewed resource/evidence transitions.");
|
|
43
|
+
if (command.action === "reserve_iteration" && command.trigger.kind !== "manual") throw new Error("This example declares manual-trigger continual iterations only.");
|
|
44
|
+
// Reservation may be dispatched automatically by the host and incur compute.
|
|
45
|
+
// The original policy/trigger/operationId is the lost-reply reconciliation key.
|
|
46
|
+
const operation = await client.command(command, options);
|
|
47
|
+
const iteration = operation.resources.find((resource) => resource.schemaVersion === "openpond.learningIteration.v1");
|
|
48
|
+
result = { operation, ...(iteration ? { iteration } : {}) };
|
|
49
|
+
} else {
|
|
50
|
+
if (!input || typeof input !== "object" || !("iteration" in input)) throw new Error("Use a retained response containing iteration; never invent a replacement reservation.");
|
|
51
|
+
const retained = LearningIterationSchema.parse(input.iteration);
|
|
52
|
+
let iteration = await client.get("iteration", retained.id, undefined, options);
|
|
53
|
+
if (iteration.id !== retained.id || (iteration.policy.id !== retained.policy.id || iteration.policy.revision !== retained.policy.revision || iteration.policy.contentHash !== retained.policy.contentHash) || iteration.dispatchId !== retained.dispatchId) throw new Error("Iteration binding changed.");
|
|
54
|
+
if (action === "cancel" || action === "retry") {
|
|
55
|
+
if (!("operationId" in input) || typeof input.operationId !== "string") throw new Error("Add a new stable control operationId to the retained response before dispatch.");
|
|
56
|
+
if (action === "retry") {
|
|
57
|
+
const dispatch = await client.get("dispatch", iteration.dispatchId, undefined, options);
|
|
58
|
+
if (dispatch.iterationId !== iteration.id || dispatch.state !== "blocked") throw new Error("Only the same blocked dispatch can be retried.");
|
|
59
|
+
}
|
|
60
|
+
const command = LearningCommandSchema.parse({ action: action === "cancel" ? "cancel_iteration" : "retry_iteration_dispatch", operationId: input.operationId, iterationId: iteration.id, expectedRevision: iteration.revision });
|
|
61
|
+
const operation = await client.command(command, options);
|
|
62
|
+
result = { operation, iteration: operation.resources.find((resource) => resource.schemaVersion === "openpond.learningIteration.v1") ?? iteration };
|
|
63
|
+
} else {
|
|
64
|
+
// Empty list is legitimate before the worker creates this dispatch.
|
|
65
|
+
const page = await client.list("dispatch", { parentId: iteration.id, limit: 2 }, options);
|
|
66
|
+
if (page.nextCursor || page.items.length > 1 || page.items.some((item) => item.id !== iteration.dispatchId)) throw new Error("Ambiguous dispatch identity.");
|
|
67
|
+
const dispatch = page.items[0] ?? null;
|
|
68
|
+
// Separate HTTP reads can straddle the worker's atomic commit. Refresh once.
|
|
69
|
+
iteration = await client.get("iteration", retained.id, undefined, options);
|
|
70
|
+
if (iteration.policy.id !== retained.policy.id || iteration.policy.revision !== retained.policy.revision || iteration.policy.contentHash !== retained.policy.contentHash || iteration.dispatchId !== retained.dispatchId) throw new Error("Iteration binding changed.");
|
|
71
|
+
if (dispatch && (dispatch.iterationId !== iteration.id || dispatch.policy.id !== iteration.policy.id || dispatch.policy.revision !== iteration.policy.revision || dispatch.policy.contentHash !== iteration.policy.contentHash || dispatch.execution?.id !== iteration.trainingJob?.id || dispatch.execution?.contentHash !== iteration.trainingJob?.contentHash)) throw new Error("Snapshot changed; read status again without mutation.");
|
|
72
|
+
if (!dispatch && iteration.trainingJob) throw new Error("Snapshot changed; read status again.");
|
|
73
|
+
const job = iteration.trainingJob ? await training.getJob(iteration.trainingJob.id) : null;
|
|
74
|
+
if (job && (job.id !== iteration.trainingJob?.id || job.submissionHash !== iteration.trainingJob?.contentHash)) throw new Error("Training execution content identity mismatch.");
|
|
75
|
+
if (action === "results" && (!dispatch || dispatch.state !== "settled" || !dispatch.terminalReceipt)) throw new Error("No settled authoritative execution/cleanup receipt yet; retain status and inspect again.");
|
|
76
|
+
if (action === "results" && !job && (iteration.status !== "cancelled" || dispatch?.execution)) throw new Error("Terminal job not observed.");
|
|
77
|
+
result = { iteration, dispatch, job, ...(action === "results" && job ? { outputs: await training.outputs(job.id) } : {}) };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
18
80
|
await writeFile(outputPath, JSON.stringify(result, null, 2) + "\n", { flag: "wx", mode: 0o600 });
|
|
19
|
-
console.log("Saved
|
|
81
|
+
console.log("Saved one retained transition/snapshot. Timeout does not cancel remote work; reconcile the same iteration before any explicit retry.");
|