Your first sandbox¶
A sandbox_session gives you five tools: run, read_file, write_file,
grep, and list_dir. Use them together to run code and work with files.
The SDK loads your connection from environment variables or a saved profile.
Use the five tools¶
Save either program as sandbox_tools.py. It writes a file, runs cat, reads
the file, finds a matching line, and lists the directory in the same session.
"""Use the five sandbox tools in one session."""
from contree_sdk import create_sandbox_session
def main() -> None:
with create_sandbox_session(image="tag:alpine:latest") as sandbox_session:
# Write a file in the sandbox.
sandbox_session.write_file("/work/message.txt", b"Hello from ConTree!\n")
# Run a command and print its output.
result = sandbox_session.run("cat", args=["/work/message.txt"])
print(result.stdout)
# Read the file as bytes.
content = sandbox_session.read_file("/work/message.txt")
print(content.decode())
# Search file contents.
matches = sandbox_session.grep("Hello", path="/work/message.txt")
print(matches)
# List the directory with file metadata.
directory = sandbox_session.list_dir("/work")
print(directory)
main()
import asyncio
from contree_sdk import create_sandbox_session
async def main() -> None:
async with create_sandbox_session(image="tag:alpine:latest") as sandbox_session:
# Write a file in the sandbox.
await sandbox_session.write_file("/work/message.txt", b"Hello from ConTree!\n")
# Run a command and print its output.
result = await sandbox_session.run("cat", args=["/work/message.txt"])
print(result.stdout)
# Read the file as bytes.
content = await sandbox_session.read_file("/work/message.txt")
print(content.decode())
# Search file contents.
matches = await sandbox_session.grep("Hello", path="/work/message.txt")
print(matches)
# List the directory with file metadata.
directory = await sandbox_session.list_dir("/work")
print(directory)
asyncio.run(main())
Both programs print the command output, file contents, search matches, and file metadata.
run() returns stdout, stderr, and process status. read_file() returns bytes;
write_file() accepts bytes and creates parent directories. grep() and
list_dir() return structured results that you can print directly.
Use print(directory) for a detailed listing, or directory.as_text(short=True) for names only.
What the default lazy session does¶
Without session_type, the factory selects LazySession for Sync and
AsyncLazySession for Async. Both have the same behavior. “Lazy” means the VM
starts when needed, and several commands can share one snapshot.
sequenceDiagram
participant S as SDK
participant V1 as MicroVM 1
participant V2 as MicroVM 2
S->>V1: Run commands
Note over S,V1: 10 s idle
S->>V1: Save and stop
S->>V2: Start from saved files
Select the lazy type explicitly and call uptime twice. Both calls use the same
MicroVM. These examples use your configured connection; add profile="work"
or the token arguments below when needed.
from contree_sdk import LazySession, create_sandbox_session
with create_sandbox_session(
image="tag:alpine:latest",
session_type=LazySession,
) as session:
print(session.run("uptime").stdout)
print(session.run("uptime").stdout)
import asyncio
from contree_sdk import AsyncLazySession, create_sandbox_session
async def main() -> None:
async with create_sandbox_session(
image="tag:alpine:latest",
session_type=AsyncLazySession,
) as session:
print((await session.run("uptime")).stdout)
print((await session.run("uptime")).stdout)
asyncio.run(main())
Creating the session or entering its context does not start a VM. The first
run() or write_file() starts it. Later commands reuse that VM, so installed
packages, files, and running processes remain available until a snapshot.
After the last command or file request finishes, the default idle timer waits 10 seconds. It then saves the filesystem as an image and stops the VM in the background, without another call from your code. The next command starts a new VM from that saved image automatically. Reads, searches, and directory listings can use the saved image without starting a VM.
A snapshot preserves files, including installed packages, but loses processes and memory. For example, a file remains after an idle pause; a background web server must be started again. Each VM also has a lifetime limit; see VM lifetime settings.
Normal context exit saves pending changes. An exception discards changes since the last completed snapshot. To save after every command instead, select the session type shown below.
The image tag must exist on your endpoint and provide sleep, sh, mkdir, and
cat. Replace tag:alpine:latest with an available image, or use Import a Docker image
to prepare one. Configure credentials in Connection sources.
Install the SDK¶
These guides describe the breaking 0.5 development API and require Python 3.10–3.14. Run the installation command from this repository checkout; the published 0.4 package does not provide the new API.
pip install -e ".[async]"
For synchronous programs only, pip install -e . is sufficient.
Initialize a session¶
Choose one connection source: pass a token and AI project, or select a saved profile. Both paths return the same session with all five tools. The SDK creates and closes the client for you; retries are enabled by default.
Create a session from a token¶
Replace your-token and your-ai-project with your credentials. Without url,
the SDK uses https://api.tokenfactory.nebius.com/sandboxes. Pass url=... when
using another deployment.
"""Create a sandbox session from a token."""
from contree_sdk import create_sandbox_session
from contree_sdk.session import IdleSnapshotPolicy
def main() -> None:
with create_sandbox_session(
image="tag:alpine:latest",
token="your-token",
project="your-ai-project",
session_kwargs={"snapshot_policy": IdleSnapshotPolicy(60)},
) as sandbox_session:
result = sandbox_session.run("echo", args=["Hello from ConTree!"])
print(result.stdout)
main()
import asyncio
from contree_sdk import create_sandbox_session
from contree_sdk.session import IdleSnapshotPolicy
async def main() -> None:
async with create_sandbox_session(
image="tag:alpine:latest",
token="your-token",
project="your-ai-project",
session_kwargs={"snapshot_policy": IdleSnapshotPolicy(60)},
) as sandbox_session:
result = await sandbox_session.run("echo", args=["Hello from ConTree!"])
print(result.stdout)
asyncio.run(main())
Create a session from a saved profile¶
Save this file as contree.ini in the directory where you run the program.
Replace the token and AI project values with your credentials:
[DEFAULT]
profile = work
[profile:work]
type = iam
url = https://api.tokenfactory.nebius.com/sandboxes
token = your-token
project = your-ai-project
The program selects work from this file:
"""Create a sandbox session from a saved profile."""
from contree_sdk import create_sandbox_session
from contree_sdk.session import IdleSnapshotPolicy
def main() -> None:
with create_sandbox_session(
image="tag:alpine:latest",
profile="work",
config_path="contree.ini",
session_kwargs={"snapshot_policy": IdleSnapshotPolicy(60)},
) as sandbox_session:
result = sandbox_session.run("echo", args=["Hello from ConTree!"])
print(result.stdout)
main()
import asyncio
from contree_sdk import create_sandbox_session
from contree_sdk.session import IdleSnapshotPolicy
async def main() -> None:
async with create_sandbox_session(
image="tag:alpine:latest",
profile="work",
config_path="contree.ini",
session_kwargs={"snapshot_policy": IdleSnapshotPolicy(60)},
) as sandbox_session:
result = await sandbox_session.run("echo", args=["Hello from ConTree!"])
print(result.stdout)
asyncio.run(main())
If the profile already exists in the client’s standard configuration, pass only
profile="work" and omit config_path. Do not combine profile with token,
project, or url; put those values in the profile instead.
Session options and defaults¶
Both examples use session_kwargs to set a 60-second idle interval. Omit that
argument to use the default of 10 seconds. Session constructor options belong
inside session_kwargs; client options such as retry and timeout belong
on the factory call.
The factory selects LazySession for with and AsyncLazySession for
async with. Set session_type only when selecting another class or a subclass.
Use create_sync_sandbox_session() or create_async_sandbox_session() if you
want to select the context protocol explicitly; they accept the same arguments.
Creating the context opens no connection. Entering it prepares the session; the first command starts the VM. Normal exit saves pending file changes and closes owned resources. An exception discards changes since the last snapshot.
SDK-created clients use contree_client.RetryPolicy() by default. Pass
retry=RetryPolicy(...) to customize it, or retry=None to disable retries.
A client supplied through client= keeps its existing retry configuration.
See Advanced usage for more session options and component ownership.
Save after every command¶
Select ContreeSession for Sync or ContreeAsyncSession for Async. These classes
start a separate VM for each run() and save its filesystem before returning.
The next command uses that saved image. You do not call snapshot() or wait for
an idle timeout.
sequenceDiagram
participant S as SDK
participant V1 as MicroVM 1
participant V2 as MicroVM 2
S->>V1: Run, save, stop
S->>V2: Run from saved files, save, stop
Call uptime twice with the regular session type. Each call starts a new
MicroVM; the second uses the filesystem saved by the first. Connection arguments
are the same as in the lazy example.
from contree_sdk import ContreeSession, create_sandbox_session
with create_sandbox_session(
image="tag:alpine:latest",
session_type=ContreeSession,
) as session:
print(session.run("uptime").stdout)
print(session.run("uptime").stdout)
import asyncio
from contree_sdk import ContreeAsyncSession, create_sandbox_session
async def main() -> None:
async with create_sandbox_session(
image="tag:alpine:latest",
session_type=ContreeAsyncSession,
) as session:
print((await session.run("uptime")).stdout)
print((await session.run("uptime")).stdout)
asyncio.run(main())
In the lazy example, both commands share a running VM. In this example,
the VM restarts between commands. uptime output is rounded, so two immediate
calls can print similar values in either mode. Files survive the restart;
processes and memory do not.
disposable=False is the default. Passing disposable=True to a command skips
saving its changes. By default, a completed operation is saved even if its
process exits with a nonzero code. Failed or cancelled operations do not advance
the session history. See Work with a Session for checkpoint and error behavior.
Connection sources¶
Configure the endpoint URL, token, and AI project for your deployment. The project identifies where the sandbox runs; omitting it is not a complete working configuration.
Setting |
Environment |
Profile / INI |
Direct factory argument |
|---|---|---|---|
Endpoint |
|
|
|
Token |
|
|
|
AI project |
|
|
|
The SDK delegates environment parsing to contree-client.
Use exactly one explicit source:
Source |
Arguments |
Behavior |
|---|---|---|
Direct credentials |
|
Build a regular client |
Profile object |
|
Use that exact profile. Do not combine it with direct credentials or |
Named profile |
|
Resolve that name through contree-client. |
Explicit INI |
|
Resolve the file’s active profile, or the name selected by |
Automatic |
Omit all source arguments |
Use |
Direct credentials cannot be combined with profile or config_path.
Incomplete explicit credentials raise an error rather than falling back to ENV.
An explicit source takes priority over ENV. Without one, the environment needs
both CONTREE_URL and CONTREE_TOKEN (or NEBIUS_API_KEY). Provide the project
through CONTREE_PROJECT (or NEBIUS_AI_PROJECT).
For INI resolution, contree-client uses an explicit profile name, then
CONTREE_PROFILE, then the active profile in the file. Its default files are
cli.ini and auth.ini under CONTREE_HOME; auth.ini takes priority. Without
CONTREE_HOME, the client uses its XDG configuration directory. A supplied
config_path replaces the auth file path and can be named config.ini; the
client also reads cli.ini from the same directory.
The SDK uses credentials from the selected profile; it does not acquire or
refresh credentials itself. For a profile constructed in Python, pass a
contree_client.profiles.Profile as profile=.... The profile_from_args()
helper constructs this object without writing a configuration file.
Continue with Import a Docker image to import a Docker image for your sandbox.