Build a sandboxed agent
In this tutorial you build a small AI agent, run it on the Agents page of Cosmonic Desktop, and hold a conversation with it that survives closing and reopening the session.
The agent you build runs in a sandbox. It has no network access, no filesystem, and no API key of its own. When it needs a model, it asks Desktop, and Desktop makes the call with a key the agent never sees.
Desktop works with two different kinds of agent, and this page is about only one of them.
- Sandboxed agents run inside Desktop on the Agents page. The agent itself is the sandboxed code. This tutorial builds one.
- Native coding agents such as Claude Code or Codex run on your machine with your user's permissions. Desktop sandboxes the code they build, not the agents themselves. See Connect coding agents for those.
What you'll build
You'll start from the minimal agent, a complete example in the public awesome-cosmonic repository, about 500 lines of Rust. You'll give it a personality of your own, build it, and add it to Desktop.
By the end you'll have:
- an agent image in Desktop's built-in registry;
- that agent listed in the Agent dropdown next to Desktop's own agents;
- a session where the agent streams its replies and remembers what you told it.
What you'll need
-
Cosmonic Desktop, running, with Agents in the sidebar under AI & agents.
-
wash2.8.0 or later, the wasmCloud command line tool that builds and pushes components. Install it from the wasmCloud installation guide and check it withwash --version. -
Rust 1.94 or later, plus the WebAssembly compile target. Add the target with:
rustup target add wasm32-wasip2 -
git, to fetch the example.
-
A model for the agent to talk to. Either Ollama running on your machine with a model pulled, or an API key for Anthropic, OpenAI, or an OpenAI-compatible service. You'll pick it when you start the session.
1. Get the example
Clone the repository and move into the example's directory:
git clone https://github.com/cosmonic-labs/awesome-cosmonic.git
cd awesome-cosmonic/components/minimal-agentThe files you'll touch are:
| File | What it is |
|---|---|
src/lib.rs | The agent: it answers HTTP requests from Desktop and calls the model. |
wit/world.wit | The list of host interfaces the agent is allowed to use. |
.wash/config.yaml | Tells wash how to build the project and where the output lands. |
../../wit/wkg-registries.toml | Tells wash which registry publishes the cosmonic interfaces. |
2. Fetch the agent interfaces
Your agent talks to Desktop through interfaces, typed function signatures written in a language called WIT (WebAssembly Interface Types). Think of a WIT file as a .d.ts file or an OpenAPI spec: it says what can be called and with what types, and nothing more.
Desktop's agent interfaces are a package named cosmonic:agent@0.3.0. It is published to ghcr.io/cosmonic-labs/cosmonic/agent, much like an npm package is published to the npm registry.
Point wash at the registry mapping, then fetch the package:
export WKG_CONFIG_FILE=../../wit/wkg-registries.toml
wash wit fetchwash reports WIT dependencies fetched successfully, and a new directory appears at wit/deps/cosmonic-agent-0.3.0.
Keep the export in place for the rest of this tutorial. wash build fetches the interfaces again, and fails with no registry configured for namespace "cosmonic" if the variable is not set.
Open wit/world.wit to see what the agent asks for:
world minimal-agent {
import cosmonic:agent/inference-types@0.3.0;
import cosmonic:agent/models@0.3.0;
import cosmonic:agent/chat@0.3.0;
import cosmonic:agent/session@0.3.0;
}Four imports: the types a model call uses, a way to open a model, a way to chat with it, and a place to store the conversation. That is the agent's whole reach into the outside world.
3. Give the agent a personality
Open src/lib.rs and find the system prompt near the top:
const SYSTEM_PROMPT: &str = "You are a concise, helpful assistant.";Change it to something you'll recognize in a reply:
const SYSTEM_PROMPT: &str =
"You are a cheerful ship's navigator. Answer briefly, and end every reply with a nautical sign-off.";4. Build the component
wash buildThe last line of output names the file you just built:
Successfully built component at: .../target/wasm32-wasip2/release/minimal_agent.wasmThat file is a WebAssembly component: a compiled program, under 300 KB here, that can only call the interfaces it imports. It plays the role a container image plays for a container, without a filesystem or operating system packed inside.
5. Check what the agent can reach
Before you run code, you can read exactly what it is able to call:
wash inspect target/wasm32-wasip2/release/minimal_agent.wasmworld root {
import cosmonic:agent/inference-types@0.3.0;
import cosmonic:agent/models@0.3.0;
import cosmonic:agent/chat@0.3.0;
import cosmonic:agent/session@0.3.0;
import wasi:http/types@0.3.0;
import wasi:random/random@0.3.0;
import wasi:io/poll@0.2.9;
import wasi:clocks/monotonic-clock@0.2.9;
import wasi:io/error@0.2.9;
import wasi:io/streams@0.2.9;
import wasi:cli/stdout@0.2.9;
...
export wasi:http/handler@0.3.0;
}Read the list for what is missing. There is no wasi:http/client (it cannot send an HTTP request), no wasi:sockets (no network connections), and no wasi:filesystem (no files). The agent cannot reach any of those, because the component has no way to ask for them.
The one export, wasi:http/handler, is how Desktop talks to the agent: it sends the agent HTTP requests, the way a platform invokes a serverless function.
6. Push the image to Desktop's registry
Desktop runs agents from images in an OCI registry, the same kind of registry Docker Hub is. Desktop has one built in, reachable at oci.localhost:8200 while Desktop is running, so you don't need an account anywhere.
wash oci push --insecure oci.localhost:8200/apps/minimal-agent:0.1.0 \
target/wasm32-wasip2/release/minimal_agent.wasmwash prints OCI command executed successfully. The --insecure flag is there because the built-in registry speaks plain HTTP on your own machine.
If oci.localhost does not resolve, use oci.localhost.cosmonic.sh:8200/apps/minimal-agent:0.1.0 for the push and in the next step. It is the same registry under a public name that points to your own machine.
7. Add the agent to Desktop
- In Desktop, select Agents under AI & agents in the sidebar. The Start a session screen opens. If you already have sessions, select New at the top of the session list first.
- In the Agent dropdown, choose Add a new agent…. A form opens in place.
- Fill in the form:
- Name:
Navigator - Image:
oci.localhost:8200/apps/minimal-agent:0.1.0 - Tools: leave empty. This agent calls no tools.
- Name:
- Select Check and save.
Desktop reads the image's imports, the same list you saw in step 5, and checks each one against the interfaces a sandboxed agent may use. The minimal agent passes, the form closes, and Navigator is now selected in the Agent dropdown. Under it Desktop says: Your agent, from oci.localhost:8200/apps/minimal-agent:0.1.0. It runs with no network access of its own.
If the check fails, nothing is saved and the form lists each import or export that is the reason.
8. Start a session
-
Under Model, choose a source in the first dropdown (for example Ollama on this machine, or Add a source… to enter an API key), then choose a model in the second. There is no Network choice: it appears only when a session's tools can reach a host, and this agent has no tools.
-
In the message box at the bottom, type:
My name is Ada. Please remember it. -
Press Enter.
Desktop starts a session: a fresh workload running your agent, with its own conversation store. Once the agent answers Desktop's readiness check, your message is sent and the reply streams in as the model writes it, ending in a nautical sign-off.
The model call did not come from the agent. The agent asked Desktop for its model by the name default, and Desktop resolved that to the source and model you picked, holding the key itself.
9. Check that the conversation persists
-
Send a second message:
What is my name?The agent answers with Ada. It remembered because each turn is saved in the session's store, not in the agent's memory.
-
Select New at the top of the session list, then select your session in the list again.
The whole conversation is redrawn. Desktop draws it from the session journal, a log the host keeps for this session, which your agent writes every message into. When you end the session, the journal is deleted with it.
What just happened
You ran code you built as an agent, and at no point did it hold anything worth stealing:
- No network, filesystem, or key. Its imports had no way to ask for them, and Desktop checked that before saving it.
- Model calls go through the host. The agent names an alias,
default. Desktop decides which model that means and makes the call with its own credentials. - The conversation belongs to the session. The host scopes the store to this one session, so no other workload can read it.
None of this makes the agent's own code trustworthy, and it does not stop a model from being talked into a bad answer. What it limits is what the agent can reach when that happens.
Change and rebuild
When you change the code, push under a new tag. Desktop pins each image to the exact build a tag pointed to, so pushing a new build under 0.1.0 again would leave the old build running.
wash build
wash oci push --insecure oci.localhost:8200/apps/minimal-agent:0.1.1 \
target/wasm32-wasip2/release/minimal_agent.wasmThen point Desktop at it:
- Open Settings, then Sandboxed agents.
- In the Your agents card, select Edit next to Navigator.
- Change Image to
oci.localhost:8200/apps/minimal-agent:0.1.1and select Check and save. - Start a new session with Navigator. Sessions you already started keep the image they started with, and Desktop stops sending them messages once the image changes.
If something goes wrong
- The session never gets past starting. Desktop waits up to 90 seconds for the agent to answer its readiness check. Select Open logs on the session to read what the agent printed.
- The conversation is missing when you come back. The agent is streaming replies without writing them to its session journal. The agent protocol covers what to write.
- Check and save refuses the image. Read the reasons under the form. An import of
wasi:http/client,wasi:sockets, orwasi:filesystemis the usual cause; the import allowlist lists what is allowed.
Takeaways
- A sandboxed agent is one WebAssembly component that Desktop talks to over HTTP.
- Its imports are its entire reach, and you can read them with
wash inspectbefore you run it. - You add your own agent with Add a new agent… in the Agent dropdown, and manage it later under Settings → Sandboxed agents.
- Push every rebuild under a new tag.
Next steps
- Read the agent protocol for every request, response line, journal entry, and limit your agent has to handle, including how to give it tools and how tools keep their own state.
- Browse the
cosmonic:agentinterfaces to see whatchat,session, andtoolsoffer. - Build the tools your agent calls the same way you'd build any sandboxed tool server, in Sandbox MCP servers.