From 86a3471644cdf0975bb52333e92489b6a0374498 Mon Sep 17 00:00:00 2001 From: Miyoung Choi Date: Mon, 3 Aug 2026 18:14:17 -0700 Subject: [PATCH 1/2] docs: correct observability and optimization guides Signed-off-by: Miyoung Choi --- docs/agents/insight-driven-optimization.mdx | 304 +++++++++++--------- docs/agents/observability.mdx | 154 ++++++---- docs/evaluator/experiments.mdx | 247 ++++++++++------ 3 files changed, 416 insertions(+), 289 deletions(-) diff --git a/docs/agents/insight-driven-optimization.mdx b/docs/agents/insight-driven-optimization.mdx index e902cbc3dc..0b9b77ae24 100644 --- a/docs/agents/insight-driven-optimization.mdx +++ b/docs/agents/insight-driven-optimization.mdx @@ -1,7 +1,8 @@ --- title: "Insight-Driven Optimization" -description: "" +description: "Turn agent telemetry into evidence-backed Insights, experiments, evaluation suites, and validated changes." --- + Insight-driven optimization automates the agent improvement loop. It turns @@ -19,13 +20,13 @@ The system introduces one first-class entity and a family of agents that operate on it: - The **Insight** is a named, persistent description of a recurring problem in - the agent under test (AUT), backed by the traces that evidence it. The + the agent under test (AUT), backed by the traces that provide evidence. The Insight is the unit of action across the whole loop. An experiment or an evaluation-suite change can always be traced back to the Insight that motivated it. - The **Analyst** turns raw traces into actionable Insights. - The **Experimenter** turns a single Insight into an empirically validated - pull request. + pull request (PR). - The **Eval Author** builds and extends evaluation suites in response to real-world usage. @@ -40,18 +41,18 @@ want to automate portions of the agent improvement loop. - A NeMo Platform deployment (local or remote) with Intake enabled, so the Analyst has traces to read. -- The optimization plugins installed in the same Python environment as the - NeMo CLI. From a source checkout, `uv sync` installs all three through the - default `enabled-plugins` group. The Experimenter and Eval Author - plugins require Python earlier than 3.14. +- The optimization plugins installed in the same Python environment as the NeMo command-line + interface (CLI). From a source checkout, `uv sync` installs all three through the default + `enabled-plugins` group. The Experimenter and Eval Author plugins require Python earlier than 3.14. - Access to the code base for the agent under test. The Experimenter needs a change surface (source, prompts, tool definitions, model selection, or - runtime config) and a way to run or evaluate the agent reproducibly. + runtime configuration) and a way to run or evaluate the agent reproducibly. - Traces in Intake for the agent under test. The Analyst diagnoses only what it can observe, so an agent with no telemetry cannot be analyzed. -- Model access for the optimization agents themselves. The Analyst and the - Experimenter each drive their own LLMs, which is separate from the model - access your AUT needs at runtime. You can run any LLM you want here. +- Model access for the optimization agents themselves. The Analyst uses Claude Opus 4.8 through the + NVIDIA Inference Gateway. The Experimenter and Eval Author use configurable large language models + (LLMs) through an OpenAI-compatible endpoint. This access is separate from the model access that + your AUT needs at runtime. - Train and validation datasets in a Harbor-compatible layout, plus a task template and an output directory for artifacts. This is what the Eval Author works from and what the Experimenter uses to validate changes. @@ -83,9 +84,9 @@ dependencies: - All Insights service endpoints are workspace-scoped under `/apis/insights/v2/workspaces/{workspace}/` and depend on platform auth. The service checks workspace access before reading or writing. -- The plugins register themselves with the platform through entry points - (CLI, service, controller, job, SDK, skills). For a fully local platform, - restart `nemo services run` after installing so the platform discovers them. +- The plugins register themselves with the platform through CLI, service, controller, job, software + development kit (SDK), and skill entry points. For a fully local platform, restart + `nemo services run` after installation so the platform discovers them. ### The Shared Profile @@ -140,34 +141,34 @@ Use the optimization agents when you want to: - **Keep evaluation suites relevant.** Grow datasets and metrics in response to the failures your agent actually exhibits. -Production traffic is the best fuel. New agents can start with staged replays -or generated cases and improve coverage as real traffic lands. +Production traffic provides useful evidence. New agents can start with staged replays +or generated cases and improve coverage as production traffic arrives. ### Core Concepts and Data Model -**The Insight.** +#### The Insight -The first-class entity of the loop. An Insight is a -persistent, named description of a recurring problem, stored in the platform -entity store with these fields: +An Insight is the first-class entity of the loop. It is a persistent, named description of a +recurring problem that is stored in the platform entity store with these fields: | Field | Type | Meaning | |-------|------|---------| | `title` | string | A short, human-readable sentence naming the core issue common to the linked traces. | | `description` | string | A paragraph describing the problem statement and the situations in which the failure pattern is observed. | | `agent` | string | The registered agent name the Insight is about. | -| `status` | enum | `open` (default), `resolved`, or `deleted`. An Insight starts open; you resolve it when fixed, or delete it if it isn't a real problem. | -| `trace_refs` | list[string] | Intake trace IDs the Analyst cited as evidence. Drives the evidence view in the UI and lets the loop find similar traces. | +| `status` | enum | `open` (default), `resolved`, or `deleted`. An Insight starts open; you resolve it when fixed, or delete it if it is not a real problem. | +| `trace_refs` | list[string] | Intake trace identifiers (IDs) the Analyst cited as evidence. Drives the evidence view in the user interface (UI) and lets the loop find similar traces. | The store assigns `id`, `created_at`, and `updated_at`. The Analyst aims for at least three representative traces as evidence before filing a new Insight, and when it finds more evidence for an existing Insight it *appends* trace refs rather than restating the problem. -**Insight persistence: platform versus local file.** +#### Insight Persistence: Platform Versus Local File When no profile is -discovered, the Analyst reads and writes Insights through the Insights API. +discovered, the Analyst reads and writes Insights through the Insights application programming +interface (API). When a profile governs the run, the Analyst reads and writes the shared local file at `/.nemo-optimizer/insights.yaml` instead, which is the same default the Experimenter reads. Pass `--insights-file-output ` @@ -177,16 +178,16 @@ deployments that host Intake data but do not have the Insights plugin installed. Each run merges into the file (de-duplicating trace refs) rather than overwriting it. -**Telemetry hierarchy.** +#### Telemetry Hierarchy The loop inherits Intake's model: a span is one timed operation (LLM call, tool call, and so on), a trace is one end-to-end run, and a session groups related traces. Insight evidence is cited at the trace level. -**Experiment entities.** +#### Experiment Entities An optimization run is tracked as an `ExperimentRun` -(agent, insight, config snapshot, status, rounds completed, winner, summary). +(agent, insight, configuration snapshot, status, rounds completed, winner, summary). ## The Agents @@ -195,16 +196,16 @@ An optimization run is tracked as an `ExperimentRun` The Analyst reads telemetry from Intake and emits Insights. It runs as a single reasoning agent with a set of read-only tools over Intake: -- `fetch_spans` — survey spans, either grouped (for example by `session_id`, +- `fetch_spans`: Survey spans, either grouped (for example by `session_id`, to fan out across many runs) or flat (to drill into one session). Filters include agent, status, span kind, model, provider, tool name, dataset, and time range. -- `get_span` — fetch a single span by ID. -- `fetch_scores` — read evaluator results (verifier and judge outputs) +- `get_span`: Fetch a single span by ID. +- `fetch_scores`: Read evaluator results (verifier and judge outputs) attached to a span. -- `fetch_annotations` and `get_annotation` — read feedback, labels, notes, and +- `fetch_annotations` and `get_annotation`: Read feedback, labels, notes, and metadata. Negative feedback is the strongest starting signal. -- `list_insights` — read existing Insights so findings are de-duplicated +- `list_insights`: Read existing Insights so findings are de-duplicated against what is already filed. Its method is to survey sessions broadly, gather evidence (starting from @@ -219,21 +220,21 @@ intended behavior. The Experimenter turns an Insight into an empirically validated candidate. Internally it is an evolutionary optimization loop that runs in rounds: -1. **Baseline** — build the baseline agent (`agent-0`) and evaluate it on the +1. **Baseline**: Build the baseline agent (`agent-0`) and evaluate it on the validation split; build an initial goal tree (a weighted capability rubric used for trajectory scoring). -2. **Analyze** — read the target Insight and perform root cause analysis. -3. **Propose** — generate a small number of candidate improvements targeting +2. **Analyze**: Read the target Insight and perform root cause analysis. +3. **Propose**: Generate a small number of candidate improvements targeting those root causes, each tagged with an optimization type. -4. **Implement** — a coding agent applies each proposed change to a copy of - the agent and runs an integration smoke test with a bounded repair loop. -5. **Validate** — score new candidates on the held-out validation split, +4. **Implement**: A coding agent applies each proposed change to a copy of + the agent and runs a basic integration test with a bounded repair loop. +5. **Validate**: Score new candidates on the held-out validation split, optionally adding a qualitative trajectory score against the goal tree. -6. **Select and continue** — keep a diverse Pareto front of survivors and +6. **Select and continue**: Keep a diverse Pareto front of survivors and iterate until a budget or convergence condition is met, then pick the winner. -**Insight mode.** +#### Insight Mode The Experimenter starts from a single Insight. By default it reads the local `.nemo-optimizer/insights.yaml` beside the profile; @@ -243,12 +244,12 @@ title, or zero-based index. The agent referenced by the Insight is used unless `--agent` overrides it. The Eval Author step builds an Insight-specific evaluation suite before optimization begins, which requires a task template. -**Dataset mode.** +#### Dataset Mode Pass `--no-insight` to bypass both an explicit Insight and the profile-local default and optimize directly against a dataset. -**Train and validation isolation.** +#### Train and Validation Isolation The validation split is *hidden* during candidate generation. At run start the validation data is moved into a @@ -257,7 +258,7 @@ blocks reads of that path. Candidates therefore cannot be tuned against the data they are later scored on. Validation data is temporarily restored only when validation scoring runs. -**Evaluation and rewards.** +#### Evaluation and Rewards Candidates are scored with the Harbor evaluator. Each trial yields metrics from the verifier, and the aggregate reward is the @@ -266,22 +267,26 @@ to `[0.0, 1.0]`, where 1.0 is perfect. Live and production datasets often carry no verifiable reward, so validation on those relies on curated metrics and trajectory scoring rather than a ground-truth verifier. -**Output.** +#### Output -Everything lands under `/eval-and-optimize/`: the +Output is written under `/eval-and-optimize/`: the run record, per-candidate agent code and metadata, per-round analysis and goal -trees, and evaluator results. When configured with a git source, the -Experimenter can archive candidate branches and open a draft PR or MR for -the winning candidate against the baseline ref. +trees, and evaluator results. When configured with a Git source, the +Experimenter can archive candidate branches and open a draft PR or merge request (MR) for +the winning candidate against the baseline reference. ### Eval Author -The Eval Author builds and maintains the evaluation suites the loop depends -on. Given an Insight and its evidence traces, it creates an Insight-specific -evaluation suite that can be used to validate optimized candidates aimed at -resolving that Insight. It runs as a library-only plugin that the -Experimenter invokes in Insight mode; it is configured through the -`eval_author` section of the experiment config. +The Eval Author builds and maintains the evaluation suites that the loop uses. +Given an Insight and its evidence traces, it creates an Insight-specific +evaluation suite for validating candidates that address the Insight. The +Experimenter invokes the Eval Author workflow in Insight mode and reads the +`eval_author` section of the experiment configuration. + +The plugin also exposes the canonical `nemo agents eval-author` command +namespace with `discover`, `audit`, `propose`, `run`, and `doctor` verbs. These +standalone verbs are currently scaffolding and exit with a nonzero status until +their implementations are available. ## Get Started @@ -294,12 +299,14 @@ deployment. ```bash export NMP_BASE_URL=http://localhost:8080 export WORKSPACE=default -export AGENT= # Must have traces in Intake +export NMP_STUDIO_URL="$NMP_BASE_URL/studio" +export NMP_ACCESS_TOKEN="$(nemo auth token)" +export AGENT=your-agent-name # This agent must have traces in Intake. -# The Analyst's model reads a gateway virtual key: +# The Analyst uses Claude Opus 4.8 through NVIDIA Inference Gateway. export INFERENCE_API_KEY=sk-... -# The Experimenter and Eval Author drive their own endpoint: +# The Experimenter and Eval Author use an OpenAI-compatible endpoint. export NEMO_EXPERIMENTALIST_API_BASE=https://inference-api.nvidia.com/v1 export NEMO_EXPERIMENTALIST_API_KEY=sk-... export NEMO_EXPERIMENTALIST_MODELS_SMART=openai/openai/openai/gpt-5.6-sol @@ -307,33 +314,38 @@ export NEMO_EXPERIMENTALIST_MODELS_MID=openai/openai/openai/gpt-5.6-terra export NEMO_EXPERIMENTALIST_MODELS_FAST=openai/openai/openai/gpt-5.6-luna ``` +Set `NMP_STUDIO_URL` explicitly if Studio uses a different origin or path. +For an authenticated remote deployment, log in against that base URL before +requesting the token. For local development with authentication disabled, omit +the `NMP_ACCESS_TOKEN` command and the `Authorization` header in the API example. + Confirm the plugins are installed and discoverable: ```bash -nemo insights --help -nemo experimentalist --help +nemo agents analyst --help +nemo agents experimentalist --help +nemo agents eval-author --help ``` From an agent directory with an `optimizer.yaml` profile, check that the effective inputs and credentials resolve: ```bash -nemo insights doctor -nemo experimentalist doctor +nemo agents analyst doctor +nemo agents experimentalist doctor ``` -**Write traces to Intake.** +### Send Traces to Intake -The Analyst depends on Intake as its -observability store, so your agent must have traces in Intake before it can -generate Insights. +The Analyst depends on Intake as its observability store. Your agent must have traces in Intake +before it can generate Insights. ### Generate Insights Run the Analyst against the target agent's traces: ```bash -nemo insights analyze \ +nemo agents analyst run \ --agent "$AGENT" \ --workspace "$WORKSPACE" \ --base-url "$NMP_BASE_URL" @@ -341,37 +353,49 @@ nemo insights analyze \ Useful flags: -- `--agent-spec AGENT-SPEC.md` — append a spec so the Analyst can flag +- `--agent-spec AGENT-SPEC.md`: Append a spec so the Analyst can flag divergence from intended behavior. -- `--insights-file-output tmp/insights.yaml` — read and write Insights from a +- `--insights-file-output tmp/insights.yaml`: Read and write Insights from a specific local YAML file. Trace reads still hit `--base-url`. -- `--verbose` or `-v` — stream the Analyst's tool calls and reasoning to - stderr. +- `--verbose` or `-v`: Stream the Analyst's tool calls and reasoning to + standard error. -**Confirm it worked.** +### Confirm It Worked -List the Insights the Analyst filed through the API: +The persistence target depends on whether the Analyst discovered an +`optimizer.yaml` profile. + +For a profile-based run, confirm that the local Insights file exists and +contains the generated records: ```bash -curl "$NMP_BASE_URL/apis/insights/v2/workspaces/$WORKSPACE/insights?agent=$AGENT&page=1&page_size=20" +test -s .nemo-optimizer/insights.yaml +sed -n '1,120p' .nemo-optimizer/insights.yaml ``` -Or view them in Studio at -`http://localhost:8080/studio/workspaces/default/optimizer`. +If no profile was discovered and you did not pass `--insights-file-output`, +list the Insights stored through the API: -**What good looks like:** +```bash +curl --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/insights/v2/workspaces/$WORKSPACE/insights?agent=$AGENT&page=1&page_size=20" +``` -at least one Insight appears for the agent, each with -a clear `title`, an actionable `description`, and `trace_refs` pointing at real -Intake traces. +API-stored Insights also appear in Studio at +`$NMP_STUDIO_URL/workspaces/$WORKSPACE/optimizer`. Profile-based Insights do +not appear there because they are stored in the local YAML file. + +A successful result contains at least one Insight for the agent. Each Insight +has a clear `title`, an actionable `description`, and `trace_refs` that point +to Intake traces. ### Run an Experiment -**Convert evaluations to Harbor.** +#### Convert Evaluations to Harbor -The Experimenter validates its work by -running evaluations against your agent so that it only keeps empirically -validated optimizations. The only supported evaluation backend today is +The Experimenter validates its work by running evaluations against your agent. It only keeps +empirically validated optimizations. The only supported evaluation backend today is [Harbor](https://www.harborframework.com/). Before you can run the Experimenter, express your agent's evaluation as @@ -382,13 +406,13 @@ this: ```text / - task.toml # resources, timeouts, and env passthrough (e.g. INFERENCE_API_KEY) - instruction.md # the prompt given to the agent, incl. where to write output + task.toml # resources, timeouts, and environment variable passthrough + instruction.md # the prompt given to the agent, including where to write output environment/ Dockerfile # a container that can run your agent (and the verifier) tests/ - test.sh # verifier entry point -> writes /logs/verifier/reward.json - score.py # your scorer(s) — e.g. LLM-as-judge + test.sh # verifier entry point that writes /logs/verifier/reward.json + score.py # one or more scorers, such as an LLM-based judge ``` The Experimenter relies on a classic train/test split for validation, so @@ -401,7 +425,7 @@ Two things to know about the Harbor integration: `WrappedAgent(BaseAgent)`. Its `setup()` uploads your agent directory to `/app` and runs `uv sync` on it. Its `run()` executes the agent with the task instruction as `--prompt`. -- *How eval scores reach Intake.* The Analyst and the Experimenter both +- *How evaluation scores reach Intake.* The Analyst and the Experimenter both read scores from Intake, but neither Harbor nor the verifier pushes scores there; the verifier's reward is written to local disk by default. The Experimenter uploads scores to Intake after the Harbor run completes, and @@ -409,12 +433,12 @@ Two things to know about the Harbor integration: the trace in Intake. Pushing results to Intake requires a platform client, so pass `--base-url` and `--workspace`. -**Kick off the Experimenter.** +#### Start the Experimenter Hand it a single Insight: ```bash -nemo experimentalist run \ +nemo agents experimentalist run \ --insight \ --agent \ --train-dataset ./harbor_eval/dataset/train \ @@ -422,27 +446,27 @@ nemo experimentalist run \ --task-template ./task-template \ --config \ --experiment-dir tmp/experiment \ - --workspace default \ - --base-url http://localhost:8080 + --workspace "$WORKSPACE" \ + --base-url "$NMP_BASE_URL" ``` From an agent directory with an `optimizer.yaml` profile, most of those flags come from the profile: ```bash -nemo experimentalist run +nemo agents experimentalist run ``` The Experimenter's own models are configured through environment variables -(see [Models](#models)). Train and validation datasets are required, and for -the Harbor evaluator they must be local paths. The loop runs a baseline eval, +(refer to [Models](#models)). Train and validation datasets are required, and they must be local +paths for the Harbor evaluator. The loop runs a baseline evaluation, performs root cause analysis, implements changes, then validates and picks a winner. -**Review the results.** +#### Review the Results View the experiment in Studio at -`http://localhost:8080/studio/workspaces/default/experiment`. There you can see +`$NMP_STUDIO_URL/workspaces/$WORKSPACE/experiment`. There you can see the originating Insight and compare evaluation runs across all the candidates. The Experimenter also writes intermediate artifacts along the way. Those @@ -450,18 +474,18 @@ land under `/eval-and-optimize/`, which defaults to `/.nemo-optimizer/experiments/` when a profile governs the run, and `./tmp` otherwise: -- `OPTIMIZATION.md` — the report with per-agent breakdowns and round-by-round +- `OPTIMIZATION.md`: The report with per-agent breakdowns and round-by-round root causes. -- `agents/agent-0` — the baseline, with `agents/agent-N` as the candidates. +- `agents/agent-0`: The baseline, with `agents/agent-N` as the candidates. Each holds full agent source. -- `results/` — the per-agent evaluation outputs. +- `results/`: The per-agent evaluation outputs. ## Additional Workflows ### Opt an Agent into Periodic Analysis -Instead of running `analyze` by hand, opt an agent in and let the platform run -the Analyst on a schedule: +Instead of running `nemo agents analyst run` manually, opt an agent in and let +the platform run the Analyst on a schedule: ```bash nemo insights analysis enable --agent "$AGENT" --workspace "$WORKSPACE" @@ -470,15 +494,16 @@ nemo insights analysis disable --agent "$AGENT" --workspace "$WORKSPACE" ``` A framework controller (`insights-analysis`) reconciles on a fixed 60-second -loop, finds every agent opted in across workspaces, and submits one analyze job +loop, finds every agent opted in across workspaces, and submits one Analyst job per agent that is *due* and has enough new telemetry. Runs are incremental: each successful run records a cursor (`last_successful_run_at`), and subsequent runs only consider traces newer than that cursor. ### Configure the Analysis Schedule -The global schedule lives in the Insights plugin config (or environment). -Defaults shown: +The global schedule lives in the Insights plugin configuration or environment. The `timezone` +setting uses an Internet Assigned Numbers Authority (IANA) time zone name and defaults to +Coordinated Universal Time (UTC). ```yaml insights: @@ -487,34 +512,36 @@ insights: frequency: daily # daily (default) or weekly run_at_hour: 0 # local hour-of-day, 0-23 run_on_weekday: monday # only used when frequency: weekly - timezone: UTC # IANA name; e.g. America/Denver + timezone: UTC # IANA name, such as America/Denver job_profile: default # jobs execution profile for scheduled runs ``` -`run_at_hour` is interpreted in `timezone` (an IANA name) and converted to the +`run_at_hour` is interpreted in `timezone` and converted to the server clock at evaluation time, so runs fire at the intended local hour even across daylight-saving transitions. The controller still reconciles every 60 -seconds and submits a run once the scheduled daily or weekly window is reached. +seconds and submits a run after the scheduled daily or weekly window is reached. ## Command Reference -The Analyst lives under `nemo insights` and the Experimenter lives under -`nemo experimentalist`. They are separate command namespaces that share the -`optimizer.yaml` profile, and each validates its own part of it. +The Analyst lives under `nemo agents analyst`, and the Experimenter lives under +`nemo agents experimentalist`. The Eval Author command surface lives under +`nemo agents eval-author`. These command namespaces share the `optimizer.yaml` +profile, and each validates its own configuration. Scheduled analysis remains +under `nemo insights analysis`. -### `nemo insights analyze` +### `nemo agents analyst run` -Run the Analyst once against an agent's traces. +Run the Analyst for one analysis of an agent's traces. | Flag | Required | Default | Description | |------|----------|---------|-------------| | `--agent` | yes, unless a profile supplies it | profile `agent` | Agent under test the Analyst should focus on. | -| `--agent-spec` | no | profile `agent_spec`, else `AGENT-SPEC.md` or `README.md` beside the profile | Path to a markdown spec for the AUT. | +| `--agent-spec` | no | profile `agent_spec`, else `AGENT-SPEC.md` or `README.md` beside the profile | Path to a Markdown spec for the AUT. | | `--workspace` | no | profile `workspace`, else `default` | Workspace to operate in. | | `--base-url` | no | `NMP_BASE_URL`, else `http://localhost:8080` | Running platform instance the Analyst's tools call. | -| `--profile` | no | discovered by walking up from cwd | Path to `optimizer.yaml`. | +| `--profile` | no | discovered by walking up from the current working directory | Path to `optimizer.yaml`. | | `--insights-file-output` | no | `/.nemo-optimizer/insights.yaml` when a profile is found, else the Insights API | Read and write Insights from a local YAML file. | -| `--verbose` / `-v` | no | off | Stream tool calls and reasoning to stderr. | +| `--verbose` / `-v` | no | off | Stream tool calls and reasoning to standard error. | ### `nemo insights analysis enable | disable | status` @@ -522,82 +549,91 @@ Manage per-agent opt-in for periodic analysis. `enable` and `disable` require `--agent`; `status` takes an optional `--agent` (omit it to list all configs in the workspace). All three accept `--workspace` and `--base-url`. -### `nemo insights doctor` +### `nemo agents analyst doctor` Check whether the current profile is ready for analysis. Exits non-zero when a required check fails. -### `nemo experimentalist run` +### `nemo agents experimentalist run` Run the local Experimenter loop. | Flag | Required | Default | Description | |------|----------|---------|-------------| | `--insight` | no | local `.nemo-optimizer/insights.yaml` | The Insight to optimize against: a local Insight file or a platform Insight ID. | -| `--insight-id` | no | — | Select an exact ID, exact title, or zero-based index from a local multi-Insight file. | +| `--insight-id` | no | Not applicable | Select an exact ID, exact title, or zero-based index from a local multi-Insight file. | | `--no-insight` | no | off | Run against a dataset directly rather than guided by an Insight. | | `--agent` | no | profile `agent_source` | Baseline agent override: a local directory or a git URL with optional ref (`...repo.git@main`). A git source records provenance and enables opening a draft PR for the winner. | -| `--agent-spec` | no | profile `agent_spec` | URI of a markdown file describing the AUT. | +| `--agent-spec` | no | profile `agent_spec` | Location of a Markdown file describing the AUT. | | `--train-dataset` | yes, unless the profile supplies it | profile `datasets.train` | Train dataset. Local path for the Harbor evaluator. | | `--validation-dataset` | yes, unless the profile supplies it | profile `datasets.validation` | Validation dataset. Local path for the Harbor evaluator. | | `--task-template` | required with an Insight | profile `task_template` | Evaluator-specific task-template URI, used to build the Insight-specific evaluation suite. | | `--experiment-dir` / `-o` | no | `/.nemo-optimizer/experiments/`, else `./tmp` | Local experiment directory; writes `eval-and-optimize/` here. | | `--framework-skills` | no | profile `framework_skills` | Directory of framework skills to load into the optimization agents. Repeatable. | | `--mode` | no | `local` | `local` or `remote`. Only `local` is implemented today. | -| `--profile` | no | discovered by walking up from cwd | Path to `optimizer.yaml`. | +| `--profile` | no | discovered by walking up from the current working directory | Path to `optimizer.yaml`. | | `--workspace` | no | profile `workspace` | Workspace for traces and run/candidate metadata. | | `--base-url` | no | `NMP_BASE_URL`, else `http://localhost:8080` | Running platform instance. | -| `--config` | no | profile `experiment_config` | YAML or JSON configuration for the run. | +| `--config` | no | profile `experiment_config` | YAML or JavaScript Object Notation (JSON) configuration for the run. | -### `nemo experimentalist doctor` +### `nemo agents experimentalist doctor` Diagnose the Experimenter setup: profile, credentials, Insight resolution, datasets, and the experiment plan. +### `nemo agents eval-author` + +Discover the current Eval Author command surface with `--help`. The +`discover`, `audit`, `propose`, `run`, and `doctor` verbs are placeholders and +exit with a nonzero status until their implementations are available. + ### Models -The optimization agents drive their own LLMs, configured by environment -variable: +The optimization agents use the following model configuration: | Variable | Used by | Default and notes | |----------|---------|-------------------| -| `INFERENCE_API_KEY` | Analyst | Required. API key for the Analyst model, reached through the NVIDIA Inference Gateway. | +| `INFERENCE_API_KEY` | Analyst | Required. API key for Claude Opus 4.8 through the NVIDIA Inference Gateway. The Analyst model and endpoint are fixed. | | `NEMO_EXPERIMENTALIST_API_BASE` | Experimenter, Eval Author | OpenAI-compatible model API base URL. `run` and `doctor` fall back to the NVIDIA Inference Gateway when it is unset, so it is only required against another endpoint. Also settable as `api_base` under the `experimentalist:` config section; the environment wins. | | `NEMO_EXPERIMENTALIST_API_KEY` | Experimenter, Eval Author | Required. Model API key. On the gateway, `INFERENCE_API_KEY` fills this. | | `NEMO_EXPERIMENTALIST_MODELS_SMART` | Experimenter, Eval Author | Required. High-capability model for analysis, proposing, coding, and curation. No default: a model name is only meaningful against a specific endpoint. | | `NEMO_EXPERIMENTALIST_MODELS_MID` | Experimenter, Eval Author | Required. Mid-tier model, used by the Coder's `apply_change` among others. No default. | | `NEMO_EXPERIMENTALIST_MODELS_FAST` | Experimenter, Eval Author | Required. Low-latency model for lightweight steps such as termination checks and summarization. No default. | +| `AUTHOR_API_BASE` | Eval Author | Optional override for the OpenAI-compatible model API base URL. Falls back to `NEMO_EXPERIMENTALIST_API_BASE`. | +| `AUTHOR_API_KEY` | Eval Author | Optional override for the model API key. Falls back to `NEMO_EXPERIMENTALIST_API_KEY`, or to `INFERENCE_API_KEY` for the NVIDIA Inference Gateway. | +| `AUTHOR_SMART_MODEL_NAME` | Eval Author | Optional high-capability model override. | +| `AUTHOR_MID_MODEL_NAME` | Eval Author | Optional mid-tier model override for Experimentalist helpers used by Eval Author. | +| `AUTHOR_FAST_MODEL_NAME` | Eval Author | Optional low-latency model override. | ## Troubleshooting -**`analyze` returns no Insights or aborts on the trace floor.** +### Analyst Returns No Insights or Aborts on the Trace Floor -The agent has -too few (or no) traces in Intake for the target workspace. Confirm telemetry is +The agent has too few or no traces in Intake for the target workspace. Confirm that telemetry is flowing and that you are filtering by the right `agent` name. -**Periodic analysis never fires.** +### Periodic Analysis Does Not Start Check that the agent is enabled (`nemo insights analysis status`), that `insights.analyst.enabled` is true, that the scheduled window has passed in the configured `timezone`, and that at least 10 new sessions have landed since the last run. -**Insights are not in the platform.** +### Insights Are Not in the Platform A discovered `optimizer.yaml` profile routes Insights to `.nemo-optimizer/insights.yaml` instead of the Insights API, as does an explicit `--insights-file-output`. Check that file, or run without a profile to write through the API. -**The experiment cannot score, or rewards are zero.** +### The Experiment Cannot Score or Rewards Are Zero Confirm datasets are local Harbor-compatible paths. Live and production datasets may lack verifiable rewards by design; rely on curated metrics and trajectory scoring in that case. -**No PR was opened for the winner.** +### No Pull Request Was Opened for the Winner Publishing requires a git agent source -(`--agent @`) and publishing enabled in the run config. A +(`--agent @`) and publishing enabled in the run configuration. A local-directory source cannot open a PR. diff --git a/docs/agents/observability.mdx b/docs/agents/observability.mdx index e5a9aa9d81..4a0cc78510 100644 --- a/docs/agents/observability.mdx +++ b/docs/agents/observability.mdx @@ -1,12 +1,13 @@ --- title: "Observe Agents" -description: "Ingest, store, and query agent telemetry with NeMo Intake — OTLP, chat-completion, and ATIF ingest paths, annotations, and evaluator results, reviewable in NeMo Studio." +description: "Ingest, store, and query agent telemetry with NeMo Intake, including supported ingest paths, annotations, evaluator results, and review in NeMo Studio." --- Observability on NeMo Platform is provided by **NeMo Intake**, the trace ingestion and query service -for agent telemetry, and optionally by [NeMo Studio](/documentation/studio), the web UI for browsing +for agent telemetry, and optionally by [NeMo Studio](/documentation/studio), the web user interface +(UI) for browsing that telemetry. Observability helps humans and agents understand what happened in an agent system: which models were called, which tools were used, what inputs and outputs were produced, where failures occurred, and what feedback or evaluation scores were attached. @@ -23,12 +24,13 @@ diagnose failures from traces, in a production system or in offline evaluation f - A reachable ClickHouse database. - A NeMo Platform environment with the `intake` service running. -- A telemetry source: an OpenTelemetry/OpenInference exporter, NeMo Agent Toolkit (NAT), NeMo Flow, - NeMo Evaluator runs, captured OpenAI-compatible chat-completion payloads, or ATIF trajectories. -- (Optional) [NeMo Studio](/documentation/studio) for the UI review flow. +- A telemetry source, such as an OpenTelemetry or OpenInference exporter, NeMo Agent Toolkit (NAT), + NeMo Flow, NeMo Evaluator runs, captured OpenAI-compatible chat-completion payloads, or Agent + Trajectory Interchange Format (ATIF) trajectories. +- Optional: [NeMo Studio](/documentation/studio) for the UI review flow. -You do not need to deploy your agents on NeMo Platform. The lightweight path is to run the platform, -post one representative interaction, and confirm it appears. +You do not need to deploy your agents on NeMo Platform. To start, run the platform, post one +representative interaction, and confirm that it appears. ## How It Works @@ -54,14 +56,14 @@ Use Intake when you need to: - Standardize telemetry across teams so everyone shares one vocabulary, data model, and review surface. -Production traffic is the best fuel for optimization. New agents can start with staged traffic or -generated cases, then improve coverage as real traffic arrives. +Production traffic provides useful optimization evidence. New agents can start with staged traffic +or generated cases, then improve coverage as production traffic arrives. Intake accepts three standardized ingest formats depending on your instrumentation: | Format | Example use cases | Endpoint | |--------|-------------------|----------| -| OTLP/HTTP protobuf (OTel GenAI or OpenInference semantic conventions) | NeMo Relay, LangChain Deep Agents | `/apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces` | +| OpenTelemetry Protocol (OTLP) over HTTP using Protocol Buffers (OpenTelemetry generative AI or OpenInference semantic conventions) | NeMo Relay, LangChain Deep Agents | `/apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces` | | Chat completions | Importing raw logs, instrumenting a proxy server, custom logging | `/apis/intake/v2/workspaces/{workspace}/ingest/chat-completions` | | ATIF | Running Harbor evaluations | `/apis/intake/v2/workspaces/{workspace}/ingest/atif` | @@ -69,8 +71,8 @@ Intake accepts three standardized ingest formats depending on your instrumentati Telemetry uses a three-level hierarchy: -- A **span** is one timed operation: an LLM call, tool invocation, retrieval, guardrail, evaluator, or - chain step. +- A **span** is one timed operation, such as a large language model (LLM) call, tool invocation, + retrieval, guardrail, evaluator, or chain step. - A **trace** is one end-to-end agent run, made of spans that share a trace ID. - A **session** groups related traces, such as a multi-turn conversation or a multi-system evaluation. @@ -80,7 +82,7 @@ Additional records attach signal to the same session or span: - **Evaluator results** store numeric, boolean, categorical, or text scores for a span. - **Experiments** and **Evaluations** organize evaluation runs into leaderboard rollups for comparison. An Evaluation is a named run whose sessions are individual test cases; an Experiment - rolls related Evaluations into one leaderboard. See + rolls related Evaluations into one leaderboard. Refer to [Experiments](/documentation/evaluate-models/experiments). The most useful instrumentation logs granular steps: every model call, tool call, final response, and @@ -93,32 +95,40 @@ error. A practical test: the trace can answer _where did the system go wrong, an The examples assume a running NeMo Platform reachable at `$NMP_BASE_URL`. Point at whatever you have, whether a deployed platform or a local one from `nemo setup` or `nemo quickstart up`: -```shell +```bash export NMP_BASE_URL=http://127.0.0.1:8080 export WORKSPACE=default +export NMP_STUDIO_URL="$NMP_BASE_URL/studio" +export NMP_ACCESS_TOKEN="$(nemo auth token)" ``` +Set `NMP_STUDIO_URL` explicitly if Studio uses a different origin or path. +The examples use bearer-token authentication and stop on HTTP errors. For a local development +deployment with authentication disabled, omit the `Authorization` header and the +`NMP_ACCESS_TOKEN` command. Also omit `OTEL_EXPORTER_OTLP_HEADERS` from the OTLP example. + If you are bringing the pieces up yourself from a repository checkout, start them in this order. 1. **ClickHouse** (the telemetry datastore): - ```shell + ```bash services/intake/scripts/spans/run_clickhouse.sh ``` 1. **Backend services**: `intake` plus its `auth` (access checks) and `entities` (entity store) - dependencies. `--port` defaults to `8080`; drop `uv run` if you installed the `nemo` CLI: + dependencies. `--port` defaults to `8080`; omit `uv run` if you installed the `nemo` + command-line interface (CLI): - ```shell + ```bash uv run nemo services run --services auth,entities,intake --host 127.0.0.1 --port 8080 ``` 1. **Studio** (optional, for the UI review flow). From the `web/` workspace, with the intake feature flag on and pointed at the backend: - ```shell + ```bash VITE_FF_INTAKE_ENABLED=true VITE_PLATFORM_BASE_URL=http://127.0.0.1:8080 \ pnpm --filter nemo-studio-ui start -- --host 127.0.0.1 ``` @@ -127,8 +137,10 @@ If you are bringing the pieces up yourself from a repository checkout, start the Confirm the Intake read path can reach ClickHouse: -```shell -curl -i "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?page=1&page_size=1" +```bash +curl --fail-with-body -i \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?page=1&page_size=1" ``` A `200` response with an empty list is healthy. A `503` response means the Intake service is running @@ -136,12 +148,14 @@ but cannot reach ClickHouse. ### First Workflow -Send one captured chat-completion interaction. This is the lowest-friction smoke test — it needs no +Send one captured chat-completion interaction. This basic verification test does not require an OpenTelemetry exporter: -```shell +```bash export SESSION_ID="demo-session-001" -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/chat-completions" \ +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/chat-completions" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "session_id": "'"$SESSION_ID"'", @@ -165,22 +179,24 @@ curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/chat-com Read the span back from Intake: -```shell -curl "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[session_id]=$SESSION_ID&page=1&page_size=10" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[session_id]=$SESSION_ID&page=1&page_size=10" ``` -Then open Studio and navigate to `/workspaces/default/intake/traces`. +Then open `$NMP_STUDIO_URL/workspaces/$WORKSPACE/intake/traces`. -**What good looks like:** +A successful result has the following characteristics: - The API call returns your interaction: one span with `session_id` `demo-session-001`, model `example-model`, and the prompt and response you sent as its input and output. - Studio, pointed at the same workspace, lists that trace, and opening it shows the same request and response in the span tree. -That is the full loop: something you posted is now queryable through the API and reviewable in the UI. -The fields Intake captured here — session, trace, span, request, response, status, error, plus any -feedback or evaluator results — are the vocabulary the rest of these docs build on. +The interaction is now queryable through the application programming interface (API) and reviewable +in the UI. The captured fields include session, trace, span, request, response, status, error, +feedback, and evaluator results. The remaining documentation uses this vocabulary. ## Common Workflows @@ -188,37 +204,42 @@ feedback or evaluator results — are the vocabulary the rest of these docs buil Use OTLP when your framework or collector already emits OpenTelemetry traces: -```shell +```bash export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/otlp/v1/traces" export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf +export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20$NMP_ACCESS_TOKEN" ``` -Then run the instrumented agent. Intake maps OpenInference and OTel GenAI semantic attributes onto -span fields so model, tool, status, token, and error data are queryable. +Then run the instrumented agent. Intake maps OpenInference and OpenTelemetry generative AI semantic +attributes onto span fields. You can then query model, tool, status, token, and error data. ### Send ATIF Trajectories -Use ATIF when your source system exports complete agent trajectories — steps, agent metadata, or final -metrics: +Use ATIF when your source system exports complete agent trajectories, including steps, agent +metadata, or final metrics: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/atif" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/atif" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ --data-binary @trajectory.json ``` Each trajectory is stored as a structured session of spans. If the trajectory's top-level `extra.verifier_result.rewards` is populated, Intake writes those as evaluator results automatically. -Stock Harbor output does not populate it — Harbor's rewards live in a separate `reward.json` — so it -must be enriched first. See [Capture Evaluator Results](#capture-evaluator-results) for the automatic -and explicit paths. +Stock Harbor output does not populate it. Harbor rewards are stored in a separate `reward.json`, so +you must enrich the output first. Refer to +[Capture Evaluator Results](#capture-evaluator-results) for the automatic and explicit paths. ### Add Feedback and Labels Use annotations to attach review signal after a trace lands: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/annotations" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/annotations" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "kind": "feedback", "session_id": "'"$SESSION_ID"'", "value": "negative" }' ``` @@ -230,16 +251,16 @@ Use `kind: "note"` for reviewer notes, `kind: "label"` for categorical or numeri -Evaluator results — a judge's rating, a pass/fail check, a similarity score — attach to a span. There -are two ways to get them into Intake. +Evaluator results attach a judge rating, pass or fail check, or similarity score to a span. You can +send them to Intake in two ways. **Automatically, from ATIF.** Put a `verifier_result.rewards` object in the trajectory's **top-level** `extra`, keyed by criterion (`{criterion: score}`). On ingest, Intake synthesizes an evaluator span named `harbor.verifier` and writes one evaluator result per key onto it; a bare scalar `extra.verifier_result.score` instead becomes a single `reward` result. Nothing else to send. -A stock Harbor `trajectory.json` does **not** carry this — Harbor writes rewards to a separate -`reward.json` — so you must copy them into the ATIF `extra` before ingest, or use the explicit path +A stock Harbor `trajectory.json` does **not** carry this value. Harbor writes rewards to a separate +`reward.json`, so you must copy them into the ATIF `extra` before ingestion or use the explicit path below. For example: ```json @@ -259,8 +280,10 @@ post. `data_type` picks which field carries the score: For example, a numeric score: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-results" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-results" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "span_id": "", @@ -274,18 +297,26 @@ curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-resul Read them back for one span, or list and filter across the workspace by evaluator name, data type, or value range: -```shell -curl "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans//evaluator-results" -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-results?filter[name]=faithfulness/v1&filter[value][\$gte]=0.8" +```bash +curl --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans//evaluator-results" +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-results?filter[name]=faithfulness/v1&filter[value][\$gte]=0.8" ``` ### Find Recurring Failures Query traces or spans with filters, then group spans by session or trace to find repeated errors: -```shell -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[status]=ERROR&page=1&page_size=20" -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans/groups?by=session_id&filter[status]=ERROR" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[status]=ERROR&page=1&page_size=20" +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans/groups?by=session_id&filter[status]=ERROR" ``` In Studio, start from the trace list, filter to negative feedback or error status, then open trace @@ -294,7 +325,7 @@ detail to inspect the span tree. ### Turn Evaluation Telemetry into a Leaderboard Once you are ingesting evaluation runs, group them into an **Experiment** to compare them side by -side. See [Experiments](/documentation/evaluate-models/experiments), or use the agent-assisted +side. Refer to [Experiments](/documentation/evaluate-models/experiments), or use the agent-assisted `nemo-experiments-upload` skill to go from zero to a populated leaderboard. ## Operations @@ -303,7 +334,7 @@ side. See [Experiments](/documentation/evaluate-models/experiments), or use the - OTLP request bodies are capped at 5 MiB by default. - Read endpoints default to a 30-day lookback when no time filter is supplied. -- Page size is capped at 1000 records. +- Page size is capped at 1,000 records. - Span data and trace-index data expire after 90 days. Raise the OTLP body cap with `NMP_INTAKE_OTLP_MAX_BODY_BYTES` when larger batches are required. For @@ -316,9 +347,10 @@ depends on platform auth and checks workspace access before ingesting or reading -Do not send secrets, credentials, raw PII, or regulated data unless your deployment, retention, and -access-control policy permits it. Intake preserves request and response payloads so reviewers can -diagnose behavior — useful for debugging, and important for data governance. +Do not send secrets, credentials, raw personally identifiable information (PII), or regulated data +unless your deployment, retention, and access-control policy permits it. Intake preserves request +and response payloads so reviewers can +diagnose behavior. This data is useful for debugging and important for data governance. @@ -327,8 +359,8 @@ diagnose behavior — useful for debugging, and important for data governance. ClickHouse is the telemetry datastore. Intake owns the ClickHouse schema and lazily initializes the tables on first use. -Span and trace-index tables have a 90-day TTL. Annotations and evaluator results are retained in their -ClickHouse tables without that 90-day span TTL. +Span and trace-index tables have a 90-day time-to-live (TTL) setting. Annotations and evaluator +results are retained in their ClickHouse tables without that 90-day span TTL. For production storage sizing, estimate span volume across the retained window, then size ClickHouse for interactive reads over recent traces and periodic aggregate queries. diff --git a/docs/evaluator/experiments.mdx b/docs/evaluator/experiments.mdx index 024765f83b..a7a938dc42 100644 --- a/docs/evaluator/experiments.mdx +++ b/docs/evaluator/experiments.mdx @@ -1,15 +1,14 @@ --- title: "Experiments" -description: "Compare evaluation runs on one leaderboard with NeMo Experiments — cost, latency, token, and evaluator rollups computed at read time from NeMo Intake telemetry." +description: "Compare evaluation runs on a NeMo Experiments leaderboard with cost, latency, token, and evaluator rollups computed from NeMo Intake telemetry." --- NeMo Experiments is the comparison layer for agent optimization tasks, such as evaluating the impact -of changes to a harness, infrastructure, tools, or agent code. It gives you one place to visualize and -compare evaluation results from the runner of your choice — Harbor, the NeMo Optimizer, NeMo -Evaluator, or your own — ranked on the metrics that matter, such as cost, latency, and evaluator -scores. +of changes to a harness, infrastructure, tools, or agent code. It provides one place to visualize and +compare evaluation results from Harbor, the NeMo Optimizer, NeMo Evaluator, or your own runner. You +can rank these results by cost, latency, evaluator scores, and other metrics. Those metrics are derived from [NeMo Intake](/documentation/agents/observe-agents) observability data, the same traces and evaluator results your runs already produce, so any runner that lands telemetry in @@ -30,10 +29,11 @@ decide what to promote. - The platform entity store (Postgres). - A reachable ClickHouse database. - A producer that creates Evaluations and sends their telemetry: the NeMo Optimizer, a benchmark or - evaluation framework such as Harbor, or a direct API integration. -- (Recommended) [NeMo Studio](/documentation/studio) for the full UI — trace comparison views, a - customizable leaderboard, and Pareto charts. Everything is also available through the API, but - Studio is where the experience really lives. The Experiments UI is gated by the + evaluation framework such as Harbor, or a direct application programming interface (API) + integration. +- Recommended: [NeMo Studio](/documentation/studio) for the complete web user interface (UI), + including trace comparison views, a customizable leaderboard, and Pareto charts. The API provides + the same data. The Experiments UI is gated by the `VITE_FF_EXPERIMENT` feature flag, which is off by default. ## How It Works @@ -45,9 +45,9 @@ rolls up cost, latency, tokens, and per-evaluator scores from the underlying tel and returns them as ranked rows. Rollups are computed at read time, so a leaderboard always reflects current telemetry, with no denormalized score table to maintain. -You group runs however makes sense — an insight to investigate, a model bake-off, a benchmark -leaderboard, or the top runs promoted from several groups — and a single run can belong to more than -one group. Sorting, filtering, and pinning within a group surface the runs that matter. +You can group runs by an insight to investigate, a model comparison, a benchmark leaderboard, or +promoted runs from several groups. A single run can belong to more than one group. Sorting, +filtering, and pinning within a group surface the relevant runs. ### When to Use It @@ -62,7 +62,7 @@ Use Experiments when you need to: - **Standardize comparison across a team**: everyone reads the same metrics, in the same place, with the same vocabulary. -Experiments are most useful once real evaluation telemetry exists. A new group can start empty and +Experiments are most useful after real evaluation telemetry exists. A new group can start empty and fill in as runs land. ### Core Concepts and Data Model @@ -74,7 +74,7 @@ Experiments sit on top of Intake's telemetry hierarchy (span → trace → sessi a `pareto` (default X/Y metrics for the Pareto view). - An **Evaluation** is one run and one leaderboard row. It records producer-supplied fields (`dataset_name`, `dataset_version`, `source_link`, `metadata`, `description`, `status`, - `root_cause`), the `experiment_ids` it belongs to (at least one — an Evaluation can live in more + `root_cause`), the `experiment_ids` it belongs to (at least one; an Evaluation can live in more than one Experiment), and an optional `parent_evaluation_id` linking a variant back to the run it was derived from. - A **Session** is one test-case execution within an Evaluation, a single ingested run made of spans. @@ -84,7 +84,7 @@ At read time, each Evaluation is enriched with rollups derived from its sessions | Rollup | Meaning | |--------|---------| -| `test_case_count` | Number of distinct test cases (distinct non-empty `test_case_id` values). Sessions with no `test_case_id` don't count toward it or the rollups. | +| `test_case_count` | Number of distinct test cases (distinct non-empty `test_case_id` values). Sessions with no `test_case_id` do not count toward it or the rollups. | | `cost_usd` | Cost aggregate across the Evaluation's sessions. | | `latency_ms` | Latency aggregate across the Evaluation's sessions. | | `tokens` | Average total tokens (input + output) per test case. | @@ -93,16 +93,17 @@ At read time, each Evaluation is enriched with rollups derived from its sessions Metric aggregates expose these statistics: `sum`, `mean`, `median`, `p90`, `p95`, `p99`, `count`. A **metric path** is therefore `test_case_count`, `cost_usd.`, `latency_ms.`, -`tokens.`, or `evaluators..` — the same grammar used for sorting and filtering below. +`tokens.`, or `evaluators..`. Sorting and filtering use the same grammar. Two things about evaluator rollups specifically: -- **Response shape.** `evaluators..` is the sort/filter *query* grammar. In the Evaluation - JSON the same scores come back under `aggregate_scores` — a map keyed by evaluator name, each value - carrying the stats above — alongside an `evaluator_names` list. There is no `evaluators` field in +- **Response shape.** `evaluators..` is the sort/filter *query* grammar. The Evaluation + JSON response returns the same scores under `aggregate_scores`, a map keyed by evaluator name. + Each value contains the statistics above, and an `evaluator_names` list appears alongside it. + There is no `evaluators` field in the response; `evaluators..` (query) reads `aggregate_scores[].` (response). - **Missing-value semantics.** Evaluator rollups are test-case-weighted: each stat is computed over - `test_case_count`, the full set of test cases, with a test case that didn't report a given evaluator + `test_case_count`, the full set of test cases, with a test case that did not report a given evaluator counted as `0` rather than dropped. Two Experiment-level behaviors are worth knowing: @@ -110,7 +111,7 @@ Two Experiment-level behaviors are worth knowing: - **Default sort.** An Experiment stores a `default_sort` (a sort-param string such as `-evaluators.solved.mean`) so its leaderboard opens ordered by the metric the team cares about. It defaults to `-created_at` (newest first). -- **Pinning.** Any Evaluation can be pinned to the top of its Experiment — for example, the current +- **Pinning.** Any Evaluation can be pinned to the top of its Experiment, such as the current baseline. Pins are workspace-shared: everyone with access sees the same pinned set, regardless of the active sort. @@ -122,11 +123,18 @@ Experiments are part of the `intake` service, so any running NeMo Platform alrea at whatever you have, whether a deployed platform or a local one from `nemo setup` or `nemo quickstart up`: -```shell +```bash export NMP_BASE_URL=http://127.0.0.1:8080 export WORKSPACE=default +export NMP_STUDIO_URL="$NMP_BASE_URL/studio" +export NMP_ACCESS_TOKEN="$(nemo auth token)" ``` +Set `NMP_STUDIO_URL` explicitly if Studio uses a different origin or path. +The examples use bearer-token authentication and stop on HTTP errors. For a local development +deployment with authentication disabled, omit the `Authorization` header and the +`NMP_ACCESS_TOKEN` command. + ClickHouse must be reachable either way, since the leaderboard rollups are computed from it at read time. @@ -136,35 +144,41 @@ If you are bringing the pieces up yourself from a repository checkout, start the 1. **ClickHouse** (required for the rollups): - ```shell + ```bash services/intake/scripts/spans/run_clickhouse.sh ``` 1. **Backend services**: `intake` plus its `auth` and `entities` dependencies. `--port` defaults to - `8080`; drop `uv run` if you installed the `nemo` CLI: + `8080`; omit `uv run` if you installed the `nemo` command-line interface (CLI): - ```shell + ```bash uv run nemo services run --services auth,entities,intake --host 127.0.0.1 --port 8080 ``` 1. **Studio** (optional, for the leaderboard and drill-down UI). From the `web/` workspace, with the Experiments feature flag on and intake enabled for trace drill-down: - ```shell + ```bash VITE_FF_EXPERIMENT=true VITE_FF_INTAKE_ENABLED=true VITE_PLATFORM_BASE_URL=http://127.0.0.1:8080 \ pnpm --filter nemo-studio-ui start -- --host 127.0.0.1 ``` -Confirm the Experiments read path is reachable: +Confirm that the entity read path and ClickHouse rollups are reachable: -```shell -curl -i "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments?page=1&page_size=1" +```bash +curl --fail-with-body -i \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments?sort=-created_at&page=1&page_size=1" +curl --fail-with-body -i \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?sort=-cost_usd.mean&page=1&page_size=1" ``` -A `200` with a (possibly empty) list is healthy. A `503` means the service is running but cannot reach -ClickHouse; reads that need metric rollups will fail until it recovers. +The first request checks the entity store and can return `200` while ClickHouse is unavailable. The +second request explicitly sorts by a rollup metric. A `200` response from both requests is healthy. +A `503` response from the second request means that Intake cannot compute ClickHouse rollups. ### First Workflow @@ -172,19 +186,22 @@ Create an Experiment, add an Evaluation, send it telemetry, then see it in Studi **1. Create the Experiment** (the leaderboard container): -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "reranker-prompt-iteration", - "description": "Iterating on the support-bench RAG agent'\''s reranker and system prompt." + "description": "Iterating on the support-bench retrieval-augmented generation agent'\''s reranker and system prompt." }' ``` Capture its `id` for the next step: -```shell -export EXPERIMENT_ID=$(curl -sf \ +```bash +export EXPERIMENT_ID=$(curl --fail-with-body --silent \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])') echo "experiment id: $EXPERIMENT_ID" @@ -193,8 +210,10 @@ echo "experiment id: $EXPERIMENT_ID" **2. Add an Evaluation** to it. `experiment_ids` is a list, so an Evaluation belongs to one or more existing Experiments: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "reranker-add-cross-encoder", @@ -205,20 +224,22 @@ curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ }' ``` -An Evaluation is a durable record on its own; its leaderboard metrics appear once evaluation telemetry -for it lands in Intake. +An Evaluation is a durable record. Its leaderboard metrics appear after its evaluation telemetry +lands in Intake. -**3. Send it telemetry.** Send the run's telemetry to Intake just as you would for any agent run, +**3. Send it telemetry.** Send the run's telemetry to Intake in the same way as other agent runs, whether it comes from the optimizer, a benchmark framework like Harbor, or your own code. Tag each session with the Evaluation's identity: -- For ATIF and chat-completions, add a **top-level** `evaluation_context` object to the ingest payload +- For Agent Trajectory Interchange Format (ATIF) and chat-completions, add a **top-level** + `evaluation_context` object to the ingest payload carrying `evaluation_id` (the Evaluation's **name**) and `test_case_id`. -- For OTLP, set the `nemo.experiment.id` and `nemo.test_case.id` root-span attributes. +- For OpenTelemetry Protocol (OTLP), set the `nemo.experiment.id` and `nemo.test_case.id` root-span + attributes. The per-evaluator scores on the leaderboard come from **evaluator results** captured on those sessions, either automatically from ATIF verifier rewards or explicitly through the evaluator-results -endpoint. See +endpoint. Refer to [Capture Evaluator Results](/documentation/agents/observe-agents#capture-evaluator-results) for both paths, and [Observe Agents](/documentation/agents/observe-agents) for the ingestion paths themselves. The `nemo-experiments-upload` skill walks this through end to end. @@ -226,9 +247,9 @@ The `nemo-experiments-upload` skill walks this through end to end. **`test_case_id` is required for a populated leaderboard.** A session tagged with only `evaluation_id` -still ingests and appears in the Evaluation's session list, but it doesn't count toward -`test_case_count` or any rollup — so the row reads as all zeros, with tokens, model, and agent blank -too. Always send `test_case_id` alongside `evaluation_id`. +still ingests and appears in the Evaluation's session list, but it does not count toward +`test_case_count` or any rollup. The row therefore reads as all zeros, with tokens, model, and agent +blank too. Always send `test_case_id` alongside `evaluation_id`. @@ -236,14 +257,16 @@ too. Always send `test_case_id` alongside `evaluation_id`. List the Experiment's Evaluations and confirm the new row is present: -```shell -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&page=1&page_size=20" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&page=1&page_size=20" ``` -Then open Studio and navigate to `/workspaces/default/experiment`, then open +Then open `$NMP_STUDIO_URL/workspaces/$WORKSPACE/experiment`, then open **reranker-prompt-iteration**. -**What good looks like:** the Experiment opens in Studio, your Evaluation appears as a row, and once +A successful result opens the Experiment in Studio and shows your Evaluation as a row. After its sessions are ingested the row shows non-zero `test_case_count`, cost, latency, tokens, and evaluator scores. Sorting by a metric reorders the table, and opening the Evaluation lists its individual test cases. @@ -255,8 +278,10 @@ individual test cases. This is the everyday operation: an Experiment already exists and you want to record another run in it. Create the Evaluation with the Experiment's `id` in `experiment_ids`: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ +```bash +curl --fail-with-body -X POST \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "reranker-bge-large", @@ -270,13 +295,15 @@ curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations" \ To **move or re-scope** an existing Evaluation's membership, `PATCH` its `experiment_ids`, which must stay non-empty: -```shell -curl -X PATCH "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" \ +```bash +curl --fail-with-body -X PATCH \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "experiment_ids": ["'"$EXPERIMENT_ID"'", "'"$OTHER_EXPERIMENT_ID"'"] }' ``` -An Evaluation can belong to several Experiments at once, which is useful when the same run should +An Evaluation can belong to several Experiments simultaneously, which is useful when the same run should appear on both a per-project board and a cross-project benchmark. ### Update an Evaluation @@ -284,21 +311,25 @@ appear on both a per-project board and a cross-project benchmark. Use `PATCH` for partial updates: only the fields you send change. `name`, `dataset_name`, and `dataset_version` are immutable: -```shell -curl -X PATCH "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" \ +```bash +curl --fail-with-body -X PATCH \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "winner", "root_cause": "Best groundedness at acceptable cost." }' ``` -`PUT` does a full replace of the mutable fields — omitted fields reset — so prefer `PATCH` for one-off +`PUT` does a full replacement of the mutable fields. Omitted fields reset, so prefer `PATCH` for individual edits. ### Rank an Experiment by the Metric That Matters List an Experiment's Evaluations sorted by a metric. Prefix the field with `-` for descending: -```shell -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&sort=-evaluators.solved.mean&page=1&page_size=20" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&sort=-evaluators.solved.mean&page=1&page_size=20" ``` You can sort by an entity column (`name`, `created_at`) or any rollup metric (`test_case_count`, @@ -308,16 +339,18 @@ way. ### Filter to the Evaluations You Care About -Filter by a metric range to narrow the leaderboard — for example, only Evaluations whose average cost +Filter by a metric range to narrow the leaderboard. For example, select only Evaluations whose average cost is under $0.50: -```shell -curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&filter[cost_usd.mean][\$lte]=0.5" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[experiment_id]=$EXPERIMENT_ID&filter[cost_usd.mean][\$lte]=0.5" ``` Metric filters use the same grammar as sort, such as `filter[test_case_count][\$gte]=5` or `filter[evaluators.groundedness.mean][\$gte]=0.8`. You can also filter by -`filter[metadata.]=`, `filter[status]=…`, `filter[is_pinned]=true`, and +`filter[metadata.]=`, `filter[status]=`, `filter[is_pinned]=true`, and `created_at`/`updated_at` ranges. In Studio, use the column filters on cost, latency, test-case count, and evaluator columns. @@ -325,8 +358,10 @@ and evaluator columns. Store the ordering the team should see first so no one has to re-sort each visit: -```shell -curl -X PUT "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" \ +```bash +curl --fail-with-body -X PUT \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "reranker-prompt-iteration", "default_sort": "-evaluators.solved.mean" }' ``` @@ -337,14 +372,18 @@ In Studio, set it from the Experiment's Edit dialog. Keep the current baseline, or any reference run, at the top of the Experiment for everyone: -```shell -curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-main-baseline/pin" +```bash +curl --fail-with-body -X POST \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-main-baseline/pin" # Unpin: -curl -X DELETE "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-main-baseline/pin" +curl --fail-with-body -X DELETE \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-main-baseline/pin" ``` -Pinned Evaluations float to the top of the leaderboard regardless of the active sort. List just the +Pinned Evaluations float to the top of the leaderboard regardless of the active sort. List only the pinned set with `filter[is_pinned]=true`. ### Drill into an Evaluation's Test Cases @@ -352,8 +391,10 @@ pinned set with `filter[is_pinned]=true`. Open one Evaluation's sessions to see per-test-case behavior: status, latency, cost, and evaluator scores: -```shell -curl "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-add-cross-encoder/sessions?page=1&page_size=20" +```bash +curl --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-add-cross-encoder/sessions?page=1&page_size=20" ``` Use this to move from "this candidate scores lower" to "these specific cases regressed." In Studio, @@ -361,13 +402,15 @@ open the Evaluation row to reach its sessions, then follow a session into its tr ### Configure the Pareto View -Each Experiment stores a `pareto` config: the default X/Y metrics for its Pareto (trade-off) chart in +Each Experiment stores a `pareto` configuration: the default X/Y metrics for its Pareto chart in Studio, defaulting to cost vs. latency. The axes are `x_metric` and `y_metric`, and each takes a base -metric id — `cost_usd`, `latency_ms`, or `evaluators.`, with no `.` suffix. Set it on +metric ID: `cost_usd`, `latency_ms`, or `evaluators.`, with no `.` suffix. Set it on create or update: -```shell -curl -X PUT "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" \ +```bash +curl --fail-with-body -X PUT \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "reranker-prompt-iteration", "pareto": { "x_metric": "cost_usd", "y_metric": "evaluators.solved" } }' @@ -375,23 +418,36 @@ curl -X PUT "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/rera ### Delete an Experiment or Evaluation -Deleting an Experiment soft-deletes it and cascades to its Evaluations; deleting an Evaluation -soft-deletes just that row. Soft-deleted records are hidden from list and get operations unless -explicitly requested with `filter[is_deleted]=true`: +Deleting an Experiment soft-deletes it. An Evaluation that belongs only to that Experiment is also +soft-deleted. A shared Evaluation remains active, and the deleted Experiment is removed from its +`experiment_ids` membership. Deleting an Evaluation directly soft-deletes only that Evaluation. + +```bash +curl --fail-with-body -X DELETE \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" +curl --fail-with-body -X DELETE \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" +``` + +Soft deletion renames the stored record. A `GET` request that uses the original name does not return +the deleted record. To audit deleted records, use `filter[is_deleted]=true` on a list operation: -```shell -curl -X DELETE "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations/reranker-bge-large" -curl -X DELETE "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/experiments/reranker-prompt-iteration" +```bash +curl -g --fail-with-body \ + -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluations?filter[is_deleted]=true&page=1&page_size=20" ``` ### Where Evaluations Come From -You usually don't create Evaluations by hand. Common producers: +You typically do not create Evaluations manually. Common producers include: - **Optimizer**: records each run as an Experiment and its candidates as Evaluations, so results land - automatically. See [Optimize Agents](/documentation/agents/optimize-agents). + automatically. Refer to [Optimize Agents](/documentation/agents/optimize-agents). - **Evaluation framework**: a framework like Harbor sends complete trajectories through Intake's ATIF - ingest, and each run becomes an Evaluation with its final metrics as evaluator results. See + ingest, and each run becomes an Evaluation with its final metrics as evaluator results. Refer to [Observe Agents](/documentation/agents/observe-agents). - **Direct API**: create the Experiment and Evaluations, then ingest their sessions, for a custom evaluation pipeline. @@ -432,20 +488,23 @@ regulated data unless your deployment's policy permits it. ### Retention and Storage Experiment and Evaluation metadata lives in the platform entity store and persists until you delete -it; it does not expire on the telemetry TTL. Rollups are derived from ClickHouse at read time, so -leaderboard metrics reflect whatever telemetry is still retained: span and trace-index data expire -after 90 days, while evaluator results are retained without that span TTL. An Experiment older than -the span window keeps its records and evaluator-based scores, but cost, latency, and run-count rollups -reflect only the retained telemetry window. +it. This metadata does not expire with telemetry. Span and trace-index records in ClickHouse expire +after 90 days. Evaluator-result rows do not use that 90-day time-to-live (TTL) setting. + +All leaderboard rollups depend on the retained trace index. Evaluator score rollups join the retained +trace index to evaluator-result rows at read time. After a trace-index entry expires, its evaluator +scores no longer appear on the leaderboard even if the evaluator-result rows remain. Cost, latency, +token, test-case count, and evaluator score rollups therefore reflect the retained 90-day trace +window. ### Troubleshooting | Symptom | Cause and fix | |---------|---------------| -| **Rows show zero metrics** | Three causes: no sessions have been ingested for that Evaluation yet; the sessions were ingested without `test_case_id`, so they don't count toward `test_case_count` or the rollups; or ClickHouse is unreachable. Confirm ingestion in Intake, that sessions carry `test_case_id`, and that the read path returns `200` rather than `503`. | -| **A metric sort or filter returns `503`** | Rollups can't be computed because ClickHouse is down. Retry once the read path is healthy, or fall back to an entity-column sort. | +| **Rows show zero metrics** | Three causes: no sessions have been ingested for that Evaluation yet; the sessions were ingested without `test_case_id`, so they do not count toward `test_case_count` or the rollups; or ClickHouse is unreachable. Confirm ingestion in Intake, confirm that sessions carry `test_case_id`, and run the explicit metric-sort health check. | +| **A metric sort or filter returns `503`** | Rollups cannot be computed because ClickHouse is down. Retry after the read path is healthy, or use an entity-column sort. | | **A list returns `413`** | The Experiment selected more than 1,000 Evaluations for an in-memory sort. Add filters to narrow the set. | -| **An Evaluation isn't in the Experiment** | Confirm you created it with the correct Experiment `id` in `experiment_ids` and are querying the right workspace (`filter[experiment_id]=`). | +| **An Evaluation is not in the Experiment** | Confirm that you created it with the correct Experiment `id` in `experiment_ids` and that you are querying the correct workspace (`filter[experiment_id]=`). | | **`422` on create Evaluation** | An Evaluation must belong to at least one Experiment: provide `experiment_ids`. Required fields are `name`, `experiment_ids`, and `dataset_name`; `metadata` values must be strings. | ## Related Topics From 560bc545a842a50b560b132eccbe3e460b53f2a3 Mon Sep 17 00:00:00 2001 From: Miyoung Choi Date: Tue, 4 Aug 2026 10:01:08 -0700 Subject: [PATCH 2/2] docs: correct optimization command examples Signed-off-by: Miyoung Choi --- docs/agents/insight-driven-optimization.mdx | 13 ++++++------- docs/agents/observability.mdx | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/agents/insight-driven-optimization.mdx b/docs/agents/insight-driven-optimization.mdx index 0b9b77ae24..d6fe1cc21e 100644 --- a/docs/agents/insight-driven-optimization.mdx +++ b/docs/agents/insight-driven-optimization.mdx @@ -60,8 +60,8 @@ want to automate portions of the agent improvement loop. To install only one side of the loop, use the convenience groups: ```bash -uv sync --group insights # Analyst only -uv sync --group experimentalist # Experimenter and Eval Author +uv sync --only-group insights # Analyst only +uv sync --only-group experimentalist # Experimenter and Eval Author ``` ### Dependencies @@ -439,12 +439,12 @@ Hand it a single Insight: ```bash nemo agents experimentalist run \ - --insight \ - --agent \ + --insight insight-id \ + --agent ./path-to-agent \ --train-dataset ./harbor_eval/dataset/train \ --validation-dataset ./harbor_eval/dataset/validation \ --task-template ./task-template \ - --config \ + --config ./experiment-config.yaml \ --experiment-dir tmp/experiment \ --workspace "$WORKSPACE" \ --base-url "$NMP_BASE_URL" @@ -570,7 +570,6 @@ Run the local Experimenter loop. | `--task-template` | required with an Insight | profile `task_template` | Evaluator-specific task-template URI, used to build the Insight-specific evaluation suite. | | `--experiment-dir` / `-o` | no | `/.nemo-optimizer/experiments/`, else `./tmp` | Local experiment directory; writes `eval-and-optimize/` here. | | `--framework-skills` | no | profile `framework_skills` | Directory of framework skills to load into the optimization agents. Repeatable. | -| `--mode` | no | `local` | `local` or `remote`. Only `local` is implemented today. | | `--profile` | no | discovered by walking up from the current working directory | Path to `optimizer.yaml`. | | `--workspace` | no | profile `workspace` | Workspace for traces and run/candidate metadata. | | `--base-url` | no | `NMP_BASE_URL`, else `http://localhost:8080` | Running platform instance. | @@ -597,7 +596,7 @@ The optimization agents use the following model configuration: | `NEMO_EXPERIMENTALIST_API_BASE` | Experimenter, Eval Author | OpenAI-compatible model API base URL. `run` and `doctor` fall back to the NVIDIA Inference Gateway when it is unset, so it is only required against another endpoint. Also settable as `api_base` under the `experimentalist:` config section; the environment wins. | | `NEMO_EXPERIMENTALIST_API_KEY` | Experimenter, Eval Author | Required. Model API key. On the gateway, `INFERENCE_API_KEY` fills this. | | `NEMO_EXPERIMENTALIST_MODELS_SMART` | Experimenter, Eval Author | Required. High-capability model for analysis, proposing, coding, and curation. No default: a model name is only meaningful against a specific endpoint. | -| `NEMO_EXPERIMENTALIST_MODELS_MID` | Experimenter, Eval Author | Required. Mid-tier model, used by the Coder's `apply_change` among others. No default. | +| `NEMO_EXPERIMENTALIST_MODELS_MID` | Experimenter, Eval Author | Required. Mid-tier model used for trajectory scoring and architecture documentation. No default. | | `NEMO_EXPERIMENTALIST_MODELS_FAST` | Experimenter, Eval Author | Required. Low-latency model for lightweight steps such as termination checks and summarization. No default. | | `AUTHOR_API_BASE` | Eval Author | Optional override for the OpenAI-compatible model API base URL. Falls back to `NEMO_EXPERIMENTALIST_API_BASE`. | | `AUTHOR_API_KEY` | Eval Author | Optional override for the model API key. Falls back to `NEMO_EXPERIMENTALIST_API_KEY`, or to `INFERENCE_API_KEY` for the NVIDIA Inference Gateway. | diff --git a/docs/agents/observability.mdx b/docs/agents/observability.mdx index 4a0cc78510..1c018e933d 100644 --- a/docs/agents/observability.mdx +++ b/docs/agents/observability.mdx @@ -313,10 +313,10 @@ Query traces or spans with filters, then group spans by session or trace to find ```bash curl -g --fail-with-body \ -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ - "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[status]=ERROR&page=1&page_size=20" + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[status]=error&page=1&page_size=20" curl -g --fail-with-body \ -H "Authorization: Bearer $NMP_ACCESS_TOKEN" \ - "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans/groups?by=session_id&filter[status]=ERROR" + "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans/groups?by=session_id&filter[status]=error" ``` In Studio, start from the trace list, filter to negative feedback or error status, then open trace