Skip to content

Workspace tools ​

GET/PUT/DELETE /admin/workspace-tools/{name} — workspaces register their own tool definitions on top of the four built-in agent-runtime tools (python_exec, file_read, file_write, shell_exec).

User-uploaded tools merge into the same ToolRegistry the built-ins use, so workspace-published tools dispatch through the same Sandbox adapter the operator configured (subprocess on personal-tier, Docker sibling-container on SME/enterprise). Resource caps, fs fence, and audit hooks all apply automatically — there's no escape hatch where workspace-defined tools bypass the harness.

Endpoints ​

MethodPathAuthSummary
GET/admin/workspace-toolsBearer + workspaceList the caller workspace's tool definitions.
PUT/admin/workspace-tools/{name}Bearer + workspaceUpsert one tool definition.
DELETE/admin/workspace-tools/{name}Bearer + workspaceRemove one tool definition.

Spec wire shape: WorkspaceToolSpec (packages/agent_runtime/workspace_tools.py:54):

python
class WorkspaceToolSpec(BaseModel):
    name: str
    description: str = ""
    parameters: dict[str, Any] = {}      # JSON-schema for tool args
    handler_kind: HandlerKind            # "shell_template" | "python_template"
    handler_payload: dict[str, Any] = {}

Two handler kinds ship today:

  • shell_template — handler_payload.command is a string with {arg_name} placeholders. At dispatch time the agent's tool-call arguments are str.format_map'd into the command and the result runs as bash -c "<rendered>" inside the sandbox.
  • python_template — same shape but the rendered string runs as python3 -c "<rendered>".

Both run through the sandbox; both honour the per-workspace SandboxPolicy (memory / cpu / pids / wall-clock / fs allowlist).

GET /admin/workspace-tools ​

Lists every workspace tool registered in the caller's workspace, ordered alphabetically by name. Cross-tenant tools are never enumerated.

bash
curl https://your-host/admin/workspace-tools \
  -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN"

PUT /admin/workspace-tools/{name} ​

Upsert by name. The body matches WorkspaceToolSpec. The path parameter overrides the body's name field (the route hard-codes the workspace fence so cross-tenant writes are impossible).

bash
curl -X PUT https://your-host/admin/workspace-tools/grep_repo \
  -H "Authorization: Bearer $BIGWAVE_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "grep_repo",
        "description": "Search the repo for a regex pattern.",
        "parameters": {
          "type": "object",
          "properties": {
            "pattern": { "type": "string" }
          },
          "required": ["pattern"]
        },
        "handler_kind": "shell_template",
        "handler_payload": {
          "command": "grep -rn {pattern} /workspace"
        }
      }'

Response: 200 OK with the persisted WorkspaceToolSpec.

DELETE /admin/workspace-tools/{name} ​

Removes the tool. Returns 204 No Content. 404 on unknown name (also for cross-tenant access — workspace fence).

Operator notes ​

  • Tools dispatch through the configured Sandbox. A workspace tool cannot escape the harness — every dispatch is a sandbox.run(handle, request) call, so resource caps + fs fence
    • audit hooks apply.
  • Argument shell-quoting. For shell_template handlers each argument value is automatically passed through shlex.quote before being substituted into the command string. This prevents a model-supplied value such as "; rm -rf /" from breaking out of its intended position in the rendered shell command. Operators should still constrain the parameters JSON schema (e.g. enum, pattern, maxLength) to limit what values the model may supply. Note that python_template handlers are not shell-quoted — the rendered source is passed directly as the argument to python3 -c, which is a single argv element and is never interpreted by a shell.
  • Persistence. Tool definitions live in the workspace_tools table (migration 0018). Restarting the API process preserves the registrations.
  • Merge order. On every POST /v1/agent-sessions/{id}/run, the runtime merges workspace tools on top of the four built-ins. A workspace tool with the same name as a built-in overrides the built-in for that session. Use the same name only when the override is intentional.
  • Errors:
    • 400 Bad Request — malformed body, unknown handler_kind.
    • 401 Unauthorized — missing bearer.
    • 404 Not Found — unknown name (delete) or cross-tenant.
    • 503 Service Unavailable — app.state.workspace_tool_repo isn't wired (lifespan-stripped tests; not a real-prod path).

Dual-licensed: MIT OR Apache-2.0