Skip to content

Python in the browser

For everyone

What the in-browser Python sandbox does and its limits; the last section is for operators.

Cerea runs Python in your browser, not on the server, using Pyodide (CPython compiled to WebAssembly) inside a Web Worker. You ask; the assistant writes the code; the code runs on your device and its output appears under it. A Python block in an answer runs by itself when the answer finishes, and on deployments that allow it the assistant can also run code while it answers and use the result. Nothing runs on the deployment, and the files the code opens never leave your browser.

What you can do

  • Ask for calculations and analysis. The assistant's code can use numpy, pandas and the rest of Pyodide's scientific set, plus the office libraries (python-docx, openpyxl, pypdf, python-pptx, XlsxWriter, reportlab, seaborn).
  • Ask about your files. Attach a file, or use a knowledge base, and the code can read it (it sees it under /mnt/data/<filename>). A knowledge-base document arrives as its indexed text, not the original file; an attachment arrives as the original, see Files you attach.
  • Ask for a file back. "Give me this as an Excel sheet", "make a Word report": the file appears under the answer with Download and, where the type allows, a preview (text, images, PDF, Word). When a file is what you asked for, the file is the answer and the code folds away behind a disclosure. Only files the code saves to the output folder (/mnt/output) are offered; scratch files it writes anywhere else (the working directory, /tmp) stay in the sandbox and never become cards. A run lists only the files it created or changed. Every listed file is kept with the chat for 30 days (or what Administration → Storage says) and shows up as an artifact.
  • Ask for charts. See Charts below.

Files you attach

The first time code runs in a chat chat, the files attached to its messages are put in the sandbox, and any attached later are added before the next run. Each one is at /mnt/data/<its original name> with its original bytes, so pd.read_excel("/mnt/data/sales.xlsx") works even when the assistant could not read the file's text. When the app extracted text from it (a PDF, a Word file), that text is beside it as <name>.md. They show as chips under the chat, like knowledge files.

  • When the assistant can run code, the text it sees for an attachment starts with one line saying where the file is: "The file is available to code at /mnt/data/<name> (its latest version, if it has been edited)." Without the code tool the line is left out.
  • Files made in the chat are there too. What the document tools saved (a Word file it made, a filled PDF) and what earlier runs saved to /mnt/output are mounted at /mnt/data/<name>, the latest version of each name, and a version saved between two runs replaces the older one before the next run. A file edited since it was attached shows the edit at its name; earlier versions are not mounted (the document tools take a version for them). Images the code drew (figure-1.png) or the tools saved are not mounted unless the code names them; every run waits at most five seconds for the list of files, and a download that stalls is skipped with a note and tried again on the next run.
  • The chat page registers its chat while it is created, not in an effect after it: a code card that is already on screen starts its run before the page's own effects, and the first run used to find /mnt/data empty.
  • Same name twice: attaching the same file again (same content) does not add a copy; it stays report.pdf. A different file with the same name (two report.pdf with different content) gets the next free name, report (2).pdf, and the assistant is told that name.
  • After a restart the files are back: when a run is stopped by the time limit (or the sandbox crashes), the restarted sandbox gets every mounted file again, under the same names, before the next run starts. The assistant is told so, with the list of files. Variables and files the code wrote itself are lost with the restart.
  • Size caps: a file over 20 MB is not mounted, and once 100 MB is mounted the rest are not. A chip says which file was left out and why, and the assistant is not told a path for it.
  • Not mounted: page images of scanned PDFs, pasted text, and anything in shared or read-only views or the code panel, which has its own computer.
  • Switching chats takes that chat's files out of the sandbox, so one chat's files are never visible from another. Knowledge files you mounted yourself stay.

Charts

Ask for a chart ("plot monthly sales as bars", "fammi un grafico delle vendite per mese") and it appears under the answer as an image, with a card to download it or open it in the panel. Charts are named figure-1.png, figure-2.png, … in the order that run drew them. The count restarts every run, so when you ask for a change and the assistant redraws, the new figure-1.png becomes the next version of the same artifact, not a new one.

  • Want a real name? Ask for it: "save the chart as vendite-2026.png". The chart then appears once, under that name, and not also as figure-1.png.
  • At most 20 charts per run. Ask for one chart per row of a big table and the first 20 appear; the run's output says the rest were dropped. Ask for a combined chart, or for them in batches.
  • No chart appeared? The automatic capture could not start in that run, though the code still ran. Ask the assistant to save the chart to a file, which does not depend on the capture.
  • Building on an earlier chart ("add last year's line to that chart"): the assistant can reopen an earlier run's image, because earlier files stay in the sandbox's output folder. A run only shows the files it creates or changes, so the old chart is not shown again.

If you write the cell yourself, or ask the model how it saves figures

  • No savefig needed: plt.show() is replaced by "save every open figure as figure-<n>.png in the output folder (/mnt/output), then close it". Any figure still open when the run ends, even one that failed part-way, is saved the same way.
  • A figure the code saves itself with savefig is left out of that sweep. Its own file is its card, so the image is never shown twice.
  • Past 20 figures, later ones are discarded with a note on stderr.
  • Code that mentions matplotlib, pylab or seaborn gets matplotlib imported before it runs, on the non-interactive Agg backend.
  • An earlier run's figure-<n>.png can be read back from the output directory.

Limits

Time 60 seconds per run by default (operators can change it, up to 300). A longer run is stopped and the sandbox restarts; ask for the work in smaller steps, or on a sample of the data
Output 8,000 characters per stream (stdout, stderr)
Files 50 MB per file loaded into a run; chat attachments 20 MB each and 100 MB together
Memory the WebAssembly heap; running out raises MemoryError
Network none: no fetch, sockets, WebSockets or storage APIs, including through pyfetch, micropip or the js bridge
First run loads the runtime (about 12 MB) once; later runs start immediately
Listing at most 200 files listed per run
Figures at most 20 per run
Retention files a run produced are kept 30 days by default; Administration → Storage changes it

Installing more packages

You never need to ask for an install. The assistant's imports are resolved before the code runs: Pyodide's own packages and the office libraries the deployment ships load by themselves. If the answer shows ModuleNotFoundError, the package isn't available here. For a pure-Python package from the public PyPI index, the install packages from PyPI setting (on by default; it sends no credentials) lets the code fetch it. If you or your administrator turned it off, turn it back on in your settings (when the administrator has switched it off for the whole deployment, the option is not shown), then ask again. Packages with compiled code that Pyodide does not ship cannot be installed.

What rendered artifacts can do

HTML artifacts are previewed in sandboxed frames with no access to cookies, storage or the page, and a content security policy that blocks network requests, form submission, and remote images or media (connect-src 'none', form-action 'none', img-src data: blob:). Only the libraries the preview itself loads (Tailwind, React, Mermaid) come from their CDNs. A document in a knowledge base therefore cannot turn an artifact into a way to send data out.

For operators and maintainers

  • The runtime and the extra wheels are served from the chat's own origin (<base>/pyodide/), with no CDN. They are generated from the pinned npm package and wheel list by scripts/sync_pyodide.mjs and scripts/sync_pyodide_wheels.mjs, which run before dev and build.
  • The wheel filenames are pinned exactly: micropip needs a PEP 503 index page per package (the sync script writes them), and compiled dependencies sit beside pyodide-lock.json.
  • Licences: Pyodide is MPL-2.0 and is served unmodified as static files. The vendored wheels are MIT, BSD or PSF; each is listed in static/pyodide/wheels/NOTICE.txt, and static/pyodide/NOTICE.txt carries Pyodide's attribution.
  • Imports. Before a run, Cerea scans its imports: packages in Pyodide's own set load automatically, and the office libraries the deployment ships (python-docx, python-pptx, openpyxl, pypdf, reportlab, seaborn) are installed on the spot, so from docx import Document works with no micropip.install. It is best effort: an import it cannot resolve raises its ordinary ModuleNotFoundError, and a package is installed once per interpreter. micropip.install(...) works for everything shipped with the deployment.
  • PyPI access. By default code may fetch pure-Python packages from the public PyPI index (pypi.org, files.pythonhosted.org): that is outbound traffic from the user's browser, and each user can turn it off in their settings. Operators can switch it off for everyone with CHAT_PYODIDE_PYPI_DISABLED=true, which also hides the user's option.
  • Where code runs. CHAT_CODE_EXECUTION (browser by default, off to disable every code feature; server is reserved) is set by the operator only. See configuration. On a phone, a run keeps going only while the page is open: the card says so, the screen is kept awake while it runs, and a page that goes to the background asks the server to keep the turn waiting (CODE_PAUSED_WAIT_MS) and restarts the run when it returns.
  • One residual path, for the record: code in the worker can start a dynamic import() of a remote URL. That is a one-way signal: no cookies travel, and nothing can be read back.