8000 feat(assistant): start the code sandbox's OpenShell gateway from Settings by adeelahmad · Pull Request #495 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(assistant): start the code sandbox's OpenShell gateway from Settings - #495

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

adeelahmad merged 16 commits into
mainfrom
claude/project-thread-r960xc-c7-gateway

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 17, 2026 •
Copy link
Copy Markdown
Owner

Description

Before: the code sandbox needed an NVIDIA OpenShell gateway the admin ran and wired up themselves. Its web network couldn't work either, because OpenShell resolves only the names its policy lists.

After: Settings → Code sandbox has Run the gateway on this machine. The switch has the upgrade agent start OpenShell's gateway next to Lens (the new Compose profile sandbox) and turns the sandbox on at openshell-gateway:17670. Lens signs in to the gateway over mutual TLS. It first checks that the machine can run the gateway (the Docker Compose install, a 64-bit machine, Landlock 3 in the kernel) and says why when it can't. Turning it off deletes every sandbox before the gateway stops. Agents get the same through GET/PUT /api/v1/admin/sandbox and the MCP tools sandbox_gateway and set_sandbox_gateway. The network setting web becomes hosts: the package registries plus the names an admin lists in sandbox.hosts, held to public addresses.

How:

  • openshell-init makes the gateway's certificates once, in the host's /var/lib/openshell. That folder has the same path inside and out, because the gateway has Docker mount files from it into the sandboxes.
  • openshell-key copies the client certificate to the sandbox-key volume, owned by Lens's user with mode 0400.
  • openshell-gateway runs OpenShell 0.1.4 with the Docker driver, configured by sandbox/gateway.toml. It runs as root for the Docker socket, with cap_drop: ALL, no-new-privileges, a read-only image and telemetry off.
  • The backend and worker join the sandbox network, mount sandbox-key read-only and get LENS_SANDBOX_TLS_DIR.
  • sandbox.turn() writes the services request in C6's format (action=services, profile=sandbox, turn=on|off).
  • gateway_view() reads the agent's status and probes the gateway with an authenticated call. A plain health call answers anyone, so it wouldn't show that Lens is let in.

I checked this design against a real OpenShell 0.1.4 gateway in Docker. Lens's client connected as uid 1000 over mTLS, and a caller without the certificate was refused. Sandboxes kept running with the gateway hardened. With the network off, nothing got out. A name that resolves to a private address was refused. One thing I couldn't check is real HTTPS to PyPI from a sandbox, because this environment intercepts TLS.

This is PR 2 of 3 for the code sandbox, after #473 (merged). Until C6's host-agent change ships, the agent answers a profile=sandbox request with "monitoring is on or off". Settings then says the agent is older than this Lens and shows the line to run by hand. PR 3 (#497) adds the conversation step and the file chip.

Motivation and Context

Adeel asked for standard Python and bash tools for the chat app on OpenShell (chat gaps, module C7). This PR makes it start with one switch instead of a hand-run gateway.

Screenshots (if appropriate):

n/a (a switch, banners and a hosts list in the existing Settings section).

Steps to reproduce (if appropriate):

  1. On a Linux machine installed with install.sh (kernel 6.2 or newer, Docker 28 or newer), open Settings → Code sandbox.
  2. Turn on Run the gateway on this machine. Without the new host agent, run the line Settings shows.
  3. Ask the assistant "what is 2**100?".

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: the gateway starts from Settings. The host agent's sandbox profile is C6's PR, and the conversation step is PR 3 (feat(chat): show the code sandbox's runs in the conversation and download the files it makes #497).
  • Agents: GET/PUT /api/v1/admin/sandbox and the MCP tools sandbox_gateway and set_sandbox_gateway (admins, with the write scope to change).
  • Zero setup: one switch. The certificates are made and copied by the profile, and nothing goes in .env. Without an upgrade agent, Settings shows the one line to run.
  • Fewest clicks: one switch turns the gateway on and points Lens at it. sandbox.hosts shows only with the hosts network.
  • Configurable: sandbox.hosts is in Settings with a default of none. The gateway address is still editable. The port (17670) is fixed for now and listed as refine later.
  • Light: off by default. The gateway's images (about 300 MB on disk) are pulled only when it's turned on.
  • Private and secure: mTLS between Lens and the gateway, and the port is published on 127.0.0.1 only. The client key is readable only by Lens's user. Hosts are held to public addresses. The threat model gains the profile as a variant, the admin entry point, the updated P-SANDBOX and the P-HARDENED exception for the root gateway. ci/validate_threat_model.py passes.
  • Cost: no model calls. The probe is one gRPC call per Settings view while the sandbox is on.
  • User decides: off by default. Starting it is the admin's switch, preflight problems block before anything changes, and existing defaults are unchanged.
  • Upgrades: the new sandbox.hosts has a default. sandbox.gateway's default moves to 17670. A stored web network (from feat(assistant): run Python and bash in an OpenShell sandbox per conversation #473, unreleased) reads as hosts, so no upgrade step is needed.
  • Honest: docs label the gateway Built and the host agent part In progress. The image sizes are measured. Real HTTPS egress wasn't checked here.
  • Tests, docs and generated files: 5 new tests in tests/api/test_sandbox.py (the switch end to end, an old agent, preflight, mTLS, the hosts policy) and __tests__/sandbox-gateway.test.ts. docs/assistant.md (The gateway), docs/configuration.md and docs/mcp.md are updated. The OpenAPI files come from make openapi.
  • Open: OpenShell is Apache-2.0, and its images are pulled only with the profile.
  • Reported: refine-later items are in docs/assistant.md#the-gateway: asking before a run reaches an unlisted host, the port as a setting, macOS and microsandbox, Podman, GPUs, and a real OpenShell run in CI. The C6 hand-off is in the project notes.

Local CI (ci/local-ci.sh 91274b4b --jobs backend,frontend,pre-commit,title): FastAPI pass (1089s), Next.js pass (327s), pre-commit pass (92s), PR title pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

claude added 11 commits October 17, 2026 00:09
…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
…ings

The Docker Compose install gets a sandbox profile: openshell-init makes the
gateway's certificates once, openshell-key copies the client's to a volume
only Lens reads, and openshell-gateway runs NVIDIA OpenShell 0.1.4 with the
Docker driver (no capabilities, read-only, telemetry off). Lens signs in to it
over mutual TLS.

Settings > Code sandbox > Run the gateway on this machine asks the upgrade
agent to start it (action=services, profile=sandbox) and turns the sandbox on
at openshell-gateway:17670, after a preflight (the Compose install, a 64-bit
machine, Landlock 3). Off deletes every sandbox first. The same over
GET/PUT /api/v1/admin/sandbox and the MCP tools sandbox_gateway and
set_sandbox_gateway. An agent that knows only monitoring says to start it by
hand, with the line to run.

The network setting web becomes hosts: OpenShell resolves only names its
policy lists, so the sandbox reaches the package registries and the names an
admin lists in sandbox.hosts, at public addresses only.

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
# 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
…sandbox' into claude/project-thread-r960xc-c7-gateway

# Conflicts:
#	fastapi_backend/app/domain/ai_tools.py
#	threat-model.yaml
…-r960xc-c7-gateway

# Conflicts:
#	CHANGELOG.md
#	docs/assistant.md
#	docs/configuration.md
#	fastapi_backend/app/domain/ai_tools.py
#	fastapi_backend/app/domain/sandbox.py
#	fastapi_backend/app/domain/settings.py
#	fastapi_backend/app/domain/store.py
#	fastapi_backend/tests/api/test_sandbox.py
#	fastapi_backend/tests/fake_openshell.py
#	nextjs-frontend/components/settings/bodies.tsx
#	nextjs-frontend/components/settings/model.ts
#	threat-model.json
#	threat-model.md
#	threat-model.yaml
…-r960xc-c7-gateway

# Conflicts:
#	threat-model.yaml
@adeelahmad adeelahmad self-assigned this Oct 17, 2026
…-r960xc-c7-gateway

# Conflicts:
#	CHANGELOG.md
#	fastapi_backend/app/api/mcp_tools.py
…-r960xc-c7-gateway

# Conflicts:
#	threat-model.yaml
…-r960xc-c7-gateway

# Conflicts:
#	threat-model.yaml
Ported from #512 so this PR's upgrade check passes while that lands: the
entity-types step re-types weekday Terms as Dates, which the default entity
list leaves out, so the check read them as gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
Copy link
Copy Markdown
Owner Author

"Upgrade from the last release" failed on 91274b4, but the cause isn't this PR. After the upgrade, "Monday" and "Friday" were missing from the entity list. #395's entity-types step, already on main, re-types them as Dates, and the check's entity list leaves Dates out.

#512 fixes the check, and I ported its change in 563a4c6, which also merges main. With it, ci/upgrade/upgrade.sh passes locally from v0.13.0: all 17 seeded facts read the same, all 187 records are kept, and each step ran once. The ported change does nothing once #512 is on main.


Generated by Claude Code

@adeelahmad
adeelahmad merged commit a32c3cd into main Oct 17, 2026
20 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-r960xc-c7-gateway branch October 17, 2026 05:51
adeelahmad pushed a commit that referenced this pull request Oct 18, 2026
…ay and clipper sign-in as built

Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517
and #491 merged. The README's built list now says a conversation exports as Markdown,
PDF or JSON and its answers, questions and code copy; a Tools chip picks an answer's
tools, limit and namespaces; sandbox runs open under the answer with downloads, and
Settings starts the gateway; deep research plans, runs and returns a cited report;
ready-made MCP servers (the browser, GitHub) and the optional containers start from
the web app; and the web clipper signs in or takes an API token. Each has its checks
in docs/requirements.md. In progress drops the merged PRs and lists the open feature
PRs; the Planned proof item keeps only the Raspberry Pi timing (#481 and #482 are
closed); the docs index links requirements and checks, and published feeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad added a commit that referenced this pull request Oct 18, 2026
…way (#544)

sandbox.py has asked the upgrade agent for the sandbox profile since #495, but the agent only knew mcp, browser and
monitoring, so Settings fell back to the line to run by hand. The profile's three containers (openshell-init,
openshell-key, openshell-gateway) join services_of, with a test, and the docs say which agent knows it.


Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
adeelahmad pushed a commit that referenced this pull request Oct 18, 2026
…ay and clipper sign-in as built

Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517,
#491, #400 and #506 merged. The README's built list now says the assistant answers
questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its
answers, questions and code copy; a Tools chip picks an answer's tools, limit and
namespaces; sandbox runs open under the answer with downloads, and Settings starts the
gateway; deep research plans, runs and returns a cited report; ready-made MCP servers,
the MCP servers page and the optional containers start from the web app; and the web
clipper signs in or takes an API token. Each has its checks in docs/requirements.md.
In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to
Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing;
the docs index links requirements and checks, and published feeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad added a commit that referenced this pull request Oct 18, 2026
…ay and clipper sign-in as built (#536)

Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517,
#491, #400 and #506 merged. The README's built list now says the assistant answers
questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its
answers, questions and code copy; a Tools chip picks an answer's tools, limit and
namespaces; sandbox runs open under the answer with downloads, and Settings starts the
gateway; deep research plans, runs and returns a cited report; ready-made MCP servers,
the MCP servers page and the optional containers start from the web app; and the web
clipper signs in or takes an API token. Each has its checks in docs/requirements.md.
In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to
Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing;
the docs index links requirements and checks, and published feeds.


Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
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