8000 feat(assistant): run Python and bash in an OpenShell sandbox per conversation by adeelahmad · Pull Request #473 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(assistant): run Python and bash in an OpenShell sandbox per conversation - #473

Merged
adeelahmad merged 5 commits into
mainfrom
claude/project-thread-r960xc-c7-sandbox
Oct 17, 2026
Merged

adeelahmad merged 5 commits into
mainfrom
claude/project-thread-r960xc-c7-sandbox

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Description

Before: the assistant could only guess at arithmetic, data analysis or file conversion, and had no way to run code for people.

After: an admin turns on Settings → Code sandbox (off by default) and points Lens at an NVIDIA OpenShell gateway. The assistant then gets run_python, run_bash, sandbox_files and import_sandbox_file. Code runs in a sandbox that belongs to one conversation (lens-chat-<id>), keeps its files between runs, and holds none of Lens's data or keys. It has no network unless sandbox.network allows the package registries or public addresses. Files attached to the conversation can be copied in. A file the code makes reaches a namespace only through an import_files approval.

How: domain/sandbox.py reaches the gateway through OpenShell's Python client (the new sandbox extra, fetched as a component once the sandbox is on). The sandbox's policy is built from the settings: read-only system folders, a writable workdir and /tmp, and egress that is none, PyPI/npm on 443, or public IP ranges on 80/443. The policy never allows private, loopback or link-local ranges. Each run is an activity-ledger row (sandbox.run). With the network on, a run after the answer read outside text waits for approval (Toolbox.wary). The routines loop stops idle sandboxes and deletes unused ones. Deleting a conversation deletes its sandbox.

This is PR 1 of 3 for the code sandbox. PR 2 adds the Compose profile that starts the gateway from Settings, with TLS or a token and a preflight check (Docker 28, Landlock). PR 3 adds the conversation step that shows the code and its output, and a share_sandbox_file download chip.

Motivation and Context

Adeel asked for standard Python and bash tools for the chat app on OpenShell (chat gaps, module C7).

Screenshots (if appropriate):

n/a (a Settings section with the existing field types; the conversation step comes in PR 3).

Steps to reproduce (if appropriate):

  1. Run an OpenShell gateway on a network Lens reaches.
  2. In Settings → Code sandbox, turn the switch on and set the gateway.
  3. Ask the assistant "what is 2**100?" or "convert this CSV to JSON" with a file attached.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests.
  • My change requires documentation updates, and I have updated docs/ accordingly.
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: delivers the backend, tools and settings for the code sandbox. Starting the gateway from Settings (PR 2) and the conversation step (PR 3) follow, so the goal isn't complete with this PR alone.
  • Agents: the tools are the assistant's. An admin's agent changes sandbox.* like any other editable section, through the settings API or the assistant's change_settings. A direct sandbox API for agents is listed as refine later.
  • Zero setup: n/a for now. The feature is off by default and today needs a gateway the admin runs. PR 2 starts it from Settings. The client is fetched as a component.
  • Fewest clicks: one switch, with every other value defaulted. Approvals come only when the network is on and outside text was read.
  • Configurable: all 10 sandbox.* settings are in Settings → Code sandbox, with defaults. Nothing is in the setup wizard.
  • Light: off by default. The client (about 10 MB) is fetched only once the sandbox is turned on, and there's no new container yet.
  • Private and secure: sandboxes hold no Lens data or keys, and their egress is denied by default with private ranges never allowed. threat-model.md/.yaml/.json gain the entry point, the OpenShell dependency and P-SANDBOX. ci/validate_threat_model.py passes.
  • Cost: no model calls of its own. Each run is in the activity ledger (sandbox.run) and counted for the conversation.
  • User decides: off by default, existing defaults unchanged. Imports go through an approval, and so does a networked run after outside text.
  • Upgrades: a new schemaless table code_sandbox and new defaults, with no change to existing stored data.
  • Honest: docs label it Built / In progress / Refine later. The image size is noted as about 45 MB.
  • Tests, docs and generated files: tests/api/test_sandbox.py (8 tests against a gRPC fake gateway driven by OpenShell's own client). docs/assistant.md, docs/configuration.md and docs/components.md are updated. openapi.json and the SDK are regenerated with make openapi. uv.lock comes from uv lock.
  • Open: OpenShell and its SDK are Apache-2.0, and the SDK is an optional extra.
  • Reported: refine-later items are in docs/assistant.md#code-sandbox. Automations without a conversation, Python extension tools in the sandbox, microsandbox installs, Podman, GPUs, a direct sandbox API, and a real OpenShell run in CI are all listed there.

Local checks: ruff check, ruff format --check and mypy (151 files) are clean. The full backend pytest passes after merging main (1341 passed, 20 skipped). openapi-check shows no diff. Frontend tsc, eslint, prettier and jest (957 tests) pass. mkdocs build --strict is clean.


Generated by Claude Code

…ersation

The assistant gets run_python, run_bash, sandbox_files and import_sandbox_file
once an admin turns on Settings > Code sandbox (sandbox.enabled, off by
default) and points Lens at an NVIDIA OpenShell gateway. Each conversation
gets its own sandbox (lens-chat-<id>) with none of Lens's data or keys, no
egress unless sandbox.network allows the package registries or public
addresses, and its files kept between runs. Attachments go in by id; a file
comes out only through an import_files approval. With the network on, a run
after outside text asks first. Idle sandboxes are stopped and unused ones
deleted by the routines loop, and deleting a conversation deletes its sandbox.

OpenShell's Python client is the `sandbox` extra, fetched as a component when
the sandbox is turned on; tests run it against a fake gRPC gateway.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
The last check asked for 2026-10-17's note after the call without a date had made today's note, so on that day it
found the note the deleted root had left behind. It now asks for a day no earlier call makes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
…-r960xc-c7-sandbox

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
@adeelahmad adeelahmad self-assigned this Oct 17, 2026
# Conflicts:
#	docs/assistant.md
#	fastapi_backend/app/domain/ai_tools.py
#	fastapi_backend/app/domain/components.py
#	fastapi_backend/app/domain/settings.py
#	fastapi_backend/pyproject.toml
#	fastapi_backend/uv.lock
#	threat-model.md
#	threat-model.yaml
@adeelahmad
adeelahmad merged commit 190cf2f into main Oct 17, 2026
18 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-r960xc-c7-sandbox branch October 17, 2026 01:59
adeelahmad pushed a commit that referenced this pull request Oct 17, 2026
C1 (#469), web search (#472) and the sandbox (#473) are in main. The
routes keep C5's web pages in citations alongside the export, and the
export test now checks a cited web page stays through access changes.
The OpenAPI schema and client are regenerated with make openapi's two
commands, and the threat model's prose_version is recomputed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0