@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
|
+
}
|
package/extensions/qdash.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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.
|
|
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": "
|
|
30
|
+
"@oqtopus-team/qdash-client": "1.9.21-beta.168"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@earendil-works/pi-coding-agent": "*",
|
package/skills/qdash/SKILL.md
CHANGED
|
@@ -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.
|