Files
zeekay bc4ddf332b chore: rebase the tree onto upstream master fa1a9a60
Mechanically re-derived: bin/debrand runs over a fresh import of
posthog/posthog@fa1a9a60d4 (2026-08-06).
The pipeline (.hanzo/workflows), the Dockerfiles and bin/docker*
entrypoints, compose files, LLM.md and the squashed initial migration
are restored from the shipped tree, same as the previous import.

The prune's import-closure guard kept 21 products the new core now
imports; dropping them is a refactor now, recorded rather than forced.
2026-08-06 01:25:36 -07:00

1326 lines
60 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MCP tool definition — tool entries are scaffolded from the OpenAPI schema.
# The tool list and operation IDs are kept in sync automatically:
# pnpm --filter=@hanzo/mcp run scaffold-yaml -- --sync-all
#
# To enable a tool, set enabled: true and add required scopes + annotations.
# All other fields (title, description, enrich_url, etc.) are yours to configure.
category: Experiments
feature: experiments
url_prefix: /experiments
ui_apps:
experiment:
type: detail
view_prop: experiment
experiment-list:
type: list
detail_tool: experiment-get
detail_args: '{ id: item.id }'
experiment-results:
type: detail
view_prop: data
tools:
experiment-activity:
operation: experiments_activity_retrieve
enabled: true
scopes:
- activity_log:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get experiment change history
description: >
Get the audit trail for a specific experiment by ID. Returns a paginated list of changes to the experiment
and its holdouts and shared metrics: who made each change, what was changed (field-level before/after
values), and when, newest first. Lifecycle actions (launch, end, ship variant, etc.) appear as updates to
the underlying fields (start_date, end_date, conclusion, ...). Use limit and page query params for
pagination. Note: changes to the experiment's linked feature flag (rollout, targeting) are logged on the
flag itself — use feature-flags-activity-retrieve with the flag's ID for those.
param_overrides:
id:
cast: string-int
experiment-archive:
operation: experiments_archive_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Archive experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Archive a stopped experiment to hide it from the default list view. The experiment must be stopped (end_date
set) — cannot archive draft or running experiments.
Returns 400 if already archived ("Experiment is already archived.") or not yet stopped ("Experiment must be
ended before it can be archived.").
No request body needed. Can be restored later via the unarchive tool.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-calculate-running-time:
operation: experiments_calculate_running_time_create
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Calculate experiment running time
description: >
Estimate the recommended sample size and how many days an experiment needs to run to detect an effect. This
is a stateless statistical calculation — it does not read or modify any experiment, so it works for planning
before an experiment exists.
Required: metric_type ('funnel' for conversion rates, 'mean_count' for events per user, 'mean_sum_or_avg'
for summed property values per user, 'ratio' or 'retention' for ratio-style metrics) and
minimum_detectable_effect (the smallest relative change to detect, as a percentage — e.g. 5 means a 5%
lift). number_of_variants defaults to 2 (control + one test).
Provide the baseline one of two ways: pass baseline_value directly (a conversion rate as a fraction 0-1 for
funnels, or an average per user for mean metrics), or pass baseline_stats (raw control-group statistics) and
let the server derive it. Ratio and retention metrics REQUIRE baseline_stats (with denominator_sum /
denominator_sum_squares / numerator_denominator_sum_product for the delta-method variance) or an explicit
variance — baseline_value alone is not enough.
Optionally pass exposure_rate_per_day (expected exposures per day across all variants) to also get
recommended_running_time_days. The response returns baseline_value, variance (null for funnels),
recommended_sample_size (total across variants), and recommended_running_time_days.
experiment-copy-to-project:
operation: experiments_copy_to_project_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Copy experiment to another project
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
REQUIRES EXPLICIT USER CONFIRMATION BEFORE CALLING. This writes a new experiment into a DIFFERENT project
than the one the user is currently looking at. Resolve the target project from the user's wording to a
concrete team id, then confirm the source experiment and the target project by name before invoking.
Copies an experiment into another project in the SAME organization as a new draft. The target project must
belong to the same organization — this CANNOT copy across organizations or regions. Use experiment-duplicate
instead when the copy should land in the same project.
What IS copied: name (defaults to "Original Name (Copy)", de-duplicated with a numeric suffix if that name
already exists in the target), description, type, parameters (variant split, rollout), filters, primary and
secondary metrics (each with freshly regenerated uuids and preserved ordering), stats config, scheduling
config, exposure criteria, and the only_count_matured_users setting.
What is NOT copied: saved-metric references (saved metrics are project-scoped, so they are dropped on a
cross-project copy), holdout, exposure cohort, start/end dates, results, and conclusion. The copy always
starts as a fresh draft.
Feature flag: pass feature_flag_key to control the flag key created in the target project. If omitted, the
source experiment's flag key is reused — and if a flag with that key already exists in the target project,
the copy SHARES that existing flag (its variants are reused) rather than creating a new one. A shared flag
means lifecycle operations on either experiment (shipping a variant, pausing) affect both. To avoid this,
pass a feature_flag_key that does not already exist in the target project. If an existing target flag is
reused, it must be multivariate with 2 to 20 variants, otherwise the call returns 400 ("Feature flag must
have at least 2 variants (a baseline and at least one test variant)" or "Feature flag must have at most 20
variants"). No specific variant key is required — the analysis baseline defaults to the variant keyed
"control" when present, else the first variant. Exception: copying a web experiment requires the reused
target flag to have a variant keyed "control", otherwise the call returns 400 ("Web experiments require a
variant with key 'control'").
Returns 400 if the source experiment uses legacy metrics ("Copying is not supported for experiments using
legacy metrics."). Returns 404 if the target project is not found in the organization ("Target team not
found."). Returns 403 if you lack write access to the target project ("You do not have write access to the
target project.").
The returned experiment (including its id) belongs to the TARGET project, not the source project.
param_overrides:
id:
cast: string-int
target_team_id:
cast: string-int
response:
include:
- id
- name
- description
- type
- feature_flag_key
- status
- archived
- start_date
- end_date
- created_at
- parameters
- metrics
- metrics_secondary
- conclusion
- conclusion_comment
experiment-create:
operation: experiments_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Create experiment
description: >
RULES (follow before calling):
1. Load the creating-experiments skill for the full
creation workflow.
2. If the user mentions a rollout percentage (e.g. "25%"),
load the configuring-experiment-rollout skill and
ask the user to clarify BEFORE calling this tool.
A percentage is always ambiguous — it can mean a
variant split change OR an overall rollout change.
3. Do NOT pass metrics on creation. Create the draft
first, then add metrics via experiment-update. Use
the configuring-experiment-analytics skill for
metric guidance.
Creates a new experiment in draft status (ignore the "type" param, since no-code/toolbar experiments must be
created in the Insights UI). Requires name and feature_flag_key. Feature flag is auto-created — do not create
one separately. Defaults: 50/50 control/test, 100% rollout, Bayesian stats. Launch the experiment with the
launch tool when ready.
include_params:
- name
- description
- feature_flag_key
- feature_flag
- parameters
- stats_config
- holdout_id
- allow_unknown_events
- exposure_criteria
param_overrides:
feature_flag:
description: >
Variant split, rollout scope, payloads, and experience continuity for the auto-created feature flag,
in the flag's own filters shape. This is the canonical input for flag config. If the user mentions a
specific percentage, load the configuring-experiment-rollout skill and clarify before setting these
values. Set filters.multivariate.variants (each with key and rollout_percentage; percentages must
sum to 100) to customize the variant split. Set filters.groups to a single group [{"properties": [],
"rollout_percentage": N}] (0-100) to control the overall fraction of users entering the experiment.
Default: 50/50 control/test, 100% rollout. Omit this parameter entirely when feature_flag_key refers
to a pre-existing flag: the experiment links to that flag as-is and explicit config is rejected.
No specific variant key is required. The analysis baseline defaults to the variant keyed `control`
(lowercase) when present, else the first variant; override with stats_config.baseline_variant_key.
Convention: when the user describes variants as "A/B", "old/new", "original/redesign", or any other
natural-language pair without naming explicit keys, key the baseline `control` and keep their
wording in the variant `name`. When the user asks for specific keys, use them as-is and put the
baseline first.
ui_app: experiment
response:
include:
- id
- name
- description
- type
- feature_flag_key
- status
- archived
- start_date
- end_date
- created_at
- parameters
- metrics
- metrics_secondary
- conclusion
- conclusion_comment
experiment-create-from-prompt:
operation: experiments_create_from_prompt_create
enabled: true
scopes:
- experiment:write
- llm_prompt:read
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Create experiment from LLM prompt
description: >
Create a draft experiment that compares versions of an existing LLM prompt (managed with the llma-prompt
tools). Pass the prompt's exact name (call llma-prompt-list to discover names), two or more of its version
numbers (the first entry becomes the control variant), and one or more metric templates — call
experiment-prompt-templates for the allowed template keys. Builds one variant per version, each carrying a
feature flag payload with the prompt name and version so the SDK can resolve which version to serve, and
attaches one primary metric per template, scoped to the prompt. The feature flag is auto-created — do not
create one separately. Launch the experiment with the launch tool when ready.
ui_app: experiment
response:
include:
- id
- name
- description
- type
- feature_flag_key
- status
- archived
- start_date
- end_date
- created_at
- parameters
- metrics
- metrics_secondary
- conclusion
- conclusion_comment
experiment-delete:
operation: experiments_destroy
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: true
idempotent: true
title: Delete experiment
description: >
Delete an experiment by ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Always confirm by name before deleting.
param_overrides:
id:
cast: string-int
soft_delete: true
response:
include:
- id
- name
- deleted
experiment-duplicate:
operation: experiments_duplicate_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Duplicate experiment
description: >
Load the managing-experiment-lifecycle skill for preconditions and side effects.
Create a copy of an experiment as a new draft. The duplicate includes the original's metrics, parameters,
and configuration but starts fresh with no dates or results. Rejects experiments that use legacy metrics
(ExperimentTrendsQuery/ExperimentFunnelsQuery).
IMPORTANT: always provide a unique feature_flag_key that differs from the original experiment's flag key. If
omitted or if the same key is provided, the duplicate will reuse the original experiment's feature flag —
meaning changes to one experiment's flag (e.g. shipping a variant, pausing) will affect both experiments.
Always confirm the new flag key with the user before duplicating. Optionally provide a custom name (defaults
to "Original Name (Copy)").
include_params:
- name
- feature_flag_key
param_overrides:
id:
cast: string-int
experiment-end:
operation: experiments_end_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: End experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
End a running experiment without shipping a variant. Sets end_date to now and transitions status to stopped.
The feature flag is NOT modified — users continue seeing their assigned variants and exposure events
continue to fire, but only data up to end_date is included in results.
Optionally provide conclusion ("won", "lost", "inconclusive", "stopped_early", "invalid") and
conclusion_comment.
Returns 400 if the experiment is in draft ("Experiment has not been launched yet.") or already stopped
("Experiment has already ended.").
Other options: use experiment-ship-variant to end AND roll out a winner. Use pause to temporarily deactivate
the flag without ending.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-freeze-exposure:
operation: experiments_freeze_exposure_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Freeze experiment exposure
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions, limitations, and
side effects.
Freeze exposure on a running experiment: stop enrolling NEW users while already-enrolled users keep their
variant and metrics keep flowing (end_date stays null). Snapshots the already-exposed users into a static
cohort and narrows every release condition on the feature flag to that cohort. Use for long-horizon metrics
(revenue, LTV, retention, renewals) where enrollment should stop but measurement should continue — unlike
end (stops measurement at end_date) or pause (deactivates the flag for everyone). Status becomes
"exposure_frozen". Reversible via experiment-unfreeze-exposure.
SYNCHRONOUS AND POTENTIALLY SLOW: the exposure scan and cohort snapshot run inline in this call, and
duration scales with the number of exposed persons — experiments with tens of thousands of exposed users can
take tens of seconds. Wait for the response rather than assuming failure or retrying.
Not applicable (returns 400) for: experiments that aren't running ("Experiment has not been launched yet.",
"Experiment has already ended.", "Cannot freeze a paused experiment. Resume it first.", "Experiment exposure
is already frozen."), group-aggregated experiments (a person cohort cannot freeze group-based matching),
experiments in a holdout (holdout assignment is evaluated before release conditions), flags with early
access conditions (also evaluated before release conditions), flags that are deleted, missing, or have no
release conditions, exposed sets too large to snapshot synchronously (the scan is bounded by a person cap
and a timeout), and experiments where more than a small share of exposures are anonymous ("personless")
users — they can never match a person cohort, so experiments on logged-out surfaces are not freezable. When
a freeze is rejected, explain the limitation to the user instead of retrying.
Good to know: SDKs using local evaluation cannot resolve static cohorts, so a frozen flag evaluates via the
/decide endpoint; exposures ingested in the final moments before freezing may miss the snapshot;
ship-variant and reset strip the freeze, while end leaves the flag narrowed to the snapshot cohort.
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-get:
operation: experiments_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
enrich_url: '{id}'
title: Get experiment details
description: >
Get a single experiment by ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first.
Returns the full experiment object including: status (draft/running/paused/exposure_frozen/stopped —
"paused" is a virtual value returned when the experiment is launched but its feature flag has been
deactivated, "exposure_frozen" when its flag's release groups are narrowed to the already-exposed cohort so
enrollment is closed while metrics keep flowing), start_date and end_date, linked feature_flag (with
variants, filters, active state), metrics and metrics_secondary arrays, conclusion and conclusion_comment if
ended, parameters (including feature_flag_variants and recommended_running_time), stats_config, type
(web/product), and resolved_exposure_event (the event exposures are counted on when no custom exposure event
is configured — resolved server-side to $feature_flag_called or $experiment_exposure; use it in SQL instead
of hardcoding either name).
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-holdouts-create:
operation: experiment_holdouts_create
enabled: true
scopes:
- experiment_holdout:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Create experiment holdout
description: >
Create a holdout group — a reserved slice of users excluded from experiment exposure, used as a held-back
baseline. Requires name and a non-empty filters array. Link experiments to it afterwards by passing the
returned id as holdout_id on experiment-create or experiment-update. Note that you can't change which
holdout an experiment points to once that experiment is running; the holdout itself stays editable (see
experiment-holdouts-partial-update, whose edits cascade to all linked experiments).
include_params:
- name
- description
- filters
param_overrides:
filters:
description: >
Non-empty list of release-condition groups defining the held-out population. Each element needs a
`rollout_percentage` (0–100, may be fractional) — this is the EXCLUSION percentage, i.e. the share
of users held back from every experiment that references this holdout. `properties` optionally
narrows the group by person/group properties (same shape as feature-flag release conditions); pass
an empty array for an unconditional holdout. Do NOT set `variant` — the server sets it to
`holdout-{id}` automatically. Only the first element's `rollout_percentage` is embedded into each
linked experiment's feature flag. Example: [{ "rollout_percentage": 10, "properties": [] }].
experiment-holdouts-destroy:
operation: experiment_holdouts_destroy
enabled: true
scopes:
- experiment_holdout:write
annotations:
readOnly: false
destructive: true
idempotent: true
title: Delete experiment holdout
description: >
Delete a holdout group by ID. Use experiment-holdouts-list first if you don't have the ID, and always
confirm by name before deleting.
This clears the holdout reference from every linked experiment and feature flag (sets the flag's holdout to
null and nulls Experiment.holdout) — the affected experiments stop holding back any users. Returns 204 on
success.
param_overrides:
id:
cast: string-int
experiment-holdouts-list:
operation: experiment_holdouts_list
enabled: true
scopes:
- experiment_holdout:read
annotations:
readOnly: true
destructive: false
idempotent: true
list: true
title: List experiment holdouts
description: >
List the experiment holdout groups in the current project. A holdout reserves a stable slice of users who
are excluded from experiment exposure, so you can measure the long-term effect of all experiments against a
held-back baseline. A holdout is NOT a feature flag — it is a separate object that experiments reference via
holdout_id.
Each holdout has a name, optional description, and a filters array (the held-out population definition).
Holdouts are project-scoped. Use the returned id with experiment-create / experiment-update's holdout_id to
link an experiment to a holdout.
response:
include:
- id
- name
- description
- filters
- created_at
- updated_at
experiment-holdouts-partial-update:
operation: experiment_holdouts_partial_update
enabled: true
scopes:
- experiment_holdout:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Update experiment holdout
description: >
Update a holdout group's name, description, or filters by ID. Use experiment-holdouts-list first if you
don't have the ID.
CAUTION: editing the filters (e.g. the rollout/exclusion percentage) cascades atomically to EVERY experiment
linked to this holdout — it rewrites the exclusion_percentage on each linked feature flag, silently
re-assigning the held-out population for all of those experiments at once. Confirm with the user before
changing filters on a holdout that experiments depend on.
param_overrides:
id:
cast: string-int
filters:
description: >
Non-empty list of release-condition groups defining the held-out population. Each element needs a
`rollout_percentage` (0–100, may be fractional) — the EXCLUSION percentage. Do NOT set `variant`
(the server manages it as `holdout-{id}`). Changing this cascades to every linked experiment's
feature flag. Example: [{ "rollout_percentage": 10, "properties": [] }].
experiment-holdouts-retrieve:
operation: experiment_holdouts_retrieve
enabled: true
scopes:
- experiment_holdout:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get experiment holdout details
description: >
Get a single experiment holdout group by ID, including its name, description, and filters array (the
held-out population definition). Use experiment-holdouts-list first if you don't have the ID.
param_overrides:
id:
cast: string-int
experiment-holdouts-update:
operation: experiment_holdouts_update
enabled: false
experiment-launch:
operation: experiments_launch_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Launch experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Launch a draft experiment. Validates the feature flag is multivariate with 2 to 20 variants. Activates the
feature flag (sets active=true), sets start_date to the current server time, recomputes metric fingerprints,
and transitions status from draft to running.
Returns 400 if the experiment has already been launched ("Experiment has already been launched.") or if the
flag configuration is invalid.
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
response:
include:
- id
- name
- description
- type
- feature_flag_key
- status
- archived
- start_date
- end_date
- created_at
- parameters
- metrics
- metrics_secondary
- conclusion
- conclusion_comment
experiment-list:
operation: experiments_list
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
enrich_url: '{id}'
list: true
title: Get all experiments
description: >
List experiments in the current project. This is the primary tool for resolving experiment references — load
the finding-experiments skill for guidance on searching by name, status, recency, or description.
Supports filtering by status ("draft", "running", "paused", "exposure_frozen", "stopped", "complete" which
maps to stopped, or "all"), archived state (defaults to non-archived), feature_flag_id, created_by_id, and
free-text search on name. Supports ordering by an allowlisted set of fields (model fields plus computed
"duration" and "status"). Returns paginated results with each experiment's status, dates, feature flag key,
and metrics summary. Use the returned ID for get/update/lifecycle tools.
param_overrides:
status:
description: >
Filter by experiment status. Values: "draft" (not yet launched), "running" (launched, flag active),
"paused" (launched, flag deactivated — mutually exclusive with running), "exposure_frozen"
(launched, enrollment frozen to the already-exposed cohort while metrics keep flowing), "stopped" or
"complete" (both mean ended), "all" (no filter). Defaults to all non-archived experiments.
limit:
cast: string-int
offset:
cast: string-int
feature_flag_id:
cast: string-int
ui_app: experiment-list
response:
include:
- id
- name
- description
- feature_flag_key
- feature_flag
- start_date
- end_date
- archived
- type
- status
- created_at
- updated_at
experiment-metrics-recalculation-create:
operation: experiments_metrics_recalculation_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: true
enrich_url: '{params.id}'
title: Recalculate experiment metrics
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first.
Start a fresh calculation of every metric on this experiment against the latest data. Use this when the user
asks to refresh, recalculate, or re-run results, or when results returned by
experiment-metrics-recalculation-latest look stale. Reading results does NOT trigger a new run, this tool is
the only way to start one.
This queues background work that can take minutes on large experiments. Safe to call more than once: if a
run is already pending or in progress, it returns that run (with is_existing true, HTTP 200) instead of
starting a second one.
The response has no results array, the run has only just been queued. Take the returned id and poll
experiment-metrics-recalculation-retrieve with it, or call experiment-metrics-recalculation-latest, until
status is completed or failed. Watch rows_read and completed_metrics for progress. Do not poll in a tight
loop; wait a few seconds between checks.
exclude_params:
- trigger
param_overrides:
id:
cast: string-int
experiment-metrics-recalculation-latest-retrieve:
operation: experiments_metrics_recalculation_latest_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
enrich_url: '{params.id}'
title: Get experiment results
description: >
The default way to read an experiment's current results. Requires an experiment ID. If you don't have the
ID, load the finding-experiments skill to resolve the user's reference first. To interpret what the numbers
mean, load the diagnosing-experiment-results skill.
Returns the most recent completed metrics run for the experiment: a results array with one entry per metric,
each carrying per-variant statistics (exposures, counts, means, credible intervals, chance to beat control,
significance), plus run-level status, created_at, completed_at, and query_to (the data freshness cutoff
these numbers were computed against, use it to tell the user how fresh the results are).
This is a pure read and never starts a calculation, so the numbers can be stale. If the user wants fresh
numbers, or query_to is old, call experiment-metrics-recalculation-create and then poll.
Two fields matter for reading the response correctly. active_run is present when a run is currently
executing, the returned results are from the previous run, and you can poll active_run.id with
experiment-metrics-recalculation-retrieve for live progress. result_source is 'timeseries_fallback' when the
experiment has never completed a run and these numbers are a cold-start placeholder built from daily
timeseries data, rather than 'recalculation' for a real run; say so if you report those numbers.
Returns 404 when the experiment has no results at all yet, which usually means it has not run long enough or
has never been calculated.
param_overrides:
id:
cast: string-int
experiment-metrics-recalculation-retrieve:
operation: experiments_metrics_recalculation_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
enrich_url: '{params.id}'
title: Get experiment recalculation run
description: >
Get one specific metrics recalculation run by its UUID, for the experiment it belongs to. Use this to poll a
run you started with experiment-metrics-recalculation-create, or the active_run.id returned by
experiment-metrics-recalculation-latest.
To read an experiment's current results without a specific run id, use
experiment-metrics-recalculation-latest instead.
Returns the run's status (pending, in_progress, completed, failed), progress counters (completed_metrics and
failed_metrics out of total_metrics, plus rows_read against estimated_rows_total), the results array once
metrics finish, and metric_errors for metrics that failed. metric_retries holds transient state for metrics
between failed attempts; ignore entries for metrics that already have a result, as those are stale.
Returns 404 if the run id does not exist or belongs to a different experiment.
param_overrides:
id:
cast: string-int
experiment-pause:
operation: experiments_pause_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Pause experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Pause a running experiment by deactivating its feature flag (sets flag active=false). The flag is no longer
returned by the /decide endpoint, so users fall back to the application default (typically control). No new
exposure events are recorded while paused. The experiment stays in running status — it is not ended.
Returns 400 if: experiment is in draft ("Experiment has not been launched yet."), already stopped
("Experiment has already ended."), no linked flag ("Experiment does not have a feature flag linked."), or
already paused ("Experiment is already paused.").
No request body needed. Use resume to reactivate.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-prompt-templates:
operation: experiments_prompt_templates_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: List LLM prompt metric templates
description: >
List the metric templates accepted by experiment-create-from-prompt. Each entry has a key (the value to pass
in that tool's templates parameter), a label, and a description of what the metric measures.
experiment-reset:
operation: experiments_reset_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Reset experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Reset an experiment back to draft state. Clears start_date, end_date, conclusion, conclusion_comment, and
archived flag (all set to null/false). The feature flag is left unchanged — users continue to see their
currently assigned variants.
Previously collected events still exist in the database but won't be included in results unless start_date
is manually adjusted after re-launch.
Returns 400 if the experiment is already in draft ("Experiment is already in draft state.").
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-resume:
operation: experiments_resume_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Resume experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Resume a paused experiment by reactivating its feature flag (sets flag active=true). Users are re-bucketed
deterministically into the same variants they had before the pause, and exposure tracking resumes.
Returns 400 if: experiment is in draft ("Experiment has not been launched yet."), already stopped
("Experiment has already ended."), no linked flag ("Experiment does not have a feature flag linked."), or
not currently paused ("Experiment is not paused.").
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-saved-metrics-create:
operation: experiment_saved_metrics_create
enabled: true
scopes:
- experiment_saved_metric:write
annotations:
readOnly: false
destructive: false
idempotent: false
title: Create shared experiment metric
description: >
Create a reusable shared metric. Requires name (unique per project, case-insensitive) and query
(kind='ExperimentMetric', metric_type one of 'mean', 'funnel', 'ratio', 'retention'). Optional: description,
tags.
Load the configuring-experiment-analytics skill before constructing the query, and use read-data-schema to
confirm event names. Legacy kinds (ExperimentTrendsQuery, ExperimentFunnelsQuery) are rejected.
To attach to an experiment, call experiment-update with saved_metrics_ids — each item has 'id' and
'metadata.type' ('primary' or 'secondary').
experiment-saved-metrics-destroy:
operation: experiment_saved_metrics_destroy
enabled: true
scopes:
- experiment_saved_metric:write
annotations:
readOnly: false
destructive: true
idempotent: true
title: Delete shared experiment metric
description: >
Delete a shared metric by ID. REQUIRES EXPLICIT USER CONFIRMATION — confirm by name first, and warn that any
experiments using it will lose access.
param_overrides:
id:
cast: string-int
experiment-saved-metrics-list:
operation: experiment_saved_metrics_list
enabled: true
scopes:
- experiment_saved_metric:read
annotations:
readOnly: true
destructive: false
idempotent: true
list: true
title: List shared experiment metrics
description: >
List shared metrics in the current project. Returns paginated results with id, name, description, query,
created_at, updated_at, tags.
To REUSE a metric by what it measures (rather than by its name): pass ?event=<the event you're measuring> to
find metrics that reference it — directly or via an action — then compare each candidate's 'query'
(metric_type + math) against the metric you'd otherwise build. Use 'search' (name/description/tags) only
when the user named a specific metric to attach.
param_overrides:
limit:
cast: string-int
offset:
cast: string-int
event:
description: >
Filter to shared metrics whose query references this event name — matched directly (an EventsNode)
or via the step events of any action the metric references. For finding a reusable metric by what it
measures; then confirm the match against each row's 'query'.
response:
include:
- id
- name
- description
- query
- created_at
- updated_at
- tags
experiment-saved-metrics-partial-update:
operation: experiment_saved_metrics_partial_update
enabled: true
scopes:
- experiment_saved_metric:write
annotations:
readOnly: false
destructive: false
idempotent: true
title: Update shared experiment metric
description: >
Partially update a shared metric. Only name, description, and query are mutable. Edits propagate to every
experiment the metric is attached to — REQUIRES EXPLICIT USER CONFIRMATION before changing query or name on
a metric attached to running experiments.
Legacy-format metrics (kind=ExperimentTrendsQuery / ExperimentFunnelsQuery) cannot be updated.
param_overrides:
id:
cast: string-int
experiment-saved-metrics-retrieve:
operation: experiment_saved_metrics_retrieve
enabled: true
scopes:
- experiment_saved_metric:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get shared experiment metric
description: >
Get a single shared metric by ID. Returns the full query JSON (metric_type, source, math, filters, etc.). If
you don't have the ID, call experiment-saved-metrics-list first.
param_overrides:
id:
cast: string-int
experiment-saved-metrics-update:
operation: experiment_saved_metrics_update
enabled: false
experiment-ship-variant:
operation: experiments_ship_variant_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: true
idempotent: false
enrich_url: '{id}'
title: Ship experiment variant
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
REQUIRES EXPLICIT USER CONFIRMATION BEFORE CALLING. This permanently rewrites the linked feature flag and
cannot be undone via the API. Two release modes — confirm which one the user wants before invoking:
- Default (release_to_everyone=false): updates only the variant distribution so the selected variant gets
100%. Existing release conditions and per-user variant overrides on the flag are preserved untouched — the
variant is served only to users who already match them.
- release_to_everyone=true: in addition to flipping the variant distribution, prepends a catch-all release
condition that rolls the variant out to 100% of users. This OVERRIDES any existing release conditions and
per-user variant overrides on the flag. Only use this when the user explicitly wants to release beyond the
experiment's existing population.
Even when the user has asked you to ship a specific variant, ask them to confirm with the variant key,
target experiment, and release mode before invoking this tool.
Ship a variant to 100% of users by rewriting the feature flag. Requires variant_key — the key of the variant
to ship (e.g. "test" or "control"). The flag is rewritten so the selected variant gets 100% rollout via a
new catch-all release group prepended to existing groups (preserved for rollback).
Can be called on both running and stopped experiments. If the experiment is still running, it is also ended
(end_date set, status becomes stopped). If already stopped, only the flag is rewritten — supports the "end
first, ship later" workflow.
Optionally provide conclusion ("won", "lost", "inconclusive", "stopped_early", "invalid") and
conclusion_comment.
Returns 400 if: experiment is in draft ("Experiment has not been launched yet."), variant_key not found on
the flag, or no linked feature flag. Returns 409 if an approval policy requires review before the flag
change takes effect — includes a change_request_id in the response.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-stats:
operation: experiments_stats_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get experiment velocity stats
description: >
Get aggregate experiment velocity statistics for the current project. Returns: launched_last_30d
(experiments launched in the last 30 days), launched_previous_30d (the 30 days before that), percent_change
between the two periods, active_experiments (currently running count), and completed_last_30d (experiments
ended in the last 30 days). No parameters needed.
experiment-timeseries-results:
operation: experiments_timeseries_results_retrieve
enabled: true
scopes:
- experiment:read
annotations:
readOnly: true
destructive: false
idempotent: true
title: Get experiment metric results over time
description: >
Only for the day-by-day history of a SINGLE metric — how its results moved over the course of the
experiment. For an experiment's current results across all metrics, which is what "how is this experiment
doing" almost always means, use experiment-metrics-recalculation-latest instead.
Requires metric_uuid and fingerprint as query parameters (both available from the experiment's metrics array
— each metric has a uuid and the fingerprint is computed from its configuration), so you generally need to
fetch the experiment first.
Returns a timeseries object keyed by date (YYYY-MM-DD) with per-variant statistical results for each day,
overall status ("pending", "completed", "partial", "failed"), computed_at timestamp, and
recalculation_status if a recalculation is in progress.
param_overrides:
id:
cast: string-int
ui_app: experiment-results
experiment-unarchive:
operation: experiments_unarchive_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Unarchive experiment
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Unarchive an archived experiment to restore it to the default list view.
Returns 400 if the experiment is not currently archived ("Experiment is not archived.").
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-unfreeze-exposure:
operation: experiments_unfreeze_exposure_create
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: false
enrich_url: '{id}'
title: Unfreeze experiment exposure
description: >
Requires an experiment ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Load the managing-experiment-lifecycle skill for preconditions and side effects.
Reopen enrollment on an exposure-frozen experiment. Removes the snapshot-cohort condition and freeze markers
from every release group, restoring the flag's original targeting: new users can enroll again and
already-enrolled users keep their variant (deterministic bucketing). The snapshot cohort is deleted. Status
returns to "running".
WARNING — can introduce bias: reopening enrollment re-exposes the flag to a potentially new population.
Users who enrolled before the freeze and users who enroll after the unfreeze joined at different times (and
possibly under different conditions), so mixing the two cohorts in one analysis can bias the results. Warn
the user about this before unfreezing, especially if the freeze lasted a while or something about the
audience or product changed in between. If they only wanted to sanity-check the frozen results, they may not
need to unfreeze at all.
Returns 400 if the experiment is a draft ("Experiment has not been launched yet."), already ended
("Experiment has already ended."), or its exposure is not frozen ("Experiment exposure is not frozen.").
No request body needed.
param_overrides:
id:
cast: string-int
ui_app: experiment
experiment-update:
operation: experiments_partial_update
enabled: true
scopes:
- experiment:write
annotations:
readOnly: false
destructive: false
idempotent: true
enrich_url: '{id}'
title: Update experiment
description: >
RULES (follow before calling):
1. If changing rollout or variants on a RUNNING
experiment, you MUST warn the user about user
experience AND statistical implications BEFORE
making the change and get explicit confirmation.
Load the configuring-experiment-rollout skill for
the full warning. Do NOT silently apply the change
— even if the user asked directly.
2. If the user mentions a percentage, clarify whether
they mean variant split or overall rollout BEFORE
calling.
3. If adding or changing metrics, load the
configuring-experiment-analytics skill first. Call
read-data-schema to discover available events BEFORE
suggesting or using any event name. Never suggest
event names you haven't confirmed exist.
4. For running experiments, set
update_feature_flag_params=true when changing
feature_flag config — without it, the request is
rejected because the change would not sync to the
feature flag.
5. Setting allow_unknown_events=true REQUIRES EXPLICIT
USER CONFIRMATION. When the API returns
"Event(s) '...' not found", do NOT silently retry
with allow_unknown_events=true. Default to
course-correcting: surface the missing event names
and ask whether the user meant a different (existing)
event. Only set allow_unknown_events=true when the
user explicitly confirms the event is intentional
(e.g. about to be instrumented). Course-correcting
to a known event is the expected default; flipping
the flag is the exception.
Update an experiment by ID. If you don't have the ID, load the finding-experiments skill to resolve the
user's reference first. Metrics can be changed at any time including while running. Cannot add/remove
variants on non-draft experiments (changing percentages between existing variants is allowed). Cannot change
holdout on non-draft experiments.
Do NOT use this to change lifecycle state — use the dedicated launch, end, pause, resume, archive, or
experiment-ship-variant tools instead.
include_params:
- name
- description
- feature_flag
- parameters
- running_time_calculation
- excluded_variants
- metrics
- metrics_secondary
- saved_metrics_ids
- stats_config
- exposure_criteria
- holdout_id
- conclusion
- conclusion_comment
- archived
- allow_unknown_events
- update_feature_flag_params
param_overrides:
id:
cast: string-int
feature_flag:
description: >
Variant split, rollout scope, payloads, and experience continuity for the linked feature flag, in
the flag's own filters shape. This is the canonical input for flag config. Set
filters.multivariate.variants (each with key and rollout_percentage; percentages must sum to 100;
the analysis baseline defaults to the variant keyed 'control' when present, else the first variant —
except web experiments, which must keep a variant keyed 'control') to change the variant split. Set
filters.groups to a single group [{"properties": [], "rollout_percentage": N}] (0-100) to change the
overall rollout. Config this object omits is preserved from the flag's current state. On a running
experiment this requires update_feature_flag_params=true (see rule 1: warn the user first).
running_time_calculation:
description: >
Persist a running-time / sample-size plan onto the experiment (the planning target shown in the
experiment's running-time panel). Object with optional keys: minimum_detectable_effect (percentage,
e.g. 20 for a 20% lift), recommended_sample_size (total across all variants),
recommended_running_time (days), and exposure_estimate_config.
saved_metrics_ids:
input_schema: SavedMetricsAttachSchema
ui_app: experiment
response:
include:
- id
- name
- description
- type
- feature_flag_key
- status
- archived
- start_date
- end_date
- created_at
- parameters
- running_time_calculation
- metrics
- metrics_secondary
- saved_metrics
- conclusion
- conclusion_comment
experiments-create-exposure-cohort:
operation: experiments_create_exposure_cohort_for_experiment_create
enabled: false
experiments-flag-cleanup-target-retrieve:
operation: experiments_flag_cleanup_target_retrieve
enabled: false
experiments-flag-cleanup-task-retrieve:
operation: experiments_flag_cleanup_task_retrieve
enabled: false
experiments-recalculate-timeseries:
operation: experiments_recalculate_timeseries_create
enabled: false
experiments-session-buckets-create:
operation: experiments_session_buckets_create
enabled: false
experiments-session-context-retrieve:
operation: experiments_session_context_retrieve
enabled: false
experiments-session-contexts-create:
operation: experiments_session_contexts_create
enabled: false
experiments-update:
operation: experiments_update
enabled: false