Docs / Developer mode
The Python worker
Every agent has a Python runtime, and the code it runs is written by a model. This page covers the worker process that runs it, its limits, what the code cannot see, what it may not write, and how the hosts it tries to reach are judged.
One worker per specialist
Skills are written as code that prints, and run_python returns what was printed. The code runs in a worker: a separate Python process, with the same interpreter and packages, that ivralo starts on the first call and keeps for that specialist's lifetime. Variables persist between calls, so a DataFrame loaded once stays loaded. The working directory is the workspace.
| Setting | Default | Effect |
|---|---|---|
IVRALO_PYTHON_TIMEOUT | 300 | Seconds a call may run before the worker is killed |
IVRALO_PYTHON_MAX_MB | 4096 | Memory the worker may use; 0 lifts the cap |
IVRALO_PYTHON_ISOLATION | process | inline runs the code in ivralo's own process, without limits or the network guard |
A call that runs past the timeout is killed together with every process it started, and the model is told that its earlier variables are gone. Esc in the session does the same at once. The next call starts a fresh worker. One specialist's calls run one at a time, so two calls never share a worker.
Memory is a cap, not a kill: past it an allocation fails with a MemoryError inside the code, which comes back as a traceback, and the session stays up. On Linux the cap is RLIMIT_DATA, on Windows a Job Object limit; ivralo doctor names the mechanism in force on your machine.
Nothing outlives ivralo: the worker exits when ivralo closes its input, Linux sends it a parent-death signal, and on Windows the Job Object ends its whole tree. If a worker fails to start, its errors are in .ivralo/python-worker.log.
In the session, each run_python call shows its code in the approval dialog before it runs (Editing and approvals). In a run nobody watches it runs without a dialog, inside these limits.
What the code cannot see
The worker's environment carries no credential. Before it starts, ivralo removes every variable whose name ends in _KEY, _TOKEN or _SECRET or contains PASSWORD, SECRET or WEBHOOK, ivralo's own database URL, IVRALO_SQL_URL, and every variable a databases.json names for a database. The worker removes them again after it loads .env. A print(os.environ) shows none of them.
A call that needs a credential is made by ivralo, the only holder of it. This is the credential broker: the code asks over the worker's pipe, and ivralo answers. Two kinds can be brokered:
| Kind | Inside the worker | What ivralo does |
|---|---|---|
| Your databases | query_database, list_tables, describe_table, list_databases | Runs the query on its read-only connection and returns the rows as a DataFrame, at most 10,000 rows and 8 MB per answer |
| Stock photos | search_photos, save_photo, photos_status | Calls Unsplash with IVRALO_UNSPLASH_ACCESS_KEY and writes the picture under the write guard's rules |
A specialist whose grants do not cover a kind is refused by the grant's name. Every brokered call is recorded by the credential's name, never its value, on a line after the code's output that the stored run keeps:
[ivralo answered this code's calls: photos.search with IVRALO_UNSPLASH_ACCESS_KEY (1 call, 0.4s)]
Reading a .env file is refused wherever it is, and so is opening or listing .ivralo/ or .git/, in any case spelling. Every other read is free.
The write guard
The write guard wraps open and the file calls beside it (os.replace, os.rename, os.remove, shutil.copy, shutil.move, shutil.rmtree, Path.unlink and others). A write outside the workspace is refused, and a file that is overwritten is copied into ivralo's history first.
Some paths are closed to writing even inside the workspace:
.ivralo/(history, plans, change records, the repository'spermissions.json), except history's own snapshots;.git/, because a hook or aconfigedit runs later outside any dialog;- ivralo's own configuration when the home is the workspace:
permissions.json,databases.json,mcp.json,schedules.json, the Google token, the.envfiles ivralo read, and the whole local skills shelfskills.local/.
A permissions file or a skill's frontmatter is a grant, so code that could write one could lift its own next asks. The refusal names the path and tells the code to say so to the person rather than look for another way.
The main agent's and the Code Engineer's Python only reads. For them every write mode is refused, inside the workspace too, and the refusal names edit_file and write_file instead. That makes "every edit is shown and approved" a property of the program: the route around the dialog is not open. Reading, importing and computing are untouched.
A folder you have not trusted
In a folder you have not trusted, the worker does not import the folder's own code. The workspace and the working directory leave sys.path, and import app for a module that lives in the workspace fails with a sentence naming the trust gate and ivralo trust, so the model stops instead of hunting for a way round. The standard library, installed packages and ivralo's own stay importable, and open() on a workspace file still works. In a trusted folder nothing changes.
The worker never writes bytecode, so no __pycache__ appears in your repository through run_python.
The network guard
Every host the code tries to reach is judged by ivralo first, by the same egress policy that governs the shell and the web tools (Shell commands). Inside the worker, the socket module's lookups and connects ask ivralo's process. A host you have not listed is asked about before anything is looked up, so a refusal sends the name nowhere, not even to DNS.
| Host | Verdict |
|---|---|
| Link-local, cloud metadata, multicast or reserved addresses, a name that does not resolve | Never reached, whatever your lists say |
In hosts.deny | Denied |
In hosts.allow of either permissions file | Allowed |
| Loopback, a private address, a local name, or any host not listed | Asked |
When the worker asks, the session shows "Code in run_python wants to reach api.example.com" with three answers: yes this time (for the rest of that worker's life), always (written into the repository's .ivralo/permissions.json under hosts.allow, offered only in a trusted folder), or no. With nobody there, as in ivralo ask or a schedule, it is refused. The time you take to answer does not count against the call's timeout.
A refusal reaches the code as the error a failed name lookup raises (socket.gaierror) with ivralo's reason, so an HTTP library fails the way it already knows how to. Every host the code tried is listed on the line after the result.