@hue-run/sdk 0.9.0 → 0.11.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.
Files changed (56) hide show
  1. package/CLI.md +240 -49
  2. package/ENVIRONMENTS.md +48 -9
  3. package/EVALUATIONS.md +41 -21
  4. package/README.md +70 -30
  5. package/dist/cli/eval-direct.d.ts +12 -1
  6. package/dist/cli/eval-direct.js +145 -33
  7. package/dist/cli/eval.d.ts +28 -1
  8. package/dist/cli/eval.js +504 -81
  9. package/dist/cli/listen.d.ts +76 -0
  10. package/dist/cli/listen.js +816 -0
  11. package/dist/cli/mcp.d.ts +65 -7
  12. package/dist/cli/mcp.js +271 -73
  13. package/dist/client.d.ts +7 -4
  14. package/dist/client.js +36 -10
  15. package/dist/evals/checkpoint.d.ts +15 -1
  16. package/dist/evals/checkpoint.js +34 -3
  17. package/dist/evals/client.d.ts +24 -4
  18. package/dist/evals/client.js +92 -38
  19. package/dist/evals/environment-target.d.ts +18 -3
  20. package/dist/evals/environment-target.js +22 -10
  21. package/dist/evals/exit-cleanup.d.ts +4 -0
  22. package/dist/evals/exit-cleanup.js +26 -0
  23. package/dist/evals/files.d.ts +69 -3
  24. package/dist/evals/files.js +255 -42
  25. package/dist/evals/json.js +116 -9
  26. package/dist/evals/local-worker.d.ts +21 -3
  27. package/dist/evals/local-worker.js +14 -0
  28. package/dist/evals/runner.d.ts +45 -0
  29. package/dist/evals/runner.js +199 -21
  30. package/dist/evals/scenarios.d.ts +7 -5
  31. package/dist/evals/scenarios.js +71 -14
  32. package/dist/evals/scorer-publication.js +58 -3
  33. package/dist/evals/simulation.d.ts +17 -4
  34. package/dist/evals/simulation.js +8 -0
  35. package/dist/evals/types.d.ts +48 -0
  36. package/dist/evals/verdicts.d.ts +26 -2
  37. package/dist/evals/verdicts.js +72 -9
  38. package/dist/evals.d.ts +4 -3
  39. package/dist/evals.js +2 -2
  40. package/dist/inline-files.d.ts +4 -0
  41. package/dist/inline-files.js +48 -17
  42. package/dist/provider-tools.d.ts +12 -1
  43. package/dist/provider-tools.js +192 -106
  44. package/dist/safety.d.ts +7 -1
  45. package/dist/safety.js +9 -7
  46. package/dist/setup/cli.js +31 -8
  47. package/dist/setup/peers.d.ts +13 -0
  48. package/dist/setup/peers.js +44 -0
  49. package/dist/snapshot.d.ts +2 -2
  50. package/dist/snapshot.js +47 -12
  51. package/dist/tool-definitions.d.ts +18 -0
  52. package/dist/tool-definitions.js +232 -0
  53. package/dist/transport.js +2 -2
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/package.json +1 -1
package/CLI.md CHANGED
@@ -42,7 +42,7 @@ project manifest are refused because managers can update ancestor locks; Python
42
42
  The generated `hue.setup.mjs` or `hue_setup.py` always selects `captureContent: false` /
43
43
  `capture_content=False`. For a supported application, setup installs the dependency and adds the
44
44
  managed import and middleware registration to the existing entrypoint; an unreferenced helper is
45
- not a completed integration. TypeScript uses `@hue-run/sdk@0.9.0`, `@opentelemetry/api@1.9.1` and
45
+ not a completed integration. TypeScript uses `@hue-run/sdk@0.11.0`, `@opentelemetry/api@1.9.1` and
46
46
  `@opentelemetry/context-async-hooks@2.11.0`; Python setup uses its separately tested package pin.
47
47
  Content capture requires an ordinary account-managed key and a later explicit application decision.
48
48
 
@@ -113,6 +113,7 @@ hue setup --format human # explicit append-only terminal rendering
113
113
  hue setup --origin http://127.0.0.1:PORT # isolated loopback tests only
114
114
  hue login # validate keys created in Hue and store them in ./.env.hue
115
115
  hue mcp install --client claude-code # write the Hue MCP configuration for a coding agent
116
+ hue listen --subscription <id> --forward-to http://localhost:3000/slack/events # deliver events to a local bot
116
117
  ```
117
118
 
118
119
  Hosted setup accepts HTTPS origins only. HTTP is accepted solely for `localhost`, `127.0.0.1` and
@@ -176,30 +177,130 @@ warns; `--gitignore` appends the file name to the `.gitignore` next to it. Exit
176
177
 
177
178
  ## Install the MCP for your coding agent
178
179
 
179
- `hue mcp install --client <name>` writes, runs or prints the configuration for Hue's MCP server.
180
- Every shape matches the snippet Hue shows in Settings: server name `hue`, the endpoint (default
181
- `https://mcp.hue.run/mcp`; `--url https://mcp.staging.hue.run/mcp` for staging) and a reference to
182
- the `HUE_MCP_KEY` environment variable. A key value is never written.
183
-
184
- | Client | Result |
185
- | --- | --- |
186
- | `claude-code` | Merges `mcpServers.hue` into `./.mcp.json`. `--scope user` runs `claude mcp add --transport http --scope user hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'` when `claude` is on `PATH`, otherwise prints it. |
187
- | `cursor` | Merges `mcpServers.hue` into `./.cursor/mcp.json` with `${env:HUE_MCP_KEY}`. |
188
- | `codex` | Runs `codex mcp add hue --url URL --bearer-token-env-var HUE_MCP_KEY`, or prints the `[mcp_servers.hue]` TOML block for `~/.codex/config.toml`. |
189
- | `vscode` | Merges `servers.hue` and the `hue-mcp-key` password input into `./.vscode/mcp.json`. |
190
- | `windsurf` | Prints the `serverUrl` snippet for `~/.codeium/windsurf/mcp_config.json`; nothing is written to the home directory. |
191
- | `gemini` | Runs `gemini mcp add --transport http hue URL -H 'Authorization: Bearer $HUE_MCP_KEY'`, or prints it. |
180
+ `hue mcp install --client <name>` writes, runs or prints the configuration for Hue's MCP server,
181
+ in the shapes of the [connection guide](https://docs.hue.run/agents/mcp-server): server name
182
+ `hue` and the endpoint (default `https://mcp.hue.run/mcp`; `--url https://mcp.staging.hue.run/mcp`
183
+ for staging). Hue's Settings page does not show these snippets; this command keeps its own copy.
184
+
185
+ `--auth` chooses how the client authenticates:
186
+
187
+ - `key` (the default, except for `conductor`) references the `HUE_MCP_KEY` environment variable,
188
+ which `hue login` stores in `.env.hue`. A key value is never written.
189
+ - `oauth` configures the URL only. The client opens Hue in a browser, where you sign in and
190
+ approve the connection; no key or header is involved. A new sign-in connection has **Read and
191
+ write** access to every active project in the organization you choose, within your role, so an
192
+ agent first calls `list_projects` and passes the chosen project's id as `project_id`. It is available
193
+ for `claude-code`, `codex` and `conductor`. Hue does not yet accept Cursor's sign-in callback, so
194
+ `cursor` and the other clients use a key.
195
+
196
+ `--read-only` adds `?read_only=true` to a key configuration's URL, so Hue hides and rejects write
197
+ tools whatever the key allows. A project key, with or without it, keeps reaching its one project.
198
+ With `--auth oauth` it is refused: a sign-in connection has **Read and write** access, so use a
199
+ **Read** project key for read-only access.
200
+
201
+ `--toolsets <names>` lists only the named tools, for clients with a tool-count limit or agents that
202
+ only read production: `observe` (the production reads: projects, traces, spans and their recorded
203
+ content, sessions, trace checks, intents and documentation), `all`, or the groups `project`, `traces`, `evals`, `environments`,
204
+ `intents` and `docs`, comma-separated. A key configuration adds `?toolsets=<names>` to the URL. A
205
+ sign-in configuration keeps the URL bare and sends the `X-Hue-MCP-Toolsets` header instead,
206
+ because toolsets are not part of the connection's identity: Claude Code stores the header, and for
207
+ Codex the command prints the `http_headers` line to add to `~/.codex/config.toml`, since
208
+ `codex mcp add` stores none. `--toolsets` replaces a selection already in `--url`, and a selection in `--url` is kept without
209
+ it; otherwise `cursor` selects `observe`. `--toolsets all` lists every tool. Unknown names are refused rather than passed on, because Hue
210
+ ignores them and would list every tool. `get_project_context` is always listed and reports the
211
+ selection.
212
+
213
+ | Client | Key (`--auth key`) | Sign-in (`--auth oauth`) |
214
+ | --- | --- | --- |
215
+ | `claude-code` | Merges `mcpServers.hue` into `./.mcp.json`. `--scope user` runs `claude mcp add --transport http --scope user hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'` when `claude` is on `PATH`, otherwise prints it. | Merges `mcpServers.hue` with only `type` and `url` into `./.mcp.json`; `--scope user` runs `claude mcp add --transport http --scope user hue URL`. Then run `/mcp` in Claude Code, select `hue` and choose **Authenticate**. |
216
+ | `codex` | Runs `codex mcp add hue --url URL --bearer-token-env-var HUE_MCP_KEY`, or prints the `[mcp_servers.hue]` TOML block for `~/.codex/config.toml`. | Runs `codex mcp add hue --url URL`, which can start sign-in right away; `codex mcp login hue` starts or repeats it. |
217
+ | `conductor` | Runs the Claude Code user-scope and Codex key commands above. | The default: runs `claude mcp add --transport http --scope user hue URL` and `codex mcp add hue --url URL`. Then use `hue`'s authentication action in Conductor's MCP status (the plug icon or `/mcp-status`). |
218
+ | `cursor` | Merges `mcpServers.hue` into `./.cursor/mcp.json` with `${env:HUE_MCP_KEY}`. | Refused. |
219
+ | `vscode` | Merges `servers.hue` and the `hue-mcp-key` password input into `./.vscode/mcp.json`. | Refused. |
220
+ | `windsurf` | Prints the `serverUrl` snippet for `~/.codeium/windsurf/mcp_config.json`; nothing is written to the home directory. | Refused. |
221
+ | `gemini` | Runs `gemini mcp add --scope user --transport http hue URL --header 'Authorization: Bearer ${HUE_MCP_KEY}'`, or prints it. | Refused. |
222
+
223
+ Conductor has no MCP configuration of its own: its Claude Code and Codex agents read their user
224
+ configuration in every workspace, so `conductor` registers the server at user scope with each CLI
225
+ on `PATH` and prints the command for a CLI that is missing. A failing CLI does not stop the other;
226
+ the command then exits `1`. Hue has not yet verified the complete signed-in Conductor flow (consent
227
+ through a trace read) end to end; if Hue's tools do not load after signing in, run the command
228
+ again with `--auth key`.
192
229
 
193
230
  JSON files are parsed and merged: other servers, inputs and top-level fields are kept, only the
194
231
  `hue` entry is replaced, and invalid JSON (including comments) is refused together with the snippet
195
232
  to add by hand. Files are written with mode `0644` through a temporary file and an atomic rename;
196
- symlinks are refused. `--dry-run` prints the resulting file content or command without writing or
233
+ symlinks are refused. `--dry-run` prints the resulting file content or commands without writing or
197
234
  running; `--print` prints only the snippet. Client CLIs run without a shell, so the
198
- `${HUE_MCP_KEY}` and `$HUE_MCP_KEY` references reach them literally; the printed commands use
199
- single quotes for the same reason. After installation the command reminds you to export
200
- `HUE_MCP_KEY` in the shell that starts the client (VS Code prompts for the key instead) and prints
201
- the verification prompt: `Use the Hue MCP: call get_project_context, then show my 5 most recent
202
- error traces with links.` Exit codes: `0` done or printed, `1` failed, `2` usage error.
235
+ `${HUE_MCP_KEY}` reference reaches them literally; the printed commands use single quotes for the
236
+ same reason, and quote a URL with a query such as `?read_only=true`, whose `?` is a zsh glob.
237
+
238
+ After a key installation the command reminds you to export `HUE_MCP_KEY` in the shell that starts
239
+ the client (VS Code prompts for the key instead). A desktop app started from the Dock or a launcher
240
+ does not see that shell's variables, and Conductor's agents read the login-shell environment that
241
+ Conductor captures, so sign-in is the simpler choice there. After a sign-in installation it names
242
+ the client's authentication action. Both end with the prompt that verifies the connection:
243
+ `Use the Hue MCP: call list_projects and confirm which project to inspect. Then call get_project_context and show that project's traces from the last 24 hours that need attention or have errors, with links; if there are none, show its 5 most recent traces. For an organization connection, pass the project's id as project_id on each call after list_projects.` `list_projects` returns a project key's one project too. Exit codes:
244
+ `0` done or printed, `1` failed, `2` usage error.
245
+
246
+ ## Deliver simulated events to a local bot
247
+
248
+ `hue listen` delivers a simulated world's events to a receiver on your machine, so an event-driven
249
+ agent such as a Slack bot can be tested without a public URL, the way `stripe listen --forward-to`
250
+ delivers webhooks. Hue signs each request as the provider does (Slack Events API requests today);
251
+ the command pulls them, forwards each one unchanged to `--forward-to` and reports the bot's answer
252
+ to Hue, which settles or retries the event on the provider's schedule. It needs a **listen** event
253
+ subscription on the world or on a connection key; the subscription's id and signing secret are shown
254
+ once when it is created. Configure the bot with that signing secret where `SLACK_SIGNING_SECRET`
255
+ went, then start the command: Hue offers the URL verification first, and events follow once the bot
256
+ has answered it. Event subscriptions are not yet available on `https://app.hue.run`; until the
257
+ origin offers them, the command stops with Hue's refusal.
258
+
259
+ ```sh
260
+ hue listen --subscription <id> --forward-to http://localhost:3000/slack/events --env-path .env.world
261
+ ```
262
+ The credential is the subscription's own, read from the environment (or `--env-path`, loaded first;
263
+ a variable already set keeps its value): `HUE_WORLD_TOKEN` for a world's subscription or
264
+ `HUE_CONNECTION_KEY` for a connection key's, chosen with `--credential world-token|connection-key`
265
+ when both are set. It is never taken from the command line, where other local processes and shell
266
+ history can read it. A project key is refused: a value equal to `HUE_API_KEY` or `HUE_MCP_KEY` exits
267
+ 2 before any request, and Hue answers any credential that is not the subscription's with `401`.
268
+ `--origin` (default `HUE_BASE_URL`, then `https://app.hue.run`) must be HTTPS, or plain HTTP on a
269
+ loopback test server.
270
+
271
+ Each request reaches the bot with its body bytes and headers unchanged, so the provider's signature
272
+ verifies as it would in production; only `Host`, `Content-Length` and connection headers are the
273
+ local request's own, and Hue's credential is never forwarded. No redirect is followed: a `3xx` is
274
+ reported as the answer. An answer counts only within three seconds, Slack's acknowledgement window;
275
+ a later one is a timeout. A failing answer with `x-slack-no-retry: 1` is reported so Hue stops
276
+ retrying it. Only a successful URL verification answer's body (at most 4 KiB, for its challenge) is
277
+ sent to Hue; any other answer body stays on this machine. Each pull waits for at most 20 seconds and
278
+ leases up to `--max` deliveries (1 to 10, default 10), which are forwarded concurrently; the next
279
+ pull starts once they are acknowledged, and a delivery beyond `--max` is never forwarded. A provider
280
+ retry of an event is a new delivery and is forwarded again with its `X-Slack-Retry-Num` and
281
+ `X-Slack-Retry-Reason`, so the bot deduplicates by `event_id` as it does with Slack. A delivery Hue
282
+ hands out a second time is not sent again; its recorded answer is repeated. An acknowledgement Hue
283
+ did not answer is repeated until the delivery's lease ends, as Hue stated it (30 seconds from the
284
+ lease).
285
+
286
+ `--forward-to` must name this machine: `localhost`, a `.localhost` name or a loopback address, and a
287
+ name is refused unless every address it resolves to is a loopback address. `--allow-remote-forward`
288
+ permits another host over HTTPS only, because every request carries the subscription's verification
289
+ token and a valid signature; plain HTTP still reaches only loopback addresses. Credentials and
290
+ fragments in the URL are refused. Output is one line per delivery (time, event id, retry number, the
291
+ bot's status and duration, and the state Hue recorded), never a body, a signature or a credential;
292
+ credentials in error messages are replaced with `[redacted]`.
293
+
294
+ Ctrl+C or `SIGTERM` stops pulling (a waiting pull is cancelled), finishes the deliveries in flight
295
+ and acknowledges them, then exits `0`; a second Ctrl+C abandons the forwards and acknowledgements
296
+ still in flight and exits `130`. Nothing is acknowledged before the bot answered or its window
297
+ closed, and a lease left unacknowledged lapses into a timeout that Hue retries on the provider's
298
+ schedule, so no event is lost and none is marked delivered unanswered. Network errors, `429`, `5xx`
299
+ and another open pull for the same subscription (a second `hue listen`) are retried with backoff up
300
+ to 30 seconds; a `Retry-After` can lengthen a wait to at most 60 seconds, never shorten it. Exit
301
+ codes: `0` stopped, `1` Hue refused the credential, the subscription (unknown, revoked, or one that
302
+ delivers to a request URL) or the pull itself (a redirect, another refusal or an unreadable answer),
303
+ `2` usage error, `130` interrupted twice.
203
304
 
204
305
  ## Local state and conflicts
205
306
 
@@ -305,7 +406,7 @@ node packages/sdk-typescript/scripts/verify-package.mjs --artifacts-dir .artifac
305
406
  # Set project to an existing supported fixture; use the same directory on resume.
306
407
  project=/absolute/path/to/supported-fixture
307
408
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
308
- --archive .artifacts/typescript/hue-run-sdk-0.9.0.tgz \
409
+ --archive .artifacts/typescript/hue-run-sdk-0.11.0.tgz \
309
410
  --origin https://STAGING_ORIGIN \
310
411
  --project "$project" --command setup \
311
412
  --evidence .context/setup-staging-before-claim.json
@@ -316,7 +417,7 @@ the private local handoff and finish the real browser claim, then reconcile the
316
417
 
317
418
  ```sh
318
419
  node packages/sdk-typescript/scripts/verify-setup-live.mjs \
319
- --archive .artifacts/typescript/hue-run-sdk-0.9.0.tgz \
420
+ --archive .artifacts/typescript/hue-run-sdk-0.11.0.tgz \
320
421
  --origin https://STAGING_ORIGIN \
321
422
  --project "$project" --command claim \
322
423
  --evidence .context/setup-staging-after-claim.json
@@ -351,8 +452,11 @@ the CLI never prints it. The optional `zod` peer of `@hue-run/sdk/evals` must be
351
452
  Write an adapter module that hands the case inputs and the world's tools or MCP connection to
352
453
  the real agent. The module exports `default` or `runMyAgent`; `context` is the SDK's
353
454
  `SimulationTargetContext` (`world`, `mcp`, `tools`, `config`, `item`, `executionId`,
354
- `environmentRunId`, `signal`; `world` carries the provider mirror URLs, the world token, `env` and
355
- `mcpConfig` where Hue's simulation gateway serves the world):
455
+ `environmentRunId`, `files`, `outputDirectory`, `signal`; `world` carries the provider mirror URLs,
456
+ the world token, `env` and `mcpConfig` where Hue's simulation gateway serves the world). When the
457
+ case also pins input files, `files` holds verified copies of its agent-visible ones (see
458
+ [Evaluate a document eval set](#evaluate-a-document-eval-set)); return
459
+ `withFiles(output, files)` from `@hue-run/sdk/evals` to upload documents the agent produced:
356
460
 
357
461
  ```ts
358
462
  // hue-agent.ts: erasable TypeScript only; Node.js 24 and Bun strip the types natively.
@@ -368,66 +472,136 @@ export default function runMyAgent(inputs: JsonValue, context: SimulationTargetC
368
472
  ```
369
473
 
370
474
  ```sh
371
- hue eval --case "Refund an eligible charge" ./hue-agent.ts --env-file .env.hue
372
- hue eval --case https://app.hue.run/projects/demo/scenarios/<id> ./hue-agent.ts --baseline <run id>
373
- hue eval --set "Billing regressions" --scorer-version <id> ./hue-agent.ts --save-version
475
+ hue eval --case "Refund an eligible charge" ./hue-agent.ts --content --env-file .env.hue
476
+ hue eval --case https://app.hue.run/projects/demo/scenarios/<id> ./hue-agent.ts --content --baseline <run id>
477
+ hue eval --set "Billing regressions" --scorer-version <id> ./hue-agent.ts --content --save-version
374
478
  hue eval --case "Refund an eligible charge" --command "python agent.py" --timeout 120 --content
375
- hue eval --worker ./hue-agent.ts --agent-key support-agent --env-file .env.hue
479
+ hue eval --worker ./hue-agent.ts --agent-key support-agent --content --env-file .env.hue
376
480
  ```
377
481
 
378
482
  `--scenario` remains an alias for `--case` for existing scripts. Pass one selection flag.
379
483
 
380
- The one-shot mode resolves the selection (`--case` by name, ID or URL; `--set` by name, ID or
381
- URL with explicit `--scorer-version` pins; or `--dataset-version` with `--scorer-version`),
484
+ The one-shot mode resolves the selection (`--case` by name, or by the ID or URL of the published
485
+ case or of the case conversion that published it, among the project's first 1,000 Scenarios;
486
+ `--set` by name, ID or URL with explicit `--scorer-version` pins; or `--dataset-version` with `--scorer-version`),
382
487
  creates a fresh run from those immutable pins named `<agent key> @ <revision>` (a commit hash is
383
488
  shortened to 7 characters)
384
489
  (`--name` overrides), prints `Run: <url>` and `Experiment: <id>` as soon as the experiment exists,
385
490
  one line per case event (world created, agent started, world sealed), then
386
491
  `Waiting for Hue checks...` and a table with one row per case: boolean metrics as `PASS`/`FAIL`,
387
492
  numbers as values, text and category metrics as-is, an overall result per case and a pass count.
388
- Failing cases print the scorer explanation. `--baseline <experiment id|url>` adds improvement,
493
+ Failing cases print the scorer explanation. An evaluator Hue records as not applicable to a case
494
+ (its outcome scoring has no outcome criteria or conversion rubric to grade there) shows `n/a` in
495
+ that case's columns and neither passes nor fails it, so in a mixed eval set a case is decided by
496
+ the evaluators that apply to it; the pass line counts the not-applicable results. A case that no
497
+ pinned evaluator applies to is an error that names what they need. A result of a pinned Hue judge
498
+ (an evaluator of kind `world_judge`) that Hue marks advisory, as it does every judge's today, is
499
+ shown in its column, marked `(advisory)`, but never decides a case or the exit code; the pass line
500
+ counts advisory failures as not counted, and a case only advisory results ran for is an error. Any
501
+ other evaluator's result, an error, or a metric that carries `passed` is never advisory, whatever
502
+ its evidence says. A metric name that
503
+ several evaluators report, such as two judges' `verdict`, gets a column per evaluator, labelled
504
+ with the version's first eight characters. `--baseline <experiment id|url>` adds improvement,
389
505
  regression and unchanged counts with per-case deltas. `--json` prints one JSON document
390
506
  (`experimentId`, `runId`, `runUrl`, `complete`, `cases`, `totals`, optional `baseline`) on stdout
391
- and sends progress to stderr. `--wait <seconds>` (default 300) bounds the verdict wait because
507
+ and sends progress to stderr; each case lists its not-applicable evaluator versions in
508
+ `notApplicable` and its advisory ones in `advisory`, and `totals.notApplicable` counts the
509
+ former; a case failed for its telemetry has `state: "error"`,
510
+ `passed: false` and
511
+ `telemetry: { code: "telemetry_not_accepted", issues: [{ signal, kind, status?, count }] }`. `--wait <seconds>` (default 300) bounds the verdict wait because
392
512
  Hue-owned `world_outcome` checks are graded after the world seals. An experiment always covers
393
513
  every case of the saved version; there is no case subset.
394
514
 
395
515
  `--command "<shell command>"` spawns the command once per case with the world's environment
396
516
  (`HUE_WORLD_ID`, `HUE_WORLD_TOKEN`, one `HUE_SIM_<SURFACE ID>_URL` per provider mirror,
397
517
  `HUE_MCP_CONFIG` naming an owner-only `mcpServers` file that is removed after the case, and
398
- `HUE_MCP_URL`, `HUE_MCP_TOKEN`, `HUE_MCP_EXPIRES_AT` for the first MCP mirror), plus
518
+ `HUE_MCP_URL`, `HUE_MCP_TOKEN`, `HUE_MCP_EXPIRES_AT` for the first MCP mirror; these name the
519
+ world's simulated MCP server and replace a `HUE_MCP_URL` that `hue login` stored for Hue's project
520
+ MCP server), plus
399
521
  `HUE_EXECUTION_ID`, `HUE_ENVIRONMENT_RUN_ID`, `HUE_CASE_ID` and `HUE_CASE_KEY`, and
400
522
  `{"inputs": ..., "config": ...}` on stdin. The child does not receive `HUE_API_KEY`, `HUE_MCP_KEY`
401
523
  or any other Hue control-plane credential unless `--allow-hue-credentials` is passed; the rest of
402
- the parent environment (model keys, application settings) is inherited. Its stdout is the answer
524
+ the parent environment (model keys, application settings) is inherited. `HUE_CASE_DIR`,
525
+ `HUE_CASE_INPUTS` and `HUE_CASE_OUTPUT_DIR` name a private case directory with the layout direct
526
+ cases use: the case inputs, verified copies of the case's agent-visible pinned files under
527
+ `files/<role>/`, and `output/`, whose files are uploaded after the world seals (the command still
528
+ starts in the current directory). The case directory never holds the world token and is removed
529
+ after the case. Its stdout is the answer
403
530
  (JSON when it parses, otherwise trimmed text; empty means no output); a non-zero exit or the
404
- per-case `--timeout` (default 600 seconds) is a target failure. The world token is never logged.
531
+ per-case `--timeout` (default 600 seconds) is a target failure. A timed-out or interrupted command
532
+ is stopped as a whole process group on macOS and Linux: SIGTERM, then SIGKILL for anything still
533
+ running 5 seconds later, including an agent a compound command started after its shell exited;
534
+ the case settles only once nothing in the group is left. A command that exits normally but leaves
535
+ processes in its group has them stopped the same way before its answer and files are read. A
536
+ second Ctrl+C during that grace kills the group at once, removes the world case's files, the MCP
537
+ configuration file and the checkpoint locks, and exits with 130. The world token is never logged.
405
538
  A world created while the deployment's simulation gateway is off gets the legacy `hue_sim_`
406
539
  capability under the same `HUE_MCP_*` names. The agent key defaults to the slug of the command's
407
540
  script name; `--revision` is sent to Hue as the agent revision of every world.
408
541
 
409
542
  `--worker` registers the adapter through `runLocalAgent()` with key `--agent-key` (default: the
410
543
  adapter filename slug), name `--agent-name` (default: the key), revision `--revision` (default:
411
- `AGENT_REVISION`, then the Git `HEAD` short hash, then `dev`) and capability `environment:v1`,
412
- prints the registration and each claimed run with its URL, executes runs launched from Hue until
413
- Ctrl+C or `--max-runs <n>`, and prints the verdict table after each run. Selection flags do not
414
- apply; Hue chooses the pinned experiment. The worker exits 0 when it stops normally.
544
+ `AGENT_REVISION`, then the Git `HEAD` short hash, then `dev`) and capability `environment:v1`
545
+ plus any repeated `--capability <value>`, prints the registration and each claimed run with its
546
+ URL, executes runs launched from Hue until Ctrl+C or `--max-runs <n>`, and prints the verdict table
547
+ after each run. Selection flags do not apply; Hue chooses the pinned experiment. The worker exits 0
548
+ when it stops normally. Hue offers a case whose world comes with agent-visible files only to a
549
+ registration that declares `environment-files:v1` and `input:<extension>` for each file type, for
550
+ example `--capability environment-files:v1 --capability input:pdf`; the adapter or command then
551
+ receives the files as described above. Hue keeps each registered revision's capabilities fixed,
552
+ so declare new ones under a new `--revision`. A world pinned to provider profiles, a Gmail world
553
+ for example, needs a reviewed `attemptBaselineV2` (see
554
+ [Pinned provider-profile preflight](ENVIRONMENTS.md#pinned-provider-profile-preflight)); `--worker`
555
+ registers none, so put `"attemptBaselineV2"` in the run's configuration JSON when you launch the
556
+ run from Hue.
415
557
 
416
558
  A Scenario or eval set whose dataset version is not saved cannot back an experiment: the command
417
559
  exits 1 and asks for **Save eval-set version** in Hue or `--save-version`, which freezes that
418
560
  version at its current revision. Connection settings are `HUE_API_KEY` and `HUE_BASE_URL`
419
561
  (default `https://app.hue.run`), loaded from `--env-file <path>` (or its alias `--env-path`) first when given; `--origin`
420
- overrides the origin. Telemetry content capture stays off unless `--content` is passed; in
421
- one-shot mode it also decides whether case outputs, error messages and explanations are persisted
422
- to Hue. `--worker` always persists them, because a run launched from Hue is read on its run page:
423
- that is `runLocalAgent()`'s contract and `--content` does not change it. Trace evidence is required
424
- for every case.
562
+ overrides the origin. Telemetry content capture stays off unless `--content` is passed; the
563
+ examples pass it so the run's case spans carry content. Model and tool spans inside the agent come
564
+ only from the agent's own instrumentation. Case outputs, error messages and explanations are
565
+ stored in Hue whether or not `--content` is passed, so a case's answer can be graded and read on
566
+ its run page. A `--command`'s stdout is its answer unless it writes a result file, and is stored,
567
+ so it must not print credentials or debug logs there; before storing, `hue eval` replaces the
568
+ credentials it handed the case (the world token and MCP headers, a legacy MCP token, attempt
569
+ bearers) and every Hue control-plane credential in its environment with `[redacted]`, in the
570
+ answer, an adapter's thrown message and name, generated file names and the local checkpoint.
571
+ `--no-output` keeps outputs, error messages and explanations out of a one-shot run's stored
572
+ results; with `--content` the case span still carries the output. `--worker` always stores them, because a run launched from Hue is read
573
+ on its run page (that is `runLocalAgent()`'s contract), and refuses `--no-output`. An interrupted
574
+ one-shot run keeps the choice it started with, so one run never mixes stored and unstored outputs:
575
+ rerunning it with other `--no-output` or `--content` flags is refused with the flags it started
576
+ with. Resume a run that an earlier SDK started without `--content` by passing `--no-output`.
577
+
578
+ Files the agent writes to `output/` are uploaded as generated documents with or without
579
+ `--no-output`, and they are not redacted the way the answer is: `hue eval` replaces the same
580
+ credentials only in the UTF-8 `.txt`, `.csv` and `.json` documents it collects, while PDF, Office
581
+ and image documents, and files an adapter returns by `path`, are uploaded as written. A direct
582
+ case also keeps the agent's raw `output/` files on disk under its checkpoint directory
583
+ (`.hue/eval/<agent-key>/<project>/direct/<experiment>/files/`). Never write credentials to
584
+ `output/`.
585
+
586
+ Trace evidence is required for every case: when Hue does not accept a case's traces or logs, the
587
+ case is completed as failed (error `TelemetryNotAccepted`, evidence omitted as
588
+ `telemetry_not_accepted`, no output or generated files attached) instead of being left started,
589
+ and the run goes on. Each export of the CLI's own telemetry may take 30 seconds, retries
590
+ included: a slow acknowledgement is waited for, and a 429, 502, 503, 504 or network error is
591
+ retried within that time, honouring `Retry-After`; any other response, or rejected records, fails
592
+ the case at once. Stderr names the case as it completes, with the export
593
+ issue counts, for example
594
+ `[refund] telemetry not accepted, case failed: telemetry_not_accepted: traces failed 1 (HTTP 400)`;
595
+ the case counts as an error in the table and JSON whatever its scores, and the command exits 1.
596
+ Counts carry signals, kinds, HTTP statuses and record numbers only, never content or credentials.
425
597
  Resumable checkpoints live in `.hue/eval/<agent-key>/<project id>/` (a `.gitignore` is written
426
598
  inside `.hue/eval/`); `--checkpoint-dir` overrides the root. Rerunning the same selection resumes
427
599
  an interrupted run without invoking the agent again; a different selection is refused until the
428
600
  unfinished one is resumed or its directory is removed.
429
601
 
430
- Exit codes: `0` every case passed, `1` a case failed, errored, was skipped or Hue's checks were
602
+ Exit codes: `0` every case passed the evaluators that apply to it (advisory ones never count),
603
+ `1` a case failed, errored (including one no pinned evaluator applies to or decides), was skipped
604
+ or Hue's checks were
431
605
  still pending at `--wait`, `2` usage or configuration error (including a missing key), `130`
432
606
  interrupted. On Node.js 22, load TypeScript adapters with `NODE_OPTIONS=--experimental-strip-types`;
433
607
  non-erasable syntax (enums, parameter properties, namespaces) needs a loader such as `--import tsx`
@@ -445,8 +619,8 @@ published version, beside or instead of explicit `--scorer-version` IDs.
445
619
  ```sh
446
620
  hue eval --set gia-d1-citation --scorer gia-d1-citation \
447
621
  --command "pnpm --filter @august/frontend run hue:gia-agent" \
448
- --revision prompt-v10 --wait 1800 --json --env-file .env.hue
449
- hue eval --set gia-d1-citation --set-version 1 --scorer gia-d1-citation ./hue-agent.ts --baseline <experiment id>
622
+ --revision prompt-v10 --wait 1800 --content --json --env-file .env.hue
623
+ hue eval --set gia-d1-citation --set-version 1 --scorer gia-d1-citation ./hue-agent.ts --content --baseline <experiment id>
450
624
  ```
451
625
 
452
626
  For each case the command is spawned once **inside a private case directory** with
@@ -460,6 +634,11 @@ For each case the command is spawned once **inside a private case directory** wi
460
634
  <case dir>/output/ write the generated documents here
461
635
  ```
462
636
 
637
+ Hue verifies each generated document before the case completes. Once a document is reserved and
638
+ its bytes are uploaded, a verification that outlasts a request is waited out for up to three
639
+ minutes, so a completion that cannot reach Hue takes that long to fail; a reservation or upload
640
+ that cannot reach Hue still fails at once.
641
+
463
642
  Every regular file the command leaves under `output/` is uploaded as a generated document
464
643
  (accepted: `.pdf .docx .pptx .xlsx .json .txt .csv .png .jpg .jpeg .webp`; another extension or an
465
644
  empty file is the case's error). Optional helpers: `manifest.json` (`{"primary": "<filename>",
@@ -469,6 +648,17 @@ the command's stdout is the output (JSON when it parses). A single generated fil
469
648
  document by default. Model and provider credentials stay in the command's own environment; the
470
649
  scoped case files are copies under `--checkpoint-dir` (default `.hue/eval/<agent-key>/<project>/direct/<experiment>`).
471
650
 
651
+ The same collection serves world cases, and it does not trust paths the agent controls. `output/`
652
+ must be a real directory, and a helper name, when present, a regular file; a symlink, FIFO or
653
+ directory in either place is the case's error. Symlinks and other special files among the
654
+ documents are skipped, as are hidden and lock entries. Every file is opened without following a
655
+ final symlink or blocking, must be the regular file the listing saw and within its limit (4 MiB
656
+ for a helper, 25 MiB for a document, checked before reading), and its upload is staged from the
657
+ bytes read. A file or directory that vanished or changed while it was collected, more than 32
658
+ documents or more than 1024 entries is the case's error too. A hard link is an ordinary file and
659
+ is collected like one: an agent running as your user could copy the file just as well, so run an
660
+ agent that must not reach your files under another account or in a sandbox.
661
+
472
662
  An adapter file works too: it is called with `(inputs, context)` where `context.mode` is
473
663
  `"direct"` and `context` carries `config`, `item`, `executionId`, `files` (agent-visible pinned
474
664
  files on disk), `outputDirectory` and `signal`. Return a JSON value, or `withFiles(output, files)`
@@ -478,5 +668,6 @@ Code evaluators pinned to the run are **not** executed on your machine: the CLI
478
668
  deferred (`deferUnboundLocalScorers`) and Hue's grading executor scores the uploaded documents;
479
669
  evaluator-only pinned files such as a legal corpus are never downloaded. `Waiting for Hue
480
670
  checks...` then covers that grading, so size `--wait` to the evaluator's runtime. The
481
- `--json` document gains `"mode": "direct"` and `"deferredScorerVersionIds"`. Exit codes, `--baseline`
482
- and checkpoints behave as for Scenarios.
671
+ `--json` document gains `"mode": "direct"` and `"deferredScorerVersionIds"`. Exit codes, `--baseline`,
672
+ `--content`, `--no-output` and checkpoints behave as for Scenarios; generated documents are
673
+ uploaded with or without them.
package/ENVIRONMENTS.md CHANGED
@@ -62,7 +62,7 @@ to the callback and is never written to checkpoints.
62
62
 
63
63
  ### Published pins and verdicts
64
64
 
65
- `scenario: { kind: "pins", datasetVersionId, scorerVersionIds, config?, name? }` runs already
65
+ `definition: { kind: "pins", datasetVersionId, scorerVersionIds, config?, name? }` runs already
66
66
  published immutable pins, such as a Scenario's frozen case and Hue-owned outcome checks or a saved
67
67
  eval set with explicitly chosen scorer versions. `resolveScenarioPins(client, selector)` reads
68
68
  those pins from a Scenario ID, its Hue URL or its name (`listScenarios` and `getScenario` expose
@@ -76,9 +76,15 @@ bound `local_code` pins.
76
76
  Hue grades `world_outcome` pins after the world seals, so the runner's report precedes the
77
77
  verdicts. `waitForResults(client, { runId, scorerVersionIds, subjectIds, timeoutMillis })` polls
78
78
  until every item has a terminal result for every pin or the budget elapses (`complete: false`);
79
- `summarizeVerdicts` turns results into per-case rows with `passed` and totals; `compareVerdicts`
80
- diffs two summaries by case key; and `collectExperimentVerdicts` combines those reads for one
81
- experiment. The `hue eval` command uses the same path; see
79
+ `summarizeVerdicts` turns results into per-case rows with `passed` and totals; a result Hue marks
80
+ `notApplicable` (the evaluator has no outcome criteria or conversion rubric to grade in that case)
81
+ neither passes nor fails its case and is listed in the row's `notApplicable`, and a case no pinned
82
+ evaluator applies to is an error. A result of a Hue judge listed in `waitForResults`'
83
+ `judgeScorerVersionIds` (`collectExperimentVerdicts` passes the experiment's `world_judge` pins)
84
+ whose evidence says `advisory: true` keeps its metrics in the row and is listed in its
85
+ `advisory`, but never decides the case; a case only advisory results ran for is an error. Any
86
+ other result, an error, or one with a metric carrying `passed` is never advisory. `compareVerdicts` diffs two summaries by case key, and
87
+ `collectExperimentVerdicts` combines those reads for one experiment. The `hue eval` command uses the same path; see
82
88
  [Evaluate an agent against a case](CLI.md#evaluate-an-agent-against-a-case).
83
89
 
84
90
  ### Pinned provider-profile preflight
@@ -223,6 +229,8 @@ its own Gmail MCP or REST client, the mirror URL, the world token where the Goog
223
229
  import {
224
230
  agentEnvironment,
225
231
  createEnvironmentClient,
232
+ HueEnvironmentError,
233
+ isTransientEnvironmentError,
226
234
  worldHandoff,
227
235
  writeMcpConfig,
228
236
  } from "@hue-run/sdk/environment";
@@ -252,12 +260,22 @@ try {
252
260
  );
253
261
  const sealDeadline = Date.now() + 30_000;
254
262
  let sealed = false;
255
- while (Date.now() < sealDeadline) {
256
- if ((await environmentClient.getRun(run.id)).status !== "open") {
257
- sealed = true;
258
- break;
263
+ while (!sealed && Date.now() < sealDeadline) {
264
+ let wait = 250;
265
+ try {
266
+ // The deadline also ends a read that hangs, with the client's retries inside it.
267
+ const state = await environmentClient.getRun(run.id, {
268
+ signal: AbortSignal.timeout(Math.max(0, sealDeadline - Date.now())),
269
+ });
270
+ sealed = state.status !== "open";
271
+ } catch (error) {
272
+ if (!isTransientEnvironmentError(error)) throw error; // a refusal such as 404 is final
273
+ wait = Math.max(wait, (error as HueEnvironmentError).retryAfterMs ?? 0);
259
274
  }
260
- await new Promise((resolve) => setTimeout(resolve, 250));
275
+ if (!sealed)
276
+ await new Promise((resolve) =>
277
+ setTimeout(resolve, Math.min(wait, Math.max(0, sealDeadline - Date.now()))),
278
+ );
261
279
  }
262
280
  if (!sealed) throw new Error("World was not sealed after its completion grace");
263
281
  }
@@ -272,6 +290,27 @@ world token, so an adapter that read `HUE_MCP_URL` and `HUE_MCP_TOKEN` keeps wor
272
290
  no Hue-native `tools` (Hue refuses them); a world created while the gateway is off keeps its tools
273
291
  and the `hue_sim_` capability and emits a one-time `DeprecationWarning`.
274
292
 
293
+ A case pinned to a world may also pin input files. Before the case's execution starts, the
294
+ helpers download each agent-visible file (`source`, `attached_template`, `attached_reference`,
295
+ `original`), verify its size and SHA-256 against the case manifest and hand the verified copies to
296
+ the callback as `context.files` beside `context.world`, in a private directory (mode 0700, files
297
+ 0600), with a private `context.outputDirectory`. Evaluator-only files (`org_template`,
298
+ `evaluator_reference`) never reach the agent; a local code evaluator's copies are checked before
299
+ the execution starts but saved only after the agent finished. Bytes that differ from the manifest raise
300
+ `CaseFileError` with code `case_file_mismatch`; a name that is not one safe file name (a path
301
+ separator, `.` or `..`, a C0 or C1 control character, a character Windows reserves such as `:` or
302
+ `?`, a Windows device name such as `CON`, a trailing dot or space, more than 200 bytes) raises
303
+ `case_file_name_refused`. Either stops the run before an
304
+ execution or world exists for the case. Return `withFiles(output, files)` to upload what the agent
305
+ produced: the files are published and linked to the execution as `artifactIds` and
306
+ `primaryArtifactId`, within the limits of direct cases (at most 32 files, 25 MiB each, the
307
+ accepted document types). The world token is never written into the case directory, which is
308
+ removed when the case ends; only staged outputs an interrupted upload resumes from are kept until
309
+ it does. `hue eval --command` receives the same files through
310
+ `HUE_CASE_DIR`, `HUE_CASE_INPUTS` and `HUE_CASE_OUTPUT_DIR`. A `runLocalAgent` worker is offered
311
+ such cases only when it declares `environment-files:v1` and `input:<extension>` for each file
312
+ type; see [EVALUATIONS.md](EVALUATIONS.md#direct-cases-and-files).
313
+
275
314
  A refusal such as 409 `simulation_gateway_required` exposes its validated server code as `HueEnvironmentError.diagnostic`.
276
315
 
277
316
  `agentEnvironment` removes Hue control-plane credentials from the child by default: `HUE_API_KEY`,