Agent Sandbox Registry

agent-sandbox

Decision: @sandbox.register decorator-based command (registered on the persistent HTTP server endpoint)

无中文版

.agents/notes/implemented/feature/2026-09-04-sandbox-register-command.md

Decision: @sandbox.register decorator-based command (registered on the persistent HTTP server endpoint)

Status: implemented Implemented: 2026-09-09 Task: #98, #105, #118

Problem

Users need to register Python functions as named commands invokable through abx run. Currently custom commands can only be declared via YAML (the custom_commands: section of template.yaml); there is no way to register them directly from Python code. A decorator API is needed that maps a function name to a command name and the function signature to CLI options.

Decision

Add the @sandbox.register decorator. Commands are registered on the endpoints of the persistent in-container HTTP server, and the client invokes them over HTTP.

⚠ Key change (2026-09-05): the original ADR used a "source-delivery execution" mechanism (files.write to deliver a script + commands.run to execute it) and explicitly did not introduce a new in-container server. That decision has been reversed. Reasons: (1) every invocation required the full files.write + commands.run chain, with high latency; (2) it could not support persistent state; (3) the new agent_sandbox.server module (see 2026-09-05-sandbox-server-module.md) has been added and provides a persistent HTTP server to host registered commands.

Core design

  1. Commands are registered as persistent HTTP server endpoints: functions decorated with @sandbox.register are registered as HTTP endpoints on the agent_sandbox.server module. Clients invoke them via POST /commands/{name} with a JSON kwargs body; the response is {"result": ...} or {"error": ..., "type": ...}.

  2. Function name → command name: for a function decorated with @sandbox.register, func.__name__ becomes the command name, isomorphic to the keys of YAML custom_commands:. The final output is a CustomCommand object, identical to what YAML parsing produces, so all downstream consumers (Sandbox.run placeholder substitution, list_commands discovery output, CLI --arg parsing) can be reused with zero changes.

  3. Function signature → CLI options: at import time, the decorator runs inspect.signature(fn) and converts parameter name / type annotation / default value into CustomCommandArg (including a type field).

  4. Parameter-type restriction: V1 supports only the four scalars str/int/float/bool.

WIRE CONTRACT (client → server communication protocol)

  1. Request format: POST https://{port}-{sandbox_id}.{domain}/commands/{name}
  2. Success response: 200 OK, {"result": <return_value>}
  3. Failure response: 4xx/5xx, {"error": "<message>", "type": "<exception_class>"}
  4. Discovery endpoint: GET /commands → returns all registered commands and their argument schemas.

O7 naming decision: keep @sandbox.register

  1. Naming-conflict resolution: by changing sandbox into a _SandboxFactory callable — it simultaneously supports __call__ (the @sandbox(template=...) semantics) and a register method.

API Design

@sandbox.register
def demo(x: int, y: str = "hello") -> str:
    """Run a demo command."""
    return f"{x}: {y}"

# SDK: await sandbox.server.call("demo", x=42, y="world")
# CLI: abx run <sandbox_id> demo -a x=42 -a y="world"
# Discovery: GET /commands

Alternatives considered

Dependencies

Test Strategy

Acceptance criteria

Implementation

Evidence