microcld
Python SDK and CLI for microcloud: hosting (apps, managed Postgres, S3 buckets) and sandboxes for AI agents — isolated micro-VMs that start in a fraction of a second, run code, sleep for free and clone with their whole state. Execution and data stay in Mongolia.
pip install microcld
export SANDBOX_API_KEY=mc_... # organization API key
export SANDBOX_API_URL=http://127.0.0.1:7900 # your API endpoint (this is the default)
from microcld import Sandbox
with Sandbox.create(cpus=1, memory_mb=512, egress="none") as sb: # deleted on exit
sb.files.write("/tmp/data.csv", "a,b\n1,2\n3,4\n")
r = sb.exec("awk -F, 'NR>1 {s+=$2} END {print s}' /tmp/data.csv")
print(r.check().stdout) # 6; check() raises CommandError if the exit code is not 0
sb.sleep() # memory goes to a snapshot, not billed
sb.wake() # ~0.1 s, everything is still there
with sb.fork() as clone: # a clone with memory and disk - try another path
clone.exec("rm /tmp/data.csv")
print(sb.files.read_text("/tmp/data.csv")) # the original is intact
Code interpreter (python template)
Stateful Python, like a Jupyter kernel: variables and imports live between calls, the last expression is returned, plots come back as images.
with Sandbox.create(template="python", memory_mb=1024) as sb:
sb.files.write("/work/sales.csv", open("sales.csv", "rb").read())
sb.run_code("import pandas as pd\ndf = pd.read_csv('sales.csv')")
r = sb.run_code("df.describe()")
print(r.result) # repr of the last expression
r = sb.run_code("df.plot(); import matplotlib.pyplot as plt; plt.title('Sales')")
open("plot.png", "wb").write(r.images[0]) # PNG
r = sb.run_code("df.nope")
print(r.ok, r.error.name) # False AttributeError - an error in the result, not an exception
CodeResult: stdout, stderr, result, error (name, value,
traceback), images (PNG bytes), duration_ms, .ok, .text (all text
together - handy to return to a model as a tool result). timeout
interrupts the code but keeps the state; reset_code() clears it. The state
survives sleep/wake and is copied by fork(). A template without an
interpreter raises InterpreterError.
Async works the same way, with await:
from microcld import AsyncSandbox
async with await AsyncSandbox.create() as sb:
r = await sb.exec("echo hi")
Tools for AI agents (function calling)
A sandbox as a set of model tools: run_python (stateful interpreter),
run_shell, read_file, write_file. Definitions fit the Anthropic
Messages API (strict: true) and OpenAI-compatible function calling; the
handler runs the call in the sandbox and returns a ready result. Errors
(code crashed, no such file) go back to the model with is_error instead
of raising, so the agent sees them and corrects itself. Plots from
run_python and images from read_file are returned as image blocks (the
model sees them). Output is truncated (max_output, 20,000 characters).
from microcld import Sandbox, tools
sb = Sandbox.create(template="python", egress="none")
t = tools.for_sandbox(sb) # or enabled=["run_python"], timeout=..., max_output=...
response = client.messages.create(model="claude-opus-5-5", max_tokens=16000,
tools=t.anthropic(), messages=messages)
messages.append({"role": "assistant", "content": response.content})
results = [t.handle_anthropic(b) for b in response.content if b.type == "tool_use"]
messages.append({"role": "user", "content": results}) # all results in one message
OpenAI-compatible APIs: tools=t.openai(), and for each tool_call
messages.append(t.handle_openai(call)) (images are not included - only a
note). For AsyncSandbox the methods are the same, with await.
A ready-made analyst agent: examples/data_analyst.py.
The CSV is uploaded to a sandbox without internet access, Claude analyzes it
with pandas, looks at its own plots and answers; the plots are saved next to
the script.
pip install anthropic
python examples/data_analyst.py examples/sales.csv "How did sales change by region? Plot it."
Storage bucket
An organization bucket (created in the dashboard or with POST /v1/buckets)
can be attached to a sandbox: the sandbox gets its own key, and every
command sees S3_ENDPOINT, S3_BUCKET and AWS_* in its environment,
which boto3 and aws understand. Storage is reachable even with
egress="none". A fork gets its own key, and deleting the sandbox
(including by TTL) revokes the key.
sb = Sandbox.create(template="python", egress="none", bucket="data") # bucket_mode="read" for read-only
sb.exec("python3 -c 'import boto3, os; boto3.client(\"s3\").upload_file(\"out.csv\", os.environ[\"S3_BUCKET\"], \"out.csv\")'")
Sandbox API
Sandbox.create(template=, cpus=, memory_mb=, disk_gb=, idle_timeout_secs=, ttl_secs=, port=, egress=, bucket=, bucket_mode=) |
Create. Returns when the sandbox is ready for exec. egress: "internet" (default, no SMTP), "none", or a list such as ["1.1.1.1:53", "10.0.0.0/8"] (only those). ttl_secs deletes it after N seconds (insurance against forgotten sandboxes), idle_timeout_secs puts an idle one to sleep, bucket attaches a storage bucket (see above) |
Sandbox.connect(id) |
Connect to an existing sandbox |
sb.exec(cmd, env=, cwd=, timeout=60, stdin=) |
A string runs through sh -c, a list is used as argv. Returns ExecResult(exit_code, stdout, stderr, timed_out, truncated), .ok, .check() |
sb.exec_stream(cmd, ...) · sb.exec(cmd, on_stdout=, on_stderr=) |
Output as it appears (no 1 MiB limit): an iterator of OutputChunk(stream, data), then .result (ExecResult). Leaving the loop kills the command |
sb.files.write(path, data, mode=) · read(path) · read_text(path) |
Files (up to 64 MiB; binary data as bytes) |
sb.run_code(code, timeout=60) · sb.reset_code() |
Stateful Python (python template) -> CodeResult |
sb.sleep() · sb.wake() |
Sleep / wake; exec on a sleeping sandbox raises ConflictError |
sb.fork(snapshot=None, ttl_secs=None) · sb.snapshot(name) |
A clone (from the current state or a snapshot) / a named snapshot |
sb.set_idle_timeout(secs) |
Sleep after N idle seconds (0 - never, None - platform default); immediate, no restart |
sb.set_ttl(secs) · sb.info.expires_at |
Extend the lifetime (from now) or remove it (None); when it will be deleted |
sb.set_egress(...) |
Change the network policy immediately (drops open connections) |
sb.delete() |
Delete (already gone is not an error); with does it for you |
Client(api_key, base_url) |
A shared connection: client.create(), client.list(), client.get(id), client.usage(start, end) |
Errors are subclasses of SandboxError (.status is the HTTP code):
AuthError 401, QuotaError 403 (organization limit), NotFoundError 404,
ConflictError 409, BadRequestError 400/422, CapacityError 503 (no
capacity - retry), TimeoutError 504, ServerError - anything else and API
unavailability; InterpreterError - run_code on a template without an
interpreter.
Hosting: apps, databases, buckets
from microcld import Client
c = Client()
c.databases.create("pg", size_gb=5)
c.buckets.create("media")
c.apps.create("web", port=8080, git={"url": "https://github.com/me/web"})
c.apps.attach_database("web", "pg") # DATABASE_URL in the app env
c.apps.attach_bucket("web", "media", env_prefix="MEDIA")
c.apps.add_domain("web", "example.com")
print(c.apps.logs("web"), c.account.billing())
| Resource | Methods |
|---|---|
c.apps |
list get create update delete deploy rollback deployments deployment_log logs rotate_webhook · domains add_domain verify_domain remove_domain · attach_database detach_database attach_bucket detach_bucket |
c.databases |
list get create (branch with from_database, from_snapshot) update delete · snapshots create_snapshot delete_snapshot restore · dumps download_dump |
c.buckets |
list get create delete(force=) · keys create_key revoke_key · presign |
c.account |
me billing pricing limits |
Responses are dicts, as in the REST API. AsyncClient has the same methods;
every call must be awaited (await c.apps.list()).
CLI
microcld login # key -> ~/.config/microcld/config.json (mode 0600)
microcld apps create web --port 8080 --git https://github.com/me/web
microcld db create pg --size-gb 5 && microcld apps attach-db web pg
microcld buckets create media && microcld buckets presign media a.csv --expires 3600
microcld sandbox create --template python # prints the id
microcld sandbox exec <id> -- python3 -V # exit code is the command's
microcld balance
Groups: apps (ls show create deploy rollback deployments logs rm env domain
attach-db detach-db attach-bucket detach-bucket), db (ls show create rm
snapshots snapshot snapshot-rm restore dumps), buckets (ls show create rm
key presign), sandbox (ls create exec rm), plus whoami balance
pricing limits usage. --json before the command prints JSON. The
environment (SANDBOX_API_KEY, SANDBOX_API_URL) wins over the file saved
by login. Exit codes: 0 success, 1 API error, 2 bad arguments.
Development
cd sdk/python
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest -q # fake API (httpx.MockTransport)
SANDBOX_E2E=1 SANDBOX_API_KEY=mc_... .venv/bin/python -m pytest tests/test_e2e.py -v -s # against a real API
Metadata
Release files for microcld 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| microcld-0.1.0.tar.gz | 26.9 kB | Without provenance | Details for microcld-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| microcld-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for microcld-0.1.0-py3-none-any.whl |
Total release size: 58.4 kB
Release files / microcld-0.1.0.tar.gz
| Download URL | microcld-0.1.0.tar.gz |
|---|---|
| Size | 26.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68b3cf65ac213f1cc62ecc42671eb522cd88ea6397bd97a30d883077e8ba67c3
|
|
BLAKE2b-256 checksum How to use checksums |
70179325fea32e00ce4c66332d4f8ddb4294d5de52c71524185ee2e89fff2359
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / microcld-0.1.0-py3-none-any.whl
| Download URL | microcld-0.1.0-py3-none-any.whl |
|---|---|
| Size | 31.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5cc53795d0ae4e5a6d4906f56a796e73c9fa8919646ec34b7f0045ff533fc82
|
|
BLAKE2b-256 checksum How to use checksums |
59eb49669560c3859d7c6311ffd5eb2c01635ba5a53972fbf1babd816b38251f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for microcld 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.