Skip to content

docs(rfc): add RFC 0018 for multi-sandbox and HA supervisors - #4377

Draft
FrostGod wants to merge 8 commits into
NVIDIA:mainfrom
FrostGod:docs/4311-multi-sandbox-ha-supervisors-rfc/FrostGod
Draft

FrostGod wants to merge 8 commits into
NVIDIA:mainfrom
FrostGod:docs/4311-multi-sandbox-ha-supervisors-rfc/FrostGod

Conversation

@FrostGod

@FrostGod FrostGod commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds RFC 0018, which proposes four ways to deploy supervisors: per workspace, per node, per sandbox, and per gateway. It sets out one shared set of safety rules for replacing a failed supervisor (fencing), and a plan to compare the four models and choose one.

Related Issue

Refs #4311. This PR only adds the RFC and changes no code. It also builds on #3661 and #2385.

Changes

  • Adds rfc/0018-multi-sandbox-ha-supervisors/README.md with goals, the four proposed models, and the shared rules (fenced recovery, ownership epoch, leases).
  • Describes how the models behave on failure and how to compare them, and lists the risks, alternatives, and open questions.

Testing

Docs-only change, so there are no unit or E2E tests. The RFC links, the Mermaid diagrams, and the headings are checked by eye. The full mise run pre-commit has not completed on this branch: the first run failed on a local uv environment error unrelated to the RFC, and the rerun did not start.

  • Checks appropriate to the affected code and behavior pass
  • Unit tests added/updated (if applicable)
  • E2E tests added/updated (if applicable)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable)

@copy-pr-bot

copy-pr-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@FrostGod
FrostGod marked this pull request as draft October 9, 2026 16:20
Propose four supervisor deployment models (per workspace, per node, per sandbox, per gateway) with shared safety rules for fenced recovery, and set out how to compare them and choose one. Refs NVIDIA#4311.

Signed-off-by: divesh <dgude@nvidia.com>
@FrostGod
FrostGod force-pushed the docs/4311-multi-sandbox-ha-supervisors-rfc/FrostGod branch from 65bc1ef to 21a0f97 Compare October 9, 2026 16:53
@mayawang

mayawang commented Oct 9, 2026

Copy link
Copy Markdown

Thanks for opening this. Some input from the Agent Substrate side, mostly on one case the RFC doesn't cover yet: a backend suspending a sandbox on purpose.

Suspended sandboxes

On Agent Substrate an idle sandbox is suspended and uses no compute, and it can resume later, possibly on a different node. The RFC's resume path is about replacing a failed supervisor. For a backend-initiated suspend, we'd hope for the following:

  • While a sandbox is suspended, it holds no supervisor session. Otherwise sleeping sandboxes still take supervisor capacity, and reconnect attempts wake them back up (feat(supervisor): hold reconnect while a sandbox is suspended by its backend #4352).
  • On resume, the sandbox can be assigned to any healthy replica, with fresh authority. The fenced recovery flow in section 3 looks close: suspend could be a planned release, and resume a new assignment.
  • The runtime's reconnect window doesn't count time spent suspended. In our prototype it only started counting after resume, which worked well.
  • Binary-identity pins and policy revisions survive a suspend, in line with section 4. On a backend that snapshots memory, they come back with the sandbox.

Placement

Our lean is a pool that isn't tied to nodes: one HA deployment per install, or per gateway group, scaled horizontally, with sessions assignable to any replica. Per node fits a suspend-capable backend poorly, because sandboxes move between nodes on resume and many nodes would mostly hold sleeping sandboxes. Per workspace tends to split capacity.

Capacity

If suspended sandboxes release their sessions, the number to size supervisors for on a backend like ours is active sandboxes, not total sandboxes.

Happy to walk through what we saw in our prototype, and to review as the comparison of the four models takes shape.

@FrostGod

FrostGod commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

#4377 (comment)

will take this into consideration and update the RFC, thanks @mayawang

@EmilienM EmilienM left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction makes sense to me. The main gaps are things that exist today and would stop or still authorize the old supervisor (driver reconciler, sandbox bearer, launch Secrets, namespace fence), plus how the lease gets renewed. Details inline.

Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Comment thread rfc/0018-multi-sandbox-ha-supervisors/README.md Outdated
Rewrite the proposal in plain language and shorten it. Address review notes on defaults, readiness, relay caps, the network fence, node drain, the driver reconciler, and token fencing on takeover.

Signed-off-by: divesh <dgude@nvidia.com>
@FrostGod

Copy link
Copy Markdown
Collaborator Author

Thanks @mayawang. Suspend isn't supported in OpenShell yet, so I've listed backend-initiated suspend (#4352) as a separate non-goal, but the design leaves scope for it as a planned release plus a fresh assignment on wake. I also noted that per-node fits poorly with backends that move sandboxes, and that capacity should be sized by active sandboxes.

Trim the proposal without dropping content. Note backend-initiated suspend as a separate task, per-node fit with backends that move sandboxes, and sizing by active sandboxes.

Signed-off-by: divesh <dgude@nvidia.com>

@EmilienM EmilienM left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there, thanks for the quick turnaround. 9 of the 13 threads are fixed in the text, so go ahead and resolve those.

Two small things before I approve:

  • L147, see inline.
  • The four items you're leaving for the prototype (per-node binding, per-replica vs per-sandbox lease, launch material delivery, lease renewal path) aren't in the RFC yet. Could you list them under Open questions so they don't get lost?

Also I think this would be a feature behind a k8s feature gate, maybe worth documenting.


`detect + fence + start candidate + restore state + confirm + margin < 30 s`

If PostgreSQL is down, no takeovers or lease renewals happen. Running sandboxes continue until their lease ends, then freeze and stop, trading availability for safety. The database must never reuse an epoch, even after failover or restore.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today a PostgreSQL outage doesn't stop running sandboxes (RFC 0012 treats gateway loss as a reconnect, not supervisor loss). Can you scope the lease to resume-enabled runs here, like we discussed in the lease thread? Otherwise a long DB outage stops every opted-in sandbox.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the lease applies only to runs with resume enabled and is checked only for takeovers. The runtime's freeze-on-disconnect is the fence, so a PostgreSQL outage never stops running sandboxes; only takeovers wait.

Signed-off-by: Dhiraj Bokde <dbokde@nvidia.com>
docs(rfc): define sandbox workload identity for shared supervisors

The exchange protocol must specify the SVID form, intended audience, and proof required by each provider. For bearer-token exchange, the client obtains a JWT-SVID for the intended recipient; an X.509-SVID certificate alone is not proof of possession. X.509 private keys MUST remain in the Sandbox Pod and must never be forwarded to the Supervisor.

Identity context and exchanged credential caches remain scoped to the sandbox/run, provider, and audience. The client refreshes the SVID before expiry and forwards updates to the active Supervisor. After fenced handover, the client supplies a current SVID to the replacement Supervisor, which validates and binds it to the new session and epoch before fetching fresh credentials. Missing, expired, or mismatched identity must fail closed for operations requiring it, without falling back to another sandbox's identity or the Supervisor's identity. Workload identity does not replace the recovery grant or fencing rules in section 3.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JWT-SVIDs should be short lived. Might it make sense for the supervisor to pull them rather than for the sandbox to push them? I.e. when the supervisor needs a new JWT-SVID, it makes a call to the sandbox which then retrieves the new token from the SPIFFE workload API and returns it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. FrostGod/OpenShell#2 proposes on-demand retrieval of short-lived, audience-specific JWT-SVIDs, with caches bounded by expiry and sandbox/run/ownership. Authorized middleware can use the same interface to inject an SVID into a configured X-Workload-SVID header for an admitted destination, replacing any agent-supplied value.

@EmilienM EmilienM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another iteration needed, mostly for the new section 5 (cc @dhirajsb). Details inline. L147 and the open questions from my last review still stand.

New->>RT: Present grant
RT->>RT: Reject epoch e, close old channels
RT-->>New: Signed receipt and saved state
New->>GW: Fetch policy, send receipt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With config push (#4320, the only mode after #4498), policy and provider credentials flow down the already-open session, so rotating the token (L105) no longer cuts the old owner off from them. Worth a line here: the epoch CAS closes any older-epoch session, and this step becomes a pushed bootstrap that the candidate acks on a session at the reserved epoch (config only, no relays) before activation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a paragraph covering both config modes. In poll mode the new token ID cuts off the old owner. In push mode the epoch CAS also closes every older-epoch session. In both, the new supervisor acknowledges a config-only setup push at the reserved epoch before activation.


Sharing a supervisor must preserve each sandbox's workload identity. For Kubernetes deployments using pod-specific SPIFFE Verifiable Identity Documents (SVIDs), the identity issuer must distinguish individual Sandbox Pods, even when they share a service account. The shared proxy Supervisor Pod's service identity does not represent the workloads it supervises.

The gRPC client running in the Sandbox Pod MUST obtain that Pod's SVID through the [SPIFFE Workload API](https://spiffe.io/docs/latest/deploying/svids/) Unix domain socket (UDS) exposed locally in the Sandbox Pod and pass the SVID over the authenticated gRPC connection to the Supervisor running in the shared proxy Supervisor Pod. Access to the UDS must preserve workload attestation so the client receives only identities authorized for its Sandbox Pod.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This puts the Workload API socket in the Pod where the agent runs, with the same UID as the runtime, so attestation can't keep the agent from minting JWT-SVIDs for any audience. The K8s driver strips spiffe-workload-api from the workload Pod today on purpose. Could the shared supervisor fetch them instead (SPIRE's Delegated Identity API)? I'd also scope this section to shared models, since per-sandbox already works. Small thing: nothing in the Sandbox Pod is a gRPC client today, the supervisor dials the runtime.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPIRE's Delegated Identity API works at the Agent scope i.e. at node scope on kubernetes. So you can't - as far as I know - allow a given supervisor to only retrieve SVIDs for the sandbox pods it is serving.

Though not ideal, the agent requesting SVIDs for any given audience is not in itself necessarily a problem. The SVID merely proves the identity of the workload that retrieved it. It is for accessed services to verify the SVIDs and determine if that workload is indeed authorized. The network restrictions also only allow the agent to communicate out through the supervisor, so it is only approved endpoints that can be reached.

Another problem with having the Workload API mounted on the sandbox pod though is when kata containers are used as the SPIRE socket isn't accessible from inside the VM.

The SPIFFE Broker API could be an option but it is still 'experimental'.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the current broker in the supervisor be extended to prevent the agent from making connect) calls to domain sockets (while allowing itself to do so)? That could restrict the agent from accessing the Workload API, but would also prevent use of any other domain sockets. Is that too restrictive? Are there legitimate cases where an agent should be allowed to access a socket?

(Still does not solve the kata problem though)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking all Unix sockets would break things agents rely on, such as language servers, tmux and ssh-agent. Blocking only the SPIFFE socket isn't safe either, because the agent can change the socket path after we check it. Simplest is to keep the socket out of the agent's view, as we do today, and let the runtime or supervisor fetch SVIDs for it. We'll pick the approach during implementation.

let me get to the kata part

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed: keep the Workload API socket outside the agent's view and leave the acquisition backend to implementation. FrostGod/OpenShell#2 scopes section 5 to shared supervisors, preserves the supervisor-initiated runtime channel, and flags node-wide delegation for separate authorization review.

Kata's host-to-guest UDS limitation is independent of supervisor topology. For Teleport, a bot sidecar inside the Kata VM can expose a guest-local socket, provided issuance is sandbox-scoped and the socket, credentials, and configuration are isolated from the agent. This remains a deployment option to validate.

Provider-->>Supervisor: Credentials scoped to that sandbox
```

The Supervisor MUST validate the SVID against the configured trust bundle and expected workload identity, including validity and audience where applicable, and bind it to the authenticated sandbox/run session and current ownership epoch. A caller-supplied sandbox ID alone cannot establish that binding. The validated Sandbox SVID MUST be used as the workload identity for dynamic Credential Provider exchange and other identity-dependent operations performed on behalf of that sandbox. The Supervisor must not substitute its own SVID: doing so would collapse distinct sandbox identities into one shared identity and lose per-sandbox authorization and attribution.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gateway needs this check too. validate_supervisor_jwt_svid checks issuer, audience and trust domain, but not that sub is this sandbox. That's OK while each supervisor can only fetch its own SVID. Once one process holds many, a mix-up can hand one sandbox's credentials to another, so let's have the gateway match sub against the authenticated sandbox.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. FrostGod/OpenShell#2 requires the gateway to independently validate the SVID and match sub to the authenticated sandbox using trusted control-plane state, with use bound to the current run and ownership epoch.

@FrostGod

Copy link
Copy Markdown
Collaborator Author

will be addressing the comments today, was not online yesterday

Signed-off-by: divesh <dgude@nvidia.com>
@FrostGod
FrostGod force-pushed the docs/4311-multi-sandbox-ha-supervisors-rfc/FrostGod branch from cd5e6ae to c3d6029 Compare October 16, 2026 06:41
Signed-off-by: Dhiraj Bokde <dbokde@nvidia.com>
…y/dhirajsb

docs(rfc): clarify workload identity for shared supervisors

This branch has not been deployed

No deployments
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.

5 participants