Agent Sandbox Registry

python-sdk

Decision: Custom Commands Schema for Templates

无中文版

.agents/notes/implemented/feature/2026-09-03-custom-commands-schema.md

Decision: Custom Commands Schema for Templates

Status: implemented

Problem

Templates need a way to expose named, parameterized commands (e.g. serve, migrate, test) so users and Agents can invoke high-level operations without knowing the underlying shell invocation. The mechanism must accept user-supplied arguments safely (no shell injection).

Decision

Add an optional custom_commands: map to template.yaml. Each entry is keyed by name and describes how to build a shell command from user arguments:

custom_commands:
  serve:
    cmd: "python -m http.server {port}"   # {placeholder} filled from args
    description: "Start a static file server"
    cwd: "/app"                            # default: /app
    env: {}                                # default: {} (empty)
    timeout: 60                            # default: 60 (seconds)
    args:
      - name: port
        default: "8000"
        required: false
        description: "Port to listen on"

Field semantics: - cmd (required): command template with {placeholder} tokens matching args[].name. - description: human/Agent-facing summary. - cwd: working directory (default /app). - env: extra environment variables (default empty). - timeout: seconds (default 60). - args: list of {name, default, required, description}. required args without a value raise an error before execution.

Injection safety: each user-supplied argument value is escaped with shlex.quote() before being substituted into the {placeholder}. Placeholders are only filled from declared args; unknown placeholders or undeclared user args are rejected.

API Design

# models/template.py
class CommandArg(BaseModel):
    name: str
    default: str | None = None
    required: bool = False
    description: str = ""

class CustomCommand(BaseModel):
    cmd: str
    description: str = ""
    cwd: str = "/app"
    env: dict[str, str] = {}
    timeout: int = 60
    args: list[CommandArg] = []

# Template model gains:
custom_commands: dict[str, CustomCommand] = {}

# Resolution (pseudocode):
#   values = {a.name: provided.get(a.name, a.default) for a in cmd.args}
#   missing_required -> raise
#   final = cmd.cmd.format(**{k: shlex.quote(v) for k, v in values.items()})

Alternatives considered

Dependencies

Test Strategy

Acceptance criteria