Code Execution
Run Python in an isolated sandbox to analyse attachments and build decks, workbooks, documents, and data files — set up from the admin GUI
Code Execution
Code execution lets the assistant run Python against the files you attach and
hand back finished files — a deck, a workbook, a document, a web page, a CSV.
The work happens in a sandbox: a short-lived, isolated container or pod with
no network access. Everything is set up in the admin GUI
(Admin → System → Sandbox). You do not edit a .env file or a compose
file, and you do not restart MCintelligentplus.
The old SANDBOX_* environment variables, the Compose broker, and the in-backend
Docker, Podman, and Kubernetes providers were removed. Only two break-glass
switches remain: SANDBOX_ENABLED (forces code execution off) and
SANDBOX_GUI_CONFIG (makes the sandbox pages read-only). If you ran the earlier
env-based setup, create a runtime profile in the GUI instead and install a
gateway (see Admin guide).
What you get
- A
code_executionsub-agent. The main assistant delegates to it when you attach files and want them analysed or converted, or want a calculation or a new file built from data you supplied. It is a built-in specialist next to the others described in Chat Agents. - Python in an isolated sandbox. It reads your attachments (audio arrives as a transcript) and any knowledge-base file it opens, computes over them, and saves results.
- Files you keep. It can build
pptx,xlsx,docx,html,md, andcsvfiles, plus other data and image types from the allowed list (see Limits and quotas). - Artifact cards. Every saved file appears in the chat as a card with a
Preview button and a Download button. The reply names files in plain
words and never shows a sandbox path such as
/workspace/.... - A multi-runtime back end. A system admin picks Docker, Podman, Kubernetes, or Daytona per profile, and different organizations can use different profiles.
Use the Deliverables tools instead when you want a plain report, deck, or workbook from a prompt with no input data. Code execution is for your own data.
User guide
- Send a first message. The composer cannot attach a file in a brand-new chat before the first message is sent. Start with a short opener, for example "I will send a spreadsheet."
- Attach your file. Documents, spreadsheets, CSVs, PDFs, and audio work. Only files you have sent are copied into the sandbox; an upload that is still unsent is never read.
- Ask for what you want, for example "Turn this workbook into a five-slide deck with a chart per region" or "Total the invoices by vendor and give me an Excel file."
- Watch the progress. The chat shows the
code_executiondelegation and its steps. - Preview and download. Each result appears as a card:
- decks and documents show a page-by-page preview;
- workbooks and CSVs show a table preview with a tab per sheet;
- markdown renders as text;
- HTML shows in a locked-down frame (scripts do not run);
- Download always gives you the original file.
Artifacts follow the chat. In a private thread only the person who owns the thread can preview or download them; in a shared thread, anyone who can read the thread can. Anyone else gets a "not found" response.
If you do not see the feature, it is off at one of the levels listed in Who can run code. Ask your system admin.
Who can run code
Four things must be true. A missing one fails closed with a named denial.
| Level | Who sets it | Where | Default |
|---|---|---|---|
| Deployment | System admin | Admin → System → Sandbox: an active default profile, then the Code execution switch | Off |
| Profile | System admin | Admin → Organizations, column Sandbox profile (empty = default profile) | Default profile |
| Workspace | System admin | Admin → Workspaces, the Code execution section of the workspace | Off |
| Role | Workspace admin | The member's role has code_execution:run. Editor, Admin, and Owner have it; Viewer does not | Editor and above |
Admin guide
You are a system admin and can open Admin → System → Sandbox. If the pages
are read-only, the operator set SANDBOX_GUI_CONFIG=false
(see Break-glass switches).
How it fits together
MCintelligentplus backend / workers --HTTPS + HMAC--> sandbox gateway --local API--> Docker | Podman | Kubernetes
|
+--HTTP :8888 (local network)--> sandbox agentThe sandbox gateway is a small service that you start once, next to the runtime. It is the only component that talks to the container runtime: it creates and deletes sandboxes and relays the backend's calls to the agent inside each one. The backend mounts no container socket, holds no cluster credential, and joins no sandbox network.
| Runtime | The gateway runs as | It talks to the runtime with |
|---|---|---|
| Docker | A container on the Docker host | The Docker socket, mounted into the gateway only |
| Podman | A rootless container of the service user, kept alive by a systemd quadlet | The user's Podman socket |
| Kubernetes | A Deployment in namespace mci-sandbox-system | Its own service account, with a Role in namespace mci-sandbox only |
| Daytona | No gateway | The Daytona API URL and key in the profile |
The backend needs one outbound connection per profile, to the gateway port
(default 9443). The runtime host never connects back. A remote Docker host, a
remote Podman host, and a remote cluster all work.
Before you start
- A Docker host, a rootless Podman 4+ host, or a Kubernetes 1.25+ cluster whose CNI enforces NetworkPolicy (k3s does; plain Flannel does not). Daytona needs only an account.
- The sandbox and gateway images. They are built locally on the runtime host
from the repository (
sandbox/image/Dockerfileandsandbox/gateway/Dockerfile); there is no registry step. The gateway runs only images that match its own allowlist, whose default is the repositorymcintelligentplus-sandbox.
The full host-side walk-through for each runtime (build commands, firewall
rules, k3s image import) is in docs/deployment/sandbox-gateway.md in the
repository.
Step 1 — Turn on code execution
Open Admin → System → Sandbox. The Code execution switch is one switch for the whole deployment. It needs an active default profile, so you can only turn it on after step 3. Changes reach the API, the workers, and the scheduler within about 15 seconds; no restart.
Step 2 — Create a runtime profile
Click Create profile. A runtime profile is one named, complete sandbox target. The wizard saves progress on the server, so you can leave and Continue setup later.
-
Runtime and name. Choose Docker, Podman, Kubernetes, or Daytona, and a name of 1–80 characters. The runtime cannot change afterwards.
-
Endpoint.
- Docker or Podman: the gateway URL, for example
https://host:9443. If the gateway runs on the same host as MCintelligentplus, tick the gateway runs on the same Docker network (plain HTTP). Plain HTTP is accepted only for that case. - Kubernetes: the gateway URL, the Service type (
NodePort,LoadBalancer, orClusterIPwhen MCintelligentplus runs in the same cluster), and the allowed CIDRs that may reach the gateway port — at least the backend's address. - Daytona: the Daytona API URL (HTTPS) and, in the next step, the API key.
- Docker or Podman: the gateway URL, for example
-
Credential. Click Generate credential and install command (or …install manifest for Kubernetes). Copy it.
The credential is shown once, inside the install command or manifest. MCintelligentplus stores it encrypted and cannot show it again. Confirm that you copied it before you continue.
Step 3 — Install the gateway and run it next to the runtime
Run the generated command (Docker or Podman) or apply the manifest (Kubernetes) on the machine that runs the sandboxes.
| Runtime | What you run |
|---|---|
| Docker | The generated docker run command, as a user that may use the Docker socket. It runs the gateway as a non-root user with a read-only root filesystem, no capabilities, and no-new-privileges. For a gateway on the same host, set MCI_APP_NETWORK to MCintelligentplus's Docker network first |
| Podman | The generated script, as the rootless service user. It writes a 0600 env file, a quadlet unit, and starts the service |
| Kubernetes | Save the manifest as install.yaml, then kubectl apply -f install.yaml and kubectl -n mci-sandbox-system rollout status deploy/sandbox-gateway |
The Kubernetes manifest creates two namespaces that enforce Pod Security
restricted: mci-sandbox-system (the gateway) and mci-sandbox (the
sandbox pods, a ResourceQuota, a LimitRange, and deny-all NetworkPolicies).
The sandbox image must be referenced by digest
(mcintelligentplus-sandbox@sha256:…). Keep that image on every node; the
manifest includes an optional pre-pull DaemonSet so Kubelet does not garbage
collect it.
The gateway prints its certificate fingerprint on first start. Read it with
docker exec <gateway container> python -m gateway fingerprint, or from the pod
log on Kubernetes.
Step 4 — Test, trust, canary, acknowledge, activate
| Wizard step | What you do | What it proves |
|---|---|---|
| Test connection | Click Test connection, then choose the sandbox image from the list the gateway reports | Runs the gateway's preflight. Every problem comes with a fix |
| Trust fingerprint | Compare the certificate fingerprint with the one the gateway printed. If they match, click Trust this certificate (or paste a CA bundle instead of pinning) | The certificate is pinned. No signed request is sent before this |
| Canary | Click Run canary | A throwaway sandbox proves it cannot reach the internet, cloud metadata, the gateway, or the host. Every target must be BLOCKED and the positive_control must be OPEN |
| Acknowledge | Record each acknowledgement the wizard asks for | shared kernel (Docker, Podman, and Kubernetes without a runtime class) and no disk limit when the host cannot bound a volume. Each is stored and audited |
| Activate | Click Activate. Optionally tick Use as default profile | Needs the credential, a pinned certificate, a passing test and a passing canary (each under 24 hours old), every acknowledgement, and an image |
Docker and Podman sandboxes share the host kernel. Use them only where every
user who can run code is trusted, or use Kubernetes with a gVisor or Kata
runtime class. Daytona skips the gateway steps: it manages the connection and
isolation, and only needs its API key.
Step 5 — Set the default profile
In Admin → System → Sandbox, choose the Default profile (it must be active). Organizations with no profile of their own use it. Running sandboxes stay on the profile they were created with.
Step 6 — Assign a profile per organization
In Admin → Organizations, use the Sandbox profile column to choose a profile for each organization. Leave it empty to use the default. A workspace resolves its profile like this:
workspace → its organization → the organization's profile → (none) → the default profileA workspace with no organization uses the default profile. Several profiles can be active at once — local Docker for one organization and a remote cluster for another. See Organizations.
Step 7 — Turn on the workspace and set quotas
Open Admin → Workspaces, find the workspace, and use its Code execution section:
- the switch turns code execution on or off for that workspace (off by default);
- the Sandbox profile shown there is read-only — change it on the Organizations page;
- the quota form and live usage meters.
| Quota | Default | Meaning |
|---|---|---|
Maximum concurrent sandboxes (max_concurrent) | 2 | Live sandboxes at once; idle ones count until they end |
Maximum resource class (max_resource_class) | SMALL | Largest of SMALL, MEDIUM, LARGE |
Maximum job time (max_job_seconds) | 3600 | Longest single job |
Maximum output size per day (max_artifact_bytes_per_day) | 1 GiB | Artifact bytes stored per UTC day |
Maximum jobs per hour (max_jobs_per_hour) | 60 | New sandboxes per rolling hour |
A workspace with no stored quota gets these defaults. Make sure the members who
should use it have the code_execution:run permission (Editor and above). Every
change is audited.
Step 8 — Verify
Ask a normal chat to compute something and save a file, for example "Use the code_execution specialist to compute the sum of squares 1..100 and save it as a CSV." A correct reply plus a file card means every level is on.
Credential rotation
Rotation keeps the gateway running; sandboxes in flight are not interrupted.
- In the profile's actions choose Rotate credential → Generate new credential (issue). The new credential is shown once. The gateway accepts the old and the new credential until you confirm.
- Run the new install command on the gateway host, or apply the new manifest (apply both). It carries both credentials, the new one first.
- Click Test connection, then Confirm rotation (Finish rotation in
the table). MCintelligentplus checks the new credential against the gateway
first and refuses with
rotation_not_appliedif the gateway does not accept it yet. - Run the install command once more with only the new credential, so the old
one stops working. Preflight shows
auth.secretsas a warning until you do.
Job mode per profile
In a profile's Settings sheet, the Long-running jobs switch lets the assistant start background jobs that keep running after a chat turn ends. It is off by default, applies to new jobs only, and is unavailable on Daytona profiles. The longest allowed job is the Longest job setting (default 28 800 s) and each workspace is also capped by its Maximum job time quota.
Disable and delete
Disable stops new sandboxes on a profile; running ones end by their time limits, and organizations assigned to it cannot start new sandboxes until you choose another profile. Delete is refused while anything uses the profile (running sandboxes, assigned organizations or workspaces, being the default, or sandbox history); the GUI says which. Disable a profile that has history instead of deleting it, and remove the gateway from the host afterwards.
Break-glass switches
These are the only SANDBOX_* environment variables. Both are for the operator
who runs the deployment.
| Variable | Effect |
|---|---|
SANDBOX_ENABLED=false | Forces code execution off whatever the GUI says. The page shows Forced off by the server environment. Remove it (or set true) to let the GUI decide. Recreate the backend and worker after changing it |
SANDBOX_GUI_CONFIG=false | Makes every profile change return 403 gui_config_locked. The pages become read-only |
Workers must also consume the sandbox queue (<default queue>.sandbox), or
expired sandboxes are never swept. See
Manual Installation.
Limits and quotas
Per-profile settings are edited in the profile's Settings sheet and apply to new sandboxes.
| Setting | Default | Meaning |
|---|---|---|
| Idle time limit | 600 s | Stop an idle sandbox (Docker, Podman); on Kubernetes it is swept and destroyed |
| Hard time limit | 3600 s | Final artifact sweep, then destroy |
| Longest job | 28800 s | Ceiling for a long-running job |
| Start timeout | 90 s | How long a call waits for a sandbox to be ready |
| Workspace disk size | 1024 MB | Size of the workspace volume |
| Default resource class | SMALL | SMALL, MEDIUM, or LARGE |
| Warm pool | 0 | Ready-made unused sandboxes. Kubernetes only; 0 turns it off |
| Allowed output file types | pptx,xlsx,docx,pdf,csv,tsv,json,txt,md,html,png,jpg,jpeg,gif,webp,svg | The only file types that leave the sandbox |
| Output limits | 25 files, 100 MiB, 50 000 characters | Per collection |
| Input limits | 25 MiB per file, 256 MiB per sandbox | What can be copied in |
Security model
A system admin who can edit profiles is effectively a runtime operator. These controls keep that safe:
- Signed requests. Every gateway call except
/healthzcarries an HMAC-SHA256 signature over the method, path, body hash, and timestamp (skew up to 60 s). The backend sends no signed request until the gateway's TLS identity is pinned (fingerprint or CA bundle). Plain HTTP is accepted only for a same-host gateway and is labelled in the GUI. - No internet egress from the sandbox. Sandboxes have no network egress. The backend pushes inputs in and pulls outputs out, and the sandbox cannot reach the gateway, MCintelligentplus, or the host. Cloud metadata addresses are blocked.
- Firewall and network policy. Kubernetes uses a default-deny NetworkPolicy
in both namespaces. Docker and Podman bind the gateway only to its
non-sandbox interface and refuse to start otherwise; the install notes add an
optional host firewall rule that the canary checks (
network.host_firewall). - Image allowlist. The gateway creates sandboxes only from images that match its install-time allowlist, and never with a bind mount, privilege, extra capability, device, host network, or host PID. The GUI can only pick images the gateway reports.
- Secrets stay encrypted. The gateway credential, the agent-token secret, and the Daytona key are encrypted at rest, write-only, and never appear in a response, log, or audit row. The gateway never holds the agent-token secret, so a stolen gateway can only proxy.
- Audit. Every create, update, activate, disable, and delete writes an audit row with the changed field names, never the values.
- Weak isolation is explicit. A
shared-kernelprofile cannot be activated until an admin records the acknowledgement. - HTML preview is sandboxed. HTML artifacts show in an iframe without
allow-scriptsorallow-same-origin, under a Content-Security-Policy that blocks scripts and connections (default-src 'none'; script-src 'none'; …). Sandbox artifacts are always served as attachments. - Size and type limits. Only the allowed file types leave the sandbox, within the output limits above. Previews of large files show the first part only; Download gives the whole file.
Troubleshooting
Test connection returns a list of preflight checks, each with an id, a
status (pass, warn, fail), a message, and a fix. The full table of check
ids per runtime is in docs/deployment/sandbox-gateway.md.
| Symptom | Likely cause | Fix |
|---|---|---|
gateway_unreachable (also shown as "MCi could not reach" followed by the gateway URL) | Port closed, gateway not running, wrong URL, or no route from the backend | Check the firewall, then docker ps or kubectl get pods -n mci-sandbox-system. The backend must reach the gateway port |
| "The gateway rejected the credential" | The command was run with a different credential | Generate a new credential and run the new command |
Rotation confirm returns rotation_not_applied | The gateway does not accept the new credential yet | Run the new install command on the gateway host, then confirm |
| "The gateway certificate does not match the pinned fingerprint" | The gateway was reinstalled, or something is impersonating it | Replace the pin only if you reinstalled the gateway yourself |
| "This gateway belongs to a different installation" | You ran another profile's command | Run the command the wizard shows for this profile |
| "This gateway speaks protocol N; MCi speaks 1" | Version mismatch | Upgrade the side the message names |
image_not_allowed | The image is not in the gateway allowlist | Pick an allowed image, or run the install command again with the repository added to GATEWAY_ALLOWED_IMAGES |
| Canary fails | A target was OPEN. On Kubernetes this usually means the CNI does not enforce NetworkPolicy | Fix the network rules on the host or CNI and run the canary again. Do not activate a profile that fails it |
Canary host_gateway_ssh open (network.host_firewall) | A sandbox reached the host through its bridge | iptables -I INPUT -i <bridge> -j DROP, or publish the port on one host IP |
activation_blocked | The server lists the blockers: no_credential, tls_not_pinned, no_recent_test, no_recent_canary, ack_required, no_image | Do the step named. Re-run the test and canary if they are over 24 hours old |
Denial deployment, workspace, role, or quota:max_concurrent | A level of the gate is off or exhausted | See Who can run code; raise or wait out the quota |
pre_gateway_lease ("the lease predates runtime profiles") | A sandbox was created before the upgrade to runtime profiles, so it has no profile and no gateway to call | Nothing to repair: start a new sandbox by sending a new request. The old one ends by its time limits |
Kubernetes pods stuck in ImagePullBackOff | The sandbox image was removed from the node by image garbage collection | Re-import the image, keep disk below 85%, and use the pre-pull DaemonSet in the manifest |
Clock warning auth.clock_skew | MCintelligentplus and the gateway clocks differ by 30 s or more (refused beyond 60 s) | Fix NTP on both hosts |
Where in the UI
- Admin → System → Sandbox (
/admin/system/sandbox) — the Code execution switch, default profile, runtime profiles table, profile wizard, settings, rotation, and disable/delete. - Admin → Organizations — the Sandbox profile column.
- Admin → Workspaces — the workspace Code execution section: switch, quota, and usage.
- Chat — artifact cards with Preview and Download.
- Admin → Audit Logs — profile and workspace changes.
Related
- ADR-0035 — runtime profiles in the admin GUI behind a sandbox gateway
- Admin Portal · Organizations · Chat Agents · Deliverables