Repository navigation
feat(chat): show the code sandbox's runs in the conversation and download the files it makes - #497
Merged
Merged
Conversation
…ersation The assistant gets run_python, run_bash, sandbox_files and import_sandbox_file once an admin turns on Settings > Code sandbox (sandbox. 10BC8 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
# Conflicts: # threat-model.yaml
…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
…load the files it makes
Each run_python or run_bash step carries the start of its stdout, the end of its stderr and how it ended; the web app shows it folded under the answer with the code. share_sandbox_file copies a file out of the sandbox, kept encrypted with the conversation, as a download button on the answer (GET /api/v1/chats/{cid}/files/{fid}), deleted with the conversation or the account.
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 # Conflicts: # fastapi_backend/app/domain/ai_tools.py # threat-model.yaml
…oject-thread-r960xc-c7-ui # Conflicts: # 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
…oject-thread-r960xc-c7-ui
…-r960xc-c7-gateway # Conflicts: # threat-model.yaml
…oject-thread-r960xc-c7-ui # Conflicts: # fastapi_backend/app/api/v1/routes/chats.py # fastapi_backend/app/domain/ai_tools.py # fastapi_backend/app/domain/chat.py # fastapi_backend/app/schemas/chats.py # nextjs-frontend/app/openapi-client/sdk.gen.ts # nextjs-frontend/app/openapi-client/types.gen.ts # nextjs-frontend/components/chat/stream.ts # nextjs-frontend/openapi.json # threat-model.yaml
…-r960xc-c7-gateway # Conflicts: # CHANGELOG.md # fastapi_backend/app/api/mcp_tools.py
…-r960xc-c7-gateway # Conflicts: # threat-model.yaml
…oject-thread-r960xc-c7-ui # Conflicts: # CHANGELOG.md # threat-model.yaml
…-r960xc-c7-gateway # Conflicts: # threat-model.yaml
…oject-thread-r960xc-c7-ui # Conflicts: # threat-model.yaml
19 of 22 tasks
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
…gateway' into port-c7u # Conflicts: # threat-model.yaml
Owner
Author
|
"Upgrade from the last release" failed on 7611ee8 for the same reason as on #495, and the cause isn't this PR. On main, #395's Generated by Claude Code |
# Conflicts: # CHANGELOG.md # docs/assistant.md # fastapi_backend/app/domain/sandbox.py # fastapi_backend/tests/api/test_sandbox.py # nextjs-frontend/app/openapi-client/index.ts # nextjs-frontend/app/openapi-client/types.gen.ts # nextjs-frontend/openapi.json # threat-model.json # threat-model.md # threat-model.yaml
# Conflicts: # threat-model.yaml
# Conflicts: # threat-model.yaml
This was referenced Oct 18, 2026
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an
6D2F
editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Before: when the assistant ran Python or bash, the conversation showed only a one-line summary such as "Ran Python (2 lines), ok". The code and what it printed weren't shown. A file the sandbox made could reach the person only as an import into a namespace, after an approval.
After: each run is a folded step under the answer. Opening it shows the code, then the start of stdout, the end of stderr (in red) and how it ended (exit code, timed out, cut short). The new assistant tool
share_sandbox_filehands the person a file the sandbox made, like a chart or a converted document, as a download button on the answer. Lens keeps the file encrypted with the conversation and deletes it with the conversation or the person's account.How:
Toolbox.detailcarries what a tool adds to its step.chat.tool_answerputs it on thestepevent, so runs getoutput(sandbox.shown, 4,000 characters of each stream) and shares getfile({id, name, size}). The steps are saved with the answer as before, andToolStepgainsoutputandfile.sandbox.sharecopies the file out (25 MB at most) and writes it todata_dir/chat-files/<chat>/<id>, mode 0600. It's AES-256-GCM encrypted with a key derived from the server's secret and bound to the file and its conversation. Achat_filerow records it, at most 20 a conversation.GET /api/v1/chats/{cid}/files/{fid}serves the file only to the conversation's person, as an attachment withnosniffand asandboxcontent policy.sandbox.forget) or removing the account deletes the files.RunDetailincomponents/chat/steps.tsxshows the code and output, andSharedFiles(components/chat/shared-files.tsx) adds the download chips to the chat page and the page assistant.This is PR 3 of 3 for the code sandbox, after #473 (merged). It's based on main and includes the gateway PR's commits (PR 2, #495) until that merges, so review the commit
feat(chat): show the code sandbox's runs in the conversation and download the files it makes.Motivation and Context
Adeel asked for standard Python and bash tools for the chat app (chat gaps, module C7). This PR is the part people see: the code that ran, its output, and the files it made.
Screenshots (if appropriate):
n/a (a step that opens to code and output, and download chips on the answer, in the existing chat styles).
Steps to reproduce (if appropriate):
Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
sandboxprofile is C6's PR. Merging is the coordinator's call.outputandfileare in the SSE stream and inGET /api/v1/chats/{cid}. An agent downloads the file fromGET /api/v1/chats/{cid}/files/{fid}.chat_filetable is defined at start.ci/validate_threat_model.pypasses.outputorfileand show as before. The new table needs no migration.tests/api/test_sandbox.py(output on the step, live and saved; share and download; owner only; encrypted on disk; the cap; deletion with the conversation and the account).__tests__/sandbox-steps.test.tsxcovers the stream, the step and the chips.docs/assistant.md(Code sandbox) is updated, and the OpenAPI files come frommake openapi.cryptographywas already required).docs/assistant.md#code-sandbox: inline images and the cap as a setting.Local CI (
ci/local-ci.sh 7611ee8b --jobs backend,frontend,pre-commit,title): FastAPI pass (1025s), Next.js pass (277s), pre-commit pass (87s), PR title pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B