bitfab-cli 0.2.379 → 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 +124 -88
- 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."),
|
|
@@ -23942,10 +23942,11 @@ var deleteAssertionCategory = {
|
|
|
23942
23942
|
var generateTraceAssertions = {
|
|
23943
23943
|
name: "generate_trace_assertions",
|
|
23944
23944
|
title: "Generate Trace Assertions",
|
|
23945
|
-
description: "Generate review-ready assertion drafts from what one original trace actually did. Call this when the user asks Bitfab to generate assertions, infer expected behavior from a trace, or turn a known root cause into regression checks. Bitfab reads the organization's existing assertion categories, selects the categories supported by trace evidence, and generates assertions one category at a time. The generated assertions are saved awaiting human review, so they are not checked on replays until a person approves them.
|
|
23945
|
+
description: "Generate review-ready assertion drafts from what one original trace actually did. Call this when the user asks Bitfab to generate assertions, infer expected behavior from a trace, or turn a known root cause into regression checks. Bitfab reads the organization's existing assertion categories, selects the categories supported by trace evidence, and generates assertions one category at a time. The generated assertions are saved awaiting human review, so they are not checked on replays until a person approves them. Generation runs on Bitfab and this call waits up to four minutes for it. If it is still running after that, the result says so, and calling again with the same traceId and without regenerate checks on it. A trace has one generation at a time, so a repeat call joins the running or finished generation instead of spending model calls again, and regenerate while one is running joins that one too. Set regenerate to true after it finishes to archive the prior generated drafts and replace them. A failed generation is returned as an error with its reason. Human-authored assertions are never replaced. Pass guidance when the user knows what the assertions should be about, most often a root cause analysis of why the trace was wrong; it narrows the evidence and produces smaller assertions. Pass categoryIds to limit generation to those assertion categories. A replay trace id is refused because assertions belong to the original trace.",
|
|
23946
23946
|
inputSchema: {
|
|
23947
23947
|
traceId: external_exports.uuid().describe("The ORIGINAL trace to generate assertions for. A replay trace id is refused, and the error names the original to retry with."),
|
|
23948
|
-
|
|
23948
|
+
guidance: external_exports.string().trim().min(1).max(1e4).optional().describe("Optional assertion guidance, such as a root cause analysis of why the trace went wrong. Use it to focus generation on the smallest assertions that would catch this failure."),
|
|
23949
|
+
categoryIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1)).optional().describe("Optional assertion category ids from list_assertion_categories. Limits generation to these categories. Omit to let Bitfab choose from every category in the organization. An id outside the organization fails the call before any work starts."),
|
|
23949
23950
|
regenerate: external_exports.preprocess(parseJsonString, external_exports.boolean()).optional().describe("Set true to rerun generation and replace prior generated drafts. Omit or set false to reuse the existing generation result.")
|
|
23950
23951
|
}
|
|
23951
23952
|
};
|
|
@@ -23973,7 +23974,7 @@ var saveTraceAssertions = {
|
|
|
23973
23974
|
var getTraceAssertions = {
|
|
23974
23975
|
name: "get_trace_assertions",
|
|
23975
23976
|
title: "Get Trace Assertions",
|
|
23976
|
-
description: `Read what SHOULD happen when one or more traces are replayed: each trace's approved assertions, their pass/fail criteria, people-only human note, who is assigned to review each one, and what part of the evaluated trace each one checks. Only assertions a person has approved are returned, because only those are checked on a replay; a trace whose assertions are all still awaiting review reads the same as one with none. Categorized assertions include category_assertion_id and the category's id, title, and description. A human note is returned so it can be read and edited, but it is never assessment evidence: judge only from the assertion, pass/fail criteria, and evaluated trace. Call this before judging a replay so the verdict is measured against what the user actually asked for rather than a guess, and before save_trace_assertions to find the assertion to edit or to avoid creating a duplicate. No span content is loaded, so one call accepts up to ${GET_TRACE_ASSERTIONS_MAX_IDS} ids. Accepts original trace ids and replay trace ids alike: a replay with no assertions of its own reads its original's, and the response says which original they came from. An assertion whose target cannot be found on the trace being evaluated is errored, never passed, so record it with save_agent_labels \`skip\` rather than a FAIL.`,
|
|
23977
|
+
description: `Read what SHOULD happen when one or more traces are replayed: each trace's approved assertions, their pass/fail criteria, people-only human note, who is assigned to review each one, and what part of the evaluated trace each one checks. Only assertions a person has approved are returned, because only those are checked on a replay; a trace whose assertions are all still awaiting review reads the same as one with none. Each trace also reports its assertion generation, if one ran: running, completed with how many drafts it saved for review, or failed with the reason. Categorized assertions include category_assertion_id and the category's id, title, and description. A human note is returned so it can be read and edited, but it is never assessment evidence: judge only from the assertion, pass/fail criteria, and evaluated trace. Call this before judging a replay so the verdict is measured against what the user actually asked for rather than a guess, and before save_trace_assertions to find the assertion to edit or to avoid creating a duplicate. No span content is loaded, so one call accepts up to ${GET_TRACE_ASSERTIONS_MAX_IDS} ids. Accepts original trace ids and replay trace ids alike: a replay with no assertions of its own reads its original's, and the response says which original they came from. An assertion whose target cannot be found on the trace being evaluated is errored, never passed, so record it with save_agent_labels \`skip\` rather than a FAIL.`,
|
|
23977
23978
|
inputSchema: {
|
|
23978
23979
|
traceIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(GET_TRACE_ASSERTIONS_MAX_IDS)).describe(`Original trace IDs to read assertions for (1-${GET_TRACE_ASSERTIONS_MAX_IDS})`)
|
|
23979
23980
|
}
|
|
@@ -24086,32 +24087,32 @@ var rerunGradersOnExperiment = {
|
|
|
24086
24087
|
title: "Re-run Graders on Experiment",
|
|
24087
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.",
|
|
24088
24089
|
inputSchema: {
|
|
24089
|
-
|
|
24090
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments"),
|
|
24090
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.")
|
|
24091
24092
|
}
|
|
24092
24093
|
};
|
|
24093
24094
|
var addGradersToExperiment = {
|
|
24094
24095
|
name: "add_graders_to_experiment",
|
|
24095
24096
|
title: "Add Graders to Experiment",
|
|
24096
|
-
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.",
|
|
24097
24098
|
inputSchema: {
|
|
24098
|
-
|
|
24099
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments"),
|
|
24099
24100
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to attach (1-100)")
|
|
24100
24101
|
}
|
|
24101
24102
|
};
|
|
24102
24103
|
var removeGradersFromExperiment = {
|
|
24103
24104
|
name: "remove_graders_from_experiment",
|
|
24104
24105
|
title: "Remove Graders from Experiment",
|
|
24105
|
-
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.",
|
|
24106
24107
|
inputSchema: {
|
|
24107
|
-
|
|
24108
|
+
experimentId: external_exports.uuid().describe("The experiment ID"),
|
|
24108
24109
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
|
|
24109
24110
|
}
|
|
24110
24111
|
};
|
|
24111
24112
|
var addGradersToExperimentGroup = {
|
|
24112
24113
|
name: "add_graders_to_experiment_group",
|
|
24113
24114
|
title: "Add Graders to Experiment Group",
|
|
24114
|
-
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.",
|
|
24115
24116
|
inputSchema: {
|
|
24116
24117
|
experimentGroupId: external_exports.uuid().describe("The experiment group id"),
|
|
24117
24118
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to assign (1-100)")
|
|
@@ -24120,7 +24121,7 @@ var addGradersToExperimentGroup = {
|
|
|
24120
24121
|
var removeGradersFromExperimentGroup = {
|
|
24121
24122
|
name: "remove_graders_from_experiment_group",
|
|
24122
24123
|
title: "Remove Graders from Experiment Group",
|
|
24123
|
-
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.",
|
|
24124
24125
|
inputSchema: {
|
|
24125
24126
|
experimentGroupId: external_exports.uuid().describe("The experiment group id"),
|
|
24126
24127
|
graderIds: external_exports.preprocess(parseJsonString, external_exports.array(external_exports.uuid()).min(1).max(100)).describe("Grader ids to detach (1-100)")
|
|
@@ -24129,7 +24130,7 @@ var removeGradersFromExperimentGroup = {
|
|
|
24129
24130
|
var listExperiments = {
|
|
24130
24131
|
name: "list_experiments",
|
|
24131
24132
|
title: "List Experiments",
|
|
24132
|
-
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.",
|
|
24133
24134
|
inputSchema: {
|
|
24134
24135
|
traceFunctionKey: external_exports.string().min(1).optional().describe("Only experiments for this trace function key"),
|
|
24135
24136
|
gitBranch: external_exports.string().min(1).optional().describe("Only experiments run from this git branch (exact branch name, e.g. feature/shorter-prompt)"),
|
|
@@ -24140,17 +24141,17 @@ var listExperiments = {
|
|
|
24140
24141
|
var listExperimentTraces = {
|
|
24141
24142
|
name: "list_experiment_traces",
|
|
24142
24143
|
title: "List Experiment Traces",
|
|
24143
|
-
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.",
|
|
24144
24145
|
inputSchema: {
|
|
24145
|
-
|
|
24146
|
+
experimentId: external_exports.uuid().describe("The experiment ID to get traces for")
|
|
24146
24147
|
}
|
|
24147
24148
|
};
|
|
24148
24149
|
var getReplayStatus = {
|
|
24149
24150
|
name: "get_replay_status",
|
|
24150
24151
|
title: "Get Replay Status",
|
|
24151
|
-
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.",
|
|
24152
24153
|
inputSchema: {
|
|
24153
|
-
|
|
24154
|
+
experimentId: external_exports.uuid().describe("The experiment ID to get status for")
|
|
24154
24155
|
}
|
|
24155
24156
|
};
|
|
24156
24157
|
var getTemplateReference = {
|
|
@@ -24183,10 +24184,10 @@ var saveTemplate = {
|
|
|
24183
24184
|
var saveExperimentGroup = {
|
|
24184
24185
|
name: "save_experiment_group",
|
|
24185
24186
|
title: "Save Experiment Group",
|
|
24186
|
-
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.",
|
|
24187
24188
|
inputSchema: {
|
|
24188
24189
|
experimentGroupId: external_exports.uuid().optional().describe("Existing experiment group id to rename. Omit when creating."),
|
|
24189
|
-
|
|
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."),
|
|
24190
24191
|
name: external_exports.string().trim().min(1).max(120).optional().describe("Human-readable group name. Optional when creating or editing."),
|
|
24191
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.")
|
|
24192
24193
|
}
|
|
@@ -24210,9 +24211,9 @@ var getExperimentGroup = {
|
|
|
24210
24211
|
var saveExperiment = {
|
|
24211
24212
|
name: "save_experiment",
|
|
24212
24213
|
title: "Save Experiment",
|
|
24213
|
-
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.",
|
|
24214
24215
|
inputSchema: {
|
|
24215
|
-
|
|
24216
|
+
experimentId: external_exports.uuid().describe("The experiment ID to update"),
|
|
24216
24217
|
name: external_exports.string().trim().min(1).max(120).optional().describe("Optional human-readable experiment name"),
|
|
24217
24218
|
notes: external_exports.string().max(1e4).optional().describe("Optional experiment notes. Omit to keep existing notes; pass an empty string to clear them."),
|
|
24218
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.")
|
|
@@ -24221,9 +24222,9 @@ var saveExperiment = {
|
|
|
24221
24222
|
var getExperiment = {
|
|
24222
24223
|
name: "get_experiment",
|
|
24223
24224
|
title: "Get Experiment",
|
|
24224
|
-
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.",
|
|
24225
24226
|
inputSchema: {
|
|
24226
|
-
|
|
24227
|
+
experimentId: external_exports.uuid().describe("The experiment ID, from list_experiments")
|
|
24227
24228
|
}
|
|
24228
24229
|
};
|
|
24229
24230
|
var getGraderLabels = {
|
|
@@ -24697,44 +24698,51 @@ var McpProxy = class {
|
|
|
24697
24698
|
};
|
|
24698
24699
|
|
|
24699
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
|
+
}
|
|
24700
24708
|
function createBitfabToolHandlers(platform2, getConfig2, pluginVersion) {
|
|
24701
24709
|
const proxy = new McpProxy(platform2, pluginVersion);
|
|
24702
24710
|
function proxyToolCall(toolName, args) {
|
|
24703
|
-
return proxy.toolCall(getConfig2(), toolName, args);
|
|
24711
|
+
return proxy.toolCall(getConfig2(), toolName, withLegacyExperimentKeys(args));
|
|
24704
24712
|
}
|
|
24705
24713
|
async function handleSaveAgentLabels(args) {
|
|
24706
|
-
const {
|
|
24714
|
+
const { experimentId, labels } = args;
|
|
24707
24715
|
return proxyToolCall("save_agent_labels", {
|
|
24708
|
-
|
|
24716
|
+
experimentId,
|
|
24709
24717
|
labels: labels.map((l) => l.originalTraceId !== void 0 && l.sourceTraceId === void 0 ? { ...l, sourceTraceId: l.originalTraceId } : l)
|
|
24710
24718
|
});
|
|
24711
24719
|
}
|
|
24712
24720
|
async function handleSaveExperiment(args) {
|
|
24713
|
-
const {
|
|
24721
|
+
const { experimentId, name, notes, experimentGroupId } = args;
|
|
24714
24722
|
if (name === void 0 && notes === void 0 && experimentGroupId === void 0) {
|
|
24715
24723
|
return errorResult("Error saving experiment: provide name, notes, and/or experimentGroupId.");
|
|
24716
24724
|
}
|
|
24717
24725
|
return proxyToolCall("save_experiment", {
|
|
24718
|
-
|
|
24726
|
+
experimentId,
|
|
24719
24727
|
name,
|
|
24720
24728
|
notes,
|
|
24721
24729
|
experimentGroupId
|
|
24722
24730
|
});
|
|
24723
24731
|
}
|
|
24724
24732
|
async function handleSaveExperimentGroup(args) {
|
|
24725
|
-
const { experimentGroupId,
|
|
24726
|
-
if (experimentGroupId === void 0 &&
|
|
24727
|
-
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.");
|
|
24728
24736
|
}
|
|
24729
|
-
if (experimentGroupId !== void 0 &&
|
|
24730
|
-
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.");
|
|
24731
24739
|
}
|
|
24732
24740
|
if (experimentGroupId !== void 0 && name === void 0 && notes === void 0) {
|
|
24733
24741
|
return errorResult("Error saving experiment group: provide name and/or notes when editing an experiment group.");
|
|
24734
24742
|
}
|
|
24735
24743
|
return proxyToolCall("save_experiment_group", {
|
|
24736
24744
|
experimentGroupId,
|
|
24737
|
-
|
|
24745
|
+
experimentIds,
|
|
24738
24746
|
name,
|
|
24739
24747
|
notes
|
|
24740
24748
|
});
|
|
@@ -24906,10 +24914,10 @@ var semver3 = __toESM(require_semver2(), 1);
|
|
|
24906
24914
|
|
|
24907
24915
|
// ../bitfab-plugin-lib/dist/bakedSdkVersions.js
|
|
24908
24916
|
var BAKED_SDK_VERSIONS = {
|
|
24909
|
-
typescript: "0.
|
|
24910
|
-
python: "0.
|
|
24911
|
-
ruby: "0.
|
|
24912
|
-
go: "0.
|
|
24917
|
+
typescript: "0.59.1",
|
|
24918
|
+
python: "0.59.1",
|
|
24919
|
+
ruby: "0.59.1",
|
|
24920
|
+
go: "0.59.1"
|
|
24913
24921
|
};
|
|
24914
24922
|
|
|
24915
24923
|
// ../bitfab-plugin-lib/dist/installedSdk.js
|
|
@@ -25986,13 +25994,15 @@ function emitJson(event) {
|
|
|
25986
25994
|
// ../bitfab-plugin-lib/dist/commands/persistReplayLabels.js
|
|
25987
25995
|
import { readFileSync, unlinkSync } from "fs";
|
|
25988
25996
|
var lineageFileSchema = external_exports.object({
|
|
25989
|
-
|
|
25990
|
-
|
|
25991
|
-
testRunId: external_exports.uuid(),
|
|
25997
|
+
experimentId: external_exports.uuid().optional(),
|
|
25998
|
+
testRunId: external_exports.uuid().optional(),
|
|
25992
25999
|
// Canonical; older item files carry the deprecated expectedSourceTraceIds.
|
|
25993
26000
|
expectedOriginalTraceIds: external_exports.array(external_exports.uuid()).min(1).optional(),
|
|
25994
26001
|
expectedSourceTraceIds: external_exports.array(external_exports.uuid()).min(1).optional(),
|
|
25995
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"]
|
|
25996
26006
|
}).refine((d) => d.expectedOriginalTraceIds !== void 0 || d.expectedSourceTraceIds !== void 0, {
|
|
25997
26007
|
message: "lineage file must carry expectedOriginalTraceIds (or the deprecated expectedSourceTraceIds)",
|
|
25998
26008
|
path: ["expectedOriginalTraceIds"]
|
|
@@ -26015,28 +26025,54 @@ import { dirname as dirname3, resolve as resolve3 } from "path";
|
|
|
26015
26025
|
import { createInterface } from "readline";
|
|
26016
26026
|
|
|
26017
26027
|
// ../bitfab-plugin-lib/dist/replayItemWriter.js
|
|
26018
|
-
import { mkdirSync as
|
|
26019
|
-
import { join as
|
|
26028
|
+
import { mkdirSync as mkdirSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
|
|
26029
|
+
import { join as join3 } from "path";
|
|
26020
26030
|
|
|
26021
26031
|
// ../bitfab-plugin-lib/dist/bestEffortFs.js
|
|
26022
26032
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
26023
26033
|
import { dirname } from "path";
|
|
26024
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
|
+
|
|
26025
26062
|
// ../bitfab-plugin-lib/dist/replayResultResolver.js
|
|
26026
|
-
import { readFileSync as
|
|
26027
|
-
import { resolve } from "path";
|
|
26063
|
+
import { readFileSync as readFileSync3, rmSync } from "fs";
|
|
26064
|
+
import { resolve as resolve2 } from "path";
|
|
26028
26065
|
var replayResultSchema = external_exports.object({
|
|
26029
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(),
|
|
26030
26071
|
testRunId: external_exports.string().optional(),
|
|
26031
26072
|
test_run_id: external_exports.string().optional(),
|
|
26032
26073
|
testRunUrl: external_exports.string().optional(),
|
|
26033
26074
|
test_run_url: external_exports.string().optional()
|
|
26034
|
-
}).passthrough().refine((
|
|
26035
|
-
|
|
26036
|
-
// ../bitfab-plugin-lib/dist/replayRunDir.js
|
|
26037
|
-
import { randomUUID } from "crypto";
|
|
26038
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
26039
|
-
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
26075
|
+
}).passthrough().refine((result) => extractExperimentId(result) !== null);
|
|
26040
26076
|
|
|
26041
26077
|
// ../bitfab-plugin-lib/dist/commands/replayProgress.js
|
|
26042
26078
|
var HEARTBEAT_MS = Number(process.env.BITFAB_REPLAY_HEARTBEAT_MS) || 12e3;
|
|
@@ -28823,7 +28859,7 @@ var assistantFlow = Flow.parse({
|
|
|
28823
28859
|
},
|
|
28824
28860
|
openExperiments: {
|
|
28825
28861
|
description: "Print a link to experiments.",
|
|
28826
|
-
args: "<
|
|
28862
|
+
args: "<experimentIds>"
|
|
28827
28863
|
},
|
|
28828
28864
|
startDataset: {
|
|
28829
28865
|
description: "Print a dataset link.",
|
|
@@ -28914,7 +28950,7 @@ This skill has nine invocation modes, each a different entry point into the same
|
|
|
28914
28950
|
|
|
28915
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.
|
|
28916
28952
|
|
|
28917
|
-
**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.
|
|
28918
28954
|
|
|
28919
28955
|
**Disambiguating \`benchmark\` from \`experiment\`** (both replay a dataset, so free-form text is easy to misroute):
|
|
28920
28956
|
|
|
@@ -30181,7 +30217,7 @@ Hold the chosen mode in working context. Every iteration below (\`make-change\`,
|
|
|
30181
30217
|
branches: [
|
|
30182
30218
|
{
|
|
30183
30219
|
when: "{{#claude}}bash output is `parallel` (bypass found in committed or user-global settings){{/claude}}{{^claude}}(unreachable on this editor){{/claude}}",
|
|
30184
|
-
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`,
|
|
30185
30221
|
next: "iterate/detect-replay-capabilities"
|
|
30186
30222
|
},
|
|
30187
30223
|
{
|
|
@@ -30280,7 +30316,7 @@ If \`renameFrom\` identifies the legacy TypeScript \`bitfab\` package, replace i
|
|
|
30280
30316
|
kind: "action",
|
|
30281
30317
|
toolCalls: ["bash"],
|
|
30282
30318
|
title: "Prepare experiment group before replay",
|
|
30283
|
-
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.",
|
|
30284
30320
|
next: {
|
|
30285
30321
|
byMode: {
|
|
30286
30322
|
wizard: "iterate/make-change",
|
|
@@ -30342,16 +30378,16 @@ The schema is flat, every file object is exactly \`{ path, before, after }\`. Do
|
|
|
30342
30378
|
|
|
30343
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.
|
|
30344
30380
|
|
|
30345
|
-
**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.
|
|
30346
30382
|
|
|
30347
|
-
**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.
|
|
30348
30384
|
|
|
30349
30385
|
**Choose trace selection.**
|
|
30350
30386
|
|
|
30351
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.
|
|
30352
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.
|
|
30353
30389
|
|
|
30354
|
-
**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.
|
|
30355
30391
|
|
|
30356
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.
|
|
30357
30393
|
|
|
@@ -30371,13 +30407,13 @@ cd <project-dir> && {{command:replayProgress}} --label <pipeline-name> --run-dir
|
|
|
30371
30407
|
|
|
30372
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.
|
|
30373
30409
|
|
|
30374
|
-
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.
|
|
30375
30411
|
|
|
30376
|
-
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.
|
|
30377
30413
|
|
|
30378
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.
|
|
30379
30415
|
|
|
30380
|
-
**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.
|
|
30381
30417
|
|
|
30382
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:
|
|
30383
30419
|
|
|
@@ -30403,7 +30439,7 @@ cd <project-dir> && {{command:replayProgress}} --label <pipeline-name> --run-dir
|
|
|
30403
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.
|
|
30404
30440
|
|
|
30405
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:
|
|
30406
|
-
- **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 }\`.
|
|
30407
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).
|
|
30408
30444
|
|
|
30409
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.
|
|
@@ -30502,9 +30538,9 @@ After whichever workaround the user picks, re-run \`replay-against-dataset\` and
|
|
|
30502
30538
|
kind: "branch",
|
|
30503
30539
|
toolCalls: ["getReplayStatus", "listExperimentTraces", "getTraces"],
|
|
30504
30540
|
title: "Resolve unverified replay from the server",
|
|
30505
|
-
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.
|
|
30506
30542
|
|
|
30507
|
-
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.
|
|
30508
30544
|
|
|
30509
30545
|
{{whens}}`,
|
|
30510
30546
|
branches: [
|
|
@@ -30519,7 +30555,7 @@ When the server reports \`completed\` with \`traceCount > 0\`, call {{tool:listE
|
|
|
30519
30555
|
next: "iterate/replay-against-dataset"
|
|
30520
30556
|
},
|
|
30521
30557
|
{
|
|
30522
|
-
when: "no
|
|
30558
|
+
when: "no experiment ID can be resolved, server status cannot be read, or server status remains `pending` after 30 seconds",
|
|
30523
30559
|
description: "the outcome cannot be verified. Upgrade or fix the installed SDK's reporter/result-artifact contract, then retry",
|
|
30524
30560
|
next: "iterate/replay-against-dataset"
|
|
30525
30561
|
}
|
|
@@ -30666,14 +30702,14 @@ ${FAN_OUT_JUDGING}
|
|
|
30666
30702
|
|
|
30667
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:
|
|
30668
30704
|
|
|
30669
|
-
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.
|
|
30670
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\`).**
|
|
30671
30707
|
|
|
30672
30708
|
**Lineage keying (modern SDKs, items carry \`originalTraceId\`), preferred:**
|
|
30673
30709
|
|
|
30674
30710
|
\`\`\`json
|
|
30675
30711
|
{
|
|
30676
|
-
"
|
|
30712
|
+
"experimentId": "<experimentId>",
|
|
30677
30713
|
"expectedOriginalTraceIds": ["<originalTraceId1>", "<originalTraceId2>", "..."],
|
|
30678
30714
|
"verdicts": [
|
|
30679
30715
|
{ "originalTraceId": "<originalTraceId1>", "label": true, "annotation": "Now returns the missing field; original annotation said it was empty.", "confidence": "High" },
|
|
@@ -30683,7 +30719,7 @@ ${FAN_OUT_JUDGING}
|
|
|
30683
30719
|
}
|
|
30684
30720
|
\`\`\`
|
|
30685
30721
|
|
|
30686
|
-
**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):
|
|
30687
30723
|
|
|
30688
30724
|
\`\`\`json
|
|
30689
30725
|
{
|
|
@@ -30695,7 +30731,7 @@ ${FAN_OUT_JUDGING}
|
|
|
30695
30731
|
}
|
|
30696
30732
|
\`\`\`
|
|
30697
30733
|
|
|
30698
|
-
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.
|
|
30699
30735
|
|
|
30700
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.
|
|
30701
30737
|
|
|
@@ -30709,7 +30745,7 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30709
30745
|
|
|
30710
30746
|
\`\`\`json
|
|
30711
30747
|
{
|
|
30712
|
-
"
|
|
30748
|
+
"experimentId": "<experimentId>",
|
|
30713
30749
|
"expectedOriginalTraceIds": ["<originalTraceId1>", "<originalTraceId2>"],
|
|
30714
30750
|
"verdicts": [
|
|
30715
30751
|
{ "originalTraceId": "<originalTraceId1>", "assertionId": "<assertionId1>", "label": true, "annotation": "The rebooked leg now names the carrier this assertion asks for.", "confidence": "High" },
|
|
@@ -30725,14 +30761,14 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30725
30761
|
3. Run the script:
|
|
30726
30762
|
|
|
30727
30763
|
\`\`\`bash
|
|
30728
|
-
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<
|
|
30764
|
+
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<experimentId>.json
|
|
30729
30765
|
\`\`\`
|
|
30730
30766
|
|
|
30731
30767
|
4. Read its single JSON line on stdout. Hold the parsed result for the next step.
|
|
30732
30768
|
|
|
30733
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}}
|
|
30734
30770
|
|
|
30735
|
-
**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}}`,
|
|
30736
30772
|
next: "iterate/verify-replay-labels"
|
|
30737
30773
|
},
|
|
30738
30774
|
{
|
|
@@ -30776,12 +30812,12 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30776
30812
|
},
|
|
30777
30813
|
{
|
|
30778
30814
|
when: '`status: "invalid-input"` (malformed verdicts JSON or missing fields)',
|
|
30779
|
-
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",
|
|
30780
30816
|
next: "iterate/verify-replay-labels"
|
|
30781
30817
|
},
|
|
30782
30818
|
{
|
|
30783
30819
|
when: '`status: "mcp-error"` (MCP call to save_agent_labels failed mid-batch)',
|
|
30784
|
-
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",
|
|
30785
30821
|
next: "iterate/verify-replay-labels"
|
|
30786
30822
|
},
|
|
30787
30823
|
{
|
|
@@ -30797,7 +30833,7 @@ Per-assertion and whole-trace entries travel in the same file and the same call,
|
|
|
30797
30833
|
toolCalls: ["bash", "listExperiments", "listExperimentTraces"],
|
|
30798
30834
|
commandCalls: ["pageLink", "openExperiments"],
|
|
30799
30835
|
title: "Share the experiments link",
|
|
30800
|
-
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.',
|
|
30801
30837
|
next: "iterate/evaluate-results"
|
|
30802
30838
|
},
|
|
30803
30839
|
{
|
|
@@ -30877,7 +30913,7 @@ Then ask what to do next. One option always lets the user **show this fix in Bit
|
|
|
30877
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"**.
|
|
30878
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"**.
|
|
30879
30915
|
|
|
30880
|
-
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\`).
|
|
30881
30917
|
|
|
30882
30918
|
{{branches}}`,
|
|
30883
30919
|
branches: [
|
|
@@ -30956,7 +30992,7 @@ Report the failed target result first. Say whether the replay produced a real FA
|
|
|
30956
30992
|
|
|
30957
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:
|
|
30958
30994
|
|
|
30959
|
-
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\`.
|
|
30960
30996
|
|
|
30961
30997
|
{{branches}}`,
|
|
30962
30998
|
branches: [
|
|
@@ -31025,7 +31061,7 @@ Set \`fixAddedToDataset = true\` only once the attach above is confirmed. Tell t
|
|
|
31025
31061
|
toolCalls: ["bash"],
|
|
31026
31062
|
commandCalls: ["pageLink"],
|
|
31027
31063
|
title: "Share the targeted before/after link",
|
|
31028
|
-
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.",
|
|
31029
31065
|
next: "iterate/fix-target-replay-status"
|
|
31030
31066
|
},
|
|
31031
31067
|
{
|
|
@@ -31082,7 +31118,7 @@ Then route by the approved plan, **without asking the user** (they already confi
|
|
|
31082
31118
|
{
|
|
31083
31119
|
id: "quick-replay",
|
|
31084
31120
|
title: "Phase Replay: Single-Trace Quick Replay",
|
|
31085
|
-
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.",
|
|
31086
31122
|
stepStyle: "list",
|
|
31087
31123
|
steps: [
|
|
31088
31124
|
{
|
|
@@ -31142,7 +31178,7 @@ ${REPLAY_SAFETY_CHECK}
|
|
|
31142
31178
|
# Ruby: cd <project-dir> && bundle exec bitfab-replay --registry <registry-path> <pipeline> --trace-ids <trace-id>
|
|
31143
31179
|
\`\`\`
|
|
31144
31180
|
|
|
31145
|
-
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.
|
|
31146
31182
|
|
|
31147
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\`.`,
|
|
31148
31184
|
next: "quick-replay/verdict"
|
|
@@ -31201,7 +31237,7 @@ Then compare the new output against the original trace's assertions, label, and
|
|
|
31201
31237
|
\`\`\`
|
|
31202
31238
|
|
|
31203
31239
|
\`\`\`bash
|
|
31204
|
-
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<
|
|
31240
|
+
{{command:persistReplayLabels}} <repoRoot>/.bitfab/tmp/verdicts-<experiment-id>.json
|
|
31205
31241
|
\`\`\`
|
|
31206
31242
|
|
|
31207
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.
|
|
@@ -31242,7 +31278,7 @@ Then compare the new output against the original trace's assertions, label, and
|
|
|
31242
31278
|
toolCalls: ["bash", "grep"],
|
|
31243
31279
|
title: "Re-seed the trace in place",
|
|
31244
31280
|
emit: "Re-seeding",
|
|
31245
|
-
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.
|
|
31246
31282
|
|
|
31247
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.
|
|
31248
31284
|
|
|
@@ -31378,7 +31414,7 @@ var setupFlow = Flow.parse({
|
|
|
31378
31414
|
},
|
|
31379
31415
|
openExperiments: {
|
|
31380
31416
|
description: "Print a link to experiments.",
|
|
31381
|
-
args: "<
|
|
31417
|
+
args: "<experimentIds>"
|
|
31382
31418
|
},
|
|
31383
31419
|
startDataset: {
|
|
31384
31420
|
description: "Print a dataset link.",
|
|
@@ -32777,7 +32813,7 @@ Leave the live request path untouched: only the replayed function reads the bran
|
|
|
32777
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.
|
|
32778
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.
|
|
32779
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\`.
|
|
32780
|
-
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.
|
|
32781
32817
|
|
|
32782
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.
|
|
32783
32819
|
|
package/package.json
CHANGED