bitfab-cli 0.2.380 → 0.2.381
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +120 -85
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23783,7 +23783,7 @@ var listTraceFunctions = {
|
|
|
23783
23783
|
var searchTraces = {
|
|
23784
23784
|
name: "search_traces",
|
|
23785
23785
|
title: "Search Traces",
|
|
23786
|
-
description: "Search and filter traces for a traced function. Returns matching traces with IDs, status, timestamps, total duration (ms), total token usage (in/out/cached/total), model, and output preview (all when available). Replay traces (from
|
|
23786
|
+
description: "Search and filter traces for a traced function. Returns matching traces with IDs, status, timestamps, total duration (ms), total token usage (in/out/cached/total), model, and output preview (all when available). Replay traces (from experiments) carry the same totals. Use get_traces for per-span duration, tokens, time-to-first-token, and model. Supports keyword search, date range, status filter, and regex matching. Use drillDown to refine previous results.",
|
|
23787
23787
|
inputSchema: {
|
|
23788
23788
|
traceFunctionKey: external_exports.string().describe("The trace function key to search"),
|
|
23789
23789
|
searchQuery: external_exports.string().optional().describe("Full-text keyword search"),
|
|
@@ -23804,8 +23804,8 @@ var searchTraces = {
|
|
|
23804
23804
|
labelSource: external_exports.enum(["human", "agent"]).optional().describe('Filter by who authored the label: "human" for human-authored labels only, "agent" for agent-authored labels only. Omit to include any source. This is a pure authorship filter - for trust/validation, use the separate `validated` parameter.'),
|
|
23805
23805
|
validated: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe('If true, only return traces with a validated label - a label that is either human-authored OR an agent-authored label that a human has approved (`approvedAt` is set). Use this when you want trustworthy/reviewed labels. Combine with `labelSource: "agent"` to get only approved-agent labels (excluding pure-human labels). Omit (or pass false) to include unapproved labels too.'),
|
|
23806
23806
|
labelResult: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Filter by label result: true for passing labels, false for failing labels"),
|
|
23807
|
-
includeReplays: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Include replayed traces (traces from
|
|
23808
|
-
|
|
23807
|
+
includeReplays: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Include replayed traces (traces from experiments) in results. Defaults to false because replays have new inputs, not the original production inputs, so they're not useful when selecting traces to label or when sampling real user behavior. Set to true only when you specifically want to inspect replay results."),
|
|
23808
|
+
experimentId: external_exports.uuid().optional().describe("Filter to traces from a specific experiment ID. Automatically sets includeReplays to true."),
|
|
23809
23809
|
datasetId: external_exports.uuid().optional().describe("Filter to traces belonging to a specific dataset. Combine with other filters (status, labelResult, etc.) to narrow within the dataset."),
|
|
23810
23810
|
hasDbSnapshot: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Filter by whether the trace captured a database snapshot reference. true returns only traces that can be replayed against a pinned historical DB branch (the 'Snapshot captured' traces); false returns only traces with no snapshot. Omit to include both.")
|
|
23811
23811
|
}
|
|
@@ -23854,13 +23854,13 @@ var labelEvidenceShape = external_exports.preprocess(parseJsonString, external_e
|
|
|
23854
23854
|
var saveAgentLabels = {
|
|
23855
23855
|
name: "save_agent_labels",
|
|
23856
23856
|
title: "Save Agent Labels",
|
|
23857
|
-
description: "Set, skip, or archive the agent's pass/fail verdict on one or more traces (`labelSource=\"agent\"`). Use this AFTER you have read the traces with get_traces and decided yourself whether each one looks like a pass, a fail, or genuinely cannot be judged. To set a verdict, pass `label` (true=PASS, false=FAIL) and `annotation` (your reasoning, shown to the human reviewer in the labeling UI). Add `evidence` as `{ spanId, text }` entries to ground that reasoning in spans on the evaluated trace. Optionally pass `confidence` (`VeryLow|Low|Medium|High|VeryHigh`) to record how confident you are - surfaced in the labeling UI so reviewers can prioritize low-confidence verdicts. To explicitly skip a trace you cannot decide on (instead of leaving it unlabeled), pass `skip: true` and omit label/annotation - this records an explicit skip so coverage checks know you intentionally did not verdict it. To clear a previously-set agent verdict (e.g., you changed your mind or labeled the wrong trace), pass `archive: true` and omit label/annotation. New verdicts start unapproved (`approvedAt=null`); once a human approves one, it joins the validated dataset (queryable via `search_traces` with `validated: true`). Archiving is non-destructive - the row is hidden from all reads but kept for audit, and you can immediately re-label the trace from scratch. For replay results, key each label by the replay item's `originalTraceId` (the original trace it was replayed from; `sourceTraceId` is accepted as a deprecated alias) plus the top-level `
|
|
23857
|
+
description: "Set, skip, or archive the agent's pass/fail verdict on one or more traces (`labelSource=\"agent\"`). Use this AFTER you have read the traces with get_traces and decided yourself whether each one looks like a pass, a fail, or genuinely cannot be judged. To set a verdict, pass `label` (true=PASS, false=FAIL) and `annotation` (your reasoning, shown to the human reviewer in the labeling UI). Add `evidence` as `{ spanId, text }` entries to ground that reasoning in spans on the evaluated trace. Optionally pass `confidence` (`VeryLow|Low|Medium|High|VeryHigh`) to record how confident you are - surfaced in the labeling UI so reviewers can prioritize low-confidence verdicts. To explicitly skip a trace you cannot decide on (instead of leaving it unlabeled), pass `skip: true` and omit label/annotation - this records an explicit skip so coverage checks know you intentionally did not verdict it. To clear a previously-set agent verdict (e.g., you changed your mind or labeled the wrong trace), pass `archive: true` and omit label/annotation. New verdicts start unapproved (`approvedAt=null`); once a human approves one, it joins the validated dataset (queryable via `search_traces` with `validated: true`). Archiving is non-destructive - the row is hidden from all reads but kept for audit, and you can immediately re-label the trace from scratch. For replay results, key each label by the replay item's `originalTraceId` (the original trace it was replayed from; `sourceTraceId` is accepted as a deprecated alias) plus the top-level `experimentId` instead of a `traceId`: the server resolves it to the replay trace via lineage, so you never need a server-generated replay trace id. When the experiment ran each trace more than once, also pass the item's `attempt` so each attempt gets its own verdict. Returns an agent-readable summary with one parseable effective label line per updated trace, keyed by the id you supplied (`originalTraceId` for replay verdicts, otherwise `traceId`), so command callers can verify persistence. Before judging a replay, call get_trace_assertions on the original trace ids: an assertion says what the user asked this specific case to do, so the verdict is measured against that rather than a guess. When an assertion's target cannot be found on the trace you are judging, the check never ran, so pass `skip: true` for that trace rather than recording a FAIL. Pass `assertionId` to score ONE assertion on the trace (the `[ID: ...]` get_trace_assertions prints), one entry per assertion, all in the same call. Omit it for the trace's single whole-trace verdict. get_trace_labels reads these back one line per assertion, keyed by the same assertionId, so a per-assertion write is verifiable per assertion.",
|
|
23858
23858
|
inputSchema: {
|
|
23859
|
-
|
|
23859
|
+
experimentId: external_exports.uuid().optional().describe("The experiment ID. Required when any label targets a trace by originalTraceId (replay verdicts); ignored otherwise."),
|
|
23860
23860
|
labels: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.object({
|
|
23861
23861
|
traceId: external_exports.uuid().optional().describe("The trace ID to update. Provide this OR originalTraceId, not both."),
|
|
23862
23862
|
assertionId: external_exports.uuid().optional().describe("The assertion this verdict scores, taken from the `[ID: ...]` get_trace_assertions prints. Omit it to set the trace's whole-trace verdict, of which there is one. A replay may name an assertion it inherited from the original trace it was replayed from."),
|
|
23863
|
-
originalTraceId: external_exports.uuid().optional().describe("For replay verdicts: the original trace ID the replay item was replayed from (the replay item's originalTraceId). The server resolves it to the replay trace via lineage, so you never need a server replay trace id. Requires the top-level
|
|
23863
|
+
originalTraceId: external_exports.uuid().optional().describe("For replay verdicts: the original trace ID the replay item was replayed from (the replay item's originalTraceId). The server resolves it to the replay trace via lineage, so you never need a server replay trace id. Requires the top-level experimentId. Provide this OR traceId, not both."),
|
|
23864
23864
|
sourceTraceId: external_exports.uuid().optional().meta({ deprecated: true }).describe("Deprecated alias for originalTraceId. Prefer originalTraceId; accepted for back-compat."),
|
|
23865
23865
|
attempt: external_exports.number().int().min(0).optional().describe("For replay verdicts on an experiment that ran each trace more than once: which attempt of originalTraceId this verdict is for (0-based, the replay item's attempt). Defaults to 0, so single-attempt callers never need it."),
|
|
23866
23866
|
archive: external_exports.boolean().optional().describe("If true, archive the existing agent label for this trace and ignore label/annotation/skip/confidence. Default false."),
|
|
@@ -24087,32 +24087,32 @@ var rerunGradersOnExperiment = {
|
|
|
24087
24087
|
title: "Re-run Graders on Experiment",
|
|
24088
24088
|
description: "Re-score an experiment's completed replay traces with its attached graders, overwriting their previous verdicts. Use this after attaching a grader to an already-completed experiment (which persists the assignment but does not grade the existing traces) or after editing a grader's criteria. Only the replays belonging to this experiment are graded, never the originals they were compared against. Omit graderIds to re-run every grader attached to the experiment, or pass a subset; ids that are not attached are rejected. Waits up to 90 seconds and reports how many traces were graded; if it is still running, call again with the same arguments to check progress. Read the resulting verdicts with get_grader_labels.",
|
|
24089
24089
|
inputSchema: {
|
|
24090
|
-
|
|
24090
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments"),
|
|
24091
24091
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).optional().describe("Grader ids to re-run (1-100). Omit to re-run every grader attached to the experiment.")
|
|
24092
24092
|
}
|
|
24093
24093
|
};
|
|
24094
24094
|
var addGradersToExperiment = {
|
|
24095
24095
|
name: "add_graders_to_experiment",
|
|
24096
24096
|
title: "Add Graders to Experiment",
|
|
24097
|
-
description: "Attach one or more graders directly to an experiment
|
|
24097
|
+
description: "Attach one or more graders directly to an experiment so they run against its replay traces. Idempotent: attaching a grader already assigned is a no-op. Graders must be active (or live) and belong to this organization and the experiment's trace function; ids that are archived, not runnable, or mismatched are skipped and reported. The effective grader set at completion is the union of these direct attachments and the dataset's current runnable graders. Attaching to an already-completed experiment persists the assignment but does not immediately grade its existing replay traces; those traces are graded only the next time the experiment runs completion again (for example an SDK replay retry that re-runs complete) or a fresh replay runs. Use list_experiments to find experiment ids and list_graders to find grader ids.",
|
|
24098
24098
|
inputSchema: {
|
|
24099
|
-
|
|
24099
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments"),
|
|
24100
24100
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to attach (1-100)")
|
|
24101
24101
|
}
|
|
24102
24102
|
};
|
|
24103
24103
|
var removeGradersFromExperiment = {
|
|
24104
24104
|
name: "remove_graders_from_experiment",
|
|
24105
24105
|
title: "Remove Graders from Experiment",
|
|
24106
|
-
description: "Detach one or more graders from an experiment
|
|
24106
|
+
description: "Detach one or more graders from an experiment. Grader ids that are not currently attached are ignored and reported. The graders themselves are not deleted. Effect depends on run state: for an in-progress experiment, a detached grader that is also assigned to the experiment's dataset is re-added from the dataset when the experiment completes (so it still runs); for an already-completed experiment the grader set is a finalized snapshot with no dataset re-union, so detaching permanently drops that grader from the run's results.",
|
|
24107
24107
|
inputSchema: {
|
|
24108
|
-
|
|
24108
|
+
experimentId: external_exports.uuid().describe("The experiment ID"),
|
|
24109
24109
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
|
|
24110
24110
|
}
|
|
24111
24111
|
};
|
|
24112
24112
|
var addGradersToExperimentGroup = {
|
|
24113
24113
|
name: "add_graders_to_experiment_group",
|
|
24114
24114
|
title: "Add Graders to Experiment Group",
|
|
24115
|
-
description: "Assign one or more graders to every experiment
|
|
24115
|
+
description: "Assign one or more graders to every experiment currently in an experiment group. Grader assignments are stored on each experiment, not on the group, so experiments added later do not inherit them automatically. Completed experiments immediately queue any missing grader evaluations; pending experiments use the graders when they complete. Idempotent: assigning a grader already attached to an experiment is a no-op.",
|
|
24116
24116
|
inputSchema: {
|
|
24117
24117
|
experimentGroupId: external_exports.uuid().describe("The experiment group id"),
|
|
24118
24118
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to assign (1-100)")
|
|
@@ -24121,7 +24121,7 @@ var addGradersToExperimentGroup = {
|
|
|
24121
24121
|
var removeGradersFromExperimentGroup = {
|
|
24122
24122
|
name: "remove_graders_from_experiment_group",
|
|
24123
24123
|
title: "Remove Graders from Experiment Group",
|
|
24124
|
-
description: "Detach one or more graders from every experiment
|
|
24124
|
+
description: "Detach one or more graders from every experiment currently in an experiment group, undoing add_graders_to_experiment_group. Grader ids not assigned to any experiment in the group are ignored and reported. The graders themselves are not deleted, and archived graders can still be detached. Because assignments live on each experiment rather than on the group, this only affects experiments currently in the group. Effect depends on run state, matching remove_graders_from_experiment: an in-progress experiment re-adds a detached grader from its dataset when it completes if the dataset still assigns it, while an already-completed experiment has a finalized grader set, so detaching permanently drops that grader from its results.",
|
|
24125
24125
|
inputSchema: {
|
|
24126
24126
|
experimentGroupId: external_exports.uuid().describe("The experiment group id"),
|
|
24127
24127
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
|
|
@@ -24130,7 +24130,7 @@ var removeGradersFromExperimentGroup = {
|
|
|
24130
24130
|
var listExperiments = {
|
|
24131
24131
|
name: "list_experiments",
|
|
24132
24132
|
title: "List Experiments",
|
|
24133
|
-
description: "List experiments (
|
|
24133
|
+
description: "List experiments (replays against changed code) newest first, with status, pass/fail totals, delta (fixed/regressed/still-passing/still-failing), and the git state each ran from. Every filter is optional and they combine: narrow by traced function, git branch, dataset, or any mix; with no filters it lists the organization's most recent experiments. Call this to see past experiment results, or at the start of experiment mode to understand what's been tried before.",
|
|
24134
24134
|
inputSchema: {
|
|
24135
24135
|
traceFunctionKey: external_exports.string().min(1).optional().describe("Only experiments for this trace function key"),
|
|
24136
24136
|
gitBranch: external_exports.string().min(1).optional().describe("Only experiments run from this git branch (exact branch name, e.g. feature/shorter-prompt)"),
|
|
@@ -24141,17 +24141,17 @@ var listExperiments = {
|
|
|
24141
24141
|
var listExperimentTraces = {
|
|
24142
24142
|
name: "list_experiment_traces",
|
|
24143
24143
|
title: "List Experiment Traces",
|
|
24144
|
-
description: "Get individual trace results for an experiment
|
|
24144
|
+
description: "Get individual trace results for an experiment, including each replay trace's verdict (fixed, regressed, still-passing, still-failing, unpaired) by comparing against the original trace's label, plus token usage (input, output, cached, total) for the replay and the paired original so you can reason about cost and cache-read deltas. Use after list_experiments to drill into a specific experiment's results.",
|
|
24145
24145
|
inputSchema: {
|
|
24146
|
-
|
|
24146
|
+
experimentId: external_exports.uuid().describe("The experiment ID to get traces for")
|
|
24147
24147
|
}
|
|
24148
24148
|
};
|
|
24149
24149
|
var getReplayStatus = {
|
|
24150
24150
|
name: "get_replay_status",
|
|
24151
24151
|
title: "Get Replay Status",
|
|
24152
|
-
description: "Read the current
|
|
24152
|
+
description: "Read the current status of an experiment's replay, including local replay trace ID to server replay trace ID mapping. Safe to poll while replay is still running.",
|
|
24153
24153
|
inputSchema: {
|
|
24154
|
-
|
|
24154
|
+
experimentId: external_exports.uuid().describe("The experiment ID to get status for")
|
|
24155
24155
|
}
|
|
24156
24156
|
};
|
|
24157
24157
|
var getTemplateReference = {
|
|
@@ -24184,10 +24184,10 @@ var saveTemplate = {
|
|
|
24184
24184
|
var saveExperimentGroup = {
|
|
24185
24185
|
name: "save_experiment_group",
|
|
24186
24186
|
title: "Save Experiment Group",
|
|
24187
|
-
description: "Create or edit an experiment group. To create, omit experimentGroupId and provide
|
|
24187
|
+
description: "Create or edit an experiment group. To create, omit experimentGroupId and provide experimentIds plus an optional name and notes; the supplied experiments are grouped together. To edit, provide experimentGroupId and at least one of name or notes, but omit experimentIds; editing metadata does not change membership. Creation is idempotent when all supplied experiments already share one group. All resources must belong to the active organization.",
|
|
24188
24188
|
inputSchema: {
|
|
24189
24189
|
experimentGroupId: external_exports.uuid().optional().describe("Existing experiment group id to rename. Omit when creating."),
|
|
24190
|
-
|
|
24190
|
+
experimentIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).optional().describe("Experiment IDs to group (1-100). Required when creating; omit when editing."),
|
|
24191
24191
|
name: external_exports.string().trim().min(1).max(120).optional().describe("Human-readable group name. Optional when creating or editing."),
|
|
24192
24192
|
notes: external_exports.string().max(1e4).optional().describe("Optional group notes. Omit to keep existing notes when editing; pass an empty string to clear them.")
|
|
24193
24193
|
}
|
|
@@ -24211,9 +24211,9 @@ var getExperimentGroup = {
|
|
|
24211
24211
|
var saveExperiment = {
|
|
24212
24212
|
name: "save_experiment",
|
|
24213
24213
|
title: "Save Experiment",
|
|
24214
|
-
description: "Update an existing experiment
|
|
24214
|
+
description: "Update an existing experiment. Provide the experiment ID and at least one of name, notes, or experimentGroupId. Omitted fields keep their current values; pass an empty notes string to clear notes, a group id to move the experiment to that existing group, or null to remove it from its group.",
|
|
24215
24215
|
inputSchema: {
|
|
24216
|
-
|
|
24216
|
+
experimentId: external_exports.uuid().describe("The experiment ID to update"),
|
|
24217
24217
|
name: external_exports.string().trim().min(1).max(120).optional().describe("Optional human-readable experiment name"),
|
|
24218
24218
|
notes: external_exports.string().max(1e4).optional().describe("Optional experiment notes. Omit to keep existing notes; pass an empty string to clear them."),
|
|
24219
24219
|
experimentGroupId: external_exports.uuid().nullable().optional().describe("Existing experiment group id to move this experiment into. Omit to keep its current group; pass null to remove it from its group.")
|
|
@@ -24222,9 +24222,9 @@ var saveExperiment = {
|
|
|
24222
24222
|
var getExperiment = {
|
|
24223
24223
|
name: "get_experiment",
|
|
24224
24224
|
title: "Get Experiment",
|
|
24225
|
-
description: "Get a single experiment
|
|
24225
|
+
description: "Get a single experiment by experiment ID, with its name, notes, status, pass/fail totals, delta, experiment group, and grader results: the aggregate passing/failing checks and pass rate, plus a per-grader breakdown (each assigned grader's own passing/failing and pass rate on the run). Use this when you already have an experiment ID and want just that run's results, instead of paging the whole function's history with list_experiments.",
|
|
24226
24226
|
inputSchema: {
|
|
24227
|
-
|
|
24227
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments")
|
|
24228
24228
|
}
|
|
24229
24229
|
};
|
|
24230
24230
|
var getGraderLabels = {
|
|
@@ -24698,44 +24698,51 @@ var McpProxy = class {
|
|
|
24698
24698
|
};
|
|
24699
24699
|
|
|
24700
24700
|
// ../bitfab-plugin-lib/dist/bitfabToolHandlers.js
|
|
24701
|
+
function withLegacyExperimentKeys(args) {
|
|
24702
|
+
return {
|
|
24703
|
+
...args,
|
|
24704
|
+
...args.experimentId === void 0 ? {} : { testRunId: args.experimentId },
|
|
24705
|
+
...args.experimentIds === void 0 ? {} : { testRunIds: args.experimentIds }
|
|
24706
|
+
};
|
|
24707
|
+
}
|
|
24701
24708
|
function createBitfabToolHandlers(platform2, getConfig2, pluginVersion) {
|
|
24702
24709
|
const proxy = new McpProxy(platform2, pluginVersion);
|
|
24703
24710
|
function proxyToolCall(toolName, args) {
|
|
24704
|
-
return proxy.toolCall(getConfig2(), toolName, args);
|
|
24711
|
+
return proxy.toolCall(getConfig2(), toolName, withLegacyExperimentKeys(args));
|
|
24705
24712
|
}
|
|
24706
24713
|
async function handleSaveAgentLabels(args) {
|
|
24707
|
-
const {
|
|
24714
|
+
const { experimentId, labels } = args;
|
|
24708
24715
|
return proxyToolCall("save_agent_labels", {
|
|
24709
|
-
|
|
24716
|
+
experimentId,
|
|
24710
24717
|
labels: labels.map((l) => l.originalTraceId !== void 0 && l.sourceTraceId === void 0 ? { ...l, sourceTraceId: l.originalTraceId } : l)
|
|
24711
24718
|
});
|
|
24712
24719
|
}
|
|
24713
24720
|
async function handleSaveExperiment(args) {
|
|
24714
|
-
const {
|
|
24721
|
+
const { experimentId, name, notes, experimentGroupId } = args;
|
|
24715
24722
|
if (name === void 0 && notes === void 0 && experimentGroupId === void 0) {
|
|
24716
24723
|
return errorResult("Error saving experiment: provide name, notes, and/or experimentGroupId.");
|
|
24717
24724
|
}
|
|
24718
24725
|
return proxyToolCall("save_experiment", {
|
|
24719
|
-
|
|
24726
|
+
experimentId,
|
|
24720
24727
|
name,
|
|
24721
24728
|
notes,
|
|
24722
24729
|
experimentGroupId
|
|
24723
24730
|
});
|
|
24724
24731
|
}
|
|
24725
24732
|
async function handleSaveExperimentGroup(args) {
|
|
24726
|
-
const { experimentGroupId,
|
|
24727
|
-
if (experimentGroupId === void 0 &&
|
|
24728
|
-
return errorResult("Error saving experiment group:
|
|
24733
|
+
const { experimentGroupId, experimentIds, name, notes } = args;
|
|
24734
|
+
if (experimentGroupId === void 0 && experimentIds === void 0) {
|
|
24735
|
+
return errorResult("Error saving experiment group: experimentIds is required when creating an experiment group.");
|
|
24729
24736
|
}
|
|
24730
|
-
if (experimentGroupId !== void 0 &&
|
|
24731
|
-
return errorResult("Error saving experiment group:
|
|
24737
|
+
if (experimentGroupId !== void 0 && experimentIds !== void 0) {
|
|
24738
|
+
return errorResult("Error saving experiment group: experimentIds cannot be provided when editing an experiment group; editing does not change membership.");
|
|
24732
24739
|
}
|
|
24733
24740
|
if (experimentGroupId !== void 0 && name === void 0 && notes === void 0) {
|
|
24734
24741
|
return errorResult("Error saving experiment group: provide name and/or notes when editing an experiment group.");
|
|
24735
24742
|
}
|
|
24736
24743
|
return proxyToolCall("save_experiment_group", {
|
|
24737
24744
|
experimentGroupId,
|
|
24738
|
-
|
|
24745
|
+
experimentIds,
|
|
24739
24746
|
name,
|
|
24740
24747
|
notes
|
|
24741
24748
|
});
|
|
@@ -24907,10 +24914,10 @@ var semver3 = __toESM(require_semver2(), 1);
|
|
|
24907
24914
|
|
|
24908
24915
|
// ../bitfab-plugin-lib/dist/bakedSdkVersions.js
|
|
24909
24916
|
var BAKED_SDK_VERSIONS = {
|
|
24910
|
-
typescript: "0.59.
|
|
24911
|
-
python: "0.59.
|
|
24912
|
-
ruby: "0.59.
|
|
24913
|
-
go: "0.59.
|
|
24917
|
+
typescript: "0.59.1",
|
|
24918
|
+
python: "0.59.1",
|
|
24919
|
+
ruby: "0.59.1",
|
|
24920
|
+
go: "0.59.1"
|
|
24914
24921
|
};
|
|
24915
24922
|
|
|
24916
24923
|
// ../bitfab-plugin-lib/dist/installedSdk.js
|
|
@@ -25987,13 +25994,15 @@ function emitJson(event) {
|
|
|
25987
25994
|
// ../bitfab-plugin-lib/dist/commands/persistReplayLabels.js
|
|
25988
25995
|
import { readFileSync, unlinkSync } from "fs";
|
|
25989
25996
|
var lineageFileSchema = external_exports.object({
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
testRunId: external_exports.uuid(),
|
|
25997
|
+
experimentId: external_exports.uuid().optional(),
|
|
25998
|
+
testRunId: external_exports.uuid().optional(),
|
|
25993
25999
|
// Canonical; older item files carry the deprecated expectedSourceTraceIds.
|
|
25994
26000
|
expectedOriginalTraceIds: external_exports.array(external_exports.uuid()).min(1).optional(),
|
|
25995
26001
|
expectedSourceTraceIds: external_exports.array(external_exports.uuid()).min(1).optional(),
|
|
25996
26002
|
verdicts: external_exports.array(external_exports.unknown()).min(1)
|
|
26003
|
+
}).refine((d) => d.experimentId !== void 0 || d.testRunId !== void 0, {
|
|
26004
|
+
message: "lineage file must carry experimentId (or the deprecated testRunId)",
|
|
26005
|
+
path: ["experimentId"]
|
|
25997
26006
|
}).refine((d) => d.expectedOriginalTraceIds !== void 0 || d.expectedSourceTraceIds !== void 0, {
|
|
25998
26007
|
message: "lineage file must carry expectedOriginalTraceIds (or the deprecated expectedSourceTraceIds)",
|
|
25999
26008
|
path: ["expectedOriginalTraceIds"]
|
|
@@ -26016,28 +26025,54 @@ import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
|
26016
26025
|
import { createInterface } from "readline";
|
|
26017
26026
|
|
|
26018
26027
|
// ../bitfab-plugin-lib/dist/replayItemWriter.js
|
|
26019
|
-
import { mkdirSync as
|
|
26020
|
-
import { join as
|
|
26028
|
+
import { mkdirSync as mkdirSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
26029
|
+
import { join as join3 } from "path";
|
|
26021
26030
|
|
|
26022
26031
|
// ../bitfab-plugin-lib/dist/bestEffortFs.js
|
|
26023
26032
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
26024
26033
|
import { dirname } from "path";
|
|
26025
26034
|
|
|
26035
|
+
// ../bitfab-plugin-lib/dist/replayRunDir.js
|
|
26036
|
+
import { randomUUID } from "crypto";
|
|
26037
|
+
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
|
|
26038
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
26039
|
+
var EXPERIMENT_ID_KEYS = [
|
|
26040
|
+
"experimentId",
|
|
26041
|
+
"experiment_id",
|
|
26042
|
+
"testRunId",
|
|
26043
|
+
"test_run_id"
|
|
26044
|
+
];
|
|
26045
|
+
function firstString(result, keys) {
|
|
26046
|
+
if (!result || typeof result !== "object") {
|
|
26047
|
+
return null;
|
|
26048
|
+
}
|
|
26049
|
+
const rec = result;
|
|
26050
|
+
for (const key of keys) {
|
|
26051
|
+
const value = rec[key];
|
|
26052
|
+
if (typeof value === "string") {
|
|
26053
|
+
return value;
|
|
26054
|
+
}
|
|
26055
|
+
}
|
|
26056
|
+
return null;
|
|
26057
|
+
}
|
|
26058
|
+
function extractExperimentId(result) {
|
|
26059
|
+
return firstString(result, EXPERIMENT_ID_KEYS);
|
|
26060
|
+
}
|
|
26061
|
+
|
|
26026
26062
|
// ../bitfab-plugin-lib/dist/replayResultResolver.js
|
|
26027
|
-
import { readFileSync as
|
|
26028
|
-
import { resolve } from "path";
|
|
26063
|
+
import { readFileSync as readFileSync3, rmSync } from "fs";
|
|
26064
|
+
import { resolve as resolve2 } from "path";
|
|
26029
26065
|
var replayResultSchema = external_exports.object({
|
|
26030
26066
|
items: external_exports.array(external_exports.unknown()),
|
|
26067
|
+
experimentId: external_exports.string().optional(),
|
|
26068
|
+
experiment_id: external_exports.string().optional(),
|
|
26069
|
+
experimentUrl: external_exports.string().optional(),
|
|
26070
|
+
experiment_url: external_exports.string().optional(),
|
|
26031
26071
|
testRunId: external_exports.string().optional(),
|
|
26032
26072
|
test_run_id: external_exports.string().optional(),
|
|
26033
26073
|
testRunUrl: external_exports.string().optional(),
|
|
26034
26074
|
test_run_url: external_exports.string().optional()
|
|
26035
|
-
}).passthrough().refine((
|
|
26036
|
-
|
|
26037
|
-
// ../bitfab-plugin-lib/dist/replayRunDir.js
|
|
26038
|
-
import { randomUUID } from "crypto";
|
|
26039
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
26040
|
-
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
26075
|
+
}).passthrough().refine((result) => extractExperimentId(result) !== null);
|
|
26041
26076
|
|
|
26042
26077
|
// ../bitfab-plugin-lib/dist/commands/replayProgress.js
|
|
26043
26078
|
var HEARTBEAT_MS = Number(process.env.BITFAB_REPLAY_HEARTBEAT_MS) || 12e3;
|
|
@@ -28824,7 +28859,7 @@ var assistantFlow = Flow.parse({
|
|
|
28824
28859
|
},
|
|
28825
28860
|
openExperiments: {
|
|
28826
28861
|
description: "Print a link to experiments.",
|
|
28827
|
-
args: "<
|
|
28862
|
+
args: "<experimentIds>"
|
|
28828
28863
|
},
|
|
28829
28864
|
startDataset: {
|
|
28830
28865
|
description: "Print a dataset link.",
|
|
@@ -28915,7 +28950,7 @@ This skill has nine invocation modes, each a different entry point into the same
|
|
|
28915
28950
|
|
|
28916
28951
|
When you genuinely can't tell, default \`all\`. State the basis in the same one-line confirmation as \`costRun\`, e.g. "Starting experiment for \`generate-email\`, tracking token cost (uncached)." As with \`costRun\`, deciding this once here is what lets \`<tokensSuffix>\` resolve deterministically downstream instead of each open re-guessing the basis. \`costBasis\` is meaningless when \`costRun\` is false (no lens shows at all), so only carry it on a cost run.
|
|
28917
28952
|
|
|
28918
|
-
**Reset \`costRun\` if the user changes their mind after entry** (set it true on any later cost signal, not just an explicit "show me tokens": asking what a run costs, noting token counts look high, worrying about spend; set it false when they say to stop). The same intent-not-keywords reading applies here as at entry. The entry value is the default, not a lock; resetting on an explicit request is not the autonomous step-to-step re-guessing you must avoid. On a reset, re-open whatever token-capable surface is currently open so the live view matches: the experiments page in any form (\`?experimentGroupId=\`, \`?
|
|
28953
|
+
**Reset \`costRun\` if the user changes their mind after entry** (set it true on any later cost signal, not just an explicit "show me tokens": asking what a run costs, noting token counts look high, worrying about spend; set it false when they say to stop). The same intent-not-keywords reading applies here as at entry. The entry value is the default, not a lock; resetting on an explicit request is not the autonomous step-to-step re-guessing you must avoid. On a reset, re-open whatever token-capable surface is currently open so the live view matches: the experiments page in any form (\`?experimentGroupId=\`, \`?experimentIds=\`, or \`?datasetId=\`), re-navigated with or without \`&tokens=1\`. If nothing relevant is open, just hold the new value and the next open picks it up. One asymmetry to know: re-navigating with \`&tokens=1\` turns the lens on, but a param-less re-nav cannot turn it off (the page keeps the token columns sticky once shown); resetting to false still correctly drops the chat-side token-delta reporting in \`evaluate-results\` / \`share-results\` / the scorecard. **\`costBasis\` resets the same way.** A later signal can flip the basis without touching \`costRun\` (e.g. a token-count run where the user then says "what's the bill" flips \`costBasis\` to \`uncached\`; "I just mean total tokens" flips it back to \`all\`). On a basis change, re-open the open experiments surface with the matching suffix (\`&tokens=1&tokenType=all\` for \`all\`, \`&tokens=1&tokenType=uncached\` for \`uncached\`) so the live trend recolors, and the chat-side reports pick up the new basis on the next report. The toggle in the page header is the user's own live override of the same basis.
|
|
28919
28954
|
|
|
28920
28955
|
**Disambiguating \`benchmark\` from \`experiment\`** (both replay a dataset, so free-form text is easy to misroute):
|
|
28921
28956
|
|
|
@@ -30182,7 +30217,7 @@ Hold the chosen mode in working context. Every iteration below (\`make-change\`,
|
|
|
30182
30217
|
branches: [
|
|
30183
30218
|
{
|
|
30184
30219
|
when: "{{#claude}}bash output is `parallel` (bypass found in committed or user-global settings){{/claude}}{{^claude}}(unreachable on this editor){{/claude}}",
|
|
30185
|
-
description: `**Parallel mode.** For each independent experiment, fork to a subagent using the Agent tool with \`isolation: "worktree"\` and \`subagent_type: "general-purpose"\`. The subagent edits its worktree, runs replay, returns its scored items + \`
|
|
30220
|
+
description: `**Parallel mode.** For each independent experiment, fork to a subagent using the Agent tool with \`isolation: "worktree"\` and \`subagent_type: "general-purpose"\`. The subagent edits its worktree, runs replay, returns its scored items + \`experimentId\` to this main agent`,
|
|
30186
30221
|
next: "iterate/detect-replay-capabilities"
|
|
30187
30222
|
},
|
|
30188
30223
|
{
|
|
@@ -30281,7 +30316,7 @@ If \`renameFrom\` identifies the legacy TypeScript \`bitfab\` package, replace i
|
|
|
30281
30316
|
kind: "action",
|
|
30282
30317
|
toolCalls: ["bash"],
|
|
30283
30318
|
title: "Prepare experiment group before replay",
|
|
30284
|
-
body: "Generate one experimentGroupId for this iteration before making changes or running replay. Pass that same ID through the replay registry using --experiment-group-id. Do not emit or share an experiment link yet: generating a UUID does not create the group on the server. The first persisted replay creates it. Keep reporting replay progress in chat, then share the verified group or
|
|
30319
|
+
body: "Generate one experimentGroupId for this iteration before making changes or running replay. Pass that same ID through the replay registry using --experiment-group-id. Do not emit or share an experiment link yet: generating a UUID does not create the group on the server. The first persisted replay creates it. Keep reporting replay progress in chat, then share the verified group or experiment link in the open-experiments step after replay.",
|
|
30285
30320
|
next: {
|
|
30286
30321
|
byMode: {
|
|
30287
30322
|
wizard: "iterate/make-change",
|
|
@@ -30343,16 +30378,16 @@ The schema is flat, every file object is exactly \`{ path, before, after }\`. Do
|
|
|
30343
30378
|
|
|
30344
30379
|
**Automatic fallback capture.** If you do not pass \`--code-change\`, a recent SDK auto-captures the branch's diff **against trunk** (the merge-base with \`origin/main\`) inside \`replay()\` itself and attaches it, so even a run where you did not hand-write a payload still shows a diff, as long as the SDK is new enough (\`supportsCodeChanges\`), you are in a git repo, and the working tree carries the change. This fallback is **cumulative** (whole branch vs trunk), not per-experiment. The precise per-experiment before/after still comes from writing the \`--code-change\` payload above, which always wins over the fallback, so keep doing that in \`make-change\`; the trunk diff is a last resort, not a reason to skip it.
|
|
30345
30380
|
|
|
30346
|
-
**Check the \`supportsExperimentGroups\` flag** (from \`detect-replay-capabilities\`). If true and an \`experimentGroupId\` exists, pass \`--experiment-group-id <experimentGroupId>\` (from \`open-experiments-before-replay\`) so the
|
|
30381
|
+
**Check the \`supportsExperimentGroups\` flag** (from \`detect-replay-capabilities\`). If true and an \`experimentGroupId\` exists, pass \`--experiment-group-id <experimentGroupId>\` (from \`open-experiments-before-replay\`) so the experiment is tagged with the group. This includes the initial \`fixReplayScope = "single-trace"\` pass. If false, skip the flag; the post-replay Bitfab inspection can fall back to \`experimentId\` when available.
|
|
30347
30382
|
|
|
30348
|
-
**Check the \`supportsExperimentNames\` flag** (from \`detect-replay-capabilities\`). If true, pass \`--name "<experimentName>"\` so the resulting experiment
|
|
30383
|
+
**Check the \`supportsExperimentNames\` flag** (from \`detect-replay-capabilities\`). If true, pass \`--name "<experimentName>"\` so the resulting experiment is readable in the UI. Use the one-line change description from \`make-change\` as \`<experimentName>\`; in \`benchmark\` mode use \`Benchmark: current code baseline\`. Keep it 120 characters or fewer. If false, omit \`--name\`; this is cosmetic and the replay still runs.
|
|
30349
30384
|
|
|
30350
30385
|
**Choose trace selection.**
|
|
30351
30386
|
|
|
30352
30387
|
- **Initial \`fix\` pass (\`fixReplayScope = "single-trace"\`)**: pass \`--trace-ids <targetTraceId>\` (the trace ID held from Phase Fix \`resolve\`) and do NOT pass \`--dataset-id\`. This is the user's requested targeted replay. It proves the bug turned green before anything is added to a dataset or the rest of the dataset is run.
|
|
30353
30388
|
- **Full dataset runs**: check the \`supportsDatasetId\` flag (from \`detect-replay-capabilities\`). When true, this is the **preferred way to replay the dataset**: pass \`--dataset-id <datasetId>\` (the dataset id held in working context: from \`pick-dataset\` in \`experiment\` / \`cost-optimize\` / \`benchmark\` modes, or from \`fix-add-to-dataset\` in \`fix\` mode, which skips \`pick-dataset\`) and **omit \`--trace-ids\` entirely**. The server replays exactly the dataset's traces and durably attributes the experiment to the dataset, so it shows under the dataset's experiments even when trace lineage can't be reconstructed, and you don't have to enumerate the dataset's trace IDs by hand. Only when \`supportsDatasetId\` is false do you fall back to \`--trace-ids <the dataset's resolved trace ids>\` (attribution then relies on the derived trace-lineage join). If the installed SDK lacks \`--dataset-id\`, prefer upgrading it (see \`upgrade-replay-script\`) over the trace-ids fallback.
|
|
30354
30389
|
|
|
30355
|
-
**Run the replay through \`{{command:replayProgress}}\` in the background, then relay the progress lines it prints.** Before starting, choose a unique run directory for this replay, for example \`.bitfab/replays/<experimentN>-<timestamp>-<short-random>\`, pass it with \`--run-dir\`, and hold that path in context; do not use a shared \`--events-log\` path. A foreground run blocks you for the whole replay and the user just sees "a shell is running" with no detail. \`{{command:replayProgress}}\` runs the replay, turns the SDK's per-trace \`@@bitfab:progress\` events into one self-contained line per trace on its stdout (a header, then for each trace as it finishes a pass/fail glyph, the running \`n/total\`, and that trace's duration, with the error reason inline on failure, plus a liveness heartbeat line when a slow trace goes quiet so the run never looks frozen, then a final summary with total + average time), and writes a small JSONL event log at \`<run-dir>/events.jsonl\`: \`type: "progress"\` rows as items finish, then a final \`type: "complete"\` row. Large per-item payloads are written atomically under \`<run-dir>/items/\` and referenced by \`item.itemPath\` / \`items[].itemPath\`, so outputs are not duplicated in the event log. It also tees every human line to \`<run-dir>/progress.log\`, so the user can \`tail -f\` it in a separate terminal for a live view that never collapses the way a tool card does. The wrapper writes \`<run-dir>/run.json\`; once the server
|
|
30390
|
+
**Run the replay through \`{{command:replayProgress}}\` in the background, then relay the progress lines it prints.** Before starting, choose a unique run directory for this replay, for example \`.bitfab/replays/<experimentN>-<timestamp>-<short-random>\`, pass it with \`--run-dir\`, and hold that path in context; do not use a shared \`--events-log\` path. A foreground run blocks you for the whole replay and the user just sees "a shell is running" with no detail. \`{{command:replayProgress}}\` runs the replay, turns the SDK's per-trace \`@@bitfab:progress\` events into one self-contained line per trace on its stdout (a header, then for each trace as it finishes a pass/fail glyph, the running \`n/total\`, and that trace's duration, with the error reason inline on failure, plus a liveness heartbeat line when a slow trace goes quiet so the run never looks frozen, then a final summary with total + average time), and writes a small JSONL event log at \`<run-dir>/events.jsonl\`: \`type: "progress"\` rows as items finish, then a final \`type: "complete"\` row. Large per-item payloads are written atomically under \`<run-dir>/items/\` and referenced by \`item.itemPath\` / \`items[].itemPath\`, so outputs are not duplicated in the event log. It also tees every human line to \`<run-dir>/progress.log\`, so the user can \`tail -f\` it in a separate terminal for a live view that never collapses the way a tool card does. The wrapper writes \`<run-dir>/run.json\`; once the server experiment ID is known it also writes \`<parent-of-run-dir>/by-experiment/<experimentId>/run.json\` pointing back to the run directory. If the replay finishes no traces at all (an \`@bitfab/sdk\` too old to carry the lifecycle reporter, or an SDK too old to emit installed-command lifecycle events), it closes with a \`\u26A0 done \xB7 replay finished but reported no progress\` line instead of ending silently, so a missing stream of per-trace lines never reads as a hang, fix it via \`upgrade-replay-script\` and re-run. If the replay command exits 0 but its local \`ReplayResult\` capture is unavailable, the wrapper also exits 0, emits a \`\u26A0 unverified\` capture warning, and records \`status: "unverified"\` with \`resultCaptured: false\` in \`run.json\`: this prevents the host and metadata consumers from falsely reporting either failure or completion. Progress lines alone do not verify the result. Treat the outcome as unverified until the server experiment status resolves it; then explicitly report the server-confirmed outcome. You do NOT decide what or when to print: it does the formatting; you just show each new line.
|
|
30356
30391
|
|
|
30357
30392
|
With a current replay-capable SDK, the event log also contains \`type: "started"\` lifecycle rows. These identify historical trace IDs that have entered a worker but have not necessarily settled; they are diagnostic only and must not be evaluated as completed items.
|
|
30358
30393
|
|
|
@@ -30372,13 +30407,13 @@ cd <project-dir> && {{command:replayProgress}} --label <pipeline-name> --run-dir
|
|
|
30372
30407
|
|
|
30373
30408
|
3. **Run the live replay/evaluation loop while the replay is still running.** Use the run directory you passed with \`--run-dir\` and poll \`<run-dir>/events.jsonl\` alongside the human output. Each \`type: "progress"\` JSONL row is a normalized SDK item-finish event; \`item.originalTraceId\` is the **original trace id, the key verdicts are persisted by** (every item file carries it under the same \`originalTraceId\` field, plus a deprecated \`sourceTraceId\` alias). On modern SDKs, \`item.traceId\` may already hold the server replay trace ID after the per-item flush; it is null only when that flush/readback could not confirm the ID, and the final persistence barrier/result fills it when possible. \`item.replayTraceId\` is only set by older SDKs. Treat a new successful progress row as "this original trace's replay item finished" and start evaluation for that item as soon as the row carries \`item.itemPath\` and an \`item.originalTraceId\`: read \`item.itemPath\` for the full input, replay output, original output, and metadata. Failed progress rows become unreplayable candidates immediately and should be carried forward with their error string. If the row is progress-only (old SDK/current basic reporter: no \`item.itemPath\`), do the cheap prep only and defer judging to the final \`type: "complete"\` row's item refs. If the JSONL file is missing or empty (old SDK without installed-command lifecycle events, or an unwritable log path), do not block or treat it as a replay failure: continue from the final complete event from the final result artifact.
|
|
30374
30409
|
|
|
30375
|
-
For every item you can judge during the run, key the verdict by its **original trace id** (the \`originalTraceId\` field in each item file, the same \`item.originalTraceId\` on progress rows), the original trace the item was replayed from, which the server resolves to this run's replay trace, no local-to-server id mapping step. You need the run's \`
|
|
30410
|
+
For every item you can judge during the run, key the verdict by its **original trace id** (the \`originalTraceId\` field in each item file, the same \`item.originalTraceId\` on progress rows), the original trace the item was replayed from, which the server resolves to this run's replay trace, no local-to-server id mapping step. You need the run's \`experimentId\` to persist (enriched progress rows may carry \`event.experimentId\`; older scripts may only reveal it in the final \`ReplayResult\`). As soon as a small batch of judged items has its original trace ids and you know the \`experimentId\`, persist that batch with \`{{command:persistReplayLabels}}\`. Keep a set of original trace IDs already persisted so the final reconciliation never double-writes a verdict. A verdict judged live was judged before the item's outlines existed (\`originalTraceOutline\` and \`traceOutline\` are \`null\` on progress item files), so also keep the set of original trace ids judged without outlines; step 4 re-checks those against the final item files.
|
|
30376
30411
|
|
|
30377
|
-
4. **When the background command finishes, read the final \`type: "complete"\` row from this run's \`events.jsonl\`**. Its \`result\` carries run metadata (\`
|
|
30412
|
+
4. **When the background command finishes, read the final \`type: "complete"\` row from this run's \`events.jsonl\`**. Its \`result\` carries run metadata (\`experimentId\`, \`experimentUrl\`, \`itemCount\`) and its \`items\` array carries item refs. Read each needed \`items[].itemPath\` for the full replay item (trace ID, duration, tokens, model, full original/new outputs, and the two trace outlines). Read from the **files**, not from the captured command output, which the harness truncates in the middle. On current SDKs each completed item also carries \`originalTraceOutline\` and \`traceOutline\`: the original and the replayed trace's span tree (span names, types, nesting, order, durations, tokens, model, errors, and whether each span was mocked) with no inputs or outputs. The SDK fills both in at completion from the server, so they are \`null\` on live progress rows and on older SDKs. Use them to judge execution shape (did the replay call the same tools in the same order, did a span that used to run get mocked or skipped, did a child span error) straight from the item file, instead of reading both traces back from the server for that comparison. **Re-check every item you judged live:** its verdict was derived before the outlines existed, so re-read its final item file (the complete row's write fills both outlines) and run the execution-shape comparison now. When that comparison changes the verdict, include the corrected \`{ originalTraceId, label, annotation }\` in the next \`{{command:persistReplayLabels}}\` batch: a new agent verdict for the same original trace replaces the earlier one in place, so this correction is the one case where re-persisting an already-persisted id is right. When it does not change the verdict, leave the persisted verdict alone.
|
|
30378
30413
|
|
|
30379
30414
|
**Before running: verify the installed replay command returns the full original and new output values AND at least one verdict persist key (\`item.originalTraceId\` or \`item.traceId\`) for every item** (not just lengths, counts, hashes, or truncated previews) so the run's \`items/*.json\` files carry them. Modern items may carry both: prefer lineage persistence by \`item.originalTraceId\` (older SDKs may print it under the deprecated \`sourceTraceId\` alias), and fall back to the server replay \`item.traceId\` only when original lineage is absent. After an item's replay trace is flushed, its \`item.traceId\` is available to lifecycle callbacks and in the final result. The oldest SDKs have no \`item.originalTraceId\` and persist by \`item.traceId\`. If the installed command returns neither, upgrade the SDK first; the Replay Output Contract and registry examples live in the SDK reference at \`https://docs.bitfab.ai/<language>-sdk.md\`. Subagents can't evaluate an improvement from \`5 \u2192 7 (+2)\`, and an item that carries no persist key blocks verdict persistence for that item.
|
|
30380
30415
|
|
|
30381
|
-
**Capture the \`
|
|
30416
|
+
**Capture the \`experimentId\` from the replay complete event**: read the final \`type: "complete"\` row in this run's \`events.jsonl\`; it carries \`experimentId\` and \`experimentUrl\` when the SDK returned them. Track every \`experimentId\` produced across all iterations of this phase for the \`open-experiments\` fallback.
|
|
30382
30417
|
|
|
30383
30418
|
**If an external child span fails during replay, verify it is actually mockable before tagging it.** When a non-root span throws because a paid call, external service, or stored dependency is unavailable, it can block the whole trace even though the failure is outside the function you're iterating on. The short-term fix is recorded-output mocking, but only after the wrapper and execution-context checks below clear:
|
|
30384
30419
|
|
|
@@ -30404,7 +30439,7 @@ cd <project-dir> && {{command:replayProgress}} --label <pipeline-name> --run-dir
|
|
|
30404
30439
|
Use this when the goal is to unblock iteration on the root function, not when the child itself is what you're trying to improve.
|
|
30405
30440
|
|
|
30406
30441
|
**After the run, check whether verdicts can be persisted, and by which key.** For every completed item, confirm it carries a persist key and note which keying the run uses:
|
|
30407
|
-
- **Lineage keying (modern SDKs):** the item carries \`item.originalTraceId\` (the original trace it was replayed from; older SDKs print it under the deprecated \`sourceTraceId\` alias). This is the preferred path, verdicts persist by \`{
|
|
30442
|
+
- **Lineage keying (modern SDKs):** the item carries \`item.originalTraceId\` (the original trace it was replayed from; older SDKs print it under the deprecated \`sourceTraceId\` alias). This is the preferred path, verdicts persist by \`{ experimentId, originalTraceId }\`.
|
|
30408
30443
|
- **Direct keying:** the item has no \`originalTraceId\`; \`item.traceId\` is the trace's own id, so verdicts persist by \`traceId\` directly. This is the general way any trace is labeled by its id. In the replay flow the label targets the replay trace, so direct keying applies when the replay trace's own id is known client-side (which older SDKs surface, writing the server id into \`item.traceId\` after the run).
|
|
30409
30444
|
|
|
30410
30445
|
Hold a boolean flag (\`canPersistVerdicts\`) for the \`check-verdict-persistence\` step, true when every completed item carries one of those keys, and remember which keying the run uses (they don't mix within a run: \`item.originalTraceId\` when present, else the direct \`item.traceId\`). If some completed items carry neither, the SDK is too old to persist those verdicts to the server. Do NOT stop here, just flag it.
|
|
@@ -30503,9 +30538,9 @@ After whichever workaround the user picks, re-run \`replay-against-dataset\` and
|
|
|
30503
30538
|
kind: "branch",
|
|
30504
30539
|
toolCalls: ["getReplayStatus", "listExperimentTraces", "getTraces"],
|
|
30505
30540
|
title: "Resolve unverified replay from the server",
|
|
30506
|
-
body: `**Run only when the replay command exited 0 but local ReplayResult capture was unavailable.** Extract the \`
|
|
30541
|
+
body: `**Run only when the replay command exited 0 but local ReplayResult capture was unavailable.** Extract the \`experimentId\` from \`run.json\` or the command output (older SDKs print it as \`testRunId\`). If it exists, poll {{tool:getReplayStatus}} every 5 seconds while the server status is \`pending\`, for at most 30 seconds total. If it is still pending after 30 seconds, stop polling and report that the outcome remains unverified.
|
|
30507
30542
|
|
|
30508
|
-
When the server reports \`completed\` with \`traceCount > 0\`, call {{tool:listExperimentTraces}} for the same
|
|
30543
|
+
When the server reports \`completed\` with \`traceCount > 0\`, call {{tool:listExperimentTraces}} for the same experiment to enumerate every server replay trace ID and its \`original\` trace ID, then call {{tool:getTraces}} with the server replay IDs and \`scope: "full"\` to recover their inputs, outputs, and errors. Use each experiment trace's \`original\` field as its \`originalTraceId\` lineage key. Do not use the keys of {{tool:getReplayStatus}}'s \`traceIds\` mapping as original trace IDs: those keys are local client \`sourceTraceId\` values. Recompute \`completed\`, \`shapeErrored\`, \`mockErrored\`, \`setupErrored\`, \`dependencyErrored\`, \`codeErrored\`, and \`canPersistVerdicts\` from those server traces exactly as \`replay-against-dataset\` does, set \`serverRecovered = true\`, then continue normally. Do not infer success from the child exit code or local progress lines.
|
|
30509
30544
|
|
|
30510
30545
|
{{whens}}`,
|
|
30511
30546
|
branches: [
|
|
@@ -30520,7 +30555,7 @@ When the server reports \`completed\` with \`traceCount > 0\`, call {{tool:listE
|
|
|
30520
30555
|
next: "iterate/replay-against-dataset"
|
|
30521
30556
|
},
|
|
30522
30557
|
{
|
|
30523
|
-
when: "no
|
|
30558
|
+
when: "no experiment ID can be resolved, server status cannot be read, or server status remains `pending` after 30 seconds",
|
|
30524
30559
|
description: "the outcome cannot be verified. Upgrade or fix the installed SDK's reporter/result-artifact contract, then retry",
|
|
30525
30560
|
next: "iterate/replay-against-dataset"
|
|
30526
30561
|
}
|
|
@@ -30667,14 +30702,14 @@ ${FAN_OUT_JUDGING}
|
|
|
30667
30702
|
|
|
30668
30703
|
**Persist via \`persistReplayLabels.js\`.** Write verdicts to a tmp JSON file then run the script. You may do this incrementally for complete batches as replay results arrive, and again at the end for any remaining original trace IDs that were not already persisted. Each call is one batched MCP write; the script parses \`save_agent_labels\`'s agent-readable effective label lines internally to verify each verdict actually persisted, then deletes the file on success. Its JSON status is the signal you route on:
|
|
30669
30704
|
|
|
30670
|
-
1. Pick an **absolute** tmp path. The script reads the file relative to its own process cwd, which in parallel-worktree mode is NOT the project root, so a relative path can resolve to a different directory than where you wrote the file (\`ENOENT\`). Recommended: \`<repoRoot>/.bitfab/tmp/verdicts-<
|
|
30705
|
+
1. Pick an **absolute** tmp path. The script reads the file relative to its own process cwd, which in parallel-worktree mode is NOT the project root, so a relative path can resolve to a different directory than where you wrote the file (\`ENOENT\`). Recommended: \`<repoRoot>/.bitfab/tmp/verdicts-<experimentId>.json\` where \`<repoRoot>\` is the output of \`git rev-parse --show-toplevel\` (create the dir if missing). Falls back to an absolute path under \`os.tmpdir()\` if the project root isn't writable.
|
|
30671
30706
|
2. Use the {{tool:write}} tool to write the verdicts file. **The script accepts two shapes; use the one that matches this run's keying (from \`check-verdict-persistence\`).**
|
|
30672
30707
|
|
|
30673
30708
|
**Lineage keying (modern SDKs, items carry \`originalTraceId\`), preferred:**
|
|
30674
30709
|
|
|
30675
30710
|
\`\`\`json
|
|
30676
30711
|
{
|
|
30677
|
-
"
|
|
30712
|
+
"experimentId": "<experimentId>",
|
|
30678
30713
|
"expectedOriginalTraceIds": ["<originalTraceId1>", "<originalTraceId2>", "..."],
|
|
30679
30714
|
"verdicts": [
|
|
30680
30715
|
{ "originalTraceId": "<originalTraceId1>", "label": true, "annotation": "Now returns the missing field; original annotation said it was empty.", "confidence": "High" },
|
|
@@ -30684,7 +30719,7 @@ ${FAN_OUT_JUDGING}
|
|
|
30684
30719
|
}
|
|
30685
30720
|
\`\`\`
|
|
30686
30721
|
|
|
30687
|
-
**Direct keying (the item has no \`originalTraceId\`; it carries the trace's own id in \`item.traceId\`):** same shape but keyed by \`traceId\`, and **omit \`
|
|
30722
|
+
**Direct keying (the item has no \`originalTraceId\`; it carries the trace's own id in \`item.traceId\`):** same shape but keyed by \`traceId\`, and **omit \`experimentId\`** (the trace id already identifies the trace to label):
|
|
30688
30723
|
|
|
30689
30724
|
\`\`\`json
|
|
30690
30725
|
{
|
|
@@ -30696,7 +30731,7 @@ ${FAN_OUT_JUDGING}
|
|
|
30696
30731
|
}
|
|
30697
30732
|
\`\`\`
|
|
30698
30733
|
|
|
30699
|
-
In lineage keying, \`
|
|
30734
|
+
In lineage keying, \`experimentId\` is this replay run's experiment ID (from the progress rows or the final \`ReplayResult\`). The server uses it to resolve each original trace to its replay trace within this run. The \`expected*\` list MUST be the full set of ids covered by this call's batch (and across all batches, every completed \`item.error\`-unset replay item must be persisted exactly once, no fewer, per the mandatory-coverage rule above). For the final end-of-run call, use only the ids not already successfully persisted by an earlier batch. \`verdicts\` MUST cover every id in the \`expected*\` list, keyed by the same id field as that list. A trace with no assertions gets exactly one entry, either a \`{label, annotation, confidence?}\` verdict or a \`{skip: true}\` explicit skip (skips allowed only for the three enumerated skip cases above, never for an environmental doubt). A trace that has assertions gets one entry per assertion instead, in the shapes the per-assertion block below fixes. \`confidence\` is optional but recommended (\`VeryLow|Low|Medium|High|VeryHigh\`). It surfaces in the labeling UI so reviewers can prioritize low-confidence verdicts. If any expected id gets no entry at all, the script returns \`status: "missing-coverage"\` and the verify step routes you back to fill the gaps.
|
|
30700
30735
|
|
|
30701
30736
|
**A replay inherits the original trace's assertions, so score them one at a time.** Before you judge this batch, call {{tool:getTraceAssertions}} once with the batch's **original** trace ids (one call covers up to 100 ids, so make it once and before you judge anything). Each assertion comes back under its trace as \`[ID: <uuid>] checks <target>: <assertion>\`, and that \`[ID: <uuid>]\` value is the \`assertionId\` its verdict carries. Only assertions a person has approved are listed, since only those are checked on a replay. A trace that comes back "no approved assertions" has nothing for you to score one at a time.
|
|
30702
30737
|
|
|
@@ -30710,7 +30745,7 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30710
30745
|
|
|
30711
30746
|
\`\`\`json
|
|
30712
30747
|
{
|
|
30713
|
-
"
|
|
30748
|
+
"experimentId": "<experimentId>",
|
|
30714
30749
|
"expectedOriginalTraceIds": ["<originalTraceId1>", "<originalTraceId2>"],
|
|
30715
30750
|
"verdicts": [
|
|
30716
30751
|
{ "originalTraceId": "<originalTraceId1>", "assertionId": "<assertionId1>", "label": true, "annotation": "The rebooked leg now names the carrier this assertion asks for.", "confidence": "High" },
|
|
@@ -30726,14 +30761,14 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30726
30761
|
3. Run the script:
|
|
30727
30762
|
|
|
30728
30763
|
\`\`\`bash
|
|
30729
|
-
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<
|
|
30764
|
+
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<experimentId>.json
|
|
30730
30765
|
\`\`\`
|
|
30731
30766
|
|
|
30732
30767
|
4. Read its single JSON line on stdout. Hold the parsed result for the next step.
|
|
30733
30768
|
|
|
30734
30769
|
**Spill working notes to a separate tmp file if context gets big.** Don't conflate working notes with the verdicts file, the script deletes the verdicts file on success.{{#claude}}
|
|
30735
30770
|
|
|
30736
|
-
**If you're a worktree subagent** (parallel mode from \`pick-execution-mode\`): after the script returns, hand the parsed result + \`
|
|
30771
|
+
**If you're a worktree subagent** (parallel mode from \`pick-execution-mode\`): after the script returns, hand the parsed result + \`experimentId\` + unreplayable list back to the main agent and exit. The main agent collects results from all parallel experiments before \`open-experiments\`.{{/claude}}`,
|
|
30737
30772
|
next: "iterate/verify-replay-labels"
|
|
30738
30773
|
},
|
|
30739
30774
|
{
|
|
@@ -30777,12 +30812,12 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30777
30812
|
},
|
|
30778
30813
|
{
|
|
30779
30814
|
when: '`status: "invalid-input"` (malformed verdicts JSON or missing fields)',
|
|
30780
|
-
description: "the verdicts file you wrote doesn't match either accepted shape. Read the script's `message` field, fix the JSON (most common: missing annotation on a non-skip entry; a lineage file missing `
|
|
30815
|
+
description: "the verdicts file you wrote doesn't match either accepted shape. Read the script's `message` field, fix the JSON (most common: missing annotation on a non-skip entry; a lineage file missing `experimentId` or with an empty `expectedOriginalTraceIds`; a verdict keyed by the wrong id field for the file's mode), and re-run the script. Loop back here",
|
|
30781
30816
|
next: "iterate/verify-replay-labels"
|
|
30782
30817
|
},
|
|
30783
30818
|
{
|
|
30784
30819
|
when: '`status: "mcp-error"` (MCP call to save_agent_labels failed mid-batch)',
|
|
30785
|
-
description: "Read the script's `message` field. In lineage mode, if it says an original trace has no replay trace in this run or is not in this organization, confirm the `
|
|
30820
|
+
description: "Read the script's `message` field. In lineage mode, if it says an original trace has no replay trace in this run or is not in this organization, confirm the `experimentId` matches the run these items came from and that each `originalTraceId` is the original trace it was replayed from (from the item files, not a local or replay trace id). In direct mode, if trace ids are invalid, confirm each verdicts-file `traceId` is the server replay id (`item.traceId`) from the item files. Fix the file and re-run. For network or auth errors, the script's `partialIds` lists which ids were already persisted; tell the user, recommend re-running the script (it's idempotent, already-persisted labels just upsert), and loop back here. If it keeps failing, stop and surface the error",
|
|
30786
30821
|
next: "iterate/verify-replay-labels"
|
|
30787
30822
|
},
|
|
30788
30823
|
{
|
|
@@ -30798,7 +30833,7 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30798
30833
|
toolCalls: ["bash", "listExperiments", "listExperimentTraces"],
|
|
30799
30834
|
commandCalls: ["pageLink", "openExperiments"],
|
|
30800
30835
|
title: "Share the experiments link",
|
|
30801
|
-
body: 'Skip the link for a benchmark without the `page` flag or a single-trace fix awaiting its verdict, or a full-dataset fix with fixDatasetBitfab false. Otherwise call {{tool:listExperiments}} for the trace function and verify the returned
|
|
30836
|
+
body: 'Skip the link for a benchmark without the `page` flag or a single-trace fix awaiting its verdict, or a full-dataset fix with fixDatasetBitfab false. Otherwise call {{tool:listExperiments}} for the trace function and verify the returned experiment IDs exist on the server. If a persisted run belongs to the experimentGroupId prepared before replay, run {{command:pageLink}} "/experiments?experimentGroupId=<experimentGroupId><tokensSuffix>" with that same group ID and relay the URL. Use an empty tokensSuffix for quality runs and &tokens=1&tokenType=<costBasis> for cost runs, preserving the selected all or uncached basis. If the registry did not persist the group, run {{command:openExperiments}} <experimentIds> with the verified IDs instead; include --show-tokens for cost runs, plus --uncached when costBasis is uncached. If no run was persisted, report that fact without emitting a broken link. Both commands exit immediately. Continue to evaluating and persisting replay labels.',
|
|
30802
30837
|
next: "iterate/evaluate-results"
|
|
30803
30838
|
},
|
|
30804
30839
|
{
|
|
@@ -30878,7 +30913,7 @@ Then ask what to do next. One option always lets the user **show this fix in Bit
|
|
|
30878
30913
|
- **The dataset has sibling traces:** the headline choice is whether to re-run the entire dataset now to check the fix against every trace in it (you'll then ask Bitfab vs terminal-only). Present all four options below and recommend **"Re-run the entire dataset"**.
|
|
30879
30914
|
- **No siblings** (the added trace is the dataset's only trace, or the user continued without saving so nothing was added): do **NOT** offer "Re-run the entire dataset", there is no other dataset trace to run, and re-running a one-trace dataset would just replay the trace you already proved green. Present only "Show in Bitfab", "Keep iterating", and "Stop and wrap up", and recommend **"Stop and wrap up"**.
|
|
30880
30915
|
|
|
30881
|
-
When the user picks **Show in Bitfab**, open it with \`fixSingleTraceExperimentGroupId\` (or the \`
|
|
30916
|
+
When the user picks **Show in Bitfab**, open it with \`fixSingleTraceExperimentGroupId\` (or the \`experimentIds\` fallback), then ask this question again. When they pick **Keep iterating**, clear \`fixDatasetSkipped\` if it was set (this is a fresh attempt, so a later green should re-offer the save prompt they declined only on the previous pass), set \`fixReplayScope = "single-trace"\` and \`fixSkipMakeChange = false\`. When they pick **Stop and wrap up**, the saved trace stays if one was added (\`fixAddedToDataset\`); nothing is saved if they continued without adding (\`fixDatasetSkipped\`).
|
|
30882
30917
|
|
|
30883
30918
|
{{branches}}`,
|
|
30884
30919
|
branches: [
|
|
@@ -30957,7 +30992,7 @@ Report the failed target result first. Say whether the replay produced a real FA
|
|
|
30957
30992
|
|
|
30958
30993
|
Then ask what to do next. Offer inspection, another fix attempt, saving the trace as a failing test to revisit later, or stopping. Do not offer a full-dataset run here: there is no dataset trace to run unless it was already added earlier, and running one before the target passes would defeat the test-first add-to-dataset flow:
|
|
30959
30994
|
|
|
30960
|
-
When the user picks **Show in Bitfab**, open it with \`fixSingleTraceExperimentGroupId\` (or the \`
|
|
30995
|
+
When the user picks **Show in Bitfab**, open it with \`fixSingleTraceExperimentGroupId\` (or the \`experimentIds\` fallback), then ask this question again. Recommend **Keep iterating** only when the replay produced a real FAIL; for an unreplayable or shape-incompatible result, recommend making the trace replayable or stopping instead. When they pick it, set \`fixReplayScope = "single-trace"\` and \`fixSkipMakeChange = false\`.
|
|
30961
30996
|
|
|
30962
30997
|
{{branches}}`,
|
|
30963
30998
|
branches: [
|
|
@@ -31026,7 +31061,7 @@ Set \`fixAddedToDataset = true\` only once the attach above is confirmed. Tell t
|
|
|
31026
31061
|
toolCalls: ["bash"],
|
|
31027
31062
|
commandCalls: ["pageLink"],
|
|
31028
31063
|
title: "Share the targeted before/after link",
|
|
31029
|
-
body: "Run {{command:pageLink}} /experiments?
|
|
31064
|
+
body: "Run {{command:pageLink}} /experiments?experimentIds=<experimentId>&autoOpenFirst=1 (quote the entire path) and relay the returned URL so the user can inspect the before/after. Return to the fix decision in chat.",
|
|
31030
31065
|
next: "iterate/fix-target-replay-status"
|
|
31031
31066
|
},
|
|
31032
31067
|
{
|
|
@@ -31083,7 +31118,7 @@ Then route by the approved plan, **without asking the user** (they already confi
|
|
|
31083
31118
|
{
|
|
31084
31119
|
id: "quick-replay",
|
|
31085
31120
|
title: "Phase Replay: Single-Trace Quick Replay",
|
|
31086
|
-
intro: "Reached only from `replay` mode. The user already has a trace ID and (usually) already made a fix; they just want to replay that one trace and hear whether it worked. This is the **minimal, atomic** path: no Bitfab/browser, no dataset, no experiment groups. Locate the replay registry, read the trace, run replay against the single trace ID, compare the new output to the original, and report a one-line verdict in chat. **Whenever you derive a pass/fail verdict, persist it onto the replay trace** (the same local label you show in chat, saved via {{command:persistReplayLabels}}) so it isn't silently thrown away. The one exception is an SDK too old to expose replay trace IDs: persistence is then impossible, so the verdict stays in-chat only with an upgrade nudge. The replay itself creates
|
|
31121
|
+
intro: "Reached only from `replay` mode. The user already has a trace ID and (usually) already made a fix; they just want to replay that one trace and hear whether it worked. This is the **minimal, atomic** path: no Bitfab/browser, no dataset, no experiment groups. Locate the replay registry, read the trace, run replay against the single trace ID, compare the new output to the original, and report a one-line verdict in chat. **Whenever you derive a pass/fail verdict, persist it onto the replay trace** (the same local label you show in chat, saved via {{command:persistReplayLabels}}) so it isn't silently thrown away. The one exception is an SDK too old to expose replay trace IDs: persistence is then impossible, so the verdict stays in-chat only with an upgrade nudge. The replay itself creates an experiment intrinsically (the SDK does this); persistence just adds the agent verdict on top.",
|
|
31087
31122
|
stepStyle: "list",
|
|
31088
31123
|
steps: [
|
|
31089
31124
|
{
|
|
@@ -31143,7 +31178,7 @@ ${REPLAY_SAFETY_CHECK}
|
|
|
31143
31178
|
# Ruby: cd <project-dir> && bundle exec bitfab-replay --registry <registry-path> <pipeline> --trace-ids <trace-id>
|
|
31144
31179
|
\`\`\`
|
|
31145
31180
|
|
|
31146
|
-
This is a single-trace, in-chat path: run the replay directly, no progress-bar wrapper (one item has nothing to track). Do **not** pass \`--code-change\` or \`--experiment-group-id\`, this minimal path skips code-change payloads and experiment groups (persisting the verdict in the next step needs neither). Capture the full replay-result JSON and exit code, and from it hold the run's
|
|
31181
|
+
This is a single-trace, in-chat path: run the replay directly, no progress-bar wrapper (one item has nothing to track). Do **not** pass \`--code-change\` or \`--experiment-group-id\`, this minimal path skips code-change payloads and experiment groups (persisting the verdict in the next step needs neither). Capture the full replay-result JSON and exit code, and from it hold the run's experiment ID (\`experimentId\` in TS, \`experiment_id\` in Python/Ruby; older SDKs print \`testRunId\` / \`test_run_id\`) and the completed item's trace id (\`traceId\` in TS, \`trace_id\` in Python/Ruby). **In the final replay result this trace id is already the SERVER replay trace id** (the SDK's \`completeReplay\` overwrites the local id with the server row id before returning), so the verdict step persists against it directly, no \`get_replay_status\` mapping. **If it is \`null\`, persistence is impossible this run** (an old server/SDK that returns no server-trace-id mapping), note that so the verdict step falls back to an in-chat-only verdict.
|
|
31147
31182
|
|
|
31148
31183
|
**Quick health check.** If the replay crashed (non-zero exit, no items) or the single item has \`item.error\` set, hold the error for the verdict step. Otherwise hold the completed item's new output alongside the original output you read in \`setup\`.`,
|
|
31149
31184
|
next: "quick-replay/verdict"
|
|
@@ -31202,7 +31237,7 @@ Then compare the new output against the original trace's assertions, label, and
|
|
|
31202
31237
|
\`\`\`
|
|
31203
31238
|
|
|
31204
31239
|
\`\`\`bash
|
|
31205
|
-
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<
|
|
31240
|
+
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<experiment-id>.json
|
|
31206
31241
|
\`\`\`
|
|
31207
31242
|
|
|
31208
31243
|
\`label\` is \`true\` for Pass, \`false\` for Still-failing / Regressed. Read the script's single JSON status line: \`ok\` means the verdict is now on the replay trace, add "\xB7 saved" to your one-line report.
|
|
@@ -31243,7 +31278,7 @@ Then compare the new output against the original trace's assertions, label, and
|
|
|
31243
31278
|
toolCalls: ["bash", "grep"],
|
|
31244
31279
|
title: "Re-seed the trace in place",
|
|
31245
31280
|
emit: "Re-seeding",
|
|
31246
|
-
body: `**Run the trace again for real and record the result under the same id.** A re-seed is a seed, not a replay: nothing is mocked, no
|
|
31281
|
+
body: `**Run the trace again for real and record the result under the same id.** A re-seed is a seed, not a replay: nothing is mocked, no experiment is created, and the trace keeps its id, labels, assertions, dataset membership, name, and metadata. The previous run is kept as its own trace, linked back to this one, so nothing is deleted.
|
|
31247
31282
|
|
|
31248
31283
|
**It runs the function exactly as production does, side effects included.** If the safety check in \`setup\` found any unsafe action (an email, a payment, a write to a live system), say exactly which call would run for real and get an explicit go-ahead before continuing; a re-seed has no mocking to hide behind.
|
|
31249
31284
|
|
|
@@ -31379,7 +31414,7 @@ var setupFlow = Flow.parse({
|
|
|
31379
31414
|
},
|
|
31380
31415
|
openExperiments: {
|
|
31381
31416
|
description: "Print a link to experiments.",
|
|
31382
|
-
args: "<
|
|
31417
|
+
args: "<experimentIds>"
|
|
31383
31418
|
},
|
|
31384
31419
|
startDataset: {
|
|
31385
31420
|
description: "Print a dataset link.",
|
|
@@ -32778,7 +32813,7 @@ Leave the live request path untouched: only the replayed function reads the bran
|
|
|
32778
32813
|
1. Resolve the exact fresh trace ID. Before triggering the instrumented function, call {{tool:searchTraces}} with \`{ traceFunctionKey: "<key>", limit: 10 }\` and retain the returned IDs. Record the current timestamp, then run the instrumented function once (or have the user trigger it). Poll the same search once or twice and compare the returned IDs with the before set. Select the ID only when exactly one new root trace has a timestamp after the recorded time. If there are zero new IDs, stop and report that no fresh trace arrived; if concurrent traffic produces more than one candidate, stop and ask the user for the intended trace ID. \`search_traces\` does not return trace inputs, so never guess based on an assumed input match. Confirm the exact ID is eligible by calling {{tool:searchTraces}} with \`{ traceFunctionKey: "<key>", traceIds: ["<fresh-trace-id>"], hasDbSnapshot: true, limit: 1 }\`. If it is absent, do not replay it: report that the fresh trace did not capture a snapshot and diagnose SDK freshness/database connection first.
|
|
32779
32814
|
2. **Mandatory replay-safety check, before any replay command.** Read the replay registry module, the real production root it imports, and every reachable external-action span. Inventory database writes, outbound mutations, queue publishes, email, payments, filesystem writes, and similar unsafe actions. Confirm each one is behind a manual replay-mockable descendant selected by the registry entry's actual strategy and has a serializable recorded output; \`mock: "marked"\` requires \`mockOnReplay\` on that boundary. Confirm the boundary runs in the same replay context: Python worker threads require \`trace_across_threads=True\`; Python async-generator spans cannot be mocked; Ruby child threads, pre-created consumers, and processes do not inherit replay interception; TypeScript synchronous selected spans cannot use lazy \`marked\` output and may use \`all\` only when freezing every matched child is acceptable. Also reject unsafe import-time/module initialization, because it runs before replay interception. Confirm the replay call requests \`dbBranch\` / \`db_branch\`. If any unsafe action is unselected, unmockable, outside context, or uncertain, **do not run the smoke test**; report the exact blocker and required boundary change. Mocking, not the app environment, is the safety boundary.
|
|
32780
32815
|
3. Run the SDK-installed replay command against only the verified ID using \`--trace-ids <fresh-trace-id>\` (for example, \`pnpm with-env bitfab-replay --registry scripts/replayRegistry.ts <pipeline> --trace-ids <fresh-trace-id>\`, \`poetry run bitfab-replay --registry scripts/replay_registry.py <pipeline> --trace-ids <fresh-trace-id>\`, or \`bundle exec bitfab-replay --registry scripts/replay_registry.rb <pipeline> --trace-ids <fresh-trace-id>\`, with the app's normal environment loader). Never substitute \`--limit 1\`.
|
|
32781
|
-
4. Confirm the branch was injected: inside the replayed function, \`getCurrentReplayBranch()\` (TypeScript), \`get_current_replay_branch()\` (Python), or \`Bitfab.current_replay_branch\` (Ruby) must be non-null. Compare its \`databaseUrl\` / \`database_url\` host and database with the app's normal \`DATABASE_URL\`; they should differ. Print the
|
|
32816
|
+
4. Confirm the branch was injected: inside the replayed function, \`getCurrentReplayBranch()\` (TypeScript), \`get_current_replay_branch()\` (Python), or \`Bitfab.current_replay_branch\` (Ruby) must be non-null. Compare its \`databaseUrl\` / \`database_url\` host and database with the app's normal \`DATABASE_URL\`; they should differ. Print the experiment URL from the replay output (\`experimentUrl\` in TypeScript, \`experiment_url\` in Python/Ruby; older SDKs print \`testRunUrl\` / \`test_run_url\`) so the user can open the experiment.
|
|
32782
32817
|
|
|
32783
32818
|
If the branch accessor is null for a freshly captured trace, check that the source database is connected, that the trace actually carries a snapshot reference, and that the SDK supports always-on capture (upgrade with \`{{cmd}}update\` when needed). Re-check the dashboard Database section in step {{step:db-snapshot/connect-db}}; there is no separate replay-environment active flag.
|
|
32784
32819
|
|
package/package.json
CHANGED