Repository navigation
Conversation
Draft proposal for letting agents create sub-agent and tool sandboxes through a gateway-hosted MCP endpoint, with user-set grants, lineage, prover-checked ceilings and a supervisor door. RFC number to be assigned from the originating issue. Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…olicy as a maximum; host callers act as the user; MCP on by default Also adds sandboxes-from-sandboxes use cases (RL environments) and future work on returning results and agent-to-agent communication. Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…/2 proxy, or gRPC-Web/Connect) Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
… heading Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…nt profiles as future work Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…e 2, grant tools and token audience Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…lags; grant get Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…esult collection - grant create replaces a revoked or expired grant for the running tree. - Grant listing and changes are limited to the grant's user or a Workspace Admin, checked before any change. - request_id is scoped to the parent and reserved with the slot; duplicates get IN_PROGRESS; limits are charged atomically. - The walkthrough collects a result with an explicit done signal. - The proof-of-concept paragraph points to the commit-specific result. Signed-off-by: patkraw <pkrawczuk@nvidia.com>
…nters, revocation and production gaps Signed-off-by: patkraw <pkrawczuk@nvidia.com>
Signed-off-by: patkraw <pkrawczuk@nvidia.com>
|
All contributors have signed the DCO ✍️ ✅ |
|
I have read the DCO document and I hereby sign the DCO. |
…r and why tool sandboxes skip depth Signed-off-by: patkraw <pkrawczuk@nvidia.com>
| the gateway proves the child's policy against the parent's current policy, | ||
| without provider layers. The lead's children are bounded by the lead's policy, | ||
| its grandchildren by its children's policies, and so on, so each level can only | ||
| narrow: `child ≤ parent ≤ global maximum`, the model in the spike on agents |
There was a problem hiding this comment.
I have a few thoughts around the policy layers here.
Let's assume an use case like this:
There is a lead sandbox running harness and a child sandbox using tools (e.g. send an email).
I want my child sandbox to have access to sending emails, but I actually wouldn't want to have the parent to have that permission. If the parent has that permission, it means it could either send the email itself, or delegate it to the child, right?
In this model, you couldn't tell what the parent actually needs vs permissions that are in parent's policy only because its children need it (and removing a permission from a child would need to propagate to the parent?).
I wonder if there are other ways to organize this.
One thought is that what this RFC describes is another way of grouping sandboxes, today we have global, workspace, sandbox. With this we have global, workspace, tree, sandbox. Perhaps there is a way to represent this "maximum tree policy" and "current tree policy" on the tree level?
Then, if the child requests permissions, we don't evaluate them through the parent's policy, but through tree's policy.
That being said, the "parent's policy is a superset of all childred" is simpler to reason about, so that may be the best option after-all, but I'm also curious if other options were explored. CC @johnnygreco @vishwa-raman
There was a problem hiding this comment.
This is a good question and is something we've debated a bit. My take is we should focus first on getting the functionality in with as little friction as possible. For example, trying to check that the child's policy is least-privilege would be best practice but still requires research.
In my mind the simplest approach is the child inherits the parents policy by default but can have a less permissive policy (which our policy prover can guarantee). A child can never have permission to do something the parent can't do. If a child needs more permissions, the parent needs to get them first.
^ This isn't the endgame, but feels defensible and is easy to reason about.
The question of grouping sandboxes is interesting. I think the idea of a "tree" could be useful as a generic framing, since this will be used for more than spawning subagents specifically.
| sync from the supervisor, or an approved proposal. A sandbox in a tree can | ||
| never be widened past its ceiling after it starts. | ||
|
|
||
| **Global policy as a maximum.** A gateway-global policy is meant to state what |
There was a problem hiding this comment.
I feel like the maximum policy and one of use cases for the global policy (i.e. kill switch to limit all running sandboxes), these 2 should be separate.
With this model, if you set the global_policy_mode to maximum, would all policies of running sandboxes be updated to adhere to that maximum?
Also, I could see value of setting maximum policy on a workspace level. It could be worth checking if there are any indications this would be useful, perhaps global maximum policy is what's needed.
| ```proto | ||
| service SandboxChildren { | ||
| rpc CreateChildSandbox(CreateChildSandboxRequest) returns (CreateChildSandboxResponse) { | ||
| option (authorization) = { auth_mode: "dual" workspace_role: "user" scope: "sandbox:write" }; | ||
| } | ||
| rpc WaitChildSandbox(WaitChildSandboxRequest) returns (WaitChildSandboxResponse) { | ||
| option (authorization) = { auth_mode: "dual" workspace_role: "user" scope: "sandbox:read" }; | ||
| } | ||
| rpc ExecChildSandbox(ExecChildSandboxRequest) returns (ExecChildSandboxResponse) { | ||
| option (authorization) = { auth_mode: "sandbox" }; | ||
| } | ||
| rpc GetChildSandboxFile(GetChildSandboxFileRequest) returns (GetChildSandboxFileResponse) { | ||
| option (authorization) = { auth_mode: "sandbox" }; | ||
| } | ||
| rpc StopChildSandbox(StopChildSandboxRequest) returns (StopChildSandboxResponse) { | ||
| option (authorization) = { auth_mode: "dual" workspace_role: "user" scope: "sandbox:write" }; | ||
| } | ||
| rpc ListChildSandboxes(ListChildSandboxesRequest) returns (ListChildSandboxesResponse) { | ||
| option (authorization) = { auth_mode: "dual" workspace_role: "user" scope: "sandbox:read" }; | ||
| } | ||
| rpc GetSandboxLimits(GetSandboxLimitsRequest) returns (GetSandboxLimitsResponse) { | ||
| option (authorization) = { auth_mode: "dual" workspace_role: "user" scope: "sandbox:read" }; | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Alternative approach for most of this API would be to utilize the existing rpcs (e.g. CreateSandbox, ExecSandbox). The authorization layer could then determine limits around what's allowed in a given context.
E.g. if the way to request a child sandbox is different enough from creating a regular sandbox, having separate APIs may be the right approach and it may make it easier to control rules that govern both approaches. At the same time, new APIs come with a cost, we'd need to figure out if/how to include these in the gateway interceptors, etc.
| with an index on `parent_sandbox_id` for cascade stop. A child's workspace is | ||
| always its parent's. | ||
|
|
||
| Lineage is deliberately a separate record rather than an owner field on the |
There was a problem hiding this comment.
I'm curious about this one: why wouldn't we expand the sandbox record? Do we need to record the owner, if our authorization works on a workspace level anyways?
| ### Grants | ||
|
|
||
| A grant is a budget for one tree of sandboxes, set by the user. It answers four | ||
| questions: may this tree create sandboxes at all, how large may it grow, which | ||
| program may ask, and whether agents may write their children's policies | ||
| (`child_policy`). What a child may be given is not part of the grant: every | ||
| child is bounded by its parent's policy (see | ||
| [Ceiling and policy proof](#ceiling-and-policy-proof)). |
There was a problem hiding this comment.
I'm wondering about the naming here.
As I read it, the idea here is to indicate that this sandbox can be a root of a tree, right? And if yes, what are the limits.
It feels like this could be seen as sandbox policy, but for control plane (where our existing sandbox policy is for data plane).
The idea here is to answer questions like: can this sandbox create sandboxes, what's allowed depth of the tree. Perhaps it could also answer "what's the maximum policy for this sandbox"?
Perhaps grant could work, but I feel like openshell sandbox create --grant-... isn't very clear on what that grant is for. In my mind the clearest would be something like openshell sandbox create --tree-policy ... or --subagent-policy or --child-policy (this is too overloaded).
There was a problem hiding this comment.
+1 that --grant isn't explicit enough. I had the same reaction @pimlock. Perhaps we should seriously consider "tree" as a noun here.
There was a problem hiding this comment.
Could it suffice to just have tree-depth as one parameter in the create command? Like openshell sandbox create --tree-depth 3.
If it's set to 0, the sandbox can't spawn other sandboxes. And I think we should allow all parents to propose the child's sandbox.
| - **Harnesses work around this with host services.** Prototypes for Pi and | ||
| OpenWorker run a service on the host that holds a user credential and creates | ||
| sandboxes when an agent asks. Each such service is harness-specific, keeps its | ||
| security checks outside OpenShell, and becomes a second source of truth for | ||
| who may create what. Operators have to trust and audit each one separately. |
There was a problem hiding this comment.
How would this MCP work in terms of integrating with harnesses?
I can imagine that harnesses have a "spawn-subagent" tool that is built into the harness and the harness' code somehow implements it (launches a thread/subprocess, runs a prompt and can see the progress/output).
Our MCP would somehow compete with that built-in tool? So if I told my agent "spawn a subagent to do X" it would somehow decide between these two? I'm trying to figure out how would this work in a harness-agnostic way.
A deeper integration would require the harness to know about OpenShell, right? This is possibly one of the hooks that the Harness Protocol would support? CC @johnnygreco
There was a problem hiding this comment.
Great question that really hits at the challenge for OpenShell to be truly harness agnostic.
MCP was chosen here to expose the control plane to all harnesses, rather than be a deep integration point for a specific harness. For example, if you said "use the openshell mcp to launch a secured agent on this task: ...", it should work out of the box for most harnesses (@patkraw is planning to do some e2e tests that dig into this more). And this should work inside and outside of sandboxes.
For a deeper integration, harnesses would likely want to use the primitives directly – e.g., through the SDK. The RFC calls out that under the hood, the CLI/MCP/SDK should all use the same primitives.
We plan to have a Dev Note or two showing a deep integration with Pi. And like you point out @pimlock, this becomes super relevant for the protocols we have been discussing.
| by OpenShell, proved against a ceiling with the policy prover, limited by a | ||
| grant that the user sets when starting the tree's first sandbox or attaches to | ||
| it later, and recorded in a lineage so that stopping a parent stops its children | ||
| first. |
There was a problem hiding this comment.
Nice work on this RFC! 👏
I went through most of it and started a few discussions points. I need more time to organize my thoughts about it, so will add more things over the next few days.
Few high-level comments (adding here, rather than a global comment, since global comments don't support threads):
- It's worth exploring what the gateway interceptors can do today, perhaps some of the limits could be implemented as interceptors initially to keep the scope smaller (e.g. limiting depth/breath could be implemented with interceptors if we added the lineage info). Perhaps it still makes sense to support these in-tree, but worth exploring as an option.
- (nit) I feel like the RFC name doesn't represent the main feature here, which it to have a way to spawn and control child sandboxes. The MCP is just a mechanism to expose that feature in the sandbox, but the main feature is the trees.
|
Could we replace MCP with a skill that teaches the agent to spawn and manage child sandboxes through a simple command? flowchart LR
A["Agent + skill"] -->|"Spawn command"| S["Supervisor"]
S -->|"Authenticated gRPC"| G["Gateway checks permissions and creates child"]
|
|
@pimlock please take a look at my first comment, I feel MCP might be a bit of overkill here in this scenario, but the concept is a real usecase |
|
Is a live-file handshake acceptable for the first supported harnesses? Specify the keep-alive/acknowledgment contract and test parent delays and worker crashes. Consider retaining a small final result if one-shot agents are a core use case. |
@FrostGod – MCP is something @pimlock, @drew, and myself have been talking about adding for a long time. It's purpose will be much more than spawning subagents. It's about exposing the control plane via MCP, which has standard discoverability for all major harnesses. |
|
@johnnygreco, if it has more usecases that's going to be coming along I think that should be fine 😄 then , The angle I am coming from is purely memory optimization, if we are going to introduce MCP only for spawning sub-agents, I would say skills should be enough, you don't need any additional MCP server per gateway, and MCP tools loaded into each sandbox during boot up. |
Summary
RFC 0021 proposes letting an agent inside a sandbox create child sandboxes (sub-agents and tool sandboxes) through an MCP endpoint served by the gateway, within a grant the user sets. Every child's policy is proved to fit inside its parent's, depth, breadth and total limits hold across the tree, the agent holds no credential, and stopping a parent stops its children first.
Related Issue
Closes #4574
Maintainers accepted the issue and assigned RFC 0021. If you'd prefer a separate RFC tracking issue (as with #4610), I'll switch the link.
Changes
rfc/0021-gateway-mcp-sandbox-creation/README.mdindraftstate.Implementation
A proof of concept is on
patkraw/OpenShellbranchgateway-mcp-poc, with hands-on tutorials for the CLI, the SDKs and MCP. Its end-to-end checks (74/74) run against a dev gateway with the Docker driver. Feedback is especially welcome on the grant model, the policy boundary, the API, and the name ("gateway MCP" may be confused with MCP gateways such as agentgateway).Testing
Checklist