Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.celesto.ai/llms.txt

Use this file to discover all available pages before exploring further.

Callbacks let you run your own Python code every time the sandbox executes a command. You can use them to inspect what an agent is about to run, log results, or block commands you consider unsafe. This is useful when an LLM or agent is driving the sandbox and you want a final say before a command reaches the guest. A callback is a Python class that SmolVM calls at set points in a command’s lifecycle — before a command runs, after it finishes, or when it errors. You subclass Callback, override only the hooks for the moments you care about, and pass instances to SmolVM(..., callbacks=[...]). There are three hooks:
  • on_pre_run — runs before a command reaches the guest. This is the only hook that can block a command.
  • on_post_run — runs after a command finishes successfully.
  • on_run_error — runs when a command fails to execute.
Every hook receives one argument, which you’ll see called ctx in the examples below. This is a run context — a single object that describes the command in flight: what was run, on which sandbox, and (afterwards) its result. You read from it to decide what to do. The run context section lists every field; for now, just know that ctx.command is the command string and ctx.vm_id is the sandbox ID.

When to use callbacks

  • Block unsafe commands — Stop destructive commands like rm -rf / before they reach the guest.
  • Audit and log — Record every command an agent runs, along with its exit code and output.
  • Observe failures — Collect telemetry when a command errors out, without changing the rest of your code.

Block unsafe commands before they run

The pre-run hook is the only hook that can stop a command. If on_pre_run raises, the command is aborted and the exception is raised to the caller. Raise CommandBlockedError for an explicit, typed block.
from smolvm import SmolVM, Callback, CommandBlockedError

class SafetyGuard(Callback):
    DENY = ("rm -rf /", "mkfs", ":(){ :|:& };:")

    def on_pre_run(self, ctx):
        if any(bad in ctx.command for bad in self.DENY):
            raise CommandBlockedError(
                f"Blocked unsafe command: {ctx.command!r}",
                vm_id=ctx.vm_id,
                command=ctx.command,
            )

with SmolVM(callbacks=[SafetyGuard()]) as vm:
    vm.run("echo hello")     # runs normally
    vm.run("rm -rf /")       # raises CommandBlockedError; never reaches the guest
A blocked command does not tear down the sandbox or the SSH session — the next allowed run() call uses the same connection.

Log every command an agent runs

The post-run hook fires after a command completes. The context object carries the result, so you can log the exit code, stdout, and stderr.
from smolvm import SmolVM, Callback

class AuditLog(Callback):
    def on_post_run(self, ctx):
        print(f"[{ctx.vm_id}] {ctx.command!r} -> exit={ctx.result.exit_code}")

    def on_run_error(self, ctx):
        print(f"[{ctx.vm_id}] {ctx.command!r} errored: {ctx.error}")

with SmolVM(callbacks=[AuditLog()]) as vm:
    vm.run("uname -r")
on_post_run and on_run_error are passive observers. If they raise, the exception is logged and swallowed so a buggy logger never breaks a command that already ran.

Attach callbacks to an existing sandbox

You can also attach callbacks to a sandbox you have already created. add_callback() returns the sandbox so calls can be chained.
from smolvm import SmolVM, Callback

class HelloHook(Callback):
    def on_pre_run(self, ctx):
        print(f"about to run: {ctx.command}")

vm = SmolVM()
vm.add_callback(HelloHook())
vm.start()
vm.run("echo hi")
vm.stop()
vm.delete()
vm.close()

Read command details from the run context

Every hook receives a single RunContext object. Read from it to decide what to do.
FieldTypeAvailable inDescription
vm_idstrall hooksID of the sandbox running the command.
commandstrall hooksThe command string passed to run().
shellstrall hooks"login" or "raw" execution mode.
timeoutintall hooksPer-command timeout in seconds.
resultCommandResult | Noneon_post_runExit code, stdout, and stderr.
errorException | Noneon_run_errorThe transport error raised during the run.
For the full API, see the Callback reference.

Scope and limitations

Callbacks fire around the synchronous SmolVM.run() method. They cover both the SSH and vsock transports, because the hooks run on the facade before any transport is selected. This first release intentionally covers command hooks only. Lifecycle hooks (start, stop, snapshot), file-transfer hooks, and the async run() path are not wired up yet.
Last modified on June 2, 2026