Mountable

LLM provider starters

Copy-paste starters for OpenAI, Anthropic, Gemini and Grok. The API key stays on the orchestrator; the sandbox gets a one-time ticket on stdin.

These starters mint a Mountable ticket from an agent orchestrator and mount that filesystem inside an ephemeral sandbox. They follow Mount from a sandbox and the agent workflow. Tested modules live in examples/llm-providers in the public CLI repository. They use the Python standard library only.

What crosses the boundary

MOUNTABLE_API_KEY stays in the orchestrator environment. The mount process reads the one-time ticket on stdin. In a shell, that looks like this, where MOUNTABLE_TICKET is the ticket and not the API key:

mkdir -p /mnt/work
printf '%s\n' "$MOUNTABLE_TICKET" | mountable mount --ticket-stdin --json /mnt/work &

The & leaves mount running so it can keep renewing the session certificate. Wait for the JSON line with event mounted before using /mnt/work. The ticket is single-use and must be exchanged within five minutes. Keep the session id on the orchestrator and revoke with mountable sessions revoke SESSION_ID. A revoked mount stops admitting new operations within fifteen seconds. Retry rules for a lost ticket are on Agents. Do not mount when the response has no ticket.

When the orchestrator calls prepare, start the child with mount.env, which already has the API key removed. When it only has the tool result, use sandbox_env instead of passing os.environ through unchanged. In the sandbox, run mkdir -p /mnt/work before starting sandbox.argv, write sandbox.stdin to that child's stdin, and leave the process running. Do not wait for it to exit. A remote sandbox does not inherit the orchestrator environment; send it sandbox.argv and sandbox.stdin only.

Shared handoff

On the orchestrator, with MOUNTABLE_API_KEY set:

from handoff import prepare
 
mount = prepare(filesystem_id, idempotency_key, read_only=False)
# In the sandbox, run mkdir -p /mnt/work before starting mount.argv.
# Start that child and leave it running. Write mount.stdin to its stdin,
# using mount.env so the API key is absent. Read stdout until the mounted
# event. Do not wait for the process to exit.

prepare runs mountable ticket create FS_ID --idempotency-key KEY --json (and --ro when read_only is true). The API key is an environment variable of that process, not a command argument. The same create_mount_session function is what each provider tool calls. Its return value is the tool result: session_id for a later revoke, and sandbox for the mount.

OpenAI Agents SDK and Responses API

examples/llm-providers/openai_responses.py exports TOOL and create_mount_session.

Agents SDK, in the orchestrator process:

from agents import Agent, function_tool
from openai_responses import create_mount_session
 
agent = Agent(
    name="orchestrator",
    instructions="Mint a Mountable ticket before starting a sandbox. Pass only sandbox.stdin into the sandbox.",
    tools=[function_tool(create_mount_session)],
)

Responses API: send TOOL in tools. TOOL is a strict function named create_mount_session with filesystem_id, idempotency_key and read_only. On a function_call, run create_mount_session on the orchestrator and return its JSON. Start the sandbox from sandbox, not from the orchestrator's environment.

Anthropic Claude Code and tool use

Claude Code on the trusted machine should use the MCP server. MOUNTABLE_API_KEY belongs in the MCP server environment. Call create_mount_ticket. The result includes ticket and mount_command. Pass the ticket to the sandbox on stdin. Do not copy the MCP env block into the sandbox.

For the Messages API, examples/llm-providers/anthropic_tools.py exports TOOL (name, description, input_schema) and the same create_mount_session function. Run that function on the orchestrator when the model calls the tool.

Gemini ADK and function calling

examples/llm-providers/gemini_adk.py exports create_mount_session. ADK turns that function into a tool:

from google.adk.agents.llm_agent import Agent
from gemini_adk import create_mount_session
 
root_agent = Agent(
    name="orchestrator",
    model="gemini-flash-latest",
    instruction="Mint a Mountable ticket before starting a sandbox. Pass only sandbox.stdin into the sandbox.",
    tools=[create_mount_session],
)

A researcher agent and a coder agent can share one filesystem: mint rw for the writer and read_only=True for the reader. Raw function calling uses FUNCTION_DECLARATION in the same file. The function still runs on the orchestrator.

xAI Grok

examples/llm-providers/xai_grok.py uses the same flat Responses tool as OpenAI. Point the client at xAI and handle the call with create_mount_session on the orchestrator:

import os
from openai import OpenAI
from xai_grok import TOOL
 
client = OpenAI(base_url="https://api.x.ai/v1", api_key=os.environ["XAI_API_KEY"])
# client.responses.create(model="grok-4.7", input=messages, tools=[TOOL])

XAI_API_KEY is the Grok credential. It is separate from MOUNTABLE_API_KEY, and neither one goes into the sandbox. The xAI SDK's xai_sdk.chat.tool accepts the same name, description and parameters.

After the mount

Wait for the JSON line with event mounted before reading or writing /mnt/work. Unmount with mountable unmount /mnt/work so pending writes finish. The sandbox needs FUSE (/dev/fuse) and the mountable CLI; see Mount from a sandbox.