Metadata
IMPORTANT: The very first step should ALWAYS be validating this metadata section to maintain a CLEAN development workflow.
pull_request_title: "FROM feat/[issue#]-openshell-sandbox TO development"
branch: "feat/[issue#]-openshell-sandbox"
worktree_path: "$WORKSPACE/.worktrees/feat-[issue#]"
User Stories
- As a platform admin, I want OpenShell as a third sandbox backend option so that DeepAgents can execute tools in NVIDIA OpenShell sandboxes alongside Daytona and State.
- As a user, I want to select OpenShell from the sandbox dropdown in settings so that my agent runs execute in an OpenShell sandbox when I have it configured.
- As a developer, I want the auto-fallback chain to try Daytona → OpenShell → State so that the system gracefully degrades when a preferred sandbox is unavailable.
Summary
Add OpenShell as a third sandbox backend option alongside Daytona and State, following the exact existing provider patterns. The integration is "minimal" — it mirrors the Daytona integration structure: a conditional import guard, a factory function, registration in _SANDBOX_FACTORIES, extension of the auto-fallback chain, and exposure through the existing user settings and frontend sandbox selector. No new APIs, no new database columns, no new routes — just a new backend choice threaded through existing wiring.
Fallback chain (auto mode): Daytona → OpenShell → State
Visual Reference
- See
specs/spec-a-minimal-backend.md for full architecture details and code samples.
Key Integration Points
| File |
Function(s) |
Role |
backend/pyproject.toml |
dependencies |
Add openshell>=0.0.10 dependency |
backend/src/constants/__init__.py |
UserTokenKey, env vars |
Add OPENSHELL_API_KEY, OPENSHELL_GATEWAY, OPENSHELL_SANDBOX_NAME |
backend/src/agents/__init__.py |
create_openshell_backend(), resolve_sandbox_backend() |
Conditional import, factory, updated fallback chain |
backend/src/agents/openshell.py |
OpenShellBackend |
New file — BaseSandbox implementation wrapping OpenShell sessions |
backend/src/schemas/entities/settings.py |
SandboxType |
Add OPENSHELL enum value |
backend/src/controllers/llm.py |
llm_invoke() |
OpenShell error handling (mirrors Daytona pattern) |
backend/src/utils/stream.py |
stream_generator() |
OpenShell error handling (mirrors Daytona pattern) |
backend/src/workers/tasks.py |
_execute_agent_stream() |
OpenShell error handling (mirrors Daytona pattern) |
UI Integration Points
| Component / Route |
Change Type |
Description |
frontend/src/lib/services/userSettingsService.ts |
Modify |
Add "openshell" to SandboxType union |
frontend/src/lib/config/sandbox.ts |
Modify |
Add OpenShell to SANDBOX_OPTIONS, update normalizeSandboxValue() |
frontend/src/components/settings/SandboxSettings.tsx |
Modify |
Filter visibility on OPENSHELL_API_KEY provider key |
frontend/src/components/status/ThreadSandboxStatus.tsx |
Modify |
Filter visibility on OPENSHELL_API_KEY provider key |
Storage
- Persistence layer: Existing
UserSettings entity (LangGraph Store)
- Namespace / table:
(user_id, "settings") — default_sandbox field
- Model pattern: Extends existing
SandboxType enum — no new tables or columns
Architectural Decisions
- Source of truth: Backend
SandboxType enum validates allowed values; frontend mirrors the union type.
- State management: Existing React Query cache invalidation on user settings mutations.
- Auth / scoping: User-scoped via session
user_id. OPENSHELL_API_KEY is a sentinel (not a real API key) — OpenShell uses mTLS auth from ~/.config/openshell/.
- Sync gRPC calls:
openshell SDK uses synchronous gRPC. Acceptable for now (same pattern as Daytona's sync HTTP calls). Can wrap in asyncio.to_thread() as follow-up.
Documentation
- Full spec:
specs/spec-a-minimal-backend.md
- Reference implementation pattern:
backend/src/agents/daytona.py
Development Setup
Dependencies
| Service |
Address |
Notes |
| Redis |
localhost:6379 |
Docker container |
| Postgres |
localhost:5432 |
Docker container |
Environment Variables
| Variable |
Required |
Default |
Description |
OPENSHELL_API_KEY |
No |
None |
Sentinel to enable OpenShell in UI (set any truthy value) |
OPENSHELL_GATEWAY |
No |
None |
Override active OpenShell gateway cluster name |
OPENSHELL_SANDBOX_NAME |
No |
None |
Connect to pre-existing named sandbox |
Commands
# See package.json / Makefile for scripts
Wiki
⚠️ IMPORTANT: Wiki lives in a separate repo. Changes to @wiki must be committed directly to the wiki repo, not the main project repo.
Design Principles
- Simplicity is beauty, complexity is pain.
- ALWAYS look at the current codebase first — achieve the goal in the least amount of changes.
- TDD-first: write tests before implementation.
- Follow the Daytona provider pattern exactly — no new abstractions.
Validation Tools
Acceptance Criteria
Metadata
User Stories
Summary
Add OpenShell as a third sandbox backend option alongside Daytona and State, following the exact existing provider patterns. The integration is "minimal" — it mirrors the Daytona integration structure: a conditional import guard, a factory function, registration in
_SANDBOX_FACTORIES, extension of the auto-fallback chain, and exposure through the existing user settings and frontend sandbox selector. No new APIs, no new database columns, no new routes — just a new backend choice threaded through existing wiring.Fallback chain (auto mode): Daytona → OpenShell → State
Visual Reference
specs/spec-a-minimal-backend.mdfor full architecture details and code samples.Key Integration Points
backend/pyproject.tomlopenshell>=0.0.10dependencybackend/src/constants/__init__.pyUserTokenKey, env varsOPENSHELL_API_KEY,OPENSHELL_GATEWAY,OPENSHELL_SANDBOX_NAMEbackend/src/agents/__init__.pycreate_openshell_backend(),resolve_sandbox_backend()backend/src/agents/openshell.pyOpenShellBackendBaseSandboximplementation wrapping OpenShell sessionsbackend/src/schemas/entities/settings.pySandboxTypeOPENSHELLenum valuebackend/src/controllers/llm.pyllm_invoke()backend/src/utils/stream.pystream_generator()backend/src/workers/tasks.py_execute_agent_stream()UI Integration Points
frontend/src/lib/services/userSettingsService.ts"openshell"toSandboxTypeunionfrontend/src/lib/config/sandbox.tsSANDBOX_OPTIONS, updatenormalizeSandboxValue()frontend/src/components/settings/SandboxSettings.tsxOPENSHELL_API_KEYprovider keyfrontend/src/components/status/ThreadSandboxStatus.tsxOPENSHELL_API_KEYprovider keyStorage
UserSettingsentity (LangGraph Store)(user_id, "settings")—default_sandboxfieldSandboxTypeenum — no new tables or columnsArchitectural Decisions
SandboxTypeenum validates allowed values; frontend mirrors the union type.user_id.OPENSHELL_API_KEYis a sentinel (not a real API key) — OpenShell uses mTLS auth from~/.config/openshell/.openshellSDK uses synchronous gRPC. Acceptable for now (same pattern as Daytona's sync HTTP calls). Can wrap inasyncio.to_thread()as follow-up.Documentation
specs/spec-a-minimal-backend.mdbackend/src/agents/daytona.pyDevelopment Setup
Dependencies
localhost:6379localhost:5432Environment Variables
OPENSHELL_API_KEYNoneOPENSHELL_GATEWAYNoneOPENSHELL_SANDBOX_NAMENoneCommands
# See package.json / Makefile for scriptsWiki
Design Principles
Validation Tools
agent-browserskill with screenshots to validate E2E. This validates test assumptions for completion promise.Acceptance Criteria
openshell>=0.0.10added tobackend/pyproject.tomlOPENSHELL_API_KEY,OPENSHELL_GATEWAY,OPENSHELL_SANDBOX_NAMEconstants addedOpenShellBackend(BaseSandbox)class created inbackend/src/agents/openshell.pyresolve_sandbox_backend()fallback chain: Daytona → OpenShell → StateSandboxType.OPENSHELLenum value addedllm_invoke(),stream_generator(),_execute_agent_stream()SandboxTypeunion includes"openshell"OPENSHELL_API_KEYis setagent-browserCLI