Two CLI tools for driving worker containers on remote boxes from the local console (intent and decisions: SPEC.md). Target: x86_64-unknown-linux-gnu.
Overview
Two CLI tools for driving worker containers on remote boxes from the local console (intent and decisions: SPEC.md). Target: x86_64-unknown-linux-gnu. - akao — local driver (this README). - oaka — inside a worker: plan generator and script compiler (see below). Testing: cargo test (hermetic, ~30 s); real-GPU smoke in TEST.md. akao doctor (console) and oaka doctor (worker) check a machine's prerequisites. akao runs here from the repo; oaka reaches workers through the library that akao init deploys. Set up once per clone: ~/.local/bin must come before /usr/bin on PATH: it also holds the ssh wrapper that makes docker's ssh:// contexts read $AKAO_CONFIG_ROOT/.ssh/config (akao doctor checks it). the hook (.githooks/post-commit) refreshes both tools: So a worker gets a new oaka only when akao init runs again for it; re-running init on an existing container is safe (every step reuses what exists) as long as --skip-setup is not given, since that skips the deploy and the link.
README
akao-workflow
Two CLI tools for driving worker containers on remote boxes from the local console
(intent and decisions: SPEC.md). Target: x86_64-unknown-linux-gnu.
akao— local driver (this README).oaka— inside a worker: plan generator and script compiler (see below).
Testing: cargo test (hermetic, ~30 s); real-GPU smoke in TEST.md.
akao doctor (console) and oaka doctor (worker) check a machine’s prerequisites.
Dev environment (the console)
akao runs here from the repo; oaka reaches workers through the library that
akao init deploys. Set up once per clone:
rustup target add x86_64-unknown-linux-musl # oaka is built static for any worker image
git config core.hooksPath .githooks # enable the post-commit hook
cargo build --release -p akao
ln -sf "$PWD/target/release/akao" ~/.local/bin/akao
export AKAO_CONFIG_ROOT=/2026/nocopy/akao-workflow-state # in your shell profile
akao doctor
~/.local/bin must come before /usr/bin on PATH: it also holds the ssh wrapper that
makes docker’s ssh:// contexts read $AKAO_CONFIG_ROOT/.ssh/config (akao doctor
checks it).
After every commit the hook (.githooks/post-commit) refreshes both tools:
| Built | Lands in | Reaches |
|---|---|---|
akao, release |
target/release/akao |
you, through the symlink above |
oaka, static musl |
oaka/bin/oaka in this checkout (git-ignored) |
workers, at their next akao init (step 8 puts it into the worker’s clone, step 9 links it onto PATH) |
CLAUDE.md, skills, utils/, oaka’s library (oaka/profiles, oaka/stacks.toml) |
nowhere: read in place from this checkout ($AKAO_REPO_ROOT) |
workers, at their next akao init (step 8 updates the worker’s own clone) |
So a worker gets a new oaka only when akao init runs again for it; re-running init on an
existing container is safe (every step reuses what exists) as long as --skip-setup is
not given, since that skips the deploy and the link. Both tools print the commit they were
built from: akao --version, oaka --version. Code that was not committed is in neither:
cargo build alone only refreshes target/.
State
Everything lives in $AKAO_CONFIG_ROOT (e.g. /2026/nocopy/akao-workflow-state):
| File | Content |
|---|---|
config.toml |
deploy_src (default /), deploy_paths (extra paths under deploy_src to ship to every box; none by default: CLAUDE.md, the skills, utils/ and oaka come from the repo), infx_repo, infx_local (this machine’s InferenceX clone, for akao mirror; default //nocopy/InferenceX), infx_dashboard and infx_snapshot (the dashboard and the GitHub tarball URL of a commit, for akao mirror --dashboard) |
hosts.tsv |
one row per box: nick model_path docker_sock host_home rest; - = default (no image: akao init --image names it) |
container_home/ |
home template, copied once per container to /container_home/akao_ |
The work year and week are not stored: they are today’s ISO year and week (2026, ww41).
The repo. $AKAO_REPO_ROOT (default /root/akao-workflow) is the checkout of this
repo akao ships: its CLAUDE.md (the orientation every agent reads first) and the role
skills (skills/) are read from it in place; every worker gets its own clone under /root
(init step 8), and nothing of it is copied under /. Only committed work travels.
Artifact roots. Each agent keeps its numbered dirs under one directory, its artifact
root: a worker’s is ///, the controllers’ ///controller.
$AKAO_ARTIFACT_ROOT names it explicitly, and then no year/week is derived for it: on the
console it is the controller’s own (akao config ls and akao doctor show it); for a
worker, akao init pins it into the container (below).
akao config ls
akao host add f19-11 --home /root/akao --model /mnt/raid/models
akao host add h21-4 --home /home/akao --model /data/models --sock /data/docker.sock \
--rest '--shm-size=64g -e HF_HOME=/model/hf'
akao host ls
akao host rm h21-4
rest is appended to the fixed docker run skeleton (--rm -d --privileged, devices,
host network/IPC, --shm-size=32g, …; on an NVIDIA box --gpus all, IPC, --shm-size=32g,
memlock/stack ulimits, SYS_NICE); it never replaces it.
akao init --image
Brings up akao_ on `` from --image, which is required: there is no default
image, per host or global. An existing akao_ must run that image (else init fails
and names the one it runs):
- resolve the nick through
ssh -G, tell AMD (/dev/kfd) from NVIDIA (/dev/nvidiactl) GPUs (NVIDIA: launch only, init stops after step 6), and reach the box’s docker; ifakao_already exists, it keeps its image and artifact root, and must mount/and/rootfrom where hosts.tsv says (a request it cannot satisfy fails here, before the host is touched) - create the artifact root under `` and
/container_homeon the host - deploy extra paths:
tarofdeploy_paths(none by default) →/, owned root:root, symlinks followed (merge, never delete;.git,.claude,__pycache__,*.pycexcluded) - copy the home template, unless that container’s home already exists
- create docker context `` (
host=ssh://) if missing, thendocker context useit docker runthe container, unless it already runs- in the container:
apt install vim less tmux docker.io git, unless all are there - the worker’s own clone of this repo at
/root/akao-workflow($AKAO_REPO_ROOTin the container): the console’s branches go over as a git bundle (no network, unpushed commits included); the first time it is cloned, later fetched asconsole/mainand fast-forwarded only while the clone is onmain, clean and behind — a worker’s own commits or edits are never moved - from that clone’s
utils/:install_gh.sh,agent.sh --yes; thenpip install sgl-eval(a failure only warns) — each skipped when already present; linkoakainto/usr/local/bin - clone InferenceX into
//nocopy/InferenceX, or bring the box’s checkout to origin’s latestmain(left as is, with a warning, when that would lose uncommitted changes or commits of its ownmain) - start tmux with window
controllerrunningclaude(with the clone as an extra working directory,--add-dir, so reading the skills never prompts), unless tmux already runs - run
oaka doctorin the worker and show its report: GPU arch and ROCm version (what plans are checked against) and every prerequisite; reported, never fatal
Mounts: model dir → /model, docker socket → /var/run/docker.sock,
/ → /, the container home → /root. The artifact root
(///, or --artifact-root under //) is the working
directory and $AKAO_ARTIFACT_ROOT in the container, so every docker exec, tmux window and
agent in it sees the same root, even after the week has changed. A container created
before this has no variable: its working directory is its root (to pin one, docker --context rm -f akao_ and init again; /root and / are bind mounts and
survive, anything installed into the image’s own filesystem does not).
Every step reuses what exists, so re-running init resumes a half-done worker.
Host-side writes use sudo -n when the ssh login is not root and has passwordless sudo,
else run as the login (fine where it owns ``).
Flags: --dry-run (probes run, changes are only printed), --skip-setup (skip steps 3, 7, 8 and 9),
--week wwNN or --artifact-root .
Attach afterwards with docker --context exec -it akao_ tmux attach. An agent
driving workers (the controller) opens the worker’s claude in a window of its own tmux
instead, and keeps a record of what it ran: $AKAO_REPO_ROOT/skills/controller/SKILL.md.
akao mirror [ []] --conf
A worker for one InferenceX benchmark config: akao init with the entry’s image, plus a
brief that its agent turns into an oaka profile and plan.
akao mirror --conf gptoss,mi355 --rev 4699ab81a^ # list: the vllm and ATOM entries
akao mirror --conf gptoss,mi355,atom --rev 4699ab81a^ # preview its brief
akao mirror m15-21 --conf gptoss,mi355,atom --rev 4699ab81a^ --week ww42 # bring it up
- Entries come from every
*master.yamlunder aconfigs/dir of the cloneinfx_local, read through git at--rev(defaultHEAD). Retired entries live in history: gpt-oss went on 2026-07-06, and when nothing matches, the error lists the commits that last touched the first term. --confterms match parts of the entry’s name, its runner, framework, model prefix, precision or model (prefixes; case,-,_,.ignored); the entry’s full name always works. Of several matches the one with the fewest name parts wins when unique (sogptoss,mi355,atompicks...-atom, not...-atom-mtp); otherwise they are listed.- Refused before the box is touched: multinode/disaggregated entries, frameworks oaka cannot
serve (it serves
sglang,vllm,atom), non-AMD runners or runners whose GPU arch akao does not know, a box whose GPUs (read from its KFD topology) are missing or of another arch, and an existingakao_running another image. - The container is named after the entry unless `` is given; the init flags apply.
- The brief lands in the worker’s artifact root as
mirror/@/:MIRROR.md(the entry, its points, the revision, and what oaka cannot reproduce as it stands: chat templates, client flags, data parallelism, per-point server overrides, setup scripts),entry.yaml(the entry’s text) andrecipes/(its srt recipes and the setup scripts they name, or the legacy bash script the runner’s launcher picks, withbenchmark_lib.sh). - Then brief the worker’s agent as usual and point it at
MIRROR.md; the worker skill’s “Mirror workers” section says what it does with it.
From the dashboard (--dashboard): mirror what the InferenceX dashboard
measured, with no clone and no revision to pick:
akao mirror --dashboard --conf mi355,vllm # its MI355X vllm curves
akao mirror --dashboard --conf gptoss,mi355,vllm # preview the brief
akao mirror n10-17 vllm_mirror --dashboard --conf gptoss,mi355,vllm # bring it up
--confterms match the dashboard’s curves, named like entries from its model key, precision, GPU, framework and variants (gptoss120b-fp4-mi355x-vllm,...-mtp,...-agentic), or its display model (gpt-oss-120b).- The curve’s latest run is mirrored (
--runfor another one it lists: the dashboard shows each sequence’s latest run, so one curve may hold several runs and images;--date YYYY-MM-DDfor its data as of a day). The container gets the image that run ran, and the brief comes from the commit it ran at, fetched as a tarball from GitHub (often a PR branch’s commit that no clone of main has). MIRROR.mdadds the run, its PR, every measured point (output tok/s as oaka reports it, per GPU, median TTFT/TPOT) and, per distinct server, the arguments it really ran with, from its stored log;dashboard/holds the rows (rows.json) and those logs.- Reads the dashboard’s public API with
curl(akao doctorchecks it); every URL is printed.
akao cp
Copies a path between boxes, or between a box and this machine, e.g. one worker’s results
for another to refer to. Every box’s / tree is later rsync’d into one superset, so a
copy must land at the same path it has on its source box: workers’ artifact roots stay
siblings, and the copy merges with its original instead of nesting inside another worker’s
dir. Name the destination from the receiving worker’s root plus a trailing ..:
# a whole worker root: /..
akao cp h21-5:ww42/a0 f19-17:ww42/b0/.. # -> f19-17:ww42/a0
# a task dir: /../
akao cp h21-5:ww42/a0/0003_baseline f19-17:ww42/b0/../a0 # -> f19-17:ww42/a0/0003_baseline
akao cp n10-17:ww42/hello/0001_probe /tmp/ # -> /tmp/0001_probe on this machine
The worker b0 then reads it at ../a0/... from its root. A destination without the ..
(f19-17:ww42/b0) works, but plants a0’s files inside b0’s root, where no rsync puts them.
- An address is
nick:rel-path, relative to that box’s/, or a bare path (no colon) on this machine...is taken as given. - Like
cp -a: the source’s basename lands inside the destination directory, which is created if missing. Files are overwritten; nothing is deleted (merge, never mirror). tar | ssh tar; written files are owned root:root, throughsudo -non a box whose ssh login is not root (without passwordless sudo: as the login, owned by it).--dry-runprints the commands only.
$AKAO_SSH overrides the ssh command akao uses (e.g. ssh -F ~/.ssh/other_config); docker’s
own ssh transport for the context still uses plain ssh.
A host entry in akao’s ssh config may be a login shell (RemoteCommand, RequestTTY force,
e.g. one exporting another HOME on a shared account): ssh keeps it, but akao’s
calls and docker’s transport (through the utils/ssh wrapper) turn it off, since ssh refuses
a command alongside it. They use host_home from hosts.tsv, never the login’s HOME.
Deploying a controller
A controller is an agent (claude) that drives workers through akao: it runs akao, opens
worker agents in tmux windows and keeps a record of what it ran. It needs a Linux machine
or container that reaches every box by ssh — this console is itself a container. Its manual
is skills/controller/SKILL.md in this repo; this section sets up the machine it runs on.
Draft (2026-10-10). Parts marked TODO(user) depend on the environment and are yours to fill in.
1. Tools. docker CLI (it talks to each box’s daemon over ssh; no local daemon needed),
openssh client, git, tmux, Rust (to build akao; rustup target add x86_64-unknown-linux-musl for the static oaka), and the claude CLI
(utils/agent.sh --yes in this repo, which also adds an AMD_LLM_API_KEY placeholder to
~/.bash_profile for you to fill in). utils/controller/Dockerfile is an Ubuntu 24.04
image with these tools except Rust and claude. TODO(user): how the console container is
started from it, and whether gh (for the worker skill’s safe_push.sh, which runs on the
controller) is needed.
2. The control plane. From this repo: CLAUDE.md, the skills and utils/, read in
place (export AKAO_REPO_ROOT= unless it is /root/akao-workflow).
Nothing under / is needed: oaka’s library is the repo’s oaka/, and the
post-commit hook builds oaka/bin/oaka there. Boxes initialized before 2026-10-10 still
hold old control-plane copies under /, which agents there may still load; workers
re-initialized since read their clone instead (oaka falls back to //oaka only
without one), so remove them, after bringing back any profile saved there (akao cp).
3. State. export AKAO_CONFIG_ROOT= in the shell profile, then in it:
config.toml (akao config set ..., all keys have defaults), hosts.tsv (akao host add ...), the
home template container_home/ (dotfiles every worker gets; it must hold the ssh wrapper
.local/bin/ssh, a copy of this repo’s utils/ssh), and .ssh/config with the keys it names, if the boxes need their own ssh
settings. TODO(user): how keys reach a new console.
4. akao on PATH, with the ssh wrapper ahead of /usr/bin (docker’s ssh transport finds
ssh through PATH only):
git clone && cd akao-workflow
git config core.hooksPath .githooks
cargo build --release -p akao && ln -sf "$PWD/target/release/akao" ~/.local/bin/akao
cp utils/ssh ~/.local/bin/ssh && cp utils/ssh "$AKAO_CONFIG_ROOT/container_home/.local/bin/ssh"
5. InferenceX for akao mirror: a clone at infx_local (default
//nocopy/InferenceX), with enough history for the revisions you mirror
(git clone https://github.com/SemiAnalysisAI/InferenceX.git; git -C ... fetch now and
then: mirror never fetches).
6. Its artifact root. Several controllers can share a console; by default they share one
record per week, ///controller/. To give one its own, start it with
AKAO_ARTIFACT_ROOT=///.
7. Check, then start it.
akao doctor # FAIL lines name their fix; tmux/claude/InferenceX are warnings
tmux new-session -s console -n controller
root="${AKAO_ARTIFACT_ROOT:-/$(date +%G)/ww$(date +%V)/controller}" # the ISO week, as akao's
mkdir -p "$root" && cd "$root"
claude # first message: "You are a controller. Load the controller skill."
The first message decides the role ($AKAO_REPO_ROOT/CLAUDE.md, “Identify yourself”). Worker
agents then open in windows of this tmux session (controller skill, “The worker’s agent”).
oaka (inside a worker)
Write a plan, compile it to stand-alone scripts, run them:
oaka draft --profile gpt-oss-120b --gpus 7 --client gsm8k --client fixed-seq
vim plan.toml && oaka check && oaka run
Commands, plan and profile fields: oaka/README.md; the profiles and
stacks.toml sit beside it in oaka/, and every commit builds a static oaka/bin/oaka
(.githooks/post-commit; git config core.hooksPath .githooks once per clone).
Why it is built this way: SPEC.md.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add akao-amd/akao-workflow