@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
package/CLI.md ADDED
@@ -0,0 +1,52 @@
1
+ # Hue setup-session CLI contract
2
+
3
+ Unreleased TypeScript `0.3.1` includes the merged local setup-session CLI core:
4
+
5
+ ```sh
6
+ hue setup
7
+ hue setup --agent
8
+ hue resume
9
+ hue status
10
+ hue claim # reports account attachment unavailable in this local core
11
+ ```
12
+
13
+ These commands belong only to an installer setup session. They do not create or launch a Hue Run,
14
+ Scenario, evaluation, or worker. `claim` only means attaching the anonymous setup project to an
15
+ account; there is no generic `connect` command or local-agent connection in this CLI.
16
+
17
+ `setup` only inspects bounded manifest and lockfile metadata. It does not execute repository code,
18
+ change project files, open a browser, ask a question, create a trial, or contact Hue. `claim` also
19
+ makes no network request in this build and reports that account attachment is unavailable. `resume`
20
+ deterministically continues the same setup-session checkpoint; `status` reads it without changing
21
+ it.
22
+
23
+ Checkpoints are secret-free JSON files outside the project, under the operating system's user state
24
+ directory. On POSIX, directories use mode `0700` and files use mode `0600`; Windows uses its
25
+ per-user local state directory without interpreting POSIX mode bits. Writes are atomic, and a
26
+ configured state location inside the project is rejected. Checkpoints contain project categories
27
+ and hashes, never environment values, credentials, source contents, or claim URLs.
28
+
29
+ `--agent` is explicitly noninteractive JSONL. It never uses ANSI, stdin, or a browser, and each
30
+ invocation emits exactly one terminal `run.completed` or `run.failed` installer event. Those names
31
+ describe the setup-session lifecycle, not a Hue Run. Human output is an append-only inline
32
+ transcript. Plain and JSONL output contain no ANSI; `NO_COLOR`, `TERM=dumb`, CI, and non-TTY output
33
+ select plain mode automatically.
34
+
35
+ Every JSONL record carries `contractVersion: 1`. The TypeScript union is exported from
36
+ `@hue-run/sdk/setup`; the JSON Schema is exported as
37
+ `@hue-run/sdk/setup-events.schema.json`. Consumers must ignore neither unknown versions nor terminal
38
+ failures.
39
+
40
+ The future Fern implementation plugs into `SetupBackendAdapter`. Its three installer operations
41
+ create an anonymous setup trial hard-pinned to `trial_metadata_v1`, verify instrumentation-only
42
+ receipt evidence, and read account-claim state. Inputs carry deterministic idempotency keys and an
43
+ optional abort signal. Adapter results must use bounded, non-secret IDs; claim URLs may be sensitive
44
+ and therefore must never be checkpointed. Receipt verification does not prove that task or
45
+ environment content was captured and must never authorize Scenario publication. The adapter must
46
+ not create a Scenario, evaluation, worker, or Hue Run. No live implementation ships in this slice.
47
+
48
+ The V1 handoff is deliberately staged: setup verifies the anonymous instrumentation trace; `hue claim`
49
+ preserves the project and trace history; then the user performs an explicit content-approved capture
50
+ or rerun, with a prepared tester as the first golden path. Only that content-approved trace passes to
51
+ the separate review/publication flow for a Scenario. The actual URL and that handoff contract remain
52
+ deferred. Setup itself does not capture content, create, publish, or run a Scenario.
package/ENVIRONMENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Simulated environments
2
2
 
3
- Install the optional evaluation runtime-contract peer with the SDK:
3
+ The provider-aware environment APIs shipped in TypeScript `0.3.0` and are publicly available:
4
4
 
5
5
  ```bash
6
6
  npm install @hue-run/sdk zod
@@ -77,11 +77,12 @@ them to checkpoints or progress events, and it never mutates global `process.env
77
77
  world seal cannot be confirmed, the checkpoint remains uncertain and resume neither reacquires
78
78
  credentials nor invokes the callback again.
79
79
 
80
- This is currently a control-plane contract. Hue can issue provider endpoints under
81
- `/api/v1/provider-facades/{bindingId}/{grantId}`, but a provider data-plane facade call has not
82
- yet been proven by the released integration. The existing generic Hue MCP capability remains the
83
- runnable hosted-tool path; do not interpret preparation or local-tool tests as evidence of a
84
- hosted Gmail or Slack MCP call.
80
+ The SDK consumes the versioned connection contract but does not itself establish provider
81
+ fidelity. Public package acceptance exercises local control-plane responses and connection-bundle
82
+ handling; it does not call the issued facade endpoint. Exact installed-registry-package to hosted-
83
+ facade acceptance remains a post-publication Fern integration gate. No public SDK test calls the
84
+ official Gmail service, and passing these tests is not evidence of universal Gmail or Slack
85
+ parity. A matching Hue deployment and verified provider profile remain required.
85
86
 
86
87
  ## Repository-authored scenarios
87
88
 
@@ -97,6 +98,13 @@ identity-affecting defaults as Hue before resolving versions and rejects unknown
97
98
  kinds. In particular, `document_verifier` is not part of this SDK contract and is rejected rather
98
99
  than published with a guessed digest.
99
100
 
101
+ The extendable `EnvironmentDefinition` name remains the V1 contract and is also exported as
102
+ `EnvironmentDefinitionV1`. Use `EnvironmentDefinitionV2` to add immutable Gmail
103
+ `providerInstances`; `PublishableEnvironmentDefinition` is the publication/repository union.
104
+ Hue canonicalizes valid synthetic-principal UUIDs to lowercase, and repository resolution does
105
+ the same before comparing immutable digests, so casing-only UUID changes reuse the stored
106
+ version without dropping provider bindings.
107
+
100
108
  ```ts
101
109
  const scenario = {
102
110
  kind: "repository" as const,
@@ -180,3 +188,13 @@ that cannot be confirmed stays uncertain and never causes the agent to be replay
180
188
  The hosted MCP connection exposes Hue's bounded native actions; it is not general Gmail or
181
189
  Slack HTTP parity and does not proxy arbitrary provider traffic. Forking, in-place reset and
182
190
  arbitrary-step diffs are outside this interface.
191
+
192
+ ## Candidate context migration
193
+
194
+ This candidate-context restriction shipped in `@hue-run/sdk@0.3.0`.
195
+
196
+ `runSimulation` now supplies `context.item` as `{ id, externalKey }`. Read candidate inputs
197
+ from the callback's first argument. Expected outcomes, case metadata and environment-version
198
+ pins are available to evaluation and scoring code, and are omitted from the candidate callback.
199
+ Inputs and configuration are cloned before invocation so candidate mutations cannot change
200
+ pinned grading data. The generic `runExperiment` evaluator interface is unchanged.
package/EVALUATIONS.md CHANGED
@@ -9,6 +9,8 @@ npm install @hue-run/sdk zod
9
9
 
10
10
  The SDK executes targets and scorers on your machine. Hue stores pinned definitions, experiment progress and results. It does not execute uploaded source code. Follow the [installation guide](https://docs.hue.run/installation) to add `@hue-run/sdk` to your application.
11
11
 
12
+ Create a **Tracing and evaluations** project service key under **Settings → Integrations & API keys** and expose it to this server-side process as `HUE_API_KEY`. A **Tracing only** key cannot author datasets or evaluation runs.
13
+
12
14
  ```ts
13
15
  import { randomUUID } from "node:crypto";
14
16
  import { createHue } from "@hue-run/sdk";
@@ -118,7 +120,11 @@ All declared metrics must appear exactly once and satisfy pinned types, bounds a
118
120
 
119
121
  ### Hosted and manual scorer pins
120
122
 
121
- The local runner leaves `llm_judge` and `manual` pins pending and reports their IDs in `deferredScorerVersionIds`. It does not upload a synthetic skipped result that would occupy their immutable result slot. Manual results require a human session. Hosted dispatch is an explicit separate API operation: inspect `getJudgeBudget()`, then call `createJudgeJobs(runId,{idempotencyKey,jobs:[{evaluationItemId,scorerVersionId}]})`. `listJudgeJobs`, `getJudgeJob` and `cancelJudgeJob` expose job progress and cancellation requests. These methods never claim that local execution has hosted provenance. Hosted job endpoints are covered by HTTP contract tests here; live hosted model execution is a separate platform acceptance phase. `listResults` and `getResult` read recorded local or hosted results.
123
+ In TypeScript `0.3.1` (unreleased), the local runner executes only the three known built-in entries and bound `local_code` scorers. It leaves every other pin pending and reports its ID in `deferredScorerVersionIds`, including kinds and built-in entries introduced by a newer server. It never uploads a placeholder result that would occupy the immutable result slot, including placeholders already saved in an older SDK's checkpoint. Direct `scoreLocally()` calls reject pins that require another executor.
124
+
125
+ `world_outcome` pins run inside Hue and need no local callback or executable source digest. A supporting server owns their execution from saved world evidence. Legacy `local_code` pins still require the exact registered callback; changing the worker cannot convert those immutable pins into hosted ones.
126
+
127
+ Manual results require a human session. Hosted model-judge dispatch is an explicit separate API operation: inspect `getJudgeBudget()`, then call `createJudgeJobs(runId,{idempotencyKey,jobs:[{evaluationItemId,scorerVersionId}]})`. `listJudgeJobs`, `getJudgeJob` and `cancelJudgeJob` expose job progress and cancellation requests. These methods never claim that local execution has hosted provenance. Hosted job endpoints are covered by HTTP contract tests here; live hosted model execution is a separate platform acceptance phase. `listResults` and `getResult` read recorded local or hosted results.
122
128
 
123
129
  When present, the budget's `authentication` reports credential resolution only. An
124
130
  `available` status or `configured: true` does not prove that a provider accepted the
@@ -150,4 +156,65 @@ The runner stops scheduling more cases after an operational failure and waits fo
150
156
 
151
157
  ## Verification boundaries
152
158
 
153
- `scripts/verify-package.mjs` installs a real packed tarball outside the monorepo and runs HTTP contract tests against a synthetic service plus actual OpenTelemetry exporters. It checks two configurations, rescoring without target invocation, absent/null output, upload resume, uncertain execution, exclusive checkpoints, source/metric contracts, content policy and terminating schema workers. `scripts/verify-evaluation-api.mjs` is a separate opt-in acceptance against a real Hue receiver/API; it creates synthetic datasets/scorers/experiments in the project associated with the supplied development key.
159
+ `scripts/verify-package.mjs` installs a real packed tarball outside the monorepo and runs HTTP contract tests against a synthetic service plus actual OpenTelemetry exporters. It checks two configurations, rescoring without target invocation, absent/null output, upload resume, uncertain execution, exclusive checkpoints, source/metric contracts, content policy and terminating schema workers. It also exercises the local worker's ready, incomplete and uncertain provider-attempt control-plane paths, but does not call an issued provider facade. `scripts/verify-evaluation-api.mjs` is a separate opt-in acceptance against a real Hue receiver/API; it creates synthetic datasets/scorers/experiments in the project associated with the supplied development key.
160
+
161
+ ## Outbound local agent worker
162
+
163
+ `runLocalAgent` shipped in `@hue-run/sdk@0.3.0` and is publicly available. Install
164
+ `npm install @hue-run/sdk zod`. Queue registration, claims, scoped MCP capabilities and sealed
165
+ evidence require a supporting Hue server and project access; the package version alone does not
166
+ establish hosted provider availability.
167
+
168
+ `runLocalAgent` registers one fixed application callback and polls for queued runs. Hue selects
169
+ the registered key/revision; it does not send executable code or shell commands. Keep the
170
+ checkpoint directory private and durable. The worker persists result content and requires
171
+ acknowledged trace and sealed environment evidence.
172
+
173
+ ```ts
174
+ import { createHue } from "@hue-run/sdk";
175
+ import { createEnvironmentClient } from "@hue-run/sdk/environment";
176
+ import { createEvaluationClient, runLocalAgent } from "@hue-run/sdk/evals";
177
+ import { runMyAgent } from "./agent.js"; // Your existing application entry point.
178
+
179
+ const connection = { apiKey: process.env.HUE_API_KEY! };
180
+ const hue = createHue({ ...connection, serviceName: "local-worker", captureContent: false });
181
+ try {
182
+ await runLocalAgent({
183
+ client: createEvaluationClient(connection),
184
+ environmentClient: createEnvironmentClient(connection),
185
+ hue,
186
+ agent: { key: "support-agent", name: "Support agent", revision: "1" },
187
+ checkpointDirectory: ".hue-checkpoints/support-agent",
188
+ scorers: [], // Hue-executed scorers require no local callback registration.
189
+ target: (inputs, tools, context) => runMyAgent({ inputs, tools, config: context.config }),
190
+ });
191
+ } finally {
192
+ await hue.shutdownSafe();
193
+ }
194
+ ```
195
+
196
+ The callback receives cloned inputs, local tools, and an allowlisted context containing
197
+ `config`, `item: {id, externalKey}`, `executionId`, `environmentRunId`,
198
+ `trace: {traceId,spanId}` and a short-lived `mcp` capability. Expected outcomes, case metadata
199
+ and original source pins remain private to grading. Pass the tools or scoped MCP capability into
200
+ the agent's actual tool boundary; their presence does not redirect provider calls. Capabilities
201
+ are not written to checkpoints. `maxRuns` limits completed runs for one-shot workers, while
202
+ `signal` stops polling. A stop signal does not forcibly cancel an already executing callback.
203
+
204
+ For an experiment with an immutable V2 attempt baseline, also supply `actualAgentManifest`, the
205
+ exact ordered `requestedProviders`, and an `mcpSurface` selected from that request. The worker
206
+ creates the world and prepares once before target code. A ready response exposes the memory-only
207
+ `connectionBundle` and keeps `context.mcp` as its selected MCP projection; it never mints the
208
+ legacy generic capability for that attempt. An incomplete response seals the world as completed
209
+ without invoking the target or scorers. A lost preparation acknowledgement remains uncertain
210
+ and is never recovered through binding reads, credential refresh or target replay. Synthetic
211
+ acceptance does not contact official Gmail or claim universal provider parity.
212
+
213
+ Completion or result-upload failures keep the run claimed by the durable worker identity.
214
+ Restart with the same checkpoint directory to resume saved uploads without invoking the
215
+ candidate again. A lost world-seal acknowledgement is recovered by reading authoritative world
216
+ state. If the seal or candidate outcome cannot be confirmed, or an outcome cannot be serialized,
217
+ the worker reports `attention` and stops; operator investigation is required. Such runs are not
218
+ automatically reclaimed, and presenting the same uncertain checkpoint again cannot replay the
219
+ candidate. Public package acceptance proves this lifecycle against local fixtures; exact
220
+ installed-registry-package to hosted-facade acceptance remains a post-publication Fern gate.
package/README.md CHANGED
@@ -395,6 +395,26 @@ published. The chatbot README describes running that external installation.
395
395
 
396
396
  The optional `@hue-run/sdk/evals` entry point supports dataset/scorer registration, frozen-version experiments, local built-in/custom scoring, upload resume, and historical rescoring. See the [evaluation guide](https://docs.hue.run/evaluations/first-evaluation) for the complete journey, content policy and checkpoint recovery contract.
397
397
 
398
+ ### App-launched local workers
399
+
400
+ `runLocalAgent()` shipped in TypeScript `0.3.0` and is available from npm. It connects a fixed local
401
+ callback to app-launched simulation work while keeping the agent and provider orchestration in
402
+ the developer's process. It shares
403
+ `runSimulation()`'s provider-aware world lifecycle, keeps scoped credentials in callback memory,
404
+ skips target/scorer execution for incomplete environments, and never reacquires or replays after
405
+ an uncertain preparation. See the
406
+ [outbound worker contract](EVALUATIONS.md#outbound-local-agent-worker) and
407
+ [simulated environment guide](ENVIRONMENTS.md). Tests exercise local control-plane fixtures, not
408
+ an issued facade endpoint or the official Gmail service, and do not claim universal provider
409
+ parity.
410
+
411
+ Unreleased TypeScript `0.3.1` defers all scorers the SDK does not execute locally. Only built-ins
412
+ and bound `local_code` callbacks run here; other pins remain pending for their authorized executor.
413
+ See [scorer execution](EVALUATIONS.md#hosted-and-manual-scorer-pins).
414
+
415
+ The [setup CLI](CLI.md) is a resumable local inspection core. Existing customers connect their
416
+ agents with `runLocalAgent()`; setup does not register workers or launch Scenarios.
417
+
398
418
  ## Managed targets
399
419
 
400
420
  Start a frozen dataset run in Hue while your agent stays in your application. Expose a
@@ -1,4 +1,4 @@
1
- import type { ActionInput, ActionResult, CoverageGapInput, CoverageGapResult, CreateRunInput, Environment, EnvironmentDefinition, EnvironmentIdentity, EnvironmentPage, EnvironmentPageOptions, EnvironmentRun, EnvironmentSummary, EnvironmentVersion, EnvironmentVersionSummary, FinishRunInput, SealedRun, StepPage, StepPageOptions } from "./types.js";
1
+ import type { ActionInput, ActionResult, CoverageGapInput, CoverageGapResult, CreateRunInput, Environment, PublishableEnvironmentDefinition, EnvironmentIdentity, EnvironmentPage, EnvironmentPageOptions, EnvironmentRun, EnvironmentSummary, EnvironmentVersion, EnvironmentVersionSummary, FinishRunInput, SealedRun, StepPage, StepPageOptions } from "./types.js";
2
2
  /** Connection and retry options for {@link createEnvironmentClient}. */
3
3
  export interface EnvironmentClientOptions {
4
4
  /** Project service key sent as a bearer token; server-side only. */
@@ -37,7 +37,7 @@ export declare class EnvironmentClient {
37
37
  /** Reads one environment and its immutable version summaries. */
38
38
  getEnvironment(id: string): Promise<Environment>;
39
39
  /** Publishes an immutable definition; this non-idempotent registry write is not retried. */
40
- publishVersion(environmentId: string, definition: EnvironmentDefinition): Promise<EnvironmentVersionSummary>;
40
+ publishVersion(environmentId: string, definition: PublishableEnvironmentDefinition): Promise<EnvironmentVersionSummary>;
41
41
  /** Reads a full immutable environment version and generated action catalog. */
42
42
  getVersion(id: string): Promise<EnvironmentVersion>;
43
43
  /** Creates or recovers one fresh isolated world using a stable idempotency key. */
@@ -170,12 +170,44 @@ export interface EnvironmentDefinition {
170
170
  /** Caller-owned immutable metadata. */
171
171
  metadata?: Record<string, JsonValue>;
172
172
  }
173
+ /** The extendable legacy name remains V1. Publication and runs select their
174
+ * explicit version; provider context is validated by the authoritative server. */
175
+ export type EnvironmentDefinitionV1 = EnvironmentDefinition;
176
+ /** One synthetic Gmail principal and its world-state collection bindings. */
177
+ export interface GmailProviderInstance {
178
+ /** Stable instance key referenced by attempt provider selection. */
179
+ providerInstanceKey: string;
180
+ /** Provider discriminator for the V2 Gmail slice. */
181
+ providerId: "google.gmail";
182
+ /** Synthetic principal UUID, canonicalized to lowercase by Hue. */
183
+ syntheticPrincipalId: string;
184
+ /** Versioned mapping from Gmail concepts to authored-world collections. */
185
+ configuration: {
186
+ /** Gmail mailbox configuration discriminator. */
187
+ kind: "gmail_mailbox/v1";
188
+ /** Collection containing synthetic messages. */
189
+ messagesCollection: string;
190
+ /** Collection containing synthetic drafts. */
191
+ draftsCollection: string;
192
+ /** Synthetic mailbox address. */
193
+ mailboxAddress: string;
194
+ };
195
+ }
196
+ /** V2 authored world with immutable provider-instance bindings. */
197
+ export interface EnvironmentDefinitionV2 extends Omit<EnvironmentDefinition, "schemaVersion"> {
198
+ /** Definition schema discriminator. */
199
+ schemaVersion: 2;
200
+ /** Provider instances available to a strict attempt profile. */
201
+ providerInstances: GmailProviderInstance[];
202
+ }
203
+ /** Definition accepted by immutable environment publication. */
204
+ export type PublishableEnvironmentDefinition = EnvironmentDefinitionV1 | EnvironmentDefinitionV2;
173
205
  /** Full immutable environment version and its generated action catalog. */
174
206
  export interface EnvironmentVersion extends EnvironmentVersionSummary {
175
207
  /** Owning environment identity. */
176
208
  environmentId: string;
177
209
  /** Stored, defaulted authored definition. */
178
- definition: EnvironmentDefinition;
210
+ definition: PublishableEnvironmentDefinition;
179
211
  /** Generated agent-visible actions. */
180
212
  actions: ActionDefinition[];
181
213
  }
@@ -1,6 +1,6 @@
1
1
  import type { ProjectConnection } from "../types.js";
2
2
  import { type AttemptConnectionBundleV2, type PrepareAttemptRequestV2 } from "./attempt.js";
3
- import type { CaseWrite, CompleteExecution, Completion, Dataset, DatasetCase, DatasetVersion, EvaluationItem, EvaluationRun, EnvironmentEvidenceSnapshot, Execution, Experiment, ExperimentCase, ExperimentItem, Identity, JsonValue, JudgeBudget, JudgeJob, Page, PageOptions, RegistryPageOptions, Result, ResultSummary, Scorer, ScorerDefinition, ScorerVersion, SimulationMcpCapability, StartExecution, Subject, StoredResult } from "./types.js";
3
+ import type { CaseWrite, CompleteExecution, Completion, Dataset, DatasetCase, DatasetVersion, EvaluationItem, EvaluationRun, EnvironmentEvidenceSnapshot, Execution, Experiment, ExperimentCase, ExperimentItem, Identity, JsonValue, LocalAgentClaim, LocalAgentRegistration, RegisteredLocalAgent, JudgeBudget, JudgeJob, Page, PageOptions, RegistryPageOptions, Result, ResultSummary, Scorer, ScorerDefinition, ScorerVersion, SimulationMcpCapability, StartExecution, Subject, StoredResult } from "./types.js";
4
4
  /** Connection options for {@link createEvaluationClient}. */
5
5
  export interface EvaluationClientOptions {
6
6
  /** Project service key sent as a Bearer token; server side only. */
@@ -176,6 +176,35 @@ export declare class EvaluationClient {
176
176
  }>;
177
177
  /** Reads the project's hosted judge budget and admission controls. */
178
178
  getJudgeBudget(): Promise<JudgeBudget>;
179
+ /** Register or refresh the fixed local agent key and revision. */
180
+ registerLocalAgent(input: LocalAgentRegistration): Promise<RegisteredLocalAgent>;
181
+ /** Claim a queued run for this agent and durable worker identity. */
182
+ claimLocalAgentRun(input: {
183
+ agentId: string;
184
+ workerId: string;
185
+ }): Promise<LocalAgentClaim | null>;
186
+ /** Refresh the lease of a claimed local run. */
187
+ heartbeatLocalAgentRun(input: {
188
+ runId: string;
189
+ workerId: string;
190
+ }): Promise<{
191
+ /** Queue-run identity. */
192
+ runId: string;
193
+ /** The worker claim remains active. */
194
+ active: true;
195
+ }>;
196
+ /** Report acknowledged completion or an execution requiring attention. */
197
+ completeLocalAgentRun(input: {
198
+ runId: string;
199
+ workerId: string;
200
+ state: "completed" | "attention";
201
+ failureType?: string;
202
+ }): Promise<{
203
+ /** Queue-run identity. */
204
+ runId: string;
205
+ /** Acknowledged terminal queue state. */
206
+ state: "completed" | "attention";
207
+ }>;
179
208
  /** Creates the legacy execution-scoped generic MCP capability for one world. */
180
209
  createSimulationMcpCapability(input: {
181
210
  runId: string;
@@ -309,6 +309,22 @@ export class EvaluationClient {
309
309
  getJudgeBudget() {
310
310
  return this.request("GET", "/judge-budget");
311
311
  }
312
+ /** Register or refresh the fixed local agent key and revision. */
313
+ registerLocalAgent(input) {
314
+ return this.request("POST", "/local-agent-worker/register", input);
315
+ }
316
+ /** Claim a queued run for this agent and durable worker identity. */
317
+ claimLocalAgentRun(input) {
318
+ return this.request("POST", "/local-agent-worker/claim", input);
319
+ }
320
+ /** Refresh the lease of a claimed local run. */
321
+ heartbeatLocalAgentRun(input) {
322
+ return this.request("POST", "/local-agent-worker/runs/heartbeat", input);
323
+ }
324
+ /** Report acknowledged completion or an execution requiring attention. */
325
+ completeLocalAgentRun(input) {
326
+ return this.request("POST", "/local-agent-worker/runs/complete", input);
327
+ }
312
328
  /** Creates the legacy execution-scoped generic MCP capability for one world. */
313
329
  createSimulationMcpCapability(input) {
314
330
  return this.request("POST", "/local-agent-worker/mcp-capability", input);
@@ -0,0 +1,84 @@
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 { HueSpan } from "../types.js";
5
+ import { type ActualAgentManifestInputV2, type AttemptBaselineV2, type AttemptConnectionBundleV2, type RequestedAttemptProviderV2, type SurfaceBindingV2 } from "./attempt.js";
6
+ import type { EvaluationClient } from "./client.js";
7
+ import type { ExperimentCase, JsonValue, SimulationMcpCapability } from "./types.js";
8
+ export type McpSurfaceKeyV2 = Extract<SurfaceBindingV2["surfaceKey"], `${string}/mcp`>;
9
+ export type ActualAgentManifestResolverV2 = ActualAgentManifestInputV2 | ((context: {
10
+ config: JsonValue;
11
+ item: ExperimentCase;
12
+ signal?: AbortSignal;
13
+ }) => ActualAgentManifestInputV2 | Promise<ActualAgentManifestInputV2>);
14
+ export interface ProviderAttemptOptionsV2 {
15
+ actualAgentManifest?: ActualAgentManifestResolverV2;
16
+ requestedProviders?: RequestedAttemptProviderV2[];
17
+ mcpSurface?: {
18
+ providerInstanceKey: string;
19
+ surfaceKey: McpSurfaceKeyV2;
20
+ };
21
+ }
22
+ export type RequestedAttemptV2 = {
23
+ actualAgentManifest: ActualAgentManifestResolverV2;
24
+ requestedProviders: RequestedAttemptProviderV2[];
25
+ mcpSurface: {
26
+ providerInstanceKey: string;
27
+ surfaceKey: McpSurfaceKeyV2;
28
+ };
29
+ };
30
+ export type PinnedAttemptV2 = RequestedAttemptV2 & {
31
+ expectedAgentManifestDigest: AttemptBaselineV2["expectedAgentManifestDigest"];
32
+ };
33
+ export declare function requestedAttemptV2(options: ProviderAttemptOptionsV2): RequestedAttemptV2 | undefined;
34
+ export declare function pinRequestedAttemptV2(requested: RequestedAttemptV2, config: JsonValue): PinnedAttemptV2;
35
+ export type EnvironmentTargetProgress = {
36
+ type: "world_created" | "target_started" | "world_sealed";
37
+ environmentRunId: string;
38
+ } | {
39
+ type: "attempt_prepared";
40
+ environmentRunId: string;
41
+ bindingId: string;
42
+ status: "ready" | "environment_incomplete";
43
+ findingCodes: string[];
44
+ executionManifestDigest?: AttemptConnectionBundleV2["parity"]["executionManifestDigest"];
45
+ };
46
+ export interface EnvironmentTargetContext {
47
+ config: JsonValue;
48
+ item: ExperimentCase;
49
+ executionId: string;
50
+ environmentRunId: string;
51
+ trace: {
52
+ traceId: string;
53
+ spanId: string;
54
+ };
55
+ tools: Record<string, EnvironmentTool>;
56
+ mcp: SimulationMcpCapability;
57
+ connectionBundle?: AttemptConnectionBundleV2;
58
+ signal?: AbortSignal;
59
+ }
60
+ interface RunnerTargetContext {
61
+ config: JsonValue;
62
+ item: ExperimentCase;
63
+ executionId: string;
64
+ span: HueSpan;
65
+ }
66
+ export interface RunEnvironmentTargetOptions {
67
+ client: EvaluationClient;
68
+ environmentClient: EnvironmentClient;
69
+ hue: HueClient;
70
+ inputs: JsonValue;
71
+ context: RunnerTargetContext;
72
+ requested?: PinnedAttemptV2;
73
+ maxSteps?: number;
74
+ ttlSeconds?: number;
75
+ signal?: AbortSignal;
76
+ onProgress?(event: EnvironmentTargetProgress): void | Promise<void>;
77
+ target(inputs: JsonValue, context: EnvironmentTargetContext): JsonValue | undefined | Promise<JsonValue | undefined>;
78
+ }
79
+ /** One authoritative environment/provider lifecycle shared by direct simulations and
80
+ * outbound local workers. Credential-bearing connections stay in this call frame and
81
+ * are never returned to either runner's checkpoint state.
82
+ */
83
+ export declare function runEnvironmentTarget(options: RunEnvironmentTargetOptions): Promise<JsonValue | undefined>;
84
+ export {};
@@ -0,0 +1,201 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { bindEnvironmentTools } from "../environment/tools.js";
3
+ import { actualAgentManifestV2, attemptBaselineV2, projectMcpConnectionV2, requestedAttemptProvidersV2, validateAttemptConnectionBundleV2, } from "./attempt.js";
4
+ import { TargetCancelledError, TargetOutcomeUncertainError } from "./runner.js";
5
+ export function requestedAttemptV2(options) {
6
+ const requested = options.requestedProviders !== undefined;
7
+ const selected = options.mcpSurface !== undefined;
8
+ if (!requested && !selected) {
9
+ if (options.actualAgentManifest !== undefined)
10
+ throw new TypeError("actualAgentManifest requires requestedProviders and mcpSurface");
11
+ return undefined;
12
+ }
13
+ if (!requested || !selected)
14
+ throw new TypeError("requestedProviders and mcpSurface must be supplied together");
15
+ const requestedProviders = requestedAttemptProvidersV2.parse(options.requestedProviders);
16
+ const mcpSurface = options.mcpSurface;
17
+ const provider = requestedProviders.find((candidate) => candidate.providerInstanceKey === mcpSurface.providerInstanceKey);
18
+ if (!provider?.surfaceKeys.includes(mcpSurface.surfaceKey))
19
+ throw new TypeError("mcpSurface must identify an exactly requested MCP surface");
20
+ const actualAgentManifest = typeof options.actualAgentManifest === "function"
21
+ ? options.actualAgentManifest
22
+ : actualAgentManifestV2.parse(options.actualAgentManifest);
23
+ return {
24
+ actualAgentManifest,
25
+ requestedProviders,
26
+ mcpSurface: { ...mcpSurface },
27
+ };
28
+ }
29
+ export function pinRequestedAttemptV2(requested, config) {
30
+ if (!config || typeof config !== "object" || Array.isArray(config))
31
+ throw new TypeError("Provider-profile simulations require an immutable V2 attempt baseline");
32
+ const source = config;
33
+ const baseline = attemptBaselineV2.safeParse(source.attemptBaselineV2);
34
+ if (!baseline.success) {
35
+ if (source.attemptBaselineV2 === undefined && source.attemptBaselineV1 !== undefined)
36
+ throw new TypeError("Legacy V1 attempts require a fresh experiment with a V2 baseline");
37
+ if (source.attemptBaselineV2 !== undefined)
38
+ throw new TypeError("The immutable V2 attempt baseline is invalid");
39
+ throw new TypeError("Provider-profile simulations require an immutable V2 attempt baseline");
40
+ }
41
+ return { ...requested, expectedAgentManifestDigest: baseline.data.expectedAgentManifestDigest };
42
+ }
43
+ /** Confirm a seal from the authoritative run after a lost acknowledgement. */
44
+ async function seal(client, runId, executionId, status) {
45
+ try {
46
+ await client.finishRun(runId, {
47
+ idempotencyKey: `execution:${executionId}:${status}`,
48
+ status,
49
+ });
50
+ }
51
+ catch (error) {
52
+ const recovered = await client.getRun(runId).catch(() => undefined);
53
+ if (recovered?.status !== status)
54
+ throw new TargetOutcomeUncertainError(executionId, { cause: error });
55
+ }
56
+ }
57
+ /** One authoritative environment/provider lifecycle shared by direct simulations and
58
+ * outbound local workers. Credential-bearing connections stay in this call frame and
59
+ * are never returned to either runner's checkpoint state.
60
+ */
61
+ export async function runEnvironmentTarget(options) {
62
+ const { context } = options;
63
+ const environmentVersionId = context.item.environmentVersionId;
64
+ if (!environmentVersionId)
65
+ throw new Error("The simulation case has no pinned environment version");
66
+ const run = await options.environmentClient.createRun({
67
+ idempotencyKey: `execution:${context.executionId}`,
68
+ environmentVersionId,
69
+ executionId: context.executionId,
70
+ maxSteps: options.maxSteps,
71
+ ttlSeconds: options.ttlSeconds,
72
+ });
73
+ const progress = (event) => options.onProgress?.(event);
74
+ let finalized = false;
75
+ try {
76
+ await progress({ type: "world_created", environmentRunId: run.id });
77
+ if (options.signal?.aborted)
78
+ throw new TargetCancelledError();
79
+ const tools = bindEnvironmentTools({
80
+ hue: options.hue,
81
+ client: options.environmentClient,
82
+ run,
83
+ parentContext: context.span.context,
84
+ });
85
+ let connectionBundle;
86
+ let mcp;
87
+ if (options.requested) {
88
+ const actualManifest = actualAgentManifestV2.parse(typeof options.requested.actualAgentManifest === "function"
89
+ ? await options.requested.actualAgentManifest({
90
+ config: structuredClone(context.config),
91
+ item: structuredClone(context.item),
92
+ signal: options.signal,
93
+ })
94
+ : options.requested.actualAgentManifest);
95
+ let prepared;
96
+ try {
97
+ prepared = await options.client.prepareAttempt({
98
+ schemaVersion: 2,
99
+ idempotencyKey: randomUUID(),
100
+ executionId: context.executionId,
101
+ environmentRunId: run.id,
102
+ expectedAgentManifestDigest: options.requested.expectedAgentManifestDigest,
103
+ actualManifest,
104
+ requestedProviders: options.requested.requestedProviders,
105
+ });
106
+ }
107
+ catch (error) {
108
+ // A transport failure or malformed credential-bearing response may follow a
109
+ // committed decision. The runner's running checkpoint prevents reacquisition
110
+ // and target replay on resume.
111
+ throw new TargetOutcomeUncertainError(context.executionId, { cause: error });
112
+ }
113
+ await progress({
114
+ type: "attempt_prepared",
115
+ environmentRunId: run.id,
116
+ bindingId: prepared.status === "ready" ? prepared.bundle.bindingId : prepared.bindingId,
117
+ status: prepared.status,
118
+ findingCodes: prepared.preflightReport.findings.map((finding) => finding.code),
119
+ ...(prepared.status === "ready"
120
+ ? { executionManifestDigest: prepared.bundle.parity.executionManifestDigest }
121
+ : {}),
122
+ });
123
+ if (prepared.status === "environment_incomplete") {
124
+ await seal(options.environmentClient, run.id, context.executionId, "completed");
125
+ finalized = true;
126
+ await Promise.resolve(progress({ type: "world_sealed", environmentRunId: run.id })).catch(() => undefined);
127
+ return undefined;
128
+ }
129
+ connectionBundle = validateAttemptConnectionBundleV2(prepared.bundle, {
130
+ requireFresh: true,
131
+ });
132
+ const projected = projectMcpConnectionV2(connectionBundle, options.requested.mcpSurface.providerInstanceKey);
133
+ if (!projected)
134
+ throw new TypeError("The prepared attempt has no selected MCP surface");
135
+ mcp = projected;
136
+ }
137
+ else {
138
+ mcp = await options.client.createSimulationMcpCapability({
139
+ runId: run.id,
140
+ executionId: context.executionId,
141
+ });
142
+ }
143
+ if (options.signal?.aborted)
144
+ throw new TargetCancelledError();
145
+ await progress({ type: "target_started", environmentRunId: run.id });
146
+ const output = await options.target(options.inputs, {
147
+ config: context.config,
148
+ item: context.item,
149
+ executionId: context.executionId,
150
+ environmentRunId: run.id,
151
+ trace: { traceId: context.span.traceId, spanId: context.span.spanId },
152
+ tools,
153
+ mcp,
154
+ ...(connectionBundle ? { connectionBundle } : {}),
155
+ signal: options.signal,
156
+ });
157
+ await seal(options.environmentClient, run.id, context.executionId, "completed");
158
+ finalized = true;
159
+ await Promise.resolve(progress({ type: "world_sealed", environmentRunId: run.id })).catch(() => undefined);
160
+ return output;
161
+ }
162
+ catch (error) {
163
+ if (error instanceof TargetOutcomeUncertainError || finalized)
164
+ throw error;
165
+ let environmentIncomplete;
166
+ try {
167
+ environmentIncomplete =
168
+ (await options.environmentClient.getRun(run.id)).validity === "environment_incomplete";
169
+ }
170
+ catch (inspectionError) {
171
+ throw new TargetOutcomeUncertainError(context.executionId, {
172
+ cause: new AggregateError([error, inspectionError]),
173
+ });
174
+ }
175
+ if (environmentIncomplete) {
176
+ try {
177
+ await seal(options.environmentClient, run.id, context.executionId, "completed");
178
+ }
179
+ catch (finalizationError) {
180
+ throw new TargetOutcomeUncertainError(context.executionId, {
181
+ cause: new AggregateError([error, finalizationError]),
182
+ });
183
+ }
184
+ finalized = true;
185
+ await Promise.resolve(progress({ type: "world_sealed", environmentRunId: run.id })).catch(() => undefined);
186
+ return undefined;
187
+ }
188
+ try {
189
+ await seal(options.environmentClient, run.id, context.executionId, "abandoned");
190
+ }
191
+ catch (finalizationError) {
192
+ throw new TargetOutcomeUncertainError(context.executionId, {
193
+ cause: new AggregateError([error, finalizationError]),
194
+ });
195
+ }
196
+ await Promise.resolve(progress({ type: "world_sealed", environmentRunId: run.id })).catch(() => undefined);
197
+ if (options.signal?.aborted && !(error instanceof TargetCancelledError))
198
+ throw new TargetCancelledError();
199
+ throw error;
200
+ }
201
+ }