From 3ffcd1a496562bd44203be079a99c9b165fbd878 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 23:21:05 +0000 Subject: [PATCH 1/7] docs(agents): plan phase 2 of the agent network for all 15 libraries The plan at agentic/specs/261024-agent-all-libraries.md takes "Use with my data" from matplotlib and seaborn to the other 13 catalogue libraries: a runtime registry, one fixed render command per language in the renderer, a harness exec mode for Node, Rscript and julia, the toolchains in the renderer image and a deterministic code profile per language, in five PRs. It records the first spike measurements (the JS harness, the Python extras under the probe harness, plotly and bokeh) and the owner questions with their defaults. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01WbjVqBweiXjUZKp626ZFWm --- agentic/specs/261024-agent-all-libraries.md | 369 ++++++++++++++++++++ changelog.d/agent-all-libraries.md | 12 + 2 files changed, 381 insertions(+) create mode 100644 agentic/specs/261024-agent-all-libraries.md create mode 100644 changelog.d/agent-all-libraries.md diff --git a/agentic/specs/261024-agent-all-libraries.md b/agentic/specs/261024-agent-all-libraries.md new file mode 100644 index 00000000000..316d95a4039 --- /dev/null +++ b/agentic/specs/261024-agent-all-libraries.md @@ -0,0 +1,369 @@ +# Feature: "Use with my data" for all 15 catalogue libraries + +## Metadata + +run_id: `session_01WbjVqBweiXjUZKp626ZFWm` +prompt: `Rebuild the AI chat mode so that generating works not only for matplotlib and seaborn but for all 15 libraries; the biggest part is that the Cloud Run renderer can also render JavaScript, R and Julia. Plan it, start implementing, get second opinions, test in local Docker where the cloud session has no gcloud access, and ask when something is not canonical.` + +## Feature Description + +The agent network ("Use with my data", `agents/`) adapts a catalogue implementation +to a user's table, renders it in a Cloud Run sandbox, reviews it and ships the plot. +Phase 1 serves matplotlib and seaborn. This plan takes the network to the other 13 +libraries of the catalogue: six more Python libraries (plotnine, altair, pygal, +lets-plot without a browser; plotly and bokeh with Chromium), five JavaScript +libraries through the browser harness (Chart.js, D3, ECharts, Highcharts, MUI X), R +(ggplot2) and Julia (Makie). + +The design already fixes the shape ([Agent network design](../../docs/concepts/agent-network.md), +"Render", "Phase 2"): later languages add `RuntimeAdapter`s, not backends; each runtime +reuses the catalogue's CI render command; the renderer image carries the toolchains; a +library is enabled through `AGENT_LIBRARIES` once its eval matrix reaches 80 %. Where +the design leaves room, this plan names the choice and marks it as an owner question +(see "Open questions"). + +What the code does today, and what has to change: + +| Layer | Today | Change | +|---|---|---| +| `agents/anyplot/render/runtimes/` | one class, `PythonRuntime`, built in three places (`pipeline.py`, `theme_render.py`, `render/__init__.py`) | a registry `runtime_for(library)`; `RUNTIME_LIBRARIES` replaces `normalise.SUPPORTED_LIBRARIES` in the settings validator | +| `agents/renderer/wire.py`, `executor.py` | `language: Literal["python"]`; `build_argv` hard-codes `python -I harness.py plot.py` | a fixed command per language in one shared table (`agents/renderer/commands.py`), chosen by the renderer from `(language, library)`, never taken from the request; the harness gets an exec mode that sets the limits and supervises the language's own command | +| `agents/renderer/Dockerfile` | Python 3.14 venv with matplotlib and seaborn, Noto fonts | all eight Python extras, Chromium (one copy, shared by Playwright, kaleido and bokeh), Node 22 with the pinned `node_modules`, R 4.6 with ggplot2 4 and ragg, Julia 1.13 with the precompiled Makie depot | +| `agents/anyplot/code/` | Python AST everywhere: normaliser, regions, readiness, loader, validator, edits' drift check | the Python modules stay for the eight Python libraries (import surfaces per library, a save-target rule per library); JavaScript, R and Julia get a text-and-tree-sitter code profile each: markers, protected regions, loader, deny-list validator | +| `agents/anyplot/policy.py`, `prompts/adapter.md` | pandas wording (`df`, `pd.read_csv`) | a runtime section per language (how the data arrives, the placeholder spelling); the library rules already come from `prompts/library/.md` | +| `agents/evals/make_fixtures.py` | 10 specs x 2 libraries x 6 perturbations | the same 10 specs for every library; the 80 % gate per library is the owner's Vertex run | +| `.github/workflows/ci-image.yml`, `agents/renderer/cloudbuild.yaml` | one 20-minute image build | a toolchain base image built rarely (owner question 1) and a fast renderer image on top | + +## Requirements + +- Every catalogue pair (spec, library) whose code follows the catalogue conventions is + eligible, and the `.adapt()` button shows for it, once its library is in + `AGENT_LIBRARIES`. +- The renderer runs the catalogue's CI render command for each language verbatim, in the + same sandbox (no egress, read-only root, the harness's resource limits, the host + watchdog), with the same `job_id` idempotency and cancel path. +- The host gates R1 (outputs), R2 (PNG hardening) and R3 (canvas) apply to every + language. The probe gates (G3 to G12) stay matplotlib's: plotnine inherits them + through matplotlib; every other runtime renders without a probe, and `evaluate` + reports no probe line for it. +- The validator stays one guardrail layer among three (sandbox, loader substitution, + validator). For Python the AST validator gains the import surface of each library. For + JavaScript, R and Julia a deny-list over the parse tree refuses network, process, + filesystem and reflection routes before the code runs. +- The exported code runs unchanged next to `data.csv` in the language's own tool + (`python plot.py`, `Rscript plot.R`, `julia plot.jl`, `node render.mjs plot.js` with + the harness, which the export names in its header). +- `AGENT_LIBRARIES` keeps its default `matplotlib,seaborn`; each further library is + switched on in the deploy after its eval matrix reaches 80 % (design rule, owner run). +- No change to the catalogue pipeline, the muix regeneration or `plots/`. + +## Relevant Files + +### Existing Files to Modify + +- `agents/anyplot/render/runtimes/__init__.py`: the registry (`runtime_for`, + `RUNTIME_LIBRARIES`, `LANGUAGE_OF`). +- `agents/anyplot/render/runtimes/python.py`: library-aware (`library` on the instance; + per-library env such as `BROWSER_PATH`, `BOKEH_RESOURCES=inline`, `SE_OFFLINE=true`; + the validator and normaliser called with the library). +- `agents/anyplot/render/contract.py`: `RuntimeAdapter` gains `regions`, `readiness`, + `validate_adaptation`, `protected_drift`, `placeholder` and `export_run_line`, so the + pipeline stops importing the Python code modules directly. +- `agents/anyplot/render/__init__.py`, `agents/anyplot/pipeline.py`, + `agents/anyplot/theme_render.py`, `agents/anyplot/opening.py`, + `agents/anyplot/render/backends/local.py`, `agents/anyplot/render/backends/sandbox.py`: + use the registry instead of `PythonRuntime()`. +- `agents/anyplot/settings.py`: `_known_libraries` checks the registry; the docstring + table. +- `agents/anyplot/code/validate.py`: `LIBRARY_IMPORTS` for plotnine (`plotnine`, + `mizani`), altair (`altair`, `vl_convert`), pygal (`pygal`, `cairosvg`), lets-plot + (`lets_plot`), plotly (`plotly`, `kaleido`), bokeh (`bokeh`); the walk of their public + submodules for `UNSAFE_SUBMODULES` and `UNSAFE_HELPERS`, pinned by the existing test. +- `agents/anyplot/code/regions.py`, `normalise.py`, `readiness.py`: the save statement + per library (`write_image`, `chart.save`, `render_to_png`, `ggsave`, `plot.save`) next + to `savefig`; the header and title rewrite unchanged; the canvas rewrite stays + matplotlib-only (plotnine through `ggsave(..., dpi=, width=, height=)` later). +- `agents/anyplot/render/harness.py`: exec mode (`--exec`): start line, limits, run the + language's command as a child, end line with the child's `rusage`; plot mode: the write + allowlist gains `plot-.html`; the spawn allowlist for one shipped Chromium path + (plotly's kaleido) and the bokeh screenshot path (owner question 9). +- `agents/anyplot/render/gates.py`: `CODE_FILE` per language for the error line; + probe gates skipped without a probe. +- `agents/anyplot/code/export.py`: the run line per language. +- `agents/anyplot/policy.py`, `agents/anyplot/prompts/adapter.md`: the runtime section. +- `agents/renderer/wire.py`: `language: Literal["python", "javascript", "r", "julia"]`. +- `agents/renderer/executor.py`, `settings.py`: argv and env from the commands table; + per-language `RLIMIT_AS` (off for Chromium, Node and Julia, which reserve address + space far beyond their resident memory); `PATH` with the toolchain directories. +- `agents/renderer/Dockerfile`, `.dockerignore`, `agents/renderer/cloudbuild.yaml`, + `.github/workflows/ci-image.yml`: the toolchains, the context allowlist + (`package.json`, `package-lock.json`, `Project.toml`, `Manifest.toml`, + `automation/js-render/render.mjs`), the smoke renders per language. +- `automation/js-render/render.mjs`: sandbox mode (`ANYPLOT_DATA_CSV` or a `data.csv` + next to the snippet becomes `window.ANYPLOT_DATA`; `page.route` deny-all; no + `plot-.html` outside the catalogue pipeline unless asked; `NODE_MODULES` from + `ANYPLOT_NODE_MODULES`; the Chromium path from `PLAYWRIGHT_BROWSERS_PATH`). The CI + pipeline's behaviour stays byte-identical without those variables. +- `agents/evals/make_fixtures.py`, `agents/evals/eligibility.py`, `agents/evals/cases.py`: + every library. +- `app/src/pages/AgentChatPage.tsx`, `app/src/sections/agent-chat/*`: syntax + highlighting and the download name per language (`plot.R`, `plot.jl`, `plot.js`, + `plot.tsx`); the library pills already carry every library. +- `docs/concepts/agent-network.md` ("Render", "Adapt and validate", Phase 2 status), + `agents/README.md` (settings, image, "Run it locally"), `agentic/docs/project-guide.md` + (ci-image jobs), `changelog.d/.md` per PR. + +### New Files to Create + +- `agents/renderer/commands.py`: `LANGUAGE_COMMANDS`, the one table of code file name, + argv and env per language, read by the executor, the local Docker backend and the + runtimes. No ADK import. +- `agents/anyplot/render/runtimes/javascript.py`, `r.py`, `julia.py`: the three + `RuntimeAdapter`s. +- `agents/anyplot/code/profiles/javascript.py`, `r.py`, `julia.py`: markers, protected + regions (theme switch, theme tokens, the save statement, the placeholder), the loader + statement, the normaliser (header strip, save target), the readiness scan + (convention markers, security) and the deny-list validator per language. +- `agents/anyplot/code/treesitter.py`: the parser wrapper (`tree-sitter`, + `tree-sitter-javascript`, `tree-sitter-typescript`, `tree-sitter-r`, + `tree-sitter-julia`, all pinned in the `agents` extra), with the query helpers the + three deny lists share. +- `agents/renderer/toolchain.Dockerfile` (owner question 1): Ubuntu 24.04 with R from + rocker's layout and Posit binaries, Python 3.14 managed by uv, Node 22, Playwright + Chromium, Julia 1.13 and the precompiled depot; published to GHCR by a workflow that + runs on changes to the lock files. +- `agents/evals/fixtures/cases/--/` for the 13 libraries + (generated). +- `tests/unit/agents/code/test_profiles_*.py`, `tests/unit/agents/renderer/test_commands.py`, + `tests/unit/agents/runtime/test_runtimes.py`; renderer-image smoke scripts under + `tests/image/` per language. + +## Implementation Plan + +IMPORTANT: Execute every step in order, top to bottom. Each numbered step is one PR. +Steps 2 to 5 each end with the library's fixtures generated and the matrix run +documented as an owner task; a library is enabled in the deploy only after that run. + +### 1. Foundation: the runtime registry and the four no-browser Python libraries + +- Add `agents/renderer/commands.py` with `LANGUAGE_COMMANDS` (`python`: the current + command; `javascript`, `r`, `julia`: the CI commands through the harness exec mode) + and `code_file(language)`. The executor builds its argv from it; the assertion that no + `--write` or `--allow-egress` reaches a sandbox stays. +- Harness exec mode: `harness.py --exec -- ` prints the start line, sets + `RLIMIT_CPU`, `RLIMIT_FSIZE` and the optional `RLIMIT_AS` and `RLIMIT_NPROC` as today, + runs the command with `subprocess.run` (the limits are inherited), prints the end line + from `resource.getrusage(RUSAGE_CHILDREN)` and exits with the child's code. Python + plots keep the in-process plot mode and the probe. +- Registry: `runtimes/__init__.py` maps every library of `core.constants` to a runtime + instance; `runtime_for(library, settings)`; `RUNTIME_LIBRARIES` is its key set. The + settings validator refuses a library without a runtime, as today. +- Route the pipeline's direct uses of `find_regions`, `scan`, `validate_adaptation`, + `protected_drift`, the placeholder constant and `export_code` through the runtime, so a + later language only adds a profile. +- `PythonRuntime(library=...)`: the validator's import surface per library; the regions + and readiness know each library's save statement; the normaliser's header and title + rewrites run for all eight, its canvas rewrite only for matplotlib and seaborn. +- Image: `uv sync` with `lib-plotnine`, `lib-altair`, `lib-pygal` and `lib-letsplot` + too; the CI job smoke-renders one catalogue file per new library through the harness. +- `wire.RenderRequest.language` widened; `gates.evaluate` without a probe; the + renderer refuses a language whose toolchain the image does not carry + (`503 runtime_unavailable`, from a startup check of the commands table). +- Fixtures: `make_fixtures.py` for plotnine, altair, pygal and lets-plot; the + eligibility sweep over all eight Python libraries. +- Tests, docs and the changelog fragment; the deploy's `_LIBRARIES` unchanged. + +### 2. JavaScript: Chart.js, D3, ECharts, Highcharts and MUI X + +- Toolchain: Node 22 under `/opt/node`, the repository's `package-lock.json` installed + to `/opt/anyplot/node_modules`, Playwright's Chromium under `/opt/pw-browsers` + (`PLAYWRIGHT_BROWSERS_PATH`), the harness at `/opt/anyplot/js-render/render.mjs`. +- `render.mjs` sandbox mode, every change behind an environment variable so the CI + pipeline sees no difference: `ANYPLOT_DATA_CSV` (path) parsed to typed records as + `window.ANYPLOT_DATA` (numbers, ISO dates, `null` for a missing cell, the dtype map + from the profile passed as `ANYPLOT_DATA_TYPES`); `page.route("**/*", deny)` so the + page can load nothing but the inlined bundle; `ANYPLOT_NO_HTML=1` leaves the + interactive page out; `ANYPLOT_NODE_MODULES`; a hard cap on the snippet's console + output. Chromium runs with `--no-sandbox` already (gVisor as uid 0). +- `RLIMIT_AS` off for `javascript` in the commands table (V8 and Chromium reserve + virtual memory far beyond use; the `MemAvailable` watchdog stays the control). +- Profile: placeholder `const data = loadUserData();`, loader + `const data = window.ANYPLOT_DATA;`; protected regions: the token lines that read + `window.ANYPLOT_TOKENS`/`window.ANYPLOT_THEME`, the `//# anyplot-orientation` + directive; the deny list over tree-sitter (`fetch`, `XMLHttpRequest`, `WebSocket`, + `navigator.*`, `import()`/`require`, `eval`, `Function`, `document.cookie`, + `localStorage`, `postMessage`, `