Your first workspace¶
This tutorial walks you through AWC (Agent Workspace Cache) in one sitting. You
bring up the local-dev smoke harness, then drive the whole loop yourself with
awcctl: snapshot a directory, fork a workspace, mount it, make an edit and run
a tool, checkpoint, diff, export, and unmount. AWC exposes five user-level
primitives:
You touch each one. When you finish, you have a working workspace and a feel for the system.
Before you start¶
The harness runs on Linux only. You need:
- Docker Engine with the
docker composeplugin. - The Rust toolchain pinned by
rust-toolchain.toml. python3,curl, and coreutils.- A FUSE (Filesystem in Userspace) stack: the
/dev/fusedevice andfusermountorfusermount3onPATH. rootprivileges. The writable mount layer uses OverlayFS, which needs mount privilege. Run the bring-up withsudo.
See the local-dev guide for full prerequisites.
1. Bring up the local-dev smoke¶
The smoke harness stands up PostgreSQL and MinIO (an S3-compatible content-addressed store, or CAS) in containers, then runs the four AWC binaries on the host. It generates the credentials, builds the binaries, and proves the real cross-process paths work.
The first run takes a few minutes to build, then drives the full import, mount, checkpoint, and remount path. A green run ends with:
The smoke leaves awc-control, awc-worker, and awcd running. Keep this
terminal open.
Development mode, loopback plaintext
The harness sets AWC_DEV_MODE=1, so internal RPCs (remote procedure calls)
use loopback plaintext instead of mutually authenticated TLS (mTLS). Outside
development mode,
awcd and awc-control MUST present TLS credentials and fail to start
without them. Do not reuse this mode in a deployment.
2. Point awcctl at the harness¶
Open a second shell in the local-dev directory. The smoke builds awcctl to
../../target/debug/awcctl. Put it on PATH, then load the CAS credentials,
the dev identity, and the service token that awc-control minted on startup:
cd deploy/local-dev
export PATH="$PWD/../../target/debug:$PATH"
set -a; . config/worker.env; . config/control.env; set +a
export AWC_CONTROL_ENDPOINT="http://127.0.0.1:8080"
export AWC_HOST_SOCKET="$PWD/run/awcd.sock"
export AWC_TENANT="$AWC_DEV_TENANT" AWC_PROJECT="$AWC_DEV_PROJECT"
export AWC_TOKEN="$(grep -m1 'awc-control dev token:' run/control.log \
| sed 's/.*awc-control dev token: //')"
AWC_HOST_SOCKET is the Unix socket awcd serves, AWC_CONTROL_ENDPOINT is
the control plane's REST (Representational State Transfer) address, and
AWC_TOKEN is the bearer token the dev bootstrap minted.
3. Check host health¶
Ask the host daemon how it is. status reaches awcd over the socket and needs
no control-plane call:
You see host health, the cache state, and any active mount sessions.
4. Snapshot a directory¶
A snapshot is an immutable, content-addressed record of a directory tree. The
harness ships a tiny fixture at seed/source. Import it:
--from-dir is the source and --name is an optional label. The command opens
an import session over the control plane, uploads the unique objects to the CAS,
and completes the import. Capture the id:
SNAP=$(python3 -c 'import json;print(json.load(open("first-snap.json"))["snapshot_id"])')
echo "$SNAP"
A successful snapshot MUST return a stable snapshot id, a root digest, and file
and byte counts. The new snapshot starts in the VALIDATING state.
5. Wait for READY, then fork¶
A workspace can fork only from a READY snapshot. The worker validates the
object closure against the CAS and advances the snapshot
VALIDATING → READY. Poll until it is ready:
until [ "$(awcctl --output json snapshot inspect "$SNAP" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["state"])')" = READY ]; do
sleep 1
done
You can also list what the project holds:
Now fork a private workspace from the snapshot. Forking creates metadata only; it MUST NOT copy content objects:
awcctl --output json workspace fork --snapshot "$SNAP" --ttl 2h --name first-ws \
> first-fork.json
WS=$(python3 -c 'import json;print(json.load(open("first-fork.json"))["id"])')
echo "$WS"
--ttl 2h sets a two-hour lifetime (required). --snapshot forks one workspace
from the snapshot id you pass.
6. Mount the workspace¶
Mounting turns the workspace into a writable directory. The control plane issues
a signed ticket; awcd redeems it, acquires an exclusive writer lease, and
mounts an OverlayFS view at your target.
mkdir -p data/binds/first
awcctl --output json workspace mount "$WS" --target "$PWD/data/binds/first" \
> first-mount.json
SESS=$(python3 -c 'import json;print(json.load(open("first-mount.json"))["mount_session_id"])')
echo "$SESS"
The target MUST fall under the host allow-list. awcd MUST NOT accept an
arbitrary path from an untrusted caller without an allow-listed root. Confirm
the kernel sees an OverlayFS mount:
7. Edit a file and run a tool¶
Enter the mount. The fixture's hello.txt and README.txt are there, fetched
from the CAS on demand.
You wrote a new file and ran an ordinary read tool against the merged tree. Any read-only tool works the same way.
8. Checkpoint¶
A checkpoint publishes your writes as a new immutable snapshot and advances the workspace head. Pass the mount session id, not the workspace id:
awcctl --output json workspace checkpoint "$SESS" \
--mode cooperative --message "first checkpoint" > first-ckpt.json
HEAD=$(python3 -c 'import json;print(json.load(open("first-ckpt.json"))["snapshot_id"])')
echo "$HEAD"
Quiesced checkpoints need a fenced cgroup
The default is a quiesced checkpoint, which freezes an
execution cgroup before it cuts the snapshot. This harness mounts without a
ticket-sealed cgroup, so a quiesced checkpoint is rejected with
quiesced checkpoint requires a ticket-sealed execution cgroup. Use
--mode cooperative here. It is a best-effort cut with no freeze, and it is
what the smoke exercises.
The checkpoint is durable only after the control plane atomically publishes the new head.
9. Diff and export¶
Compare your checkpoint with the original snapshot. A diff uses a Merkle-tree comparison, so unchanged directories are skipped without scanning their children:
The diff lists each changed path. Export the changes to a file:
$HEAD is the snapshot whose changes are exported. --against is the base.
Pass --format json. The patch-bundle and tar formats are deferred, so the
control plane serves only the inline json form today.
10. Unmount¶
Release the writer lease and tear down the mount:
--disposition discard-local is the default. It drops any writes you did not
checkpoint. Because you already checkpointed, nothing is lost here. The other
dispositions are checkpoint (cut a checkpoint while unmounting) and
preserve-local (leave the upper layer for recovery).
When you are done with the harness, stop its containers:
What you learned¶
You ran all five AWC primitives against live services: snapshot into the CAS, metadata-only fork, writable OverlayFS mount behind a signed ticket, a checkpoint that advances the head, a diff and export, and an explicit unmount.
You also saw two candid limits of this harness: it runs without mTLS under
AWC_DEV_MODE=1, and it checkpoints cooperatively because the mount has no
fenced cgroup.
Where to go next:
- See the economics — fan out many workspaces from one base and watch the backend reads collapse.
- Go deeper on each primitive: import from a directory, fork a workspace, mount a workspace, checkpoint a workspace, diff and export, and unmount a workspace.
- For the design behind these steps, read the architecture overview.