@hue-run/sdk 0.2.2 → 0.3.1

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.
Files changed (42) hide show
  1. package/CLI.md +52 -0
  2. package/ENVIRONMENTS.md +24 -6
  3. package/EVALUATIONS.md +69 -2
  4. package/README.md +20 -0
  5. package/dist/environment/client.d.ts +2 -2
  6. package/dist/environment/types.d.ts +33 -1
  7. package/dist/evals/client.d.ts +30 -1
  8. package/dist/evals/client.js +16 -0
  9. package/dist/evals/environment-target.d.ts +84 -0
  10. package/dist/evals/environment-target.js +201 -0
  11. package/dist/evals/local-worker.d.ts +88 -0
  12. package/dist/evals/local-worker.js +171 -0
  13. package/dist/evals/runner.d.ts +1 -1
  14. package/dist/evals/runner.js +22 -12
  15. package/dist/evals/scorer-publication.js +17 -1
  16. package/dist/evals/scorers.d.ts +10 -4
  17. package/dist/evals/scorers.js +11 -8
  18. package/dist/evals/simulation.d.ts +6 -6
  19. package/dist/evals/simulation.js +45 -222
  20. package/dist/evals/types.d.ts +39 -1
  21. package/dist/evals.d.ts +2 -0
  22. package/dist/evals.js +1 -0
  23. package/dist/setup/checkpoint.d.ts +14 -0
  24. package/dist/setup/checkpoint.js +186 -0
  25. package/dist/setup/cli.d.ts +2 -0
  26. package/dist/setup/cli.js +150 -0
  27. package/dist/setup/detect.d.ts +3 -0
  28. package/dist/setup/detect.js +146 -0
  29. package/dist/setup/machine.d.ts +109 -0
  30. package/dist/setup/machine.js +43 -0
  31. package/dist/setup/render.d.ts +16 -0
  32. package/dist/setup/render.js +111 -0
  33. package/dist/setup/runner.d.ts +101 -0
  34. package/dist/setup/runner.js +117 -0
  35. package/dist/setup/types.d.ts +145 -0
  36. package/dist/setup/types.js +2 -0
  37. package/dist/setup.d.ts +6 -0
  38. package/dist/setup.js +6 -0
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +12 -1
  42. package/setup-events.schema.json +134 -0
@@ -0,0 +1,88 @@
1
+ import type { HueClient } from "../client.js";
2
+ import type { EnvironmentClient } from "../environment/client.js";
3
+ import type { EnvironmentTool } from "../environment/tools.js";
4
+ import type { ActualAgentManifestInputV2, AttemptConnectionBundleV2, RequestedAttemptProviderV2 } from "./attempt.js";
5
+ import type { EvaluationClient } from "./client.js";
6
+ import { type RunnerReport } from "./runner.js";
7
+ import type { ExperimentCase, JsonValue, LocalAgentRegistration, LocalScorer } from "./types.js";
8
+ /** Candidate-visible context for one queued local agent execution. */
9
+ export interface LocalAgentTargetContext {
10
+ /** Frozen candidate configuration, cloned before invocation. */
11
+ config: JsonValue;
12
+ /** Identity only. Expected outcomes, metadata and world definitions are evaluator-private. */
13
+ item: Pick<ExperimentCase, "id" | "externalKey">;
14
+ /** Identity of this target execution. */
15
+ executionId: string;
16
+ /** Stable world identity for adapter control operations such as coverage reporting. */
17
+ environmentRunId: string;
18
+ /** Trace identities without mutable span or grading data. */
19
+ trace: {
20
+ /** OpenTelemetry trace identifier. */
21
+ traceId: string;
22
+ /** Root execution span identifier. */
23
+ spanId: string;
24
+ };
25
+ /** Short-lived, execution-scoped hosted tools for model providers that execute MCP remotely. */
26
+ mcp: {
27
+ /** Execution-scoped MCP endpoint. */
28
+ url: string;
29
+ /** Short-lived bearer, never the project service key. */
30
+ token: string;
31
+ /** Capability expiry as an ISO timestamp. */
32
+ expiresAt: string;
33
+ };
34
+ /** Credential-bearing provider connections for this callback only. Hue never
35
+ * checkpoints, logs or adds this response to parity digests. */
36
+ connectionBundle?: AttemptConnectionBundleV2;
37
+ }
38
+ /** Fixed local callback, clients and durable queue-worker settings. */
39
+ export interface RunLocalAgentOptions {
40
+ /** Evaluation client for the worker project. */
41
+ client: EvaluationClient;
42
+ /** Environment client for the same origin and project. */
43
+ environmentClient: EnvironmentClient;
44
+ /** Application telemetry client; required trace exports are acknowledged. */
45
+ hue: HueClient;
46
+ /** Private durable directory for worker identity and execution checkpoints. */
47
+ checkpointDirectory: string;
48
+ /** Fixed agent key and revision registered for queued runs. */
49
+ agent: LocalAgentRegistration;
50
+ /** Local scorer bindings matching the published source digests. */
51
+ scorers?: LocalScorer[];
52
+ /** Cases in flight, between 1 and 16; defaults to 1. */
53
+ concurrency?: number;
54
+ /** Polling interval in milliseconds, 250–60000; defaults to 2000. */
55
+ pollIntervalMillis?: number;
56
+ /** Stops polling cooperatively; does not cancel an active callback. */
57
+ signal?: AbortSignal;
58
+ /** Useful for one-shot jobs and deterministic acceptance. Omit to keep polling. */
59
+ maxRuns?: number;
60
+ /** Opt into the experiment's immutable V2 provider profile. These three values are
61
+ * validated together before the worker polls; no endpoint or credential is supplied here. */
62
+ actualAgentManifest?: ActualAgentManifestInputV2 | ((context: {
63
+ /** Frozen experiment configuration. */
64
+ config: JsonValue;
65
+ /** Full frozen case for resolving actual nonsecret evidence before candidate projection. */
66
+ item: ExperimentCase;
67
+ /** Cooperative worker stop signal. */
68
+ signal?: AbortSignal;
69
+ }) => ActualAgentManifestInputV2 | Promise<ActualAgentManifestInputV2>);
70
+ /** Exact provider instances and ordered surfaces asserted for strict preflight. */
71
+ requestedProviders?: RequestedAttemptProviderV2[];
72
+ /** Requested MCP surface projected to the backwards-compatible `context.mcp`. */
73
+ mcpSurface?: {
74
+ /** Provider instance selected from `requestedProviders`. */
75
+ providerInstanceKey: string;
76
+ /** Selected MCP surface. */
77
+ surfaceKey: "google.gmail/mcp" | "slack/mcp";
78
+ };
79
+ /** Invokes the existing agent against isolated tools and candidate-safe context. */
80
+ target(inputs: JsonValue, tools: Record<string, EnvironmentTool>, context: LocalAgentTargetContext): JsonValue | undefined | Promise<JsonValue | undefined>;
81
+ /** Called after the experiment and queue completion are acknowledged. */
82
+ onCompleted?(report: RunnerReport): void | Promise<void>;
83
+ }
84
+ /**
85
+ * Starts an outbound-only worker for one fixed local agent entry point. Hue chooses
86
+ * only the registered key/revision; no command or source is received from the cloud.
87
+ */
88
+ export declare function runLocalAgent(options: RunLocalAgentOptions): Promise<void>;
@@ -0,0 +1,171 @@
1
+ import { join, resolve } from "node:path";
2
+ import { randomUUID } from "node:crypto";
3
+ import { CheckpointStore } from "./checkpoint.js";
4
+ import { pinRequestedAttemptV2, requestedAttemptV2, runEnvironmentTarget, } from "./environment-target.js";
5
+ import { runExperiment, OutcomeSerializationError, TargetOutcomeUncertainError, UncertainExecutionError, } from "./runner.js";
6
+ /** Allowlist the candidate surface instead of forwarding the generic evaluation context. */
7
+ function localAgentTargetContext(context) {
8
+ return {
9
+ config: structuredClone(context.config),
10
+ item: { id: context.item.id, externalKey: context.item.externalKey },
11
+ executionId: context.executionId,
12
+ environmentRunId: context.environmentRunId,
13
+ trace: { traceId: context.trace.traceId, spanId: context.trace.spanId },
14
+ mcp: {
15
+ url: context.mcp.url,
16
+ token: context.mcp.token,
17
+ expiresAt: context.mcp.expiresAt,
18
+ },
19
+ ...(context.connectionBundle
20
+ ? { connectionBundle: structuredClone(context.connectionBundle) }
21
+ : {}),
22
+ };
23
+ }
24
+ function validInterval(value) {
25
+ const interval = value ?? 2_000;
26
+ if (!Number.isInteger(interval) || interval < 250 || interval > 60_000)
27
+ throw new RangeError("pollIntervalMillis must be 250–60000");
28
+ return interval;
29
+ }
30
+ function stopReason(signal) {
31
+ return signal?.reason instanceof Error
32
+ ? signal.reason
33
+ : new Error("Local worker stopped", { cause: signal?.reason });
34
+ }
35
+ function wait(milliseconds, signal) {
36
+ if (signal?.aborted)
37
+ return Promise.reject(stopReason(signal));
38
+ return new Promise((resolve, reject) => {
39
+ const timeout = setTimeout(done, milliseconds);
40
+ const aborted = () => {
41
+ clearTimeout(timeout);
42
+ reject(stopReason(signal));
43
+ };
44
+ function done() {
45
+ signal?.removeEventListener("abort", aborted);
46
+ resolve();
47
+ }
48
+ signal?.addEventListener("abort", aborted, { once: true });
49
+ });
50
+ }
51
+ function needsAttention(error, seen = new Set()) {
52
+ if (error instanceof TargetOutcomeUncertainError ||
53
+ error instanceof UncertainExecutionError ||
54
+ error instanceof OutcomeSerializationError)
55
+ return true;
56
+ if (!(error instanceof AggregateError) || seen.has(error))
57
+ return false;
58
+ seen.add(error);
59
+ return error.errors.some((nested) => needsAttention(nested, seen));
60
+ }
61
+ /**
62
+ * Starts an outbound-only worker for one fixed local agent entry point. Hue chooses
63
+ * only the registered key/revision; no command or source is received from the cloud.
64
+ */
65
+ export async function runLocalAgent(options) {
66
+ const requestedConfiguration = requestedAttemptV2(options);
67
+ const interval = validInterval(options.pollIntervalMillis);
68
+ const maxRuns = options.maxRuns ?? Number.POSITIVE_INFINITY;
69
+ if (!(maxRuns === Number.POSITIVE_INFINITY || (Number.isInteger(maxRuns) && maxRuns > 0)))
70
+ throw new RangeError("maxRuns must be a positive integer");
71
+ if (options.environmentClient.baseUrl !== options.client.baseUrl)
72
+ throw new Error("Environments and evaluations must use the same Hue origin");
73
+ const directory = resolve(options.checkpointDirectory);
74
+ const project = await options.client.checkConnection();
75
+ const store = await CheckpointStore.acquire(directory, {
76
+ kind: "local-agent-worker",
77
+ projectId: project.id,
78
+ baseUrl: options.client.baseUrl,
79
+ agent: { key: options.agent.key, revision: options.agent.revision },
80
+ });
81
+ try {
82
+ let workerId = await store.read("worker-id");
83
+ if (workerId !== undefined &&
84
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(workerId))
85
+ throw new Error("Invalid local worker identity");
86
+ if (workerId === undefined) {
87
+ workerId = randomUUID();
88
+ await store.write("worker-id", workerId);
89
+ }
90
+ let completed = 0;
91
+ while (completed < maxRuns && !options.signal?.aborted) {
92
+ const agent = await options.client.registerLocalAgent({
93
+ ...options.agent,
94
+ capabilities: options.agent.capabilities ?? ["environment:v1"],
95
+ scorerDigests: options.agent.scorerDigests ??
96
+ options.scorers?.map((item) => item.definition.sourceDigest) ??
97
+ [],
98
+ });
99
+ const claim = await options.client.claimLocalAgentRun({ agentId: agent.id, workerId });
100
+ if (!claim) {
101
+ await wait(interval, options.signal);
102
+ continue;
103
+ }
104
+ const heartbeat = setInterval(() => {
105
+ void options.client
106
+ .heartbeatLocalAgentRun({ runId: claim.runId, workerId })
107
+ .catch(() => undefined);
108
+ }, Math.min(15_000, Math.max(1_000, interval)));
109
+ let experimentFinished = false;
110
+ try {
111
+ const requested = requestedConfiguration
112
+ ? pinRequestedAttemptV2(requestedConfiguration, (await options.client.getExperiment(claim.experimentId)).config)
113
+ : undefined;
114
+ const report = await runExperiment({
115
+ client: options.client,
116
+ hue: options.hue,
117
+ experimentId: claim.experimentId,
118
+ checkpointDirectory: join(directory, `experiment-${claim.experimentId}`),
119
+ persistResultContent: true,
120
+ environmentEvidence: "required",
121
+ traceEvidence: { mode: "required" },
122
+ scorers: options.scorers,
123
+ concurrency: options.concurrency,
124
+ target: (inputs, context) => runEnvironmentTarget({
125
+ client: options.client,
126
+ environmentClient: options.environmentClient,
127
+ hue: options.hue,
128
+ inputs,
129
+ context,
130
+ requested,
131
+ signal: options.signal,
132
+ target: (targetInputs, targetContext) => options.target(structuredClone(targetInputs), targetContext.tools, localAgentTargetContext(targetContext)),
133
+ }),
134
+ });
135
+ // From this point onward the experiment outcome is authoritative. If reporting the
136
+ // queue completion fails, leave the claim intact for checkpointed recovery instead of
137
+ // rewriting a successful experiment as an agent failure.
138
+ experimentFinished = true;
139
+ await options.client.completeLocalAgentRun({
140
+ runId: claim.runId,
141
+ workerId,
142
+ state: "completed",
143
+ });
144
+ await options.onCompleted?.(report);
145
+ completed++;
146
+ }
147
+ catch (error) {
148
+ // A durable outcome can still need completion/result uploads. Leave operational
149
+ // failures claimed so the same worker can resume them through its checkpoints.
150
+ // Attention is terminal in the queue and is reserved for explicit unsafe-to-resume
151
+ // outcomes that require operator intervention.
152
+ if (!experimentFinished && needsAttention(error))
153
+ await options.client
154
+ .completeLocalAgentRun({
155
+ runId: claim.runId,
156
+ workerId,
157
+ state: "attention",
158
+ failureType: error instanceof Error ? error.name.slice(0, 200) : "WorkerError",
159
+ })
160
+ .catch(() => undefined);
161
+ throw error;
162
+ }
163
+ finally {
164
+ clearInterval(heartbeat);
165
+ }
166
+ }
167
+ }
168
+ finally {
169
+ await store.release();
170
+ }
171
+ }
@@ -90,7 +90,7 @@ export interface RunnerReport {
90
90
  subjectIds: string[];
91
91
  /** Result IDs uploaded by this call. */
92
92
  resultIds: string[];
93
- /** `llm_judge` and `manual` pins left pending for hosted or human scoring. */
93
+ /** Pins without a local implementation, left pending for their authorized executor. */
94
94
  deferredScorerVersionIds: string[];
95
95
  }
96
96
  /**
@@ -4,7 +4,7 @@ import { HueExportError } from "../transport.js";
4
4
  import { loadEnvironmentEvidence } from "./environment-evidence.js";
5
5
  import { CheckpointStore } from "./checkpoint.js";
6
6
  import { json, uuid } from "./json.js";
7
- import { persistedScore, scoreLocally, validateScorerBindings } from "./scorers.js";
7
+ import { persistedScore, scoreLocally, validateScorerBindings, isLocallyExecutable, } from "./scorers.js";
8
8
  /**
9
9
  * Thrown when a case has a started attempt without a saved outcome. The runner never reruns the
10
10
  * target; inspect the execution and authorize a new attempt explicitly through `startExecution`.
@@ -105,7 +105,8 @@ async function pool(items, concurrency, execute) {
105
105
  async function scoresFor(versions, context, options, executionId) {
106
106
  const scores = [];
107
107
  let environmentUnavailable = false;
108
- if (options.environmentEvidence === "required") {
108
+ if (options.environmentEvidence === "required" &&
109
+ versions.some((version) => isLocallyExecutable(version.definition))) {
109
110
  try {
110
111
  context = {
111
112
  ...context,
@@ -117,8 +118,8 @@ async function scoresFor(versions, context, options, executionId) {
117
118
  }
118
119
  }
119
120
  for (const version of versions) {
120
- // Hosted/manual pins remain pending for their authorized executor.
121
- if (version.definition.kind === "llm_judge" || version.definition.kind === "manual")
121
+ // Every pin without a local implementation belongs to another executor.
122
+ if (!isLocallyExecutable(version.definition))
122
123
  continue;
123
124
  const score = persistedScore(environmentUnavailable && version.definition.kind === "local_code"
124
125
  ? { state: "error", error: { type: "EnvironmentEvidenceUnavailable" } }
@@ -136,8 +137,16 @@ async function scoresFor(versions, context, options, executionId) {
136
137
  }
137
138
  return scores;
138
139
  }
139
- async function uploadScores(options, runId, scores, save) {
140
- for (const score of scores) {
140
+ async function uploadScores(options, runId, scores, save, versions) {
141
+ // A previous SDK may have checkpointed a placeholder for an unknown kind.
142
+ // Keep its evidence intact, but never upload or report it as a local result.
143
+ const local = scores.filter((score) => {
144
+ const pin = versions.find((version) => version.id === score.payload.scorerVersionId);
145
+ if (!pin)
146
+ throw new Error("Saved result references an unpinned scorer version");
147
+ return isLocallyExecutable(pin.definition);
148
+ });
149
+ for (const score of local) {
141
150
  if (score.receipt)
142
151
  continue;
143
152
  if (!score.payload.evaluationItemId)
@@ -149,6 +158,7 @@ async function uploadScores(options, runId, scores, save) {
149
158
  score.receipt = result.ids;
150
159
  await save();
151
160
  }
161
+ return local.flatMap((score) => score.receipt ?? []);
152
162
  }
153
163
  /**
154
164
  * Runs every case of a frozen experiment through `target` on this machine, completes each
@@ -204,7 +214,7 @@ export async function runExperiment(options) {
204
214
  subjectIds: [],
205
215
  resultIds: [],
206
216
  deferredScorerVersionIds: versions
207
- .filter((version) => ["llm_judge", "manual"].includes(version.definition.kind))
217
+ .filter((version) => !isLocallyExecutable(version.definition))
208
218
  .map((version) => version.id),
209
219
  };
210
220
  try {
@@ -352,9 +362,9 @@ export async function runExperiment(options) {
352
362
  score.payload.evaluationItemId = prepared.completion.evaluationItemId;
353
363
  await save();
354
364
  }
355
- await uploadScores(options, experiment.evaluation.id, prepared.scores, save);
365
+ const results = await uploadScores(options, experiment.evaluation.id, prepared.scores, save, versions);
356
366
  report.subjectIds.push(prepared.completion.subjectId);
357
- report.resultIds.push(...prepared.scores.flatMap((score) => score.receipt ?? []));
367
+ report.resultIds.push(...results);
358
368
  });
359
369
  let finish = await store.read("finish");
360
370
  if (!finish) {
@@ -395,7 +405,7 @@ export async function rescore(options) {
395
405
  subjectIds: [],
396
406
  resultIds: [],
397
407
  deferredScorerVersionIds: run.scorerVersions
398
- .filter((version) => ["llm_judge", "manual"].includes(version.definition.kind))
408
+ .filter((version) => !isLocallyExecutable(version.definition))
399
409
  .map((version) => version.id),
400
410
  };
401
411
  try {
@@ -419,9 +429,9 @@ export async function rescore(options) {
419
429
  await store.write(file, saved);
420
430
  }
421
431
  const current = saved;
422
- await uploadScores(options, run.id, current.scores, () => store.write(file, current));
432
+ const results = await uploadScores(options, run.id, current.scores, () => store.write(file, current), run.scorerVersions);
423
433
  report.subjectIds.push(item.subjectId);
424
- report.resultIds.push(...current.scores.flatMap((score) => score.receipt ?? []));
434
+ report.resultIds.push(...results);
425
435
  });
426
436
  return report;
427
437
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { json } from "./json.js";
2
+ import { json, digest } from "./json.js";
3
3
  const metricName = z
4
4
  .string()
5
5
  .min(1)
@@ -21,6 +21,15 @@ const metric = z.discriminatedUnion("type", [
21
21
  }),
22
22
  ]);
23
23
  const metrics = z.array(metric).min(1);
24
+ const worldOutcomeMetrics = [
25
+ "completed_run",
26
+ "saved_draft",
27
+ "correct_destination",
28
+ "content",
29
+ "unrelated_preserved",
30
+ "process_constraints",
31
+ "task_success",
32
+ ].map((name) => ({ name, type: "boolean" }));
24
33
  const jsonValue = z.custom((value) => {
25
34
  try {
26
35
  json(value);
@@ -73,6 +82,13 @@ const sdkScorerPublication = z.union([
73
82
  sourceDigest: z.string(),
74
83
  metrics,
75
84
  }),
85
+ z.strictObject({
86
+ kind: z.literal("world_outcome"),
87
+ entry: z.literal("hue.conversion_outcome.v1"),
88
+ metrics: metrics
89
+ .default(worldOutcomeMetrics)
90
+ .refine((value) => digest(value) === digest(worldOutcomeMetrics), "World outcome metrics are fixed by the pinned entry"),
91
+ }),
76
92
  z.strictObject({ kind: z.literal("manual"), metrics }),
77
93
  z.strictObject({ kind: z.literal("llm_judge"), config: judgeConfig, metrics }),
78
94
  ]);
@@ -17,12 +17,18 @@ export declare function defineLocalScorer(options: {
17
17
  }): LocalScorer;
18
18
  /** Check local callbacks before target invocation; never execute downloaded source code. */
19
19
  export declare function validateScorerBindings(versions: ScorerVersion[], scorers?: LocalScorer[]): void;
20
+ /** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
21
+ export declare function isLocallyExecutable(definition: {
22
+ kind: string;
23
+ entry?: string;
24
+ }): definition is Extract<ScorerDefinition, {
25
+ kind: "builtin" | "local_code";
26
+ }>;
20
27
  /**
21
- * Scores one subject with a pinned scorer version on this machine: built-ins run here, `local_code`
22
- * pins run the matching callback from `options.scorers`, `manual` pins are skipped. Failures are
23
- * returned as sanitized error scores.
28
+ * Scores one subject locally using a known built-in or a bound `local_code` callback.
29
+ * Scorer failures are returned as sanitized error scores.
24
30
  *
25
- * @throws TypeError for an `llm_judge` pin, which must be dispatched through `createJudgeJobs`.
31
+ * @throws TypeError for pins without a local implementation; their authorized executor owns scoring.
26
32
  */
27
33
  export declare function scoreLocally(version: ScorerVersion, context: ScoreContext, options?: {
28
34
  scorers?: LocalScorer[];
@@ -99,17 +99,22 @@ export function validateScorerBindings(versions, scorers = []) {
99
99
  throw new Error("A pinned local scorer has no matching language/source/entrypoint/metric binding");
100
100
  }
101
101
  }
102
+ /** Only implementations this SDK owns may produce local results. Unknown pins are deferred. */
103
+ export function isLocallyExecutable(definition) {
104
+ return (definition.kind === "local_code" ||
105
+ (definition.kind === "builtin" &&
106
+ ["hue.exact_match.v1", "hue.includes.v1", "hue.json_schema.v1"].includes(definition.entry ?? "")));
107
+ }
102
108
  /**
103
- * Scores one subject with a pinned scorer version on this machine: built-ins run here, `local_code`
104
- * pins run the matching callback from `options.scorers`, `manual` pins are skipped. Failures are
105
- * returned as sanitized error scores.
109
+ * Scores one subject locally using a known built-in or a bound `local_code` callback.
110
+ * Scorer failures are returned as sanitized error scores.
106
111
  *
107
- * @throws TypeError for an `llm_judge` pin, which must be dispatched through `createJudgeJobs`.
112
+ * @throws TypeError for pins without a local implementation; their authorized executor owns scoring.
108
113
  */
109
114
  export async function scoreLocally(version, context, options = {}) {
110
115
  const definition = version.definition;
111
- if (definition.kind === "llm_judge")
112
- throw new TypeError("Hosted judge pins require dispatch through createJudgeJobs; do not submit a local result");
116
+ if (!isLocallyExecutable(definition))
117
+ throw new TypeError("This scorer must be deferred to its authorized executor; do not submit a local result");
113
118
  const timeout = options.schemaTimeoutMillis ?? 2000;
114
119
  if (!Number.isInteger(timeout) || timeout < 100 || timeout > 60_000)
115
120
  throw new RangeError("schemaTimeoutMillis must be 100–60000");
@@ -133,8 +138,6 @@ export async function scoreLocally(version, context, options = {}) {
133
138
  if (environment !== undefined)
134
139
  validateEnvironmentEvidence(environment);
135
140
  const owned = structuredClone(context);
136
- if (definition.kind === "manual")
137
- return skip("Manual scoring requires a human session");
138
141
  if (definition.kind === "local_code") {
139
142
  const binding = options.scorers?.find((local) => digest(local.definition) === digest(definition));
140
143
  if (!binding)
@@ -1,9 +1,9 @@
1
1
  import type { HueClient } from "../client.js";
2
2
  import { type EnvironmentClient } from "../environment/client.js";
3
- import { type EnvironmentTool } from "../environment/tools.js";
4
- import type { EnvironmentDefinition, EnvironmentIdentity } from "../environment/types.js";
3
+ import type { EnvironmentTool } from "../environment/tools.js";
4
+ import type { EnvironmentIdentity, PublishableEnvironmentDefinition } from "../environment/types.js";
5
5
  import { type EvaluationClient } from "./client.js";
6
- import { type ActualAgentManifestInputV2, type AttemptConnectionBundleV2, type RequestedAttemptProviderV2 } from "./attempt.js";
6
+ import type { ActualAgentManifestInputV2, AttemptConnectionBundleV2, RequestedAttemptProviderV2 } from "./attempt.js";
7
7
  import { type RunnerReport } from "./runner.js";
8
8
  import type { ExperimentCase, Identity, JsonValue, LocalScorer, ScorerDefinition, SimulationMcpCapability } from "./types.js";
9
9
  /** Repository-authored scorer identity and its public definition or local binding. */
@@ -40,7 +40,7 @@ export type SimulationScenario = {
40
40
  /** Environment identity and authored world definition. */
41
41
  environment: EnvironmentIdentity & {
42
42
  /** Definition normalized and published as an immutable version. */
43
- definition: EnvironmentDefinition;
43
+ definition: PublishableEnvironmentDefinition;
44
44
  };
45
45
  /** Cases published into one frozen dataset version. */
46
46
  cases: RepositorySimulationCase[];
@@ -92,8 +92,8 @@ export type SimulationProgress = {
92
92
  export interface SimulationTargetContext {
93
93
  /** Frozen experiment configuration. */
94
94
  config: JsonValue;
95
- /** Frozen case including its immutable environment-version pin. */
96
- item: ExperimentCase;
95
+ /** Candidate-visible identity. References, metadata and source pins stay with grading. */
96
+ item: Pick<ExperimentCase, "id" | "externalKey">;
97
97
  /** Target execution identity. */
98
98
  executionId: string;
99
99
  /** Stable world identity for adapter control operations such as coverage reporting. */