Skip to content

feat: support provider attach and detach for running sandboxes #1171

Description

@johntmyers

Problem Statement

Issue #1081 adds a custom provider profile registry, but sandbox provider attachments are still effectively fixed at sandbox creation time. The next provider v2 chunk from #896 should let operators and agents attach or detach providers from a running sandbox and have the effective policy update reflect the sandbox's current provider set.

This is the runtime lifecycle step needed after profile import/export: create or import a profile, create a provider of that profile type, attach that provider to a sandbox, verify the profile-generated policy layer appears, detach it, and verify the effective policy returns to the previous state.

Credential injection needs an explicit boundary in this issue. Attaching a provider to a running sandbox should affect new process/session launches that resolve provider env from the sandbox's current attachments, but it should not attempt to mutate the environment of already-running processes. Existing long-running harnesses such as Codex, Claude, or OpenClaw may need to be relaunched by the user to observe newly attached provider credentials.

Related: #896, #1081, #1170

Proposed Design

Add a gateway and CLI surface for mutating a sandbox's attached provider list after creation. The attachment should be provider-record based, not a raw profile id, because the current model stores providers separately from profiles and derives profile policy from Provider.type.

Suggested CLI shape:

openshell sandbox provider list <sandbox>
openshell sandbox provider attach <sandbox> <provider-name>
openshell sandbox provider detach <sandbox> <provider-name>

Equivalent API shape could be explicit RPCs such as:

ListSandboxProviders
AttachSandboxProvider
DetachSandboxProvider

or an update operation with attach/detach semantics, as long as it avoids accidental full replacement of the provider list.

Expected behavior:

  • Attach validates that the sandbox exists.
  • Attach validates that the provider exists.
  • Attach is idempotent when the provider is already attached.
  • Detach validates that the sandbox exists.
  • Detach is idempotent or returns a clear not-attached result; choose the convention that best matches nearby APIs.
  • Attach/detach updates the persisted sandbox provider list using the existing sandbox object/schema.
  • Effective policy composition remains JIT and reads the current provider list from storage.
  • When providers_v2_enabled is true, attaching a provider whose Provider.type maps to a built-in or custom profile adds that profile-generated policy layer on the next running policy/config refresh.
  • Detaching that provider removes the profile-generated policy layer on the next running policy/config refresh.
  • When providers_v2_enabled is false, attach/detach should update the provider list without adding provider-profile policy layers.
  • Attach/detach must not mutate environment variables for already-running sandbox processes.
  • New sandbox exec/shell/harness process launches after attach should resolve provider env from the current persisted sandbox provider list.
  • New sandbox exec/shell/harness process launches after detach should not receive provider env for the detached provider.
  • Existing credential injection remains the mechanism in both modes for this issue. Do not introduce profile-defined credential injection here.
  • If the current launch path only resolves provider env once at sandbox startup, add the smallest refresh/fetch point needed so future process launches observe attach/detach without requiring a full sandbox restart.

Running sandbox behavior is the key acceptance path. If the supervisor already refreshes policy/config periodically or on demand, use that mechanism. If not, add the smallest gateway-to-supervisor notification or refresh trigger needed so a running sandbox observes provider attachment changes without restart. The same current-attachment invariant should be used for future process/session launches that perform provider env resolution.

Alternatives Considered

  • Attach raw profile ids to sandboxes. This does not match the current schema or provider credential model; sandboxes attach provider names, and profile policy is derived through Provider.type.
  • Fold this into profile import/export. That would make feat: support custom provider profile import and export #1081 too broad and mix registry management with running sandbox lifecycle behavior.
  • Mutate environment variables for already-running processes. This is not reliable for normal Unix process environments and would create confusing behavior for long-running agent harnesses.
  • Add a harness restart primitive in this issue. That may be useful later, but attach/detach can be shipped with clear semantics for new process/session launches first.
  • Wait for profile-defined credential injection. Policy attach/detach and the existing credential-injection boundary can be validated independently now, while profile-defined credential injection can remain a later provider v2 chunk.

Definition of Done

  • Gateway exposes an attach provider operation for existing sandboxes.
  • Gateway exposes a detach provider operation for existing sandboxes.
  • Gateway exposes a way to list providers attached to a sandbox, or reuses an existing sandbox get/list response clearly enough for CLI UX.
  • Attach validates sandbox existence and provider existence.
  • Attach/detach preserve existing sandbox fields and mutate only the attached provider list.
  • Attach/detach do not create duplicate provider entries.
  • Attach/detach semantics are documented in API/handler tests, including idempotency or clear errors.
  • CLI supports attaching a provider to a sandbox.
  • CLI supports detaching a provider from a sandbox.
  • CLI supports listing attached providers for a sandbox or clearly shows attachments through an existing command.
  • With providers_v2_enabled=false, attach/detach does not add provider-profile policy layers.
  • With providers_v2_enabled=true, attaching a provider backed by a built-in profile adds that provider profile policy layer on the next effective policy/config fetch.
  • With providers_v2_enabled=true, attaching a provider backed by a custom profile adds that custom provider profile policy layer on the next effective policy/config fetch.
  • Detaching a provider removes that provider profile policy layer on the next effective policy/config fetch.
  • Test coverage includes the full lifecycle: create/import profile, create provider, create sandbox, capture baseline effective policy, attach provider, verify policy includes provider layer, detach provider, verify policy returns to baseline.
  • Running sandbox behavior is covered by an integration test or the closest available supervisor/config-refresh test.
  • Existing provider credential injection behavior is unchanged by this issue.
  • Existing running process environments are not mutated by attach/detach.
  • New sandbox exec/shell/harness process launches after attach resolve provider env from current sandbox attachments.
  • New sandbox exec/shell/harness process launches after detach do not receive provider env for the detached provider.
  • Tests document the credential-env boundary: already-running process env is unchanged where testable, while future launches use current attachments.

Non-Goals

  • Do not implement profile-defined credential injection in this issue.
  • Do not implement OAuth refresh or token-store refresh in this issue.
  • Do not mutate environment variables of already-running sandbox processes.
  • Do not add a harness restart/relaunch primitive in this issue.
  • Do not add a new sandbox profile attachment field unless the existing provider-name attachment model proves insufficient.
  • Do not persist composed effective policy revisions; effective policy should remain derived from current inputs.
  • Do not change default provider behavior when providers_v2_enabled is false.
  • Do not update published docs or architecture docs in this issue; docs can be swept separately.

Agent Investigation

PR #1170 establishes the custom profile registry and confirms custom profiles participate in JIT policy composition when referenced by the current sandbox provider list. The missing lifecycle surface is mutating that provider list after sandbox creation and proving a running sandbox observes policy changes when providers are attached or detached.

Credential injection currently has process-environment constraints independent of provider profiles. This issue should preserve the current injection mechanism, make future process/session launches consult the current sandbox attachments, and explicitly avoid promising runtime env mutation for processes that are already running.

Activity

  1. johntmyers commented on May 7, 2026

    @johntmyers
    CollaboratorAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: feat
    Complexity: Medium
    Confidence: High — the sandbox already stores provider attachments in SandboxSpec.providers, and effective policy/provider env resolution already reads that list JIT.

    Summary

    Add provider attach/detach/list lifecycle support for existing sandboxes. The implementation will mutate the persisted sandbox spec.providers list, rely on the existing JIT effective-policy and provider-env fetch paths to observe the current attachments, and keep the credential boundary explicit: already-running process environments are not mutated, while future process/session launches resolve from the current sandbox attachments.

    Scope

    • proto/openshell.proto: add sandbox provider list/attach/detach RPCs plus request/response messages.
    • crates/openshell-server/src/grpc/mod.rs: wire new RPCs into the OpenShell service.
    • crates/openshell-server/src/grpc/sandbox.rs: implement validation, idempotent provider attachment mutation, provider existence checks, duplicate avoidance, and list behavior using the existing sandbox object/schema.
    • crates/openshell-server/src/auth/authz.rs: add authz mappings for the new RPCs, using sandbox read/write scopes consistent with nearby sandbox lifecycle operations.
    • crates/openshell-cli/src/main.rs: add openshell sandbox provider list|attach|detach CLI shape.
    • crates/openshell-cli/src/run.rs: call the new RPCs and render concise user-facing output.
    • CLI integration test stubs under crates/openshell-cli/tests/: update fake OpenShell services for the new generated trait methods and add focused CLI coverage where practical.
    • Gateway unit tests in crates/openshell-server/src/grpc/sandbox.rs and/or policy tests in crates/openshell-server/src/grpc/policy.rs: cover attachment mutation, policy composition effects, and provider env resolution from the current provider list.

    Implementation Steps

    1. Extend protobuf API with ListSandboxProviders, AttachSandboxProvider, and DetachSandboxProvider, then update generated bindings through the normal build path.
    2. Implement gateway handlers that fetch sandboxes by name, validate provider existence for attach, mutate only spec.providers, preserve sandbox metadata/status/policy, and persist through the existing object store.
    3. Wire authz/service dispatch and add gateway tests for validation, idempotency, duplicate avoidance, and detach/list behavior.
    4. Add CLI subcommands under openshell sandbox provider and implement list/attach/detach output.
    5. Add tests proving JIT policy/env behavior uses the current persisted attachments: attach adds provider profile policy when v2 is enabled, detach removes it, v2 disabled does not add provider-profile policy, and provider env resolution reflects the updated provider list for future launches.
    6. Update generated test service implementations and CLI integration tests for the new API surface.

    Test Plan

    • Unit tests: Gateway handler tests for attach, detach, list, missing sandbox, missing provider, idempotent attach, duplicate avoidance, and preservation of existing sandbox fields.
    • Policy/config tests: Effective policy tests showing attach/detach changes provider profile layers through current sandbox attachments when providers_v2_enabled=true, and no provider-profile layer when false.
    • Credential boundary tests: Provider env tests showing resolution reads the current sandbox attachment list after attach/detach; no attempt is made to mutate already-running process env.
    • CLI tests: Argument parsing and/or integration tests for openshell sandbox provider list|attach|detach calling the expected RPCs and surfacing clear output/errors.
    • E2E tests: N/A unless implementation needs to modify existing e2e/ coverage; the behavior can be covered with gateway and CLI integration tests against fake services/object stores.

    Risks & Open Questions

    • The generated gRPC trait change will require updating every fake OpenShell service in tests; this is mechanical but easy to miss.
    • Exact detach semantics should follow nearby API style. The proposed default is idempotent detach with a clear success response, since attach is explicitly idempotent and repeated lifecycle commands should be script-friendly.
    • If existing exec/session launch code only obtains provider env once at sandbox startup, this issue should add the smallest fetch/refresh point for future launches, but it should not add a harness restart primitive.

    Documentation Impact

    • None for this issue. The issue explicitly excludes published docs and architecture docs; docs can be swept separately.

    Revision 1 — initial plan

  2. moved this from Todo to In progress in OpenShell Roadmapon May 7, 2026
  3. johntmyers commented on May 7, 2026

    @johntmyers
    CollaboratorAuthor

    Implemented in PR #1242: #1242

    Build notes:

    • Added sandbox provider list/attach/detach gRPC APIs and CLI commands.
    • Attach/detach update persisted sandbox provider attachments, so JIT effective policy and provider environment resolution follow the current attachment set.
    • Attach is idempotent and validates the provider exists; detach is idempotent and can remove stale attachments even if the provider record no longer exists.
    • Docs/architecture updates intentionally left out of this scope.

    Verification:

    • RUSTC_WRAPPER= cargo check -p openshell-server -p openshell-cli
    • RUSTC_WRAPPER= cargo test -p openshell-server attach_sandbox_provider --lib
    • RUSTC_WRAPPER= cargo test -p openshell-server detach_sandbox_provider --lib
    • RUSTC_WRAPPER= cargo test -p openshell-server list_sandbox_providers_returns_attached_provider_records --lib
    • RUSTC_WRAPPER= cargo test -p openshell-server sandbox_config_and_provider_env_follow_attached_provider_lifecycle --lib
    • RUSTC_WRAPPER= cargo test -p openshell-cli sandbox_provider_subcommands_parse --bin openshell
    • RUSTC_WRAPPER= cargo clippy -p openshell-server --lib --tests -- -D warnings
    • RUSTC_WRAPPER= mise run pre-commit
  4. added
    state:pr-openedPR has been opened for this issue
    and removed on May 7, 2026
  5. johntmyers commented on May 7, 2026

    @johntmyers
    CollaboratorAuthor

    Updated PR #1242 with the provider credential refresh blocker fix.

    What changed:

    • Added provider_env_revision to sandbox config and provider environment responses.
    • Added sandbox-side generation-scoped credential snapshots. Future SSH/exec/SFTP launches read the latest snapshot; already-running processes keep their launch-time env.
    • Updated proxy credential resolution to retain recent generations so old placeholders remain resolvable for already-running processes.
    • Blocked provider deletion while attached to any sandbox to prevent stale references.
    • Added focused tests for revision changes, attach/detach env lifecycle, generation-scoped placeholders, delete blocking, and authz scopes.

    Verification:

    • RUSTC_WRAPPER= mise run pre-commit
  6. moved this from In progress to Done in OpenShell Roadmapon May 8, 2026
  7. added a commit that references this issue on May 8, 2026
    1d3b741
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions