Notebooks
Markdown with Python cells, run over workspace data on a cloud kernel of the person running them.
Contents
Tools
A notebook is one markdown document: prose and python cells, which read workspace data through the vistazo library (await vistazo.widget(...), vistazo.dataset, vistazo.call). run_notebook runs every cell on a cloud kernel as the person the agent acts for, behind the same review gate as a person’s Run, and bills compute per second of execution.
| Tool | What it does | |
|---|---|---|
list_notebooks |
List notebooks | Read-only |
get_notebook |
Get notebook | Read-only |
run_notebook |
Run notebook | Destructive write |
save_notebook |
Save notebook | Destructive write |
list_notebooks
Read-only
List the notebooks you may open in a workspace: analyses written as markdown with fenced Python cells that run over this workspace's data. Each row carries id (the handle — names are labels and may repeat), name, a one-line description, updatedAt/updatedBy, your role on it (owner | editor | viewer — only an editor may change it or run it here; a viewer may try its own parameter values on the page), parameters (the keys it defines), sourceUpdatedBy (an agent:<uid> value means an agent wrote the cells and a person has not vouched for them yet), lastRun — the last execution's status, when, who ran it and how many cells — and refreshCadence (the schedule, or null; change it with save_notebook's cadence). Reach for this before writing a new notebook: an existing analysis of the same question is usually worth extending. Descriptions and run summaries only; read the cells with get_notebook. Archived notebooks are hidden unless includeArchived:true.
get_notebook
Read-only
One notebook in full: its markdown source (every cell verbatim — what save_notebook's source replaces and its ops address by index), cells (each index's kind), parameters and the last run's per-cell outputs, each with the params it ran with (paramsStale once a default moved). Read it BEFORE any edit: updatedAt is every write's expectedUpdatedAt, and ops indices come from this parse. Outputs are trimmed (marked truncated); figures and large tables read as [image attachment <id>]. A failed cell carries lead, the line the page shows; a run that failed outside any cell carries error.message. lastRunDetail.stale: true: the cells changed since that run, so indices may not match. A cell that run skipped shows its newest earlier output (fromEarlierRun). format:'ipynb' returns a Jupyter document that save_notebook's ipynb reads back. versions lists the revs manage_version restore (kind 'notebook') takes; url, publicLink and embed as on get_dashboard. Requires view access.
run_notebook
Destructive write · Reaches outside Vistazo
Run a notebook on Vistazo's servers, as the person you act for: it restarts that person's kernel for the notebook and runs every Python and data cell top to bottom with the parameter defaults, so the outputs always match the code, then returns each cell's status, ms and output, trimmed the way get_notebook trims them. It stops at the first cell that raises, and the cells after it come back skipped. The outputs publish to everyone who can view the notebook, with the runner named, exactly as a person's run does, and get_notebook reads them afterwards. Compute is billed per second of execution against the person's compute allowance, the same price as a run from the app, and usage_limit means it is spent. too_many_kernels means the person already holds the most notebook kernels one person may; the message names each notebook holding one and when it frees itself. No tool stops a kernel, so tell the person which to close rather than retrying. The whole run must finish inside 9 minutes. Requires edit access on the notebook.
save_notebook
Destructive write
Create or update a notebook — the ONE write path for its text, and the tool for a regression, t-test or clustering rather than a chart. Omit notebookId to create (duplicateOf copies a notebook); pass it to replace the whole source, or send ops, ipynb or cadence instead (rules on each field). A notebook is MARKDOWN with two kinds of fenced cell and nothing else; every other paragraph is prose:
sales = await vistazo.widget('w_8f2…')
smf.ols('margin ~ units', data=sales).fit().summary2().tables[1]
CELL RULES (the runtime's). Data arrives only through vistazo, each call awaited and returning a pandas DataFrame: vistazo.widget('<widgetId>') (a widget's rows), vistazo.dataset('<file name or id>') (an uploaded table), vistazo.call('<mcpId>', '<toolName>', {…}) (one read-only call on a connected source), vistazo.tools() (the connections and read-only tools); vistazo.param('<key>') (NOT awaited) reads a set_parameter value, as {{ params.<key> }} does in data cell args or prose. NO files and NO network: pd.read_csv, open() and requests fail. Packages are FIXED: pandas 3.x (not 2.x), numpy, scipy, statsmodels, scikit-learn, matplotlib; no pip or micropip. A cell shows its last line, as in Jupyter: a DataFrame or list of dicts (or result) is a table, an open matplotlib figure a PNG. ONE output per cell: a chart before a closing table is dropped; give each its own cell. Prints before a table or figure are kept as stdout. Top-level await works. A vistazo-data fenced block is a DATA cell: one call as JSON, edited as a form (shape: ops).
A <!-- cell --> line between two prose blocks makes them two markdown cells; without it they are one.
run_notebook runs the cells on a cloud kernel; never report an output you have not read back. Caps: 100 000 characters, 50 cells, 50 vistazo calls per kernel session every ten minutes. expectedUpdatedAt (from get_notebook) refuses a concurrent edit. Creating needs full workspace membership; updating, edit access on the notebook.
cadence
The schedule: enabled plus an interval, every N minutes | hours | days | weeks (minimum 15 minutes). refresh runs once, NOW; this keeps data current. MATCH THE INTERVAL TO HOW OFTEN THE SOURCE PUBLISHES. Annual/quarterly sources: weeks (daily is 365 identical fetches a year). Daily sources (exchange rates, weather, filings): a day, or hours for same-day movement. Minutes only for a live feed. A DASHBOARD schedule is free: saving attributes it to YOU — runs execute on the dashboard owner's data-source connections, and a chart one repairs with AI (at most once a day per chart) draws your usage allowance — and re-saving un-pauses one paused for credential failures or failing sources. A SCHEDULE KEEPS THE FETCH CURRENT, NOT THE WINDOW: a widget argumentTemplate with a literal end bound (end_year 2020), or a notebook data cell / vistazo.call with a literal date, re-fetches the same window every run — check get_widget before promising it stays current. A NOTEBOOK schedule is BILLED: each run restarts the notebook and runs every cell as YOU, on your data access and compute allowance; it needs edit access, runs whatever the notebook holds at run time, and a failed run is reported to you and the owner. A seat holds at most 25 scheduled dashboards and 25 notebooks per workspace. enabled:false stops scheduling, keeping the configuration. An interval repeats from its first run (the next 15-minute sweep after enabling); the reply's note names that weekday and UTC time. Fixed weekdays/times (calendar schedules) are set in the web app's Schedule card; toggling enabled without an interval preserves one, and passing interval/unit replaces it. The reply reports the schedule AS IT NOW STANDS — nextRunAt (null when it will not fire; with a nextRunNote the run is due on the next sweep and nextRunAt is the latest it comes), lastRunAt, and pausedReason with how to resume. get_dashboard and get_notebook return the same block.
ops
Edits the notebook CELL BY CELL instead of resending the whole source, applied IN ORDER: { op: 'set', index, cell } replaces one cell, { op: 'insert', index, cell } puts one before that index (index equal to the cell count appends), { op: 'delete', index } removes one. A cell is { kind: 'markdown', text }, { kind: 'python', code } or { kind: 'data', code }. A data cell's code is a JSON object naming one call, run as <output> = await vistazo.call(...) (or .dataset) so later Python cells read output: {"source": "tool", "mcpId": "economy", "toolName": "series_observations", "args": {"series_id": "UNRATE"}, "output": "unrate"} or {"source": "dataset", "name": "orders.csv", "output": "orders"} (name and/or datasetId). In source it is a vistazo-data fenced block. One that would not run is refused; use one where a person will edit the call. Indices are the ones get_notebook returned, and later ops see the list the earlier ones produced — so expectedUpdatedAt is REQUIRED with ops (not with source): an index against text that moved edits the wrong cell, which is the failure this guards. Get the notebook, then patch. While people have it open your ops merge into what they are typing: edits to other cells land, and so do edits to other text in a code or data cell someone is typing in; only an edit overlapping theirs (or a prose cell they changed) is refused, naming who is editing. After any refusal, get_notebook again and send ops against the fresh indices, never the same ones. The caps and cell rules are source's.
ipynb
A Jupyter .ipynb document to turn into the notebook's source, as the file's text or as the parsed object; omit notebookId to create, pass it to overwrite that notebook's source. Markdown cells stay markdown and code cells become python cells, with three deliberate changes the reply lists as notes: stored outputs are DROPPED (they carry no provenance here and a run re-produces them), magics and shell lines (%matplotlib, !pip install) are COMMENTED OUT (there is no shell and no installer), and every cell that reads a file or the network is FLAGGED in a leading note rather than rewritten — only a person knows which widget, dataset or connected tool holds those rows, and the note says to read them with await vistazo.dataset(...) or await vistazo.call(...) instead; every cell importing a package the runtime does not ship is flagged the same way. Cell numbers in the notes are the 0-based indices get_notebook and ops use. Up to 1.5 MB of .ipynb on the way in, then source's caps (100 000 characters, 50 cells) on what the conversion produced. Read the imported result with get_notebook and fix the flagged cells before telling anyone it runs — then run_notebook.