Skip to content

feat: OpenShell DeepAgent Sandbox Backend (Spec A) #892

@ryaneggz

Description

@ryaneggz

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 fileBaseSandbox 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

  • Load agent-browser skill with screenshots to validate E2E. This validates test assumptions for completion promise.

Acceptance Criteria

  • openshell>=0.0.10 added to backend/pyproject.toml
  • OPENSHELL_API_KEY, OPENSHELL_GATEWAY, OPENSHELL_SANDBOX_NAME constants added
  • OpenShellBackend(BaseSandbox) class created in backend/src/agents/openshell.py
  • resolve_sandbox_backend() fallback chain: Daytona → OpenShell → State
  • SandboxType.OPENSHELL enum value added
  • Error handling added in llm_invoke(), stream_generator(), _execute_agent_stream()
  • Frontend SandboxType union includes "openshell"
  • OpenShell option visible in settings/status dropdowns when OPENSHELL_API_KEY is set
  • All previous & new tests pass, validated using agent-browser CLI
  • New code follows existing repo/service/route patterns (e.g., BaseRepo, ServiceContext)
  • Related API and user documentation updated in the wiki repo (committed directly to wiki repo)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions