@barivia/barsom-mcp 0.18.1 → 0.20.0
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 +12 -7
- package/dist/index.js +1 -1
- package/dist/job_monitor.js +230 -0
- package/dist/shared.js +1 -1
- package/dist/tools/guide_barsom.js +1 -1
- package/dist/tools/jobs.js +34 -6
- package/dist/tools/training_core.js +4 -5
- package/dist/tools/training_guidance.js +1 -1
- package/dist/views/src/views/results-explorer/index.html +28 -28
- package/dist/views/src/views/training-monitor/index.html +14 -13
- package/dist/viz-server.js +1 -71
- package/package.json +2 -2
- package/dist/tools/training_prep.js +0 -573
- package/dist/tools/training_review_store.js +0 -53
- package/dist/views/src/views/training-prep/index.html +0 -387
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @barivia/barsom-mcp
|
|
2
2
|
|
|
3
|
-
MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `train` (`map`, `siom_map`, `impute`, `floop_siom` where entitled), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/export/download/recolor), and `inference` (predict/batch_predict/impute_column/compare/project_columns/transition_flow/report). **Optional MCP App tools** (`
|
|
3
|
+
MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `train` (`map`, `siom_map`, `impute`, `floop_siom` where entitled), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/export/download/recolor), and `inference` (predict/batch_predict/impute_column/compare/project_columns/transition_flow/report). **Optional MCP App tools** (`training_monitor`, `results_explorer`) add embedded or localhost viz; they are **not required** to complete upload → train → poll → results, though `results_explorer` is the preferred way to browse figures.
|
|
4
4
|
|
|
5
|
-
**Pre-training help (pick one):** **`prepare_training`** prompt = narrative checklist (tier-scoped from the API when online); **`training_guidance`** tool = structured presets and parameter hints
|
|
5
|
+
**Pre-training help (pick one):** **`prepare_training`** prompt = narrative checklist (tier-scoped from the API when online); **`training_guidance`** tool = structured presets and parameter hints (including the resolved per-column normalization). Review either, then submit with **`train`**.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -70,11 +70,11 @@ All multi-action tools follow the `datasets` / `jobs` / `results` / `inference`
|
|
|
70
70
|
|--------|--------|
|
|
71
71
|
| Bootstrap | `guide_barsom_workflow`, prompts `info`, `prepare_training` |
|
|
72
72
|
| Data | `datasets` |
|
|
73
|
-
| Train & poll | `
|
|
73
|
+
| Train & poll | `train`, `jobs`, `training_monitor` (optional) |
|
|
74
74
|
| Results | `results`, `results_explorer` (optional) |
|
|
75
75
|
| Inference | `inference` |
|
|
76
76
|
| Account | `account` |
|
|
77
|
-
| Optional UI | `
|
|
77
|
+
| Optional UI | `training_monitor`, `results_explorer` |
|
|
78
78
|
| Advanced | `training_guidance` |
|
|
79
79
|
|
|
80
80
|
Agents should not call **`_fetch_figure`** from chat; it exists for the Results Explorer MCP App host. Use `results(action=get)` or `results_explorer` instead.
|
|
@@ -162,8 +162,6 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
|
|
|
162
162
|
|
|
163
163
|
| Tool | Role |
|
|
164
164
|
|------|------|
|
|
165
|
-
| `training_prep` | Review variables, transforms, hyperparameters before submit |
|
|
166
|
-
| `submit_prepared_training` | Submit the reviewed prep (`review_token` + `explicit_confirm=true`) |
|
|
167
165
|
| `training_monitor` | Live QE + topographic-error curves (ordering/convergence, dual y-axis), FLooP step progress when applicable; optional — `jobs(action=status)` is enough |
|
|
168
166
|
| `results_explorer` | Browse metrics and figures after training completes |
|
|
169
167
|
| `_fetch_figure` | **Host / App only** — Results Explorer invokes this for one raster figure; not for agent chat |
|
|
@@ -182,6 +180,13 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
|
|
|
182
180
|
|
|
183
181
|
### Migration notes
|
|
184
182
|
|
|
183
|
+
- **Features (0.20.0, non-breaking):**
|
|
184
|
+
- **FLooP-SIOM maps are now projectable.** `inference(action=project_columns)`, `inference(action=predict)`, and `datasets(action=add_expression, project_onto_job=<floop_job>)` work on FLooP-SIOM (free/chain) maps — values render onto the FLooP Voronoi layout instead of failing. Grid-only ops (`inference(action=transition_flow | impute_column | render_variant)`) return a clear `unsupported_topology_for_inference` message; use a fixed-grid SOM/SIOM for those. No client change required.
|
|
185
|
+
- **`datasets(action=reduce_spectral)` column selectors.** New optional `columns_range` (`[first, last]`, the server expands the inclusive numeric block — preferred for wide spectra over sending thousands of names) and `columns_except` (drop non-frequency columns like id/label/target from the block, or use alone for "all numeric except these"). `columns_except` composes with `columns_block` or `columns_range`; `columns_block` and `columns_range` are mutually exclusive. Existing `columns_block` callers are unaffected.
|
|
186
|
+
- Minimum dataset size for train/analyze relaxed from 50 to **10 rows** (10–49 allowed with a small-N stability caveat).
|
|
187
|
+
- **Surface cleanup (0.19.0, breaking):**
|
|
188
|
+
- `training_prep` and `submit_prepared_training` **removed**. The interactive prep UI is retired; for pre-train help use the `prepare_training` prompt (narrative checklist) or `training_guidance` (presets + resolved per-column normalization), then submit directly with `train(action=map | siom_map | impute | floop_siom)`.
|
|
189
|
+
- The unused `data-preview` and `map-explorer` view scaffolds were deleted (they were never wired to a tool). The two shipped MCP App UIs are now `training_monitor` and `results_explorer`, both enhanced (auto-resize, earlier render, and silent model-context sync of what the user is viewing).
|
|
185
190
|
- **Fixes (0.17.1, non-breaking):**
|
|
186
191
|
- `datasets(action=list/get)` no longer prints `ingest_error=missing` for healthy datasets (a SQL `NULL` was serialized as the literal `"missing"`; fixed server-side, and the proxy now also ignores the `missing`/`null` sentinels).
|
|
187
192
|
- `file_path` upload help now states that **relative** paths resolve against the MCP **workspace root** — in Cursor/IDE clients that root is often the MCP install dir, so set `BARIVIA_WORKSPACE_ROOT` (or pass an absolute path) if a relative path is "not accessible".
|
|
@@ -226,7 +231,7 @@ MCP Client (Cursor/Claude) ←stdio→ @barivia/barsom-mcp ←HTTPS→ api.bariv
|
|
|
226
231
|
| `401` / invalid key | `BARIVIA_API_KEY` in MCP config; check your email or manage your key in the [account dashboard](https://barivia.se/dashboard). Error text includes a **request id** for support. |
|
|
227
232
|
| Request timed out | Raise `BARIVIA_FETCH_TIMEOUT_MS` (e.g. `120000`). Large uploads already use an extended timeout; `datasets(analyze)` is async (auto-polled). Timeouts are not auto-retried, so re-running a timed-out upload is safe — an idempotency key reconciles it to the original dataset instead of duplicating. |
|
|
228
233
|
| `Path must be within the workspace` / upload can’t find file | Set `BARIVIA_WORKSPACE_ROOT` to your project directory, or use an absolute path / `file:///...` URI. |
|
|
229
|
-
| Job stuck “running” | Poll `jobs(action=status)` every 10–15s; large grids or FLooP-SIOM can take several minutes—not an MCP error. Staged datasets enqueue **`prepare_training_matrix`** (or impute prepare) first — train submit returns `prepare_job_id`; MCP auto-polls prepare when present. CFD mesh submit may return `prepare_job_id` for **`
|
|
234
|
+
| Job stuck “running” | Poll `jobs(action=status)` every 10–15s; large grids or FLooP-SIOM can take several minutes—not an MCP error. Staged datasets enqueue **`prepare_training_matrix`** (or impute prepare) first — train submit returns `prepare_job_id`; MCP auto-polls prepare when present. CFD mesh submit may return `prepare_job_id` for **`prepare_training_matrix`** (barmesh auto-polls). |
|
|
230
235
|
| `429` | Rate limit—wait and retry. |
|
|
231
236
|
| Standalone `…/viz/…` page stuck at "running 0%" or "Loading…" | The link is stale: the viz port is assigned per MCP session and changes when the proxy restarts. Re-run `training_monitor` / `results_explorer` for a fresh URL, or set `BARIVIA_VIZ_PORT` for a persistent port. Confirm with `GET /api/health?job_id=<id>`. |
|
|
232
237
|
| Embedded results explorer: switching figures shows nothing | Fixed in `0.18.0` — earlier versions pointed embedded figures at a localhost URL the webview can't load. Update the package; the embedded view now lazy-loads figures over the MCP App bridge. |
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as n,RESOURCE_MIME_TYPE as s}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as i}from"./viz-server.js";import{API_KEY as a,apiCall as l,apiRawCall as p,loadViewHtml as c,setVizPort as m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as _,JOBS_DESCRIPTION_BASE as h}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as y,RESULTS_EXPLORER_URI as w}from"./tools/explore_map.js";import{registerAccountTool as j}from"./tools/account.js";import{registerInferenceTool as v}from"./tools/inference.js";import{registerGuideBarsomTool as x}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as k}from"./tools/feedback.js";import{registerTrainingPrepTools as I,TRAINING_PREP_URI as T}from"./tools/training_prep.js";import{registerTrainingMonitorTool as A,TRAINING_MONITOR_URI as M}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as S}from"./prepare_training_prompt.js";a||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const O=new e({name:"analytics-engine",version:u,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool), then `results_explorer(job_id)` to browse the figures interactively. Then `jobs(compare)`, `results(download/recolor)`, or `inference` (predict / compare / project_columns / impute_column / transition_flow) as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool taxonomy (one tool per stage — do not mix)\n\n| Stage | Tool(s) | Contract |\n|-------|---------|----------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete — ingest + feature engineering, no map |\n| Train submit | `train` **or** `training_prep` → `submit_prepared_training` | map, siom_map, impute (sparse → map + dense imputed.csv), floop_siom (entitled). Async, returns job_id. Submit only |\n| Lifecycle | `jobs` | status, list, compare (**training runs**), cancel, delete. No training/scoring here |\n| Artifacts | `results` | get (figures="none" for metrics-only), export, download, recolor (async). Read/render completed jobs |\n| Frozen-map ops | `inference` | predict (regime-aware; "compact"|"annotated"), batch_predict, impute_column (neighbor-pool fill for a non-training column), compare (cohort density-diff), project_columns, transition_flow (time-ordered rows only), report. No weight updates |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow`, `training_guidance`, `prepare_training` prompt | orientation, parameter hints, narrative checklist (API/tier-scoped) |\n| Explore (UI) | `results_explorer`, `training_prep`, `training_monitor` | richer UX over the same API; `jobs(status)`+`results(get)` still suffice headless |\n| Other | `send_feedback` | only after user agrees |\n\n### Avoid these mis-routes\n- `jobs(compare)` = metrics table across **training runs** (pick best in a sweep). `inference(compare)` = density-diff of one map vs a **second dataset** (drift/cohort). Different tools.\n- Post-training map analysis (predict, compare, project_columns, impute_column, **transition_flow**) is all **inference** — not `results`. `results` is presentation/artifacts only (`recolor` stays here: it re-renders, it doesn\'t touch data).\n- Project a quantity onto the map: existing columns → `inference(project_columns)`; a formula → `datasets(add_expression, project_onto_job=<job>)`.\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions (incl. `transition_flow`) and `results(recolor)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints; `training_prep` = interactive UI that shows the **resolved per-column normalization / transforms / grid / epochs** and lets the user tweak before a guarded `submit_prepared_training`. Prefer `training_prep` when the user wants to review or fine-tune preprocessing before committing a job; use `train` directly when parameters are already confirmed. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `results(recolor)`, `inference(transition_flow)`, or `inference(project_columns)`, use the **new** `job_id` returned for follow-up `results` if applicable.'});n(O,w,w,{mimeType:s},async()=>{const e=await c("results-explorer");return{contents:[{uri:w,mimeType:s,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),n(O,T,T,{mimeType:s},async()=>{const e=await c("training-prep");return{contents:[{uri:T,mimeType:s,text:e??"<html><body>Training Preparation view not built yet.</body></html>"}]}}),n(O,M,M,{mimeType:s},async()=>{const e=await c("training-monitor");return{contents:[{uri:M,mimeType:s,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),x(O),y(O),I(O),A(O),f(O),g(O),_(O,h),b(O),j(O),v(O),P(O),k(O),O.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool) → `results_explorer(job_id)` to browse figures.","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom; pass `label` for readable compare rows; preset=quick for a fast first map), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/download/export/recolor; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; transition_flow; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints) · `training_prep` + `submit_prepared_training` (interactive UI showing resolved normalization/params to review before submit).","","**Browse results:** `results_explorer(job_id)` is the preferred way to explore figures after a first `results(get)` glance; `training_monitor` shows live progress. Both optional — `results` + `jobs(status)` suffice headless.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `inference(transition_flow)` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),O.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints; training_prep tool = interactive UI + submit_prepared_training.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await S(e)}}]}));const C=new t;(async function(){try{const e=await i(l,p,c);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=O.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(s))},await O.connect(C)})().catch(console.error);
|
|
2
|
+
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as s,RESOURCE_MIME_TYPE as n}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as i}from"./viz-server.js";import{API_KEY as a,apiCall as l,apiRawCall as c,loadViewHtml as p,setVizPort as m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as h,JOBS_DESCRIPTION_BASE as _}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as w,RESULTS_EXPLORER_URI as y}from"./tools/explore_map.js";import{registerAccountTool as j}from"./tools/account.js";import{registerInferenceTool as v}from"./tools/inference.js";import{registerGuideBarsomTool as x}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as k}from"./tools/feedback.js";import{registerTrainingMonitorTool as I,TRAINING_MONITOR_URI as A}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as T}from"./prepare_training_prompt.js";a||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const M=new e({name:"analytics-engine",version:u,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool), then `results_explorer(job_id)` to browse the figures interactively. Then `jobs(compare)`, `results(download/recolor)`, or `inference` (predict / compare / project_columns / impute_column / transition_flow) as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool taxonomy (one tool per stage — do not mix)\n\n| Stage | Tool(s) | Contract |\n|-------|---------|----------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete — ingest + feature engineering, no map |\n| Train submit | `train` | map, siom_map, impute (sparse → map + dense imputed.csv), floop_siom (entitled). Async, returns job_id. Submit only |\n| Lifecycle | `jobs` | status, list, compare (**training runs**), cancel, delete. No training/scoring here |\n| Artifacts | `results` | get (figures="none" for metrics-only), export, download, recolor (async). Read/render completed jobs |\n| Frozen-map ops | `inference` | predict (regime-aware; "compact"|"annotated"), batch_predict, impute_column (neighbor-pool fill for a non-training column), compare (cohort density-diff), project_columns, transition_flow (time-ordered rows only), report. No weight updates |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow`, `training_guidance`, `prepare_training` prompt | orientation, parameter hints, narrative checklist (API/tier-scoped) |\n| Explore (UI) | `results_explorer`, `training_monitor` | richer UX over the same API; `jobs(status)`+`results(get)` still suffice headless |\n| Other | `send_feedback` | only after user agrees |\n\n### Avoid these mis-routes\n- `jobs(compare)` = metrics table across **training runs** (pick best in a sweep). `inference(compare)` = density-diff of one map vs a **second dataset** (drift/cohort). Different tools.\n- Post-training map analysis (predict, compare, project_columns, impute_column, **transition_flow**) is all **inference** — not `results`. `results` is presentation/artifacts only (`recolor` stays here: it re-renders, it doesn\'t touch data).\n- Project a quantity onto the map: existing columns → `inference(project_columns)`; a formula → `datasets(add_expression, project_onto_job=<job>)`.\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions (incl. `transition_flow`) and `results(recolor)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints (presets, resolved normalization/transforms, grid/epochs). Review these, then submit with `train`. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `results(recolor)`, `inference(transition_flow)`, or `inference(project_columns)`, use the **new** `job_id` returned for follow-up `results` if applicable.'});s(M,y,y,{mimeType:n},async()=>{const e=await p("results-explorer");return{contents:[{uri:y,mimeType:n,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),s(M,A,A,{mimeType:n},async()=>{const e=await p("training-monitor");return{contents:[{uri:A,mimeType:n,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),x(M),w(M),I(M),f(M),g(M),h(M,_),b(M),j(M),v(M),P(M),k(M),M.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool) → `results_explorer(job_id)` to browse figures.","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom; pass `label` for readable compare rows; preset=quick for a fast first map), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/download/export/recolor; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; transition_flow; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints + resolved normalization/params) — review, then submit with `train`.","","**Browse results:** `results_explorer(job_id)` is the preferred way to explore figures after a first `results(get)` glance; `training_monitor` shows live progress. Both optional — `results` + `jobs(status)` suffice headless.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `inference(transition_flow)` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),M.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints. Review, then submit with train.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await T(e)}}]}));const S=new t;(async function(){try{const e=await i(l,c,p);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=M.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(n))},await M.connect(S)})().catch(console.error);
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side job monitor for barsom async training workflows.
|
|
3
|
+
* Polls GET /v1/jobs/:id until terminal (or block_until timeout) and emits
|
|
4
|
+
* compact throttled snapshots so agents avoid manual status poll loops.
|
|
5
|
+
*/
|
|
6
|
+
import { apiCall, textResult } from "./shared.js";
|
|
7
|
+
import { formatJobStatusText } from "./job_status_format.js";
|
|
8
|
+
import { pollFinalizeIfPresent, refreshJobAfterFinalize } from "./train_finalize.js";
|
|
9
|
+
export const DEFAULT_BLOCK_UNTIL_SEC = 900;
|
|
10
|
+
export const DEFAULT_POLL_INTERVAL_SEC = 5;
|
|
11
|
+
export const MIN_POLL_INTERVAL_SEC = 5;
|
|
12
|
+
export const HEARTBEAT_POLLS = 1;
|
|
13
|
+
function sleep(ms) {
|
|
14
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
15
|
+
}
|
|
16
|
+
function num(data, key) {
|
|
17
|
+
const v = data[key];
|
|
18
|
+
if (v == null || Number.isNaN(Number(v)))
|
|
19
|
+
return undefined;
|
|
20
|
+
return Number(v);
|
|
21
|
+
}
|
|
22
|
+
function str(data, key) {
|
|
23
|
+
const v = data[key];
|
|
24
|
+
if (v == null || String(v) === "")
|
|
25
|
+
return undefined;
|
|
26
|
+
return String(v);
|
|
27
|
+
}
|
|
28
|
+
function tailOrderingErrors(data, n = 4) {
|
|
29
|
+
const raw = data.ordering_errors;
|
|
30
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
return raw.slice(-n).map((x) => Number(x)).filter((x) => !Number.isNaN(x));
|
|
33
|
+
}
|
|
34
|
+
export function snapshotFromJob(data, elapsedSec, note) {
|
|
35
|
+
const status = String(data.status ?? "unknown");
|
|
36
|
+
const progress_pct = (data.progress ?? 0) * 100;
|
|
37
|
+
const snap = {
|
|
38
|
+
elapsed_sec: elapsedSec,
|
|
39
|
+
status,
|
|
40
|
+
progress_pct: Math.round(progress_pct * 10) / 10,
|
|
41
|
+
};
|
|
42
|
+
const phase = str(data, "progress_phase");
|
|
43
|
+
if (phase)
|
|
44
|
+
snap.phase = phase;
|
|
45
|
+
const epoch = num(data, "epoch");
|
|
46
|
+
const total = num(data, "total_epochs");
|
|
47
|
+
if (epoch != null)
|
|
48
|
+
snap.epoch = epoch;
|
|
49
|
+
if (total != null)
|
|
50
|
+
snap.total_epochs = total;
|
|
51
|
+
const qe = num(data, "quantization_error");
|
|
52
|
+
const te = num(data, "topographic_error");
|
|
53
|
+
if (qe != null)
|
|
54
|
+
snap.qe = Math.round(qe * 10_000) / 10_000;
|
|
55
|
+
if (te != null)
|
|
56
|
+
snap.te = Math.round(te * 10_000) / 10_000;
|
|
57
|
+
const eta = num(data, "training_eta_sec");
|
|
58
|
+
if (eta != null && eta > 0)
|
|
59
|
+
snap.eta_sec = Math.round(eta);
|
|
60
|
+
const tail = tailOrderingErrors(data);
|
|
61
|
+
if (tail && tail.length > 0)
|
|
62
|
+
snap.ordering_errors_tail = tail;
|
|
63
|
+
if (note)
|
|
64
|
+
snap.note = note;
|
|
65
|
+
return snap;
|
|
66
|
+
}
|
|
67
|
+
/** True when a new snapshot is worth recording (phase/epoch/progress/status change). */
|
|
68
|
+
export function shouldRecordSnapshot(prev, next) {
|
|
69
|
+
if (!prev)
|
|
70
|
+
return true;
|
|
71
|
+
if (prev.status !== next.status)
|
|
72
|
+
return true;
|
|
73
|
+
if (next.note)
|
|
74
|
+
return true;
|
|
75
|
+
if (prev.phase !== next.phase)
|
|
76
|
+
return true;
|
|
77
|
+
if (prev.epoch !== next.epoch)
|
|
78
|
+
return true;
|
|
79
|
+
if (Math.abs(prev.progress_pct - next.progress_pct) >= 1)
|
|
80
|
+
return true;
|
|
81
|
+
if (prev.qe !== next.qe || prev.te !== next.te)
|
|
82
|
+
return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
export function formatSnapshotLine(s) {
|
|
86
|
+
const parts = [`[+${s.elapsed_sec}s] ${s.status} ${s.progress_pct.toFixed(1)}%`];
|
|
87
|
+
if (s.phase)
|
|
88
|
+
parts.push(`phase ${s.phase}`);
|
|
89
|
+
if (s.epoch != null && s.total_epochs != null)
|
|
90
|
+
parts.push(`epoch ${s.epoch}/${s.total_epochs}`);
|
|
91
|
+
if (s.qe != null)
|
|
92
|
+
parts.push(`QE ${s.qe.toFixed(4)}`);
|
|
93
|
+
if (s.te != null)
|
|
94
|
+
parts.push(`TE ${s.te.toFixed(4)}`);
|
|
95
|
+
if (s.eta_sec != null)
|
|
96
|
+
parts.push(`ETA ~${s.eta_sec}s`);
|
|
97
|
+
if (s.ordering_errors_tail?.length) {
|
|
98
|
+
parts.push(`ordering_errors tail [${s.ordering_errors_tail.map((x) => x.toFixed(4)).join(", ")}]`);
|
|
99
|
+
}
|
|
100
|
+
if (s.note)
|
|
101
|
+
parts.push(s.note);
|
|
102
|
+
return parts.join(" | ");
|
|
103
|
+
}
|
|
104
|
+
export function formatMonitorText(result, opts) {
|
|
105
|
+
const lines = [
|
|
106
|
+
`Job ${result.job_id} monitor (block_until=${opts.block_until_sec}s, poll=${opts.poll_interval_sec}s):`,
|
|
107
|
+
];
|
|
108
|
+
for (const s of result.snapshots)
|
|
109
|
+
lines.push(formatSnapshotLine(s));
|
|
110
|
+
lines.push("");
|
|
111
|
+
if (result.timed_out) {
|
|
112
|
+
lines.push(`Timed out before terminal state. Last status: ${result.status_text}`);
|
|
113
|
+
lines.push(`Re-run jobs(action=monitor, job_id="${result.job_id}") or training_monitor(job_id="${result.job_id}").`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
lines.push(`Terminal: ${result.status_text}`);
|
|
117
|
+
}
|
|
118
|
+
lines.push(result.suggested_next_step);
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
function suggestedNextStep(job_id, data) {
|
|
122
|
+
const status = String(data.status ?? "");
|
|
123
|
+
if (status === "completed") {
|
|
124
|
+
const finalizeId = str(data, "finalize_job_id");
|
|
125
|
+
if (finalizeId) {
|
|
126
|
+
return `Compute done; finalize_training may still be running. Re-run jobs(action=monitor, job_id="${job_id}") with wait_finalize=true, then results(action=get, job_id="${job_id}").`;
|
|
127
|
+
}
|
|
128
|
+
return `Next: results(action=get, job_id="${job_id}") or results_explorer(job_id="${job_id}").`;
|
|
129
|
+
}
|
|
130
|
+
if (status === "failed") {
|
|
131
|
+
const stage = str(data, "failure_stage");
|
|
132
|
+
return `Job failed${stage ? ` at ${stage}` : ""}. Read the error above before retrying.`;
|
|
133
|
+
}
|
|
134
|
+
if (status === "cancelled")
|
|
135
|
+
return `Job cancelled. Confirm with jobs(action=status, job_id="${job_id}").`;
|
|
136
|
+
return `Still running — re-run jobs(action=monitor, job_id="${job_id}") to continue waiting.`;
|
|
137
|
+
}
|
|
138
|
+
async function fetchTrainingLogHint(job_id, data) {
|
|
139
|
+
const status = String(data.status ?? "");
|
|
140
|
+
if (status !== "completed" && status !== "failed")
|
|
141
|
+
return undefined;
|
|
142
|
+
try {
|
|
143
|
+
await apiCall("GET", `/v1/results/${job_id}/training-log`);
|
|
144
|
+
return `Training log available at GET /v1/results/${job_id}/training-log.`;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function monitorJob(job_id, options = {}) {
|
|
151
|
+
const block_until_sec = Math.max(30, options.block_until_sec ?? DEFAULT_BLOCK_UNTIL_SEC);
|
|
152
|
+
const poll_interval_sec = Math.max(MIN_POLL_INTERVAL_SEC, options.poll_interval_sec ?? DEFAULT_POLL_INTERVAL_SEC);
|
|
153
|
+
const wait_finalize = options.wait_finalize !== false;
|
|
154
|
+
const blockMs = block_until_sec * 1000;
|
|
155
|
+
const pollMs = poll_interval_sec * 1000;
|
|
156
|
+
const start = Date.now();
|
|
157
|
+
const snapshots = [];
|
|
158
|
+
let lastSnap = null;
|
|
159
|
+
let data = {};
|
|
160
|
+
let heartbeat = 0;
|
|
161
|
+
while (Date.now() - start < blockMs) {
|
|
162
|
+
data = (await apiCall("GET", `/v1/jobs/${job_id}`));
|
|
163
|
+
const elapsedSec = Math.round((Date.now() - start) / 1000);
|
|
164
|
+
const snap = snapshotFromJob(data, elapsedSec);
|
|
165
|
+
heartbeat += 1;
|
|
166
|
+
const heartbeatDue = heartbeat >= HEARTBEAT_POLLS;
|
|
167
|
+
if (shouldRecordSnapshot(lastSnap, snap) || heartbeatDue) {
|
|
168
|
+
snapshots.push(snap);
|
|
169
|
+
lastSnap = snap;
|
|
170
|
+
heartbeat = 0;
|
|
171
|
+
}
|
|
172
|
+
const status = String(data.status ?? "");
|
|
173
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
await sleep(pollMs);
|
|
177
|
+
}
|
|
178
|
+
const status = String(data.status ?? "");
|
|
179
|
+
const terminal = status === "completed" || status === "failed" || status === "cancelled";
|
|
180
|
+
const timed_out = !terminal;
|
|
181
|
+
if (terminal && status === "completed" && wait_finalize && data.finalize_job_id) {
|
|
182
|
+
const finalizeId = String(data.finalize_job_id);
|
|
183
|
+
const elapsedSec = Math.round((Date.now() - start) / 1000);
|
|
184
|
+
snapshots.push(snapshotFromJob(data, elapsedSec, `finalize_training ${finalizeId} started — waiting for figure render`));
|
|
185
|
+
try {
|
|
186
|
+
const { note } = await pollFinalizeIfPresent(job_id, data, Math.max(0, blockMs - (Date.now() - start)));
|
|
187
|
+
data = await refreshJobAfterFinalize(job_id);
|
|
188
|
+
snapshots.push(snapshotFromJob(data, Math.round((Date.now() - start) / 1000), note ?? `finalize_training ${finalizeId} completed`));
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
snapshots.push(snapshotFromJob(data, Math.round((Date.now() - start) / 1000), `finalize_training failed: ${err.message}`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const statusText = formatJobStatusText(job_id, data);
|
|
195
|
+
const logHint = terminal ? await fetchTrainingLogHint(job_id, data) : undefined;
|
|
196
|
+
let suggested = suggestedNextStep(job_id, data);
|
|
197
|
+
if (logHint)
|
|
198
|
+
suggested += ` ${logHint}`;
|
|
199
|
+
return {
|
|
200
|
+
job_id,
|
|
201
|
+
terminal,
|
|
202
|
+
timed_out,
|
|
203
|
+
snapshots,
|
|
204
|
+
status_text: statusText,
|
|
205
|
+
data,
|
|
206
|
+
suggested_next_step: suggested,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export async function runJobMonitor(args) {
|
|
210
|
+
const block_until_sec = args.block_until_sec ?? DEFAULT_BLOCK_UNTIL_SEC;
|
|
211
|
+
const poll_interval_sec = args.poll_interval_sec ?? DEFAULT_POLL_INTERVAL_SEC;
|
|
212
|
+
const result = await monitorJob(args.job_id, {
|
|
213
|
+
block_until_sec,
|
|
214
|
+
poll_interval_sec,
|
|
215
|
+
wait_finalize: args.wait_finalize,
|
|
216
|
+
});
|
|
217
|
+
const text = formatMonitorText(result, { block_until_sec, poll_interval_sec });
|
|
218
|
+
return textResult({
|
|
219
|
+
...result.data,
|
|
220
|
+
monitor: {
|
|
221
|
+
job_id: result.job_id,
|
|
222
|
+
terminal: result.terminal,
|
|
223
|
+
timed_out: result.timed_out,
|
|
224
|
+
snapshots: result.snapshots,
|
|
225
|
+
status_text: result.status_text,
|
|
226
|
+
suggested_next_step: result.suggested_next_step,
|
|
227
|
+
},
|
|
228
|
+
status_text: text,
|
|
229
|
+
});
|
|
230
|
+
}
|
package/dist/shared.js
CHANGED
|
@@ -26,7 +26,7 @@ export const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
|
26
26
|
* X-Barsom-Client-Version so the server can annotate tool guidance with the
|
|
27
27
|
* wrapper version each action requires. Keep in sync with package.json on bump.
|
|
28
28
|
*/
|
|
29
|
-
export const CLIENT_VERSION = "0.
|
|
29
|
+
export const CLIENT_VERSION = "0.20.0";
|
|
30
30
|
/** User-facing links; keep aligned with barivia.se / api.barivia.se. */
|
|
31
31
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
32
32
|
/** Self-serve account dashboard (manage plan, billing, and API keys). */
|
|
@@ -9,7 +9,7 @@ Configure \`BARIVIA_API_KEY\` and optional \`BARIVIA_API_URL\`, then call **guid
|
|
|
9
9
|
|
|
10
10
|
**Parameter hints:** call \`training_guidance\` (also API-scoped). **Async:** poll \`jobs(action=status)\` every 10–15s after submit.`;
|
|
11
11
|
export function registerGuideBarsomTool(server) {
|
|
12
|
-
registerAuditedTool(server, "guide_barsom_workflow", "Plan-scoped orientation: proxy model, tool categories, async rules, training modes, and step-by-step SOP — loaded from the Barivia API when online. Call at the start of mapping work. Offline: short stub. For field-level parameters, use training_guidance; for a narrative pre-train checklist, use the prepare_training prompt
|
|
12
|
+
registerAuditedTool(server, "guide_barsom_workflow", "Plan-scoped orientation: proxy model, tool categories, async rules, training modes, and step-by-step SOP — loaded from the Barivia API when online. Call at the start of mapping work. Offline: short stub. For field-level parameters, use training_guidance; for a narrative pre-train checklist, use the prepare_training prompt.", {}, async () => {
|
|
13
13
|
const md = await fetchWorkflowGuideFromApi();
|
|
14
14
|
if (md) {
|
|
15
15
|
const text = "Plan-scoped workflow (from Barivia API). Text below reflects your API key / plan.\n\n" + md;
|
package/dist/tools/jobs.js
CHANGED
|
@@ -3,11 +3,13 @@ import { registerAuditedTool } from "../audit.js";
|
|
|
3
3
|
import { apiCall, textResult } from "../shared.js";
|
|
4
4
|
import { formatJobCancelText, formatJobStatusText } from "../job_status_format.js";
|
|
5
5
|
import { pollFinalizeIfPresent, refreshJobAfterFinalize } from "../train_finalize.js";
|
|
6
|
+
import { DEFAULT_BLOCK_UNTIL_SEC, DEFAULT_POLL_INTERVAL_SEC, runJobMonitor } from "../job_monitor.js";
|
|
6
7
|
export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle only).
|
|
7
8
|
|
|
8
9
|
| Action | Use when |
|
|
9
10
|
|--------|----------|
|
|
10
|
-
|
|
|
11
|
+
| monitor | After async submit — blocks server-side with 5s snapshots until terminal (preferred for agents) |
|
|
12
|
+
| status | One-shot progress check (manual poll every 10–15s if not using monitor) |
|
|
11
13
|
| list | Finding job IDs / reviewing pipeline state (optionally filter by dataset_id) |
|
|
12
14
|
| compare | Picking the best TRAINING run from a set (metrics table). NOT inference(compare), which diffs one dataset's distribution against the training set. |
|
|
13
15
|
| cancel | Stopping a running or pending job to free the worker |
|
|
@@ -15,8 +17,13 @@ export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle only).
|
|
|
15
17
|
|
|
16
18
|
To SUBMIT training, use the **train** tool (train(action=map | siom_map | impute | floop_siom)); to score data or run post-training map operations (predict, batch_predict, compare, project_columns, impute_column, transition_flow), use **inference**.
|
|
17
19
|
|
|
20
|
+
ASYNC POLLING PROTOCOL (action=monitor):
|
|
21
|
+
- Preferred after train/inference submit: one call blocks until completed/failed or block_until timeout (default ${DEFAULT_BLOCK_UNTIL_SEC}s, poll every ${DEFAULT_POLL_INTERVAL_SEC}s).
|
|
22
|
+
- Snapshots include phase, epoch/total, QE/TE, ETA, ordering_errors tail when live; waits for finalize_training by default.
|
|
23
|
+
- Optional UI: training_monitor(job_id) for embedded charts; jobs(action=monitor) is the headless equivalent.
|
|
24
|
+
|
|
18
25
|
ASYNC POLLING PROTOCOL (action=status):
|
|
19
|
-
-
|
|
26
|
+
- One-shot check only. If not using monitor, poll every 10-15 seconds manually.
|
|
20
27
|
- Progress increases through phases (ordering then convergence for map training); other job types differ.
|
|
21
28
|
- For large grids (40×40+), do not assume failure before 3 minutes on CPU.
|
|
22
29
|
- Map training typical times: 10×10 ~30s | 20×20 ~3–5 min | 40×40 ~15–30 min.
|
|
@@ -137,12 +144,28 @@ export function renderCompareTable(comparisons) {
|
|
|
137
144
|
export function registerJobsTool(server, description) {
|
|
138
145
|
registerAuditedTool(server, "jobs", description, {
|
|
139
146
|
action: z
|
|
140
|
-
.enum(["status", "list", "compare", "cancel", "delete"])
|
|
141
|
-
.describe("status: check
|
|
147
|
+
.enum(["status", "monitor", "list", "compare", "cancel", "delete"])
|
|
148
|
+
.describe("status: one-shot check; monitor: block until terminal with snapshots; list/compare/cancel/delete as labeled. (Training via train; scoring via inference.)"),
|
|
142
149
|
job_id: z
|
|
143
150
|
.string()
|
|
144
151
|
.optional()
|
|
145
|
-
.describe("Job ID — required for action=status, cancel, delete."),
|
|
152
|
+
.describe("Job ID — required for action=status, monitor, cancel, delete."),
|
|
153
|
+
block_until_sec: z
|
|
154
|
+
.number()
|
|
155
|
+
.int()
|
|
156
|
+
.min(30)
|
|
157
|
+
.optional()
|
|
158
|
+
.describe(`action=monitor only: max wait seconds (default ${DEFAULT_BLOCK_UNTIL_SEC})`),
|
|
159
|
+
poll_interval_sec: z
|
|
160
|
+
.number()
|
|
161
|
+
.int()
|
|
162
|
+
.min(5)
|
|
163
|
+
.optional()
|
|
164
|
+
.describe(`action=monitor only: poll interval seconds (default ${DEFAULT_POLL_INTERVAL_SEC})`),
|
|
165
|
+
wait_finalize: z
|
|
166
|
+
.boolean()
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("action=monitor only: wait for finalize_training (default true)"),
|
|
146
169
|
job_ids: z
|
|
147
170
|
.array(z.string())
|
|
148
171
|
.optional()
|
|
@@ -152,8 +175,13 @@ export function registerJobsTool(server, description) {
|
|
|
152
175
|
.optional()
|
|
153
176
|
.describe("action=list: filter jobs by this dataset ID."),
|
|
154
177
|
}, async (args) => {
|
|
155
|
-
const { action, job_id, job_ids, dataset_id } = args;
|
|
178
|
+
const { action, job_id, job_ids, dataset_id, block_until_sec, poll_interval_sec, wait_finalize } = args;
|
|
156
179
|
// ---- Lifecycle ----
|
|
180
|
+
if (action === "monitor") {
|
|
181
|
+
if (!job_id)
|
|
182
|
+
throw new Error("jobs(monitor) requires job_id");
|
|
183
|
+
return runJobMonitor({ job_id, block_until_sec, poll_interval_sec, wait_finalize });
|
|
184
|
+
}
|
|
157
185
|
if (action === "status") {
|
|
158
186
|
if (!job_id)
|
|
159
187
|
throw new Error("jobs(status) requires job_id");
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared training surface for the barsom proxy: the Zod input schema for training
|
|
3
3
|
* parameters, the param-building helper, and preset fetch. Declared once here so
|
|
4
|
-
* the `train` tool
|
|
5
|
-
* all reuse the same definitions (no drift).
|
|
4
|
+
* the `train` tool reuses a single set of definitions (no drift).
|
|
6
5
|
*/
|
|
7
6
|
import { z } from "zod";
|
|
8
7
|
import { apiCall } from "../shared.js";
|
|
@@ -12,7 +11,7 @@ export async function fetchTrainingPresets() {
|
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
14
13
|
* Fetch presets + the plan-scoped allowed_job_types in one /v1/training/config call.
|
|
15
|
-
* Used
|
|
14
|
+
* Used to scope train-action selection to entitled modes.
|
|
16
15
|
*/
|
|
17
16
|
export async function fetchTrainingConfig() {
|
|
18
17
|
const configData = (await apiCall("GET", "/v1/training/config"));
|
|
@@ -129,8 +128,8 @@ export const TRAINING_INPUT_SCHEMA = {
|
|
|
129
128
|
anchor_percentile: z.preprocess((v) => (v !== undefined && v !== null && typeof v === "string") ? parseFloat(v) : v, z.number().optional()),
|
|
130
129
|
};
|
|
131
130
|
/**
|
|
132
|
-
* Build the FLooP-SIOM job params + summary from raw args.
|
|
133
|
-
* the `train` tool
|
|
131
|
+
* Build the FLooP-SIOM job params + summary from raw args. Single source used by
|
|
132
|
+
* the `train` tool so the growing-manifold knobs never drift.
|
|
134
133
|
* (FLooP has no fixed grid/epochs; it uses topology/max_nodes/effort + growth knobs.)
|
|
135
134
|
*/
|
|
136
135
|
export function buildFloopParams(args) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerAuditedTool } from "../audit.js";
|
|
2
2
|
import { fetchTrainingGuidanceFromApi } from "../shared.js";
|
|
3
3
|
export function registerTrainingGuidanceTool(server) {
|
|
4
|
-
registerAuditedTool(server, "training_guidance", "Structured parameter guidance for training (presets, grid/epochs/batch, model, periodic, SIOM/FLooP fields where your plan allows, categorical baselines, normalization MAD vs SEPD guide). Served from the API and filtered to allowed_job_types. Prep ladder: prepare_training prompt = narrative checklist; this tool = JSON/hints + normalization decision ladder
|
|
4
|
+
registerAuditedTool(server, "training_guidance", "Structured parameter guidance for training (presets, grid/epochs/batch, model, periodic, SIOM/FLooP fields where your plan allows, categorical baselines, normalization MAD vs SEPD guide). Served from the API and filtered to allowed_job_types. Prep ladder: prepare_training prompt = narrative checklist; this tool = JSON/hints + normalization decision ladder — review, then submit with train. For full orientation and SOP, call guide_barsom_workflow first. Optional UI: results_explorer—not required; jobs + results suffice.", {}, async () => {
|
|
5
5
|
const text = await fetchTrainingGuidanceFromApi();
|
|
6
6
|
return { content: [{ type: "text", text }] };
|
|
7
7
|
});
|