@oqtopus-team/pi-qdash 0.1.9 → 0.1.10-beta.12

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/README.md CHANGED
@@ -19,6 +19,7 @@ It uses [`@oqtopus-team/qdash-client`](https://www.npmjs.com/package/@oqtopus-te
19
19
  - **Read-only investigation**: dedicated tools to inspect chips, qubits, couplings, calibration task results, task knowledge (`qdash_get_task_knowledge`), executions, issues, and Forum posts. Secrets are redacted from tool output.
20
20
  - **Dashboards and insights**: compact dashboards, triage overviews, target-level incident reports, degradation reports, and wiring insights.
21
21
  - **Agent calibration workflow**: create agent sessions, submit and track agent actions, and commit/apply calibration candidates. Write operations are approval-gated.
22
+ - **Calibration pipelines**: compose a declarative pipeline spec from QDash's step catalog (`qdash_get_pipeline_catalog`), validate it (`qdash_plan_pipeline`), and run it as one execution (`qdash_run_pipeline`, approval-gated).
22
23
  - **Figures**: fetch calibration PNG/JSON figures and render them in the interactive TUI.
23
24
  - **Generic timeseries comparison**: inspect and align arbitrary QDash parameters/targets with explicitly mapped local CSV sensor columns, reuse schema-validated investigation presets, and report correlations plus optional shared-period/phase evidence without hard-coded metrics or schemas.
24
25
  - **Forum integration**: read Forum posts, publish task-result evidence, and upload locally generated analysis images to replies.
@@ -47,6 +48,14 @@ Requires Node.js 20 or later and pi v0.74.0 or later. The extension tracks
47
48
  `@oqtopus-team/qdash-client` 1.9.x and uses its authenticated high-level and
48
49
  generated API clients.
49
50
 
51
+ Every push to `main` also publishes a prerelease under the `beta` dist-tag
52
+ (the next patch version with a `-beta.<build>` suffix, for example
53
+ `0.1.10-beta.42`). Use it to try tools that are merged but not yet released:
54
+
55
+ ```bash
56
+ pi install npm:@oqtopus-team/pi-qdash@beta
57
+ ```
58
+
50
59
  ## Quick Start
51
60
 
52
61
  In interactive pi, set up the session context first:
@@ -28,3 +28,26 @@ export function toTextToolResult(text: string, data: unknown, details: Record<st
28
28
  details: { ...details, data: safeData },
29
29
  };
30
30
  }
31
+
32
+ /**
33
+ * Result of a tool that fetched a figure: the text summary plus, for an image,
34
+ * the image itself as model-visible content. Without the image block a
35
+ * vision model only reads that a figure exists; with it, it can judge the
36
+ * curve. Non-image figures (JSON) keep a text-only result.
37
+ */
38
+ export function toFigureToolResult(
39
+ text: string,
40
+ figure: { base64?: string; mediaType: string },
41
+ data: unknown,
42
+ details: Record<string, unknown> = {},
43
+ ) {
44
+ const result = toTextToolResult(text, data, details);
45
+ if (!figure.base64 || !figure.mediaType.startsWith("image/")) return result;
46
+ return {
47
+ ...result,
48
+ content: [
49
+ ...result.content,
50
+ { type: "image" as const, data: figure.base64, mimeType: figure.mediaType },
51
+ ],
52
+ };
53
+ }
@@ -11,6 +11,7 @@ const WRITE_TOOL_NAMES = new Set([
11
11
  "qdash_update_forum_post",
12
12
  "qdash_create_forum_evidence_reply",
13
13
  "qdash_create_forum_image_reply",
14
+ "qdash_run_pipeline",
14
15
  ]);
15
16
 
16
17
  export function installQDashWriteGate(pi: ExtensionAPI): void {
@@ -21,10 +21,11 @@ import { forumDetailComponent, forumDetailLines, forumListLines, forumPostTitle,
21
21
  import { investigationContextSummary, validateInvestigationContext, type TimeseriesInvestigationContext } from "./lib/investigation-context.js";
22
22
  import { qdashObjectLinks, qdashWebBaseUrl, qdashWebUrl, safeConfig, withQDashLinks } from "./lib/links.js";
23
23
  import { arrayFromPayload, compactItems, firstNumber, firstString, formatItem, payloadTotal, statusIcon } from "./lib/payload.js";
24
- import { toTextToolResult, toToolResult } from "./lib/results.js";
24
+ import { toFigureToolResult, toTextToolResult, toToolResult } from "./lib/results.js";
25
25
  import { ansi, boxLinesToWidth, boxed, formatNumber, textComponent } from "./lib/render.js";
26
26
  import { plotSeriesLines, timeseriesPlotComponent, timeseriesPoints } from "./lib/timeseries-plot.js";
27
27
  import { installQDashWriteGate } from "./lib/write-gate.js";
28
+ import { registerCalibrationPipelineTools } from "./tools/calibration-pipeline.js";
28
29
  import { registerTaskKnowledgeTool } from "./tools/task-knowledge.js";
29
30
  import { registerTimeseriesComparisonTool } from "./tools/timeseries.js";
30
31
  import { analyzeWiringMarkdown, wiringInsightLines, type WiringInsights } from "./lib/wiring-analysis.js";
@@ -1532,6 +1533,10 @@ async function executeQuery(params: QDashQueryParams) {
1532
1533
  export default function qdashExtension(pi: ExtensionAPI) {
1533
1534
  installQDashWriteGate(pi);
1534
1535
  registerTaskKnowledgeTool(pi, { makeClient });
1536
+ registerCalibrationPipelineTools(pi, {
1537
+ makeClient,
1538
+ resolveChipId: (client, chipId) => defaultChipId(client as QDashClient, chipId),
1539
+ });
1535
1540
 
1536
1541
  pi.registerTool({
1537
1542
  name: "qdash_config_info",
@@ -2538,7 +2543,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
2538
2543
  const details: FigureDetails = { tool: "qdash_recent_calibration_figure", taskId, path: file.path ?? "", mediaType, sizeBytes: bytes.byteLength, figurePaths: file.figurePaths ?? [], jsonFigurePaths: file.jsonFigurePaths ?? [] };
2539
2544
  if (mediaType.startsWith("image/")) details.base64 = bytes.toString("base64");
2540
2545
  else details.text = bytes.toString("utf8");
2541
- return toTextToolResult(figureResultText(details), { task, selectedPath: details.path }, details);
2546
+ return toFigureToolResult(figureResultText(details), details, { task, selectedPath: details.path }, details);
2542
2547
  },
2543
2548
  renderResult(result, _options, theme) {
2544
2549
  return figureComponent(result.details as unknown as FigureDetails, theme);
@@ -2558,7 +2563,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
2558
2563
  async execute(_toolCallId, params: { profile?: string; configPath?: string; useEnv?: boolean; path: string }) {
2559
2564
  const client = await makeClient(params);
2560
2565
  const details = await fetchFigureDetails(client, params.path);
2561
- return toTextToolResult(figureResultText(details), { path: params.path }, details);
2566
+ return toFigureToolResult(figureResultText(details), details, { path: params.path }, details);
2562
2567
  },
2563
2568
  renderResult(result, _options, theme) {
2564
2569
  return figureComponent(result.details as unknown as FigureDetails, theme);
@@ -2595,7 +2600,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
2595
2600
  };
2596
2601
  if (mediaType.startsWith("image/")) details.base64 = bytes.toString("base64");
2597
2602
  else details.text = bytes.toString("utf8");
2598
- return toTextToolResult(figureResultText(details), { taskId: params.taskId, figurePaths: details.figurePaths, jsonFigurePaths: details.jsonFigurePaths, selectedPath: details.path }, details);
2603
+ return toFigureToolResult(figureResultText(details), details, { taskId: params.taskId, figurePaths: details.figurePaths, jsonFigurePaths: details.jsonFigurePaths, selectedPath: details.path }, details);
2599
2604
  } catch (error) {
2600
2605
  return toTextToolResult(boxed("QDash Task Figures", [
2601
2606
  `task ${params.taskId}`,
@@ -0,0 +1,303 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { QDashClient } from "@oqtopus-team/qdash-client";
3
+ import { Type } from "typebox";
4
+
5
+ import { toTextToolResult } from "../lib/results.js";
6
+
7
+ /**
8
+ * Declarative calibration pipelines.
9
+ *
10
+ * The agent writes a CalibrationPipelineSpec (targets, ordered steps, run
11
+ * parameters); QDash validates it against the chip, backend, and task catalog,
12
+ * then runs it as one execution. The spec cannot reference code, only the
13
+ * Step classes and task names QDash exposes, so QDash owns the validation and
14
+ * these tools only relay it. The spec schema is deliberately not restated
15
+ * here: `qdash_get_pipeline_catalog` returns QDash's own JSON schema.
16
+ */
17
+
18
+ type ConnectionParams = { profile?: string; configPath?: string; useEnv?: boolean };
19
+
20
+ export type PipelineProblem = { path: string; message: string };
21
+
22
+ export type ResolvedPipelineStep = {
23
+ index: number;
24
+ type: string;
25
+ name: string;
26
+ kind: string;
27
+ tasks: string[];
28
+ };
29
+
30
+ export type PipelineValidation = {
31
+ valid: boolean;
32
+ problems?: PipelineProblem[];
33
+ backend_name: string;
34
+ spec?: Record<string, unknown> | null;
35
+ targets?: { qids?: string[]; mux_ids?: number[]; exclude_qids?: string[] } | null;
36
+ steps?: ResolvedPipelineStep[];
37
+ task_run_count?: number;
38
+ };
39
+
40
+ export type PipelineExecution = {
41
+ execution_id: string;
42
+ flow_run_id: string;
43
+ flow_run_url: string;
44
+ qdash_ui_url: string;
45
+ message: string;
46
+ steps?: ResolvedPipelineStep[];
47
+ targets?: PipelineValidation["targets"];
48
+ };
49
+
50
+ export type PipelineCatalog = {
51
+ backend_name: string;
52
+ step_types: Array<{
53
+ type: string;
54
+ kind: string;
55
+ description: string;
56
+ requires: string[];
57
+ provides: string[];
58
+ task_scope?: string | null;
59
+ default_tasks: string[];
60
+ }>;
61
+ one_qubit_modes: string[];
62
+ tasks: Record<string, string[]>;
63
+ spec_schema: Record<string, unknown>;
64
+ example?: Record<string, unknown>;
65
+ };
66
+
67
+ type PipelineRequest = { chip_id: string; spec: Record<string, unknown>; backend_name?: string };
68
+
69
+ /** The slice of QDashClient these tools use; tests pass a stub. */
70
+ export type PipelineClient = {
71
+ api: {
72
+ getCalibrationPipelineCatalog(params?: { backend_name?: string }): Promise<unknown>;
73
+ validateCalibrationPipeline(request: PipelineRequest): Promise<unknown>;
74
+ executeCalibrationPipeline(request: PipelineRequest): Promise<unknown>;
75
+ };
76
+ };
77
+
78
+ export type CalibrationPipelineDependencies = {
79
+ makeClient(params: ConnectionParams): Promise<PipelineClient>;
80
+ /** The chip to run on: the explicit id, else the session/default chip. */
81
+ resolveChipId(client: PipelineClient, chipId?: string): Promise<string>;
82
+ };
83
+
84
+ const TASK_PREVIEW = 8;
85
+
86
+ function describeTargets(targets: PipelineValidation["targets"]): string {
87
+ if (!targets) return "targets unknown";
88
+ if (targets.qids?.length) return `qubits ${targets.qids.join(", ")}`;
89
+ if (targets.mux_ids?.length) {
90
+ const base = `MUX ${targets.mux_ids.join(", ")}`;
91
+ return targets.exclude_qids?.length ? `${base} (excluding ${targets.exclude_qids.join(", ")})` : base;
92
+ }
93
+ return "targets unknown";
94
+ }
95
+
96
+ function taskList(tasks: string[]): string {
97
+ if (tasks.length === 0) return "no tasks (transform)";
98
+ const shown = tasks.slice(0, TASK_PREVIEW).join(", ");
99
+ const rest = tasks.length - TASK_PREVIEW;
100
+ return rest > 0 ? `${tasks.length} tasks: ${shown}, +${rest} more` : `${tasks.length} task${tasks.length === 1 ? "" : "s"}: ${shown}`;
101
+ }
102
+
103
+ export function stepLines(steps: ResolvedPipelineStep[] | undefined): string[] {
104
+ if (!steps?.length) return ["- (no steps resolved)"];
105
+ return steps.map((step) => {
106
+ const label = step.name === step.type ? step.type : `${step.name} (${step.type})`;
107
+ return `${step.index}. ${label} — ${taskList(step.tasks)}`;
108
+ });
109
+ }
110
+
111
+ export function problemLines(problems: PipelineProblem[] | undefined): string[] {
112
+ if (!problems?.length) return [];
113
+ return problems.map((problem) => `- ${problem.path}: ${problem.message}`);
114
+ }
115
+
116
+ /** Text shown for a validated (or rejected) plan. */
117
+ export function formatPipelinePlan(result: PipelineValidation, chipId: string): string {
118
+ const name = typeof result.spec?.name === "string" ? result.spec.name : undefined;
119
+ const header = `Pipeline${name ? ` '${name}'` : ""} on chip ${chipId} (backend ${result.backend_name})`;
120
+ const problems = problemLines(result.problems);
121
+ const lines = [header, `Targets: ${describeTargets(result.targets)}`, ""];
122
+ if (result.valid) {
123
+ lines.push(`Valid. ${result.task_run_count ?? 0} task runs per target.`);
124
+ } else {
125
+ lines.push(`Not valid: ${problems.length} problem${problems.length === 1 ? "" : "s"}. Fix the spec and validate again.`, ...problems);
126
+ }
127
+ if (result.steps?.length) lines.push("", "Steps:", ...stepLines(result.steps));
128
+ return lines.join("\n");
129
+ }
130
+
131
+ export function formatPipelineExecution(result: PipelineExecution, chipId: string): string {
132
+ const stepCount = result.steps?.length ?? 0;
133
+ const taskRuns = (result.steps ?? []).reduce((count, step) => count + step.tasks.length, 0);
134
+ return [
135
+ `Started: ${result.message}`,
136
+ `execution_id ${result.execution_id}`,
137
+ `chip ${chipId}, ${describeTargets(result.targets)}, ${stepCount} step${stepCount === 1 ? "" : "s"}, ${taskRuns} task runs per target`,
138
+ `qdash ${result.qdash_ui_url}`,
139
+ `prefect ${result.flow_run_url}`,
140
+ "",
141
+ "Use qdash_wait_execution with this execution_id to follow it to completion.",
142
+ ].join("\n");
143
+ }
144
+
145
+ export function formatPipelineCatalog(catalog: PipelineCatalog): string {
146
+ const lines = [`Calibration pipeline catalog (backend ${catalog.backend_name})`, "", "Step types:"];
147
+ for (const step of catalog.step_types) {
148
+ const needs = step.requires.length ? ` needs ${step.requires.join(" or ")};` : "";
149
+ const scope = step.task_scope ? ` ${step.task_scope} tasks` : "";
150
+ const defaults = step.default_tasks.length ? `; default ${step.default_tasks.length} tasks` : "";
151
+ lines.push(`- ${step.type} [${step.kind}${scope}]: ${step.description}${needs}${defaults}`);
152
+ }
153
+ lines.push("", `One-qubit modes: ${catalog.one_qubit_modes.join(", ")}`, "", "Tasks by type:");
154
+ for (const [taskType, names] of Object.entries(catalog.tasks)) {
155
+ lines.push(`- ${taskType} (${names.length}): ${names.join(", ")}`);
156
+ }
157
+ lines.push(
158
+ "",
159
+ "Targets are either {qids: [...]} or {mux_ids: [...], exclude_qids?: [...]}.",
160
+ "The full spec JSON schema is in details.data.spec_schema; validate with qdash_plan_pipeline.",
161
+ );
162
+ return lines.join("\n");
163
+ }
164
+
165
+ /** Problems carried by a 422 from /calibration-pipelines/execute, if that is what `error` is. */
166
+ export function problemsFromError(error: unknown): PipelineProblem[] | null {
167
+ if (!error || typeof error !== "object") return null;
168
+ const payload = (error as { payload?: unknown }).payload;
169
+ const detail = payload && typeof payload === "object" ? (payload as { detail?: unknown }).detail : undefined;
170
+ const problems = detail && typeof detail === "object" ? (detail as { problems?: unknown }).problems : undefined;
171
+ if (!Array.isArray(problems)) return null;
172
+ return problems
173
+ .filter((item): item is PipelineProblem => Boolean(item) && typeof item === "object" && typeof (item as PipelineProblem).path === "string")
174
+ .map((item) => ({ path: item.path, message: String(item.message ?? "") }));
175
+ }
176
+
177
+ function statusOf(error: unknown): number | undefined {
178
+ if (!error || typeof error !== "object") return undefined;
179
+ const value = error as { statusCode?: unknown; status?: unknown };
180
+ return typeof value.statusCode === "number" ? value.statusCode : typeof value.status === "number" ? value.status : undefined;
181
+ }
182
+
183
+ const connectionParams = {
184
+ profile: Type.Optional(Type.String({ description: "QDash profile name. Defaults to env when QDASH_BASE_URL is set, otherwise 'default'." })),
185
+ configPath: Type.Optional(Type.String({ description: "Optional path to qdash config.ini." })),
186
+ useEnv: Type.Optional(Type.Boolean({ description: "Force QDASH_* environment variables instead of a profile." })),
187
+ };
188
+
189
+ // Loose on purpose: QDash validates the spec and reports problems with paths,
190
+ // and a union-heavy schema here would be ignored by vLLM guided decoding anyway.
191
+ const specParam = Type.Object({}, {
192
+ additionalProperties: true,
193
+ description: "CalibrationPipelineSpec: {name?, targets: {qids} | {mux_ids, exclude_qids?}, steps: [{type, ...}], task_run_parameters?, default_run_parameters?, tags?}. Get step types and tasks from qdash_get_pipeline_catalog.",
194
+ });
195
+
196
+ export function registerCalibrationPipelineTools(pi: Pick<ExtensionAPI, "registerTool">, deps: CalibrationPipelineDependencies): void {
197
+ pi.registerTool({
198
+ name: "qdash_get_pipeline_catalog",
199
+ label: "QDash Pipeline Catalog",
200
+ description: "List the step types, task names, and spec schema for composing a calibration pipeline. Read-only. Call this before writing a pipeline spec.",
201
+ promptSnippet: "Look up the step types and tasks available for a QDash calibration pipeline",
202
+ promptGuidelines: [
203
+ "Use qdash_get_pipeline_catalog before composing a pipeline spec; use only the step types and task names it lists.",
204
+ ],
205
+ parameters: Type.Object({
206
+ ...connectionParams,
207
+ backendName: Type.Optional(Type.String({ description: "Backend to list tasks for. Defaults to the configured backend." })),
208
+ }),
209
+ async execute(_toolCallId, params: ConnectionParams & { backendName?: string }) {
210
+ const client = await deps.makeClient(params);
211
+ const catalog = (await client.api.getCalibrationPipelineCatalog(
212
+ params.backendName ? { backend_name: params.backendName } : undefined,
213
+ )) as PipelineCatalog;
214
+ return toTextToolResult(formatPipelineCatalog(catalog), catalog, { tool: "qdash_get_pipeline_catalog", backendName: catalog.backend_name });
215
+ },
216
+ });
217
+
218
+ pi.registerTool({
219
+ name: "qdash_plan_pipeline",
220
+ label: "QDash Plan Pipeline",
221
+ description: "Check a calibration pipeline spec against QDash without running it: returns problems with paths into the spec and the exact tasks each step will run. Read-only. Call this before qdash_run_pipeline.",
222
+ promptSnippet: "Validate a QDash calibration pipeline spec and preview the tasks it will run",
223
+ promptGuidelines: [
224
+ "Use qdash_plan_pipeline after composing a spec and after every edit; a spec with problems is not an error, fix it and plan again.",
225
+ "Show the user the resolved steps and task counts before running.",
226
+ ],
227
+ parameters: Type.Object({
228
+ ...connectionParams,
229
+ chipId: Type.Optional(Type.String({ description: "Chip to run on. Defaults to the session or default chip." })),
230
+ spec: specParam,
231
+ backendName: Type.Optional(Type.String({ description: "Defaults to the configured backend." })),
232
+ }),
233
+ async execute(_toolCallId, params: ConnectionParams & { chipId?: string; spec: Record<string, unknown>; backendName?: string }) {
234
+ const client = await deps.makeClient(params);
235
+ const chipId = await deps.resolveChipId(client, params.chipId);
236
+ const result = (await client.api.validateCalibrationPipeline({
237
+ chip_id: chipId,
238
+ spec: params.spec,
239
+ ...(params.backendName ? { backend_name: params.backendName } : {}),
240
+ })) as PipelineValidation;
241
+ return toTextToolResult(formatPipelinePlan(result, chipId), result, {
242
+ tool: "qdash_plan_pipeline",
243
+ chipId,
244
+ valid: result.valid,
245
+ problemCount: result.problems?.length ?? 0,
246
+ });
247
+ },
248
+ });
249
+
250
+ pi.registerTool({
251
+ name: "qdash_run_pipeline",
252
+ label: "QDash Run Pipeline",
253
+ description: "Run a calibration pipeline spec as one QDash execution. Confirmation-gated write operation. Validate with qdash_plan_pipeline first; the spec runs exactly as validated.",
254
+ promptSnippet: "Run a validated QDash calibration pipeline spec",
255
+ promptGuidelines: [
256
+ "Use qdash_run_pipeline only for a spec that qdash_plan_pipeline reported valid.",
257
+ "After it starts, follow the execution with qdash_wait_execution and summarize failed targets.",
258
+ ],
259
+ parameters: Type.Object({
260
+ ...connectionParams,
261
+ chipId: Type.String({ description: "Chip to run on." }),
262
+ spec: specParam,
263
+ backendName: Type.Optional(Type.String({ description: "Defaults to the configured backend." })),
264
+ confirmWrite: Type.Optional(Type.Boolean({ description: "Required for non-interactive execution. Set true only after explicit user confirmation." })),
265
+ }),
266
+ async execute(_toolCallId, params: ConnectionParams & { chipId: string; spec: Record<string, unknown>; backendName?: string; confirmWrite?: boolean }, _signal, _onUpdate, ctx) {
267
+ if (!params.confirmWrite) {
268
+ if (!ctx.hasUI || !(await ctx.ui.confirm("Run QDash calibration pipeline?", `This starts a calibration execution on chip ${params.chipId}.`))) {
269
+ throw new Error("qdash_run_pipeline requires explicit confirmation");
270
+ }
271
+ }
272
+ const client = await deps.makeClient(params);
273
+ try {
274
+ const result = (await client.api.executeCalibrationPipeline({
275
+ chip_id: params.chipId,
276
+ spec: params.spec,
277
+ ...(params.backendName ? { backend_name: params.backendName } : {}),
278
+ })) as PipelineExecution;
279
+ return toTextToolResult(formatPipelineExecution(result, params.chipId), result, {
280
+ tool: "qdash_run_pipeline",
281
+ chipId: params.chipId,
282
+ executionId: result.execution_id,
283
+ started: true,
284
+ });
285
+ } catch (error) {
286
+ const problems = problemsFromError(error);
287
+ if (problems) {
288
+ const text = [
289
+ `QDash rejected the pipeline spec (${problems.length} problem${problems.length === 1 ? "" : "s"}). Nothing was started.`,
290
+ ...problemLines(problems),
291
+ "",
292
+ "Fix the spec, confirm it with qdash_plan_pipeline, then run again.",
293
+ ].join("\n");
294
+ return toTextToolResult(text, { problems }, { tool: "qdash_run_pipeline", chipId: params.chipId, started: false, problemCount: problems.length });
295
+ }
296
+ if (statusOf(error) === 503) {
297
+ throw new Error("QDash cannot run pipelines right now: the worker has not registered the calibration-pipeline deployment. Ask an operator to restart the worker, then try again.");
298
+ }
299
+ throw error;
300
+ }
301
+ },
302
+ });
303
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oqtopus-team/pi-qdash",
3
- "version": "0.1.9",
3
+ "version": "0.1.10-beta.12",
4
4
  "description": "Pi extension for querying QDash via @oqtopus-team/qdash-client",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@oqtopus-team/qdash-client": "^1.9.11"
30
+ "@oqtopus-team/qdash-client": "1.9.21-beta.168"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@earendil-works/pi-coding-agent": "*",
@@ -24,6 +24,7 @@ Prefer the dedicated tool that matches the question. Use `qdash_query` only as a
24
24
  - `qdash_get_task_knowledge` — interpret a task's purpose, expected result, failure modes, and check questions
25
25
  - `qdash_list_issues` — inspect tracked issues
26
26
  - `qdash_list_flows`, `qdash_get_flow` — list or inspect calibration flows
27
+ - `qdash_get_pipeline_catalog`, `qdash_plan_pipeline` — compose and check a calibration pipeline spec before running it
27
28
  - `qdash_list_executions`, `qdash_get_execution`, `qdash_wait_execution`, `qdash_compare_executions` — inspect, await, or compare executions
28
29
  - `qdash_list_ai_reviews`, `qdash_get_provenance_stats` — inspect AI reviews or provenance status
29
30
  - `qdash_list_forum_posts`, `qdash_get_forum_post`, `qdash_list_forum_replies` — inspect Forum context
@@ -40,12 +41,33 @@ Prefer the dedicated tool that matches the question. Use `qdash_query` only as a
40
41
  - `qdash_apply_agent_candidate_commit` — apply a reviewed candidate commit
41
42
  - `qdash_create_forum_post`, `qdash_update_forum_post` — create or update Forum content
42
43
  - `qdash_create_forum_evidence_reply`, `qdash_create_forum_image_reply` — publish previously reviewed evidence replies
44
+ - `qdash_run_pipeline` — run a validated pipeline spec as one execution
43
45
  4. Agent workflow state tools:
44
46
  - `qdash_get_agent_session`, `qdash_get_agent_action`, `qdash_list_agent_actions`, `qdash_wait_agent_action` — inspect agent workflow state
45
47
  - `qdash_list_agent_action_candidates`, `qdash_get_agent_candidate_commit`, `qdash_wait_agent_candidate_apply` — inspect candidates and apply status
46
48
  5. `qdash_query` — fallback for read-only operations without a dedicated tool.
47
49
  6. `qdash_raw_get` — last-resort read-only GET for an uncovered endpoint.
48
50
 
51
+ ## Composing a calibration pipeline
52
+
53
+ A calibration pipeline spec is a JSON description of one calibration run: targets, an ordered list of steps, and run parameters. QDash validates it against the chip, backend, and task catalog and runs it as one execution. The spec can only name the step types and tasks QDash exposes; it cannot contain code.
54
+
55
+ Workflow:
56
+
57
+ 1. `qdash_get_pipeline_catalog` — read the step types (with what each needs from an earlier step and its default task list), the one-qubit modes, and the task names by type. Use only names from this catalog.
58
+ 2. Turn the request into steps. Start from the catalog defaults and the flow templates (`qdash_query` with `flow_templates`, then `flow_template` for one template's task list): `OneQubitCheck` and `OneQubitFineTune` carry the standard one-qubit sequences, `TwoQubitCalibration` the two-qubit chain. Use `CustomOneQubit` / `CustomTwoQubit` only where the user wants a different task list, and keep the task order the templates use.
59
+ 3. `qdash_plan_pipeline` — validate. A result with problems is not an error: each problem carries a path into the spec (`steps[2].tasks[0]`, `targets.qids[1]`); fix it and plan again until it is valid.
60
+ 4. Show the user the resolved steps and task counts from the plan, then `qdash_run_pipeline` with the same `chipId` and `spec`. The execution is confirmation-gated.
61
+ 5. `qdash_wait_execution` with the returned `execution_id`, then summarize which targets passed and which failed, and read `qdash_get_task_knowledge` for any task whose result needs interpretation.
62
+
63
+ Rules:
64
+
65
+ - Targets are either `qids` or `mux_ids` (with optional `exclude_qids`), never both. Prefer MUX targets for more than a few qubits; hardware scheduling works per MUX.
66
+ - `FilterByStatus` and `FilterByMetric` are only valid after a one-qubit step. Put a filter before any two-qubit step.
67
+ - Put values into `task_run_parameters` only when the user asked for them; otherwise leave QDash's defaults.
68
+ - A pipeline takes the project execution lock. Check `qdash_list_executions` for a running execution before starting one.
69
+ - Prefer short pipelines (one or two steps) when the next step depends on the result: run, inspect results and figures, then compose the next spec. Calibrated parameters persist in QDash between executions, so a later spec continues from the earlier one. Retries and branches are your decision between executions, not something the spec expresses.
70
+
49
71
  ## Session context
50
72
 
51
73
  When running in pi CLI, use `qdash_config_info` if the profile or non-secret connection settings are unclear.