feat(sandbox): support runtime.sandbox: true in agent config

Agent authors can now declare a sandbox default in the YAML itself:

  runtime:
    sandbox: true

Any caller of the agent then takes the sandbox path automatically,
without having to remember --sandbox. An explicit --sandbox=false on
the CLI still wins, matching the precedence aliases follow.

This is the per-agent counterpart of the per-alias sandbox flag, and
is especially useful for catalog agents that always need filesystem
or network isolation.

Adds the new RuntimeDefaults block to the latest config schema, the
matching JSON schema definition, and a refreshed sandbox_agent.yaml
example demonstrating the feature.
This commit is contained in:
David Gageot committed 2026-05-24 10:33:36 +02:00
1 parent 0fb27a10a8
commit f4348c5eae
7 files changed
+136 -1

No files matched your search

+15
View File
@@ -75,6 +75,10 @@
"permissions": {
"$ref": "#/definitions/PermissionsConfig",
"description": "Tool permission configuration for controlling tool approval behavior"
},
"runtime": {
"$ref": "#/definitions/RuntimeDefaults",
"description": "Execution-time defaults the agent author wants applied. Values act as defaults only — explicit CLI flags or user-config settings always win."
}
},
"additionalProperties": false,
@@ -1399,6 +1403,17 @@
},
"additionalProperties": false
},
"RuntimeDefaults": {
"type": "object",
"description": "Execution-time defaults baked into the agent config. Values act as defaults only; explicit CLI flags and user-config settings still take precedence.",
"properties": {
"sandbox": {
"type": "boolean",
"description": "When true, run the agent inside a Docker sandbox by default — equivalent to passing --sandbox on the command line. An explicit --sandbox=false on the CLI still wins."
}
},
"additionalProperties": false
},
"PermissionsConfig": {
"type": "object",
"description": "Tool permission configuration. Controls tool call approval behavior with optional argument matching.",
+9
View File
@@ -179,6 +179,15 @@ func (f *runExecFlags) runRunCommand(cmd *cobra.Command, args []string) (command
}
}
// Honour `runtime.sandbox: true` declared by the agent author.
// Best-effort: a config we can't peek at falls through to the
// normal path which will surface the load error properly.
if !f.sandbox && len(args) > 0 {
if peekAgentSandbox(ctx, args[0]) {
f.sandbox = true
}
}
if f.sandbox {
return runInSandbox(ctx, cmd, args, &f.runConfig, f.sandboxTemplate, f.sbx, f.noKit)
}
+20
View File
@@ -25,6 +25,26 @@ import (
"github.com/docker/docker-agent/pkg/skills"
)
// peekAgentSandbox returns true when the agent referenced by
// agentRef declares runtime.sandbox: true in its config. It is
// best-effort: any failure to resolve, load, or parse the config
// returns false so the caller falls through to the normal path,
// which will surface a proper error from the eventual load.
func peekAgentSandbox(ctx context.Context, agentRef string) bool {
if agentRef == "" {
return false
}
source, err := config.Resolve(agentRef, nil)
if err != nil {
return false
}
cfg, err := config.Load(ctx, source)
if err != nil {
return false
}
return cfg.Runtime != nil && cfg.Runtime.Sandbox
}
// runInSandbox delegates the current command to a Docker sandbox.
// It ensures a sandbox exists (creating or recreating as needed), then
// executes docker agent inside it via the sandbox exec command.
+44
View File
@@ -2,6 +2,8 @@ package root
import (
"errors"
"os"
"path/filepath"
"slices"
"strings"
"testing"
@@ -251,3 +253,45 @@ func TestPrintToolInstallAllowance(t *testing.T) {
})
}
}
func TestPeekAgentSandbox(t *testing.T) {
tests := []struct {
name string
yaml string
want bool
}{
{
name: "runtime sandbox true",
yaml: "runtime:\n sandbox: true\nagents:\n root:\n model: openai/gpt-4o\n description: t\n instruction: t\n",
want: true,
},
{
name: "runtime sandbox false",
yaml: "runtime:\n sandbox: false\nagents:\n root:\n model: openai/gpt-4o\n description: t\n instruction: t\n",
want: false,
},
{
name: "runtime block absent",
yaml: "agents:\n root:\n model: openai/gpt-4o\n description: t\n instruction: t\n",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "agent.yaml")
require.NoError(t, os.WriteFile(path, []byte(tt.yaml), 0o600))
assert.Equal(t, tt.want, peekAgentSandbox(t.Context(), path))
})
}
t.Run("empty ref", func(t *testing.T) {
assert.False(t, peekAgentSandbox(t.Context(), ""))
})
t.Run("unresolvable ref", func(t *testing.T) {
assert.False(t, peekAgentSandbox(t.Context(), "/nonexistent/agent.yaml"))
})
}
+28
View File
@@ -64,6 +64,34 @@ docker agent run safe-coder
An explicit `--sandbox=false` on the command line still wins, so you can opt
out of the sandbox for a single run without touching the alias.
### Bake the default into the agent config
Agent authors can declare a sandbox default in the YAML itself. Any caller of
the agent then gets the sandbox path automatically, without having to know
(or remember) to pass `--sandbox`:
```yaml
# agent.yaml
runtime:
sandbox: true
agents:
root:
model: openai/gpt-4o
description: A helpful assistant
instruction: You are a helpful assistant.
toolsets:
- type: shell
```
```bash
docker agent run agent.yaml # runs in a sandbox automatically
```
The rule is the same as for aliases: an explicit `--sandbox=false` on the
CLI overrides the config default, so you can debug an agent on the host
without editing its YAML.
## Example
```yaml
+9 -1
View File
@@ -1,10 +1,18 @@
runtime:
# Always run this agent inside a Docker sandbox so callers don't need
# to remember --sandbox on the command line. An explicit
# --sandbox=false on the CLI still wins.
sandbox: true
agents:
root:
model: openai/gpt-4o
description: |
A helpful assistant that runs shell commands in a sandboxed environment.
All commands execute inside a Docker container with limited filesystem access.
Use the --sandbox flag on the command line to enable sandboxing:
The agent declares runtime.sandbox: true above, so:
docker agent run examples/sandbox_agent.yaml
is equivalent to:
docker agent run --sandbox examples/sandbox_agent.yaml
instruction: You are a helpful assistant with access to a sandboxed shell environment.
toolsets:
+11
View File
@@ -28,6 +28,17 @@ type Config struct {
RAG map[string]RAGToolset `json:"rag,omitempty"`
Metadata Metadata `json:"metadata"`
Permissions *PermissionsConfig `json:"permissions,omitempty"`
Runtime *RuntimeDefaults `json:"runtime,omitempty"`
}
// RuntimeDefaults captures execution-time defaults the agent author
// wants applied when this config is run. The values act as defaults
// only: an explicit CLI flag or user-config setting always wins.
type RuntimeDefaults struct {
// Sandbox, when true, runs the agent inside a Docker sandbox by
// default — equivalent to passing --sandbox on the command line.
// Useful for agents that always need filesystem/network isolation.
Sandbox bool `json:"sandbox,omitempty" yaml:"sandbox,omitempty"`
}
// MCPToolset is a reusable MCP server definition stored in the top-level