Building VM
The computer an agent works on is a Linux VM run by virtx. Ailoy depends on virtx, and your code uses it directly:
- Python: the
virtxpackage (import virtx), whichpip install ailoy-pyinstalls too - Node.js:
@brekkylab/virtx(npm install @brekkylab/virtxbeside@brekkylab/ailoy) - Rust: the
virtxcrate (ailoy::console::ConsoleClientis the same type asvirtx::console::ConsoleClient)
A ConsoleClient built with virtx is handed to an AgentBuilder as is, and the agent shares its session. The session stays the console’s:
- Closing the agent leaves the console open; closing the console ends the session for the agent too, whose next command then fails.
- Your own calls on the console and the agent’s take turns, one call at a time.
Two clients do the work:
ImageClientbuilds the VM’s disk image from aRecipeand manages the built images.ConsoleClientboots a VM from an image and runs commands in it. This is what the agent gets.
Images
Section titled “Images”A Recipe is a base image plus steps, much like a Dockerfile. ImageClient builds it and stores it under a name, so a console can boot from it later without building it again.
import asyncio
from virtx import ImageClient, ImageSource, Recipe, ensure_virtx
async def main() -> None: # Fetches virtx's server into its cache the first time; a no-op after. await ensure_virtx()
async with await ImageClient.try_new() as images: recipe = Recipe("python:3.12-slim-trixie").step("pip install matplotlib") built = await images.build(recipe, "plot:latest") print(built.reference, built.digest)
for image in await images.list(): print(image.digest, image.refs)
# Remove it by its reference (or ImageSource.digest(...) for its digest). await images.remove(ImageSource.reference("plot:latest"))
asyncio.run(main())const { ImageClient, ImageSource, Recipe, ensureVirtx } = require('@brekkylab/virtx')
// Fetches virtx's server into its cache the first time; a no-op after.await ensureVirtx()
const images = await ImageClient.tryNew()try { const recipe = new Recipe('python:3.12-slim-trixie').step('pip install matplotlib') const built = await images.build(recipe, 'plot:latest') console.log(built.reference, built.digest)
for (const image of await images.list()) { console.log(image.digest, image.refs) }
// Remove it by its reference (or ImageSource.digest(...) for its digest). await images.remove(ImageSource.reference('plot:latest'))} finally { await images.close()}use virtx::image::{ImageClient, ImageSource, Recipe};
#[tokio::main]async fn main() -> anyhow::Result<()> { // Fetches virtx's server into its cache the first time; a no-op after. virtx::ensure_virtx().await?;
let mut images = ImageClient::try_new().await?; let recipe = Recipe::new("python:3.12-slim-trixie").step("pip install matplotlib"); let built = images.build(recipe, Some("plot:latest")).await?; println!("{} {}", built.reference, built.digest);
for image in images.list().await? { println!("{} {:?}", image.digest, image.refs); }
// Remove it by its reference (or ImageSource::digest(..) for its digest). images.remove(ImageSource::reference("plot:latest")).await?; Ok(())}You don’t have to build ahead of time: passing a Recipe straight to a console builds it on first use.
Consoles
Section titled “Consoles”ConsoleClient.builder() sets up the VM: the image to boot, host directories to mount, network access, CPUs, memory and GPU. build() boots it, and exec runs a command in it.
import asyncio
from virtx import ConsoleClient, ImageSource
async def main() -> None: console = await ( ConsoleClient.builder() .image(ImageSource.reference("plot:latest")) .mount("./artifacts", "/artifacts") .network(True) .build() )
async with console: result = await console.exec(["python", "-c", "import matplotlib; print(matplotlib.__version__)"])
print(result.stdout.decode(), end="")
asyncio.run(main())const { ConsoleClient, ImageSource } = require('@brekkylab/virtx')
const console_ = await ConsoleClient.builder() .image(ImageSource.reference('plot:latest')) .mount('./artifacts', '/artifacts') .network(true) .build()
try { const result = await console_.exec(['python', '-c', 'import matplotlib; print(matplotlib.__version__)']) process.stdout.write(result.stdout)} finally { await console_.close()}use virtx::{console::ConsoleClient, image::ImageSource};
#[tokio::main]async fn main() -> anyhow::Result<()> { let mut console = ConsoleClient::builder() .image(ImageSource::reference("plot:latest")) .mount(std::path::absolute("./artifacts")?, "/artifacts") .network(true) .build() .await?;
let result = console .exec(["python", "-c", "import matplotlib; print(matplotlib.__version__)"], None) .await?; print!("{}", String::from_utf8_lossy(&result.stdout)); Ok(())}Besides exec, a console can read and write files in the VM.
For more information, see the virtx README.