ov-build/skills/build/SKILL.md
MUST be invoked before any work involving: building container images, ov image build command, pushing to registries, merging layers, build caches, or Containerfile generation.
npx skillsauth add overthinkos/overthink-plugins buildInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Invoked as ov image build. See /ov-build:image for the family overview.
ov image build generates Containerfiles from image.yml and layer definitions, then builds images in dependency order using the configured build engine (Docker or Podman). Images at the same dependency level are built in parallel (up to --jobs concurrent builds).
Mode purity: ov image build reads image.yml + build.yml + layer.yml only. deploy.yml is never read during build — this is enforced by LoadConfig in ov/config.go, which calls LoadConfigRaw (no MergeDeployOverlay) to guarantee OCI labels are baked strictly from authored configuration, never from local deploy-time overrides. See /ov-dev:go "Mode purity" for the architectural invariant this protects and the bug it prevents.
IR-driven emission: since the 2026-04 refactor, ov image build emits Containerfiles via OCITarget — the build-mode implementation of the shared DeployTarget interface. Internally the flow is: image.yml + layer.yml → BuildDeployPlan (pure compiler) → InstallPlan IR → OCITarget.Emit → Containerfile text. The same IR backs ContainerDeployTarget and HostDeployTarget used by ov deploy add. See /ov-dev:install-plan for the IR and /ov-dev:generate for the Go call graph.
Three-phase templates: build.yml format and builder definitions split each install operation into phases.{prepare, install, cleanup}.{container, host} — three phases × two venues. Build-mode emission reads the container cell; host deploys read host. The legacy install_template: field still works and serves as the (install, container) fallback when phases: is absent, so migration is incremental. See /ov-build:layer "Service Declaration" for the analogue at the init-system level (init.<name>.service_schema).
| Action | Command | Description |
|--------|---------|-------------|
| Build all images | ov image build | Build all images in dependency order |
| Build specific image | ov image build <image> | Build single image for host platform |
| Build and push | ov image build --push | Build all platforms and push to registry |
| Build without cache | ov image build --no-cache | Disable build cache entirely |
| Merge layers | ov image merge <image> | Post-build layer optimization |
ov image build [image...] # Build for local platform
ov image build --push [image...] # Build for all platforms and push
ov image build --platform linux/amd64 [image...] # Specific platform
ov image build --cache registry [image...] # Registry cache (read+write)
ov image build --cache image [image...] # Image cache (read-only, default)
ov image build --cache gha [image...] # GitHub Actions cache
ov image build --no-cache [image...] # Disable cache entirely
ov image build --jobs N [image...] # Max concurrent images per DAG level (default: 4)
ov image build --podman-jobs N [image...] # Max concurrent stages within a single podman build (default: min(NCPU, 4))
ov image build <image> --include-disabled # Build an `enabled: false` image without flipping authored config
--include-disabled — operational rebuild of disabled imagesImages with enabled: false in image.yml are excluded from the build /
inspect / validate working set by default. To rebuild ONE such image without
flipping the authored flag (which would commit local-deployment intent into
shared project config), pass --include-disabled:
ov image build immich --include-disabled # builds the disabled image
git diff --quiet image.yml # confirms image.yml is untouched
Scoping. When you pass positional <image> args together with
--include-disabled, the override is scoped to those names. Other
disabled images stay filtered out — important because widening the
working set globally would surface unrelated dep errors (e.g., a
disabled image with remote layers that haven't been fetched yet).
ov image build --include-disabled (no positional args) widens
globally; ov image build immich --include-disabled only relaxes the
gate for immich.
The flag flows from BuildCmd.IncludeDisabled → ResolveOpts.IncludeDisabled
ResolveOpts.IncludeDisabledNames → ResolveAllImages (in Generator)
→ ResolveImage. The shouldIncludeDisabled(name) helper centralises the
scoping rule. Sibling commands ov image inspect <name> --include-disabled
and ov image validate --include-disabled accept the same flag for
diagnostic / validation work on disabled entries. See /ov-build:inspect
and /ov-build:validate.Containerfile generation is driven by a single declarative YAML file referenced in image.yml:
defaults:
format_config: build.yml # Unified distro + builder + init config
build.yml has three top-level sections:
distro: — Per-distro bootstrap commands (package manager setup, cache mounts, repo management), optional base_user: declaration (what uid-1000 account the upstream base image ships), and package format templates (how rpm:, pac:, deb: sections in layer.yml become RUN steps). Each format has install, repos, copr, modules, and options templates.builder: — Multi-stage builder patterns (pixi, npm, cargo, aur). Each builder has build_stage and copy_stage templates that generate the appropriate FROM builder AS ... and COPY --from=... steps.init: — Init system definitions (supervisord, systemd) including detection rules, fragment templates, entrypoint commands, and service management commands. Optional — images can omit this if they don't need an init system.All sections use Go text/template syntax with access to layer config data. Source: ov/format_config.go (loader + distro/builder types, including DistroDef.BaseUser), ov/init_config.go (init type), ov/format_template.go (rendering).
base_user: — declaring a pre-existing base-image accountAdded 2026-04 to handle Ubuntu 24.04's ubuntu:ubuntu pre-existing account (and future distros that ship something similar). Declare it under the distro when the upstream base image ships a uid-1000 account; leave it out otherwise.
distro:
ubuntu:
inherits: debian
base_user:
name: ubuntu
uid: 1000
gid: 1000
home: /home/ubuntu
# ... bootstrap inherited from debian
All four fields (name, uid, gid, home) are required when the block is present. Inherited across distro inheritance chains — if the child has no base_user: but the parent does, the child inherits it (see resolveInherits in ov/format_config.go).
Consumed by the user_policy: reconciliation in ov/config.go:ResolveImage — see /ov-build:image "user_policy" for the three-value policy (auto / adopt / create) and the decision matrix.
No base_user: currently declared for Fedora, Arch, or Debian (their canonical base images ship no pre-existing uid-1000 account). Add one in your project's build.yml override if you're basing on a distro-cloud variant that DOES ship one (e.g. debian:13-cloud).
builder: name in two placesbuilder: appears in both build.yml (top-level section) and image.yml (per-image map, plus defaults.builder). They share the name on purpose — both maps key on the same slot (the build-type name, e.g. pixi, npm, cargo, aur):
build.yml builder.pixi — definition: detection rules, stage template, cache mounts for the pixi builder.image.yml builder.pixi — selection: which image (e.g. fedora-builder) to use as the pixi builder for this image.An image's effective builder map resolves as: image.builder[type] → base_image.builder[type] → defaults.builder[type] → "". Self-references (a builder image pointing at itself) are filtered automatically.
build.yml was unified from three former files (distro.yml + builder.yml + init.yml) because they were always resolved together through the same format_config: ref. One file → one loader (LoadBuildConfigForImage) → three in-memory configs (DistroConfig / BuilderConfig / InitConfig) — the internal split is preserved, only the YAML surface is unified. The init: section is optional (absent = no init system); distro: and builder: are required.
ov image build runs ov image generate internally. You can also run it standalone to inspect generated Containerfiles:
ov image generate # Write .build/ (Containerfiles)
ov image generate --tag v1.0.0 # Override CalVer tag
cat .build/my-image/Containerfile # Inspect generated output
ov image generate internally (produces Containerfiles in .build/)engine.build)--jobs concurrent, default 4)ov image merge --all (if merge.auto enabled, skipped for --push)--jobs vs --podman-jobsov exposes two parallelism knobs with distinct meanings:
| Flag | Env var | Default | What it controls |
|---|---|---|---|
| --jobs N | OV_BUILD_JOBS | 4 | Outer concurrency: how many ov-level images to build in parallel within a DAG level (e.g., when ov image build rebuilds the whole graph). |
| --podman-jobs N | OV_PODMAN_JOBS | auto = min(NCPU, 4) | Inner concurrency: passed to podman build --jobs N, controls how many stages of a single multi-stage build run concurrently. |
The inner default is capped at 4 because podman-5.7.x races under high
concurrency in its blob-reuse code path (storage_dest.go:TryReusingBlobWithOptions
and queueOrCommit). Observed reproducibly on selkies-desktop (29-stage
DAG) with --jobs runtime.NumCPU() (16 on a 16-core host) and --cache-from:
podman SIGABRTs with a core dump in the middle of the blob-reuse path. The cap
narrows the race window enough that the bug has not been observed to fire in
practice. Core dumps are captured by systemd-coredump at
/var/lib/systemd/coredump/core.podman.*.zst — inspect with coredumpctl info
to confirm which build was faulting.
Override the cap if your podman version is known-good (upstream upgrades may eventually fix it):
ov image build <image> --podman-jobs 16 # fully parallel stages
OV_PODMAN_JOBS=8 ov update <image> --build # via env
ov image build <image> --podman-jobs 1 # fully serialised, worst-case debugging
Source: ov/build.go:resolvePodmanJobs + podmanJobsDefault. Covered by
ov/build_jobs_test.go (12 unit + integration cases). The outer --jobs knob
lives on the BuildCmd struct; the inner --podman-jobs was added as a
separate field so the two semantics don't get conflated.
| Mode | Backend | Use Case |
|------|---------|----------|
| image | <registry>/<image> | Read-only cache from registry image (default) |
| registry | <registry>/cache:<image> | Production CI/CD (read + write) |
| gha | GitHub Actions cache | CI builds on GitHub Actions |
| none | No cache | Same as --no-cache |
ov image build --cache registry my-image # Read+write registry cache
ov image build --cache image my-image # Read-only from registry image
OV_BUILD_CACHE=registry ov image build # Via environment variable
:latest)ov image build tags every image with exactly one tag — its CalVer
(e.g. ghcr.io/overthinkos/fedora-supervisord:2026.114.1022). ov does
not emit a :latest tag, ever. Short-name resolution (in
ov/local_image.go) picks the newest CalVer for a given short name
via the org.overthinkos.image=<short> + org.overthinkos.version=<calver>
OCI labels. The CLI accepts an explicit --tag <calver> for pinning;
an empty --tag resolves to newest-local automatically.
Rationale:
:latest-under---cache-from bug. The documented
caveat where podman's --cache-from silently pulls a stale
:latest from the remote registry is unreachable when ov never
emits :latest in the first place.:latest + :<calver> tag of the same
image as competing candidates; the CalVer sort picks the newest
one deterministically.Migration note: existing :latest tags from pre-cutover builds stay
in local storage until the operator prunes them (podman image prune);
they're just not refreshed by new builds.
Running ov image build <image> with no source changes completes in seconds — every RUN/COPY/LABEL step hits the cache. This is the invariant the rest of this section builds on. Cache-miss only happens when something in the build input genuinely changes; a rebuild you triggered "just to be sure" is free.
Concretely: the cache is keyed by (parent-image-SHA, instruction-text, COPY-source-content). The parent-image SHA resolves from the FROM reference at build time — a fresh CalVer tag that still points at the same image SHA keeps the subsequent RUN/COPY steps cached. Only when the underlying image SHA changes (because that upstream was rebuilt with changed content) does cache-miss cascade to the downstream steps.
Three kinds of source changes are real cache invalidators — if you see a long rebuild, one of these is the cause:
layers/<name>/ — the canonical case is layers/ov/bin/ov being rewritten by task build:ov after a Go source edit — changes the scratch stage's content hash, which invalidates COPY --from=<layer> and everything downstream that depends on it.cmd: body changes the RUN instruction text emitted for that layer, invalidating cache from that RUN onward.fedora or internal like fedora-supervisord) has different content from the last cached build, the FROM step resolves to a new SHA and downstream RUN/COPY steps all cache-miss. This cascades through the dependency graph — rebuilding fedora-supervisord forces its children to re-run from the FROM fedora-supervisord step.ARG BASE_IMAGE=<registry>/<name>:<calver> with a fresh CalVer on every generate. That ARG default appears in the Containerfile text but is not part of the cache key for subsequent RUN/COPY steps — podman/buildah resolves the FROM to an image SHA first, and caches downstream steps off that SHA. If the SHA is unchanged, cache hits. (Historical note: earlier skill revisions called this a "CalVer cascade cost"; that was a misdiagnosis. The real cost is content changes in the layer itself, not the tag.)eval: edits. LABEL directives are emitted last in every final stage (after the last USER). A eval: edit — the most common layer mutation — only re-runs the final LABEL block (~2 seconds on a 138-step stack like immich-ml). See /ov-dev:generate for the rationale.ov image build without source changes. Fully cached; seconds to complete.write: vs copy: — cache granularitywrite: tasks use stageInlineContent (content-addressed staging under .build/<image>/_inline/<layer>/<sha256>/). Editing a write: content: block changes only that single COPY layer's cache key — siblings in the same layer keep their cache.copy: tasks reference files from the layer directory. Editing any file under layers/<name>/ changes the WHOLE scratch stage's content hash (since COPY layers/<name>/ / is one instruction), invalidating every downstream COPY --from=<layer> step.| Edit | Cost |
|------|------|
| ov image build with zero source changes | Seconds — every step cache-hits |
| A eval: / label entry | ~2 sec (LABEL re-emit only) |
| A write: task's content | Just that single content-addressed COPY layer |
| A copy: source file's content | Rebuild from that layer's COPY onward + downstream |
| A cmd: / download: task body | Rebuild from that RUN onward + downstream |
| A package added/removed in rpm:/deb:/pac: | Rebuild from the install RUN onward + downstream |
| task build:ov → new layers/ov/bin/ov | Rebuild the ov layer + every image that includes it |
| An upstream image got content-changed and rebuilt | Rebuild from the FROM step onward in every descendant |
Internal base images use exact CalVer tags in Containerfiles (FROM ghcr.io/overthinkos/fedora:2026.46.1415). This ensures each image references the precise version of its parent. Both Docker and Podman resolve local images before pulling from registry.
docker buildx build --push for multi-platform buildspodman build --manifest + podman manifest pushPodman manifest push uses retry with exponential backoff (3 attempts, 5s/10s/20s delays) to handle transient registry errors (e.g., GHCR 500 errors after long builds).
Source: ov/build.go (retryCmd).
Post-build optimization that merges consecutive small layers:
ov image merge <image> --dry-run # Preview what would be merged
ov image merge <image> # Merge small layers
ov image merge --all # Merge all images with merge.auto enabled
ov image merge <image> --max-mb 512 # Custom per-layer threshold
ov image merge <image> --max-total-mb 4096 # Custom total image size limit
Configure in image.yml:
defaults:
merge:
auto: true # Auto-merge after builds
max_mb: 128 # Max merged layer size (MB)
max_total_mb: 0 # Max total image size for merge (0 = no limit)
CLI flags --max-mb and --max-total-mb override image.yml. auto is only used by ov image merge --all to select which images to merge; ov image merge <image> always merges regardless. max_total_mb controls whether large images skip merging entirely (the merge process decompresses layers in memory). Set to 0 to disable (default), or a positive value like 2048 to cap on low-memory CI runners.
<engine> save -> tarball.ImageFromPath()layer.Size()max_mbmutate.Append(), preserving OCI history alignment (empty-layer entries for ENV/USER/EXPOSE kept in correct positions)tarball.WriteToFile() -> <engine> loadMerge is idempotent -- running again after merging shows all layers as [keep]. Source: ov/merge.go.
Images are merged immediately after building, before their children are built. Child images inherit a merged (fewer-layer) base, producing smaller final images. Both local and push builds merge inline. The mergeAfterBuild() function handles this -- it checks merge.auto on the image config and runs merge if enabled.
For filtered builds (ov image build <image>), only the built images are merged. For full builds (ov image build), merge runs after each dependency level completes.
ov settings set engine.build docker # or podman
ov settings set engine.run docker # or podman
Requires: go-task, go, docker (or podman). On Arch the recommended install is yay -S overthink-git (or cd pkg/arch && makepkg -si) — the PKGBUILD pulls every dep, including task itself, and the bundled pacman post-install hook enables docker/tailscaled/virtqemud automatically. On other distros, run task build:ov from the checkout to compile and install ov to ~/.local/bin/ov.
task build:ov # Build + install ov; on Arch delegates to makepkg -si, elsewhere installs portable to ~/.local/bin
task setup:builder # Create multi-platform buildx builder
ov image build # Generate + build + merge all images
ov image build my-app
ov image build my-app
# Only changed layers are rebuilt (Docker layer cache)
# Authenticate first
docker login ghcr.io
# or: podman login ghcr.io
ov image build --push
On Arch run yay -S overthink-git (or cd pkg/arch && makepkg -si) to install system-wide. Elsewhere run task build:ov from the checkout — task itself is required (install via your distro package manager or download from go-task/task releases).
Build base images first. ov image build handles dependency ordering automatically, but if building a single image, its base must already exist.
First build on a new machine won't have cache. Use --cache registry to pull from registry cache if available.
If a build fails with conflicting requests involving libavcodec-free vs libavcodec (epoch 1), the layer is trying to install ffmpeg-free (Fedora) in an image that has negativo17's ffmpeg-libs (via cuda layer). Fix: change ffmpeg-free to ffmpeg in the layer's rpm.packages and add the fedora-multimedia repo from negativo17. See the immich layer for the correct pattern.
If you see cannot unmarshal !!str ... into int or similar YAML parsing errors on layer fields, the installed ov binary is likely stale. Rebuild with task build:install or cp bin/ov ~/.local/bin/ov. Verify with ov image validate.
ov binary produces stale ContainerfilesBeyond the YAML-unmarshal symptom above, a stale ov binary can produce syntactically valid but outdated Containerfile output — e.g. emitting an old broken form of a template that HEAD's source has already fixed. Symptom: build fails on a step whose generated shell clearly doesn't match the source you see in git grep. Quick diagnostic: ls -la $(which ov) vs. git log -1 ov/generate.go — if the binary predates the fix, rebuild:
task build:ov # rebuild + pacman-reinstall on Arch (overthink-git package)
Common on Arch where overthink-git is pacman-installed and HEAD moves faster than rebuilds. If you find yourself rebuilding ov to chase a bug and the symptom persists, the binary path on $PATH may not be the one task build:ov updated — confirm with which ov.
Cache mounts declared via --mount=type=cache,dst=<path>,uid=<u>,gid=<g> (used for pixi, npm, cargo, rattler, dnf) persist across builds and are not evicted by ov image build --no-cache (which only suppresses --cache-from; see above). A disk-full event mid-download can leave a partial package in the cache, and every subsequent build re-hits the same broken file:
error: × failed to link tzdata-2025c-hc9c84f9_1.conda
├─▶ failed to copy file ... zoneinfo/<Region>/<City>: No such file or directory
Options, in order of least to most disruptive:
echo "" >> layers/pixi/layer.yml) to evict the cache mount associated with that build-stage's hash, then revert the cosmetic change. Same workaround as the scratch-stage cache issue above.podman image prune -af is NOT the right hammer — see next note.podman image prune -af caveat — removes tagged-but-idle imagespodman image prune -a -f will delete tagged images that are not referenced by a running container. That means the image you just built via ov image build — and haven't started as a container yet — is eligible for prune. Observed sequence: ov image build → podman image prune -af → sudo podman load (manual rootful refresh) fails with image not known. Protect the fresh build by starting a container or use prune with explicit filters instead.
--no-cache does not invalidate intermediate scratch-stage cachesov image build --no-cache <image> and ov image build --cache none <image> reliably disable the
cache for the final image stage, but in observed behavior they do not propagate
to intermediate scratch stages produced by COPY layers/<x>/ / instructions
([15/25] STEP 2/2: COPY layers/labwc/ / style). Editing a single file inside a layer
directory and rebuilding with --no-cache may still pull the labwc scratch stage from
cache, leaving the new file content out of the rebuilt image.
Workaround: force a content-hash bump on the layer's layer.yml. The simplest is
adding (or removing) a trailing comment line:
echo "" >> layers/labwc/layer.yml # bump content hash
ov image build selkies-desktop # now invalidates the labwc scratch stage
git checkout -- layers/labwc/layer.yml # revert the cosmetic change
This was discovered while shipping commit febb9bd (labwc autostart race fix): the
edit to layers/labwc/autostart did not propagate to the rebuilt image until
layers/labwc/layer.yml itself was touched. Two consecutive --no-cache rebuilds
produced the same image hash (502c8012c7a5) until the layer.yml content changed.
Symptom: podman image ls shows a new tag, but podman run --rm <new tag> cat /path/to/changed/file
returns the old content.
:latest under --cache-fromov's Containerfile generator currently emits FROM <registry>/<builder>:latest
for parent builder images (not pinned CalVer). When ov invokes
podman build --cache-from <registry>/<image>, podman resolves those FROM
clauses at parse time and will pull :latest from the remote registry,
silently clobbering any locally-rebuilt :latest tag. For images that rebuild
their builder stages in the same invocation this can lead to the later
stages using the stale registry builder instead of the freshly-built one.
Symptom observed on this project: ov update selkies-desktop --build
fails deep inside selkies' pixi install && bash build.sh step with
error: can't find Rust compiler. The remote
ghcr.io/overthinkos/fedora-builder:latest predates the build-toolchain
layer adding cargo as an RPM, so its pixi env has no rustc — even though
the current local build-toolchain/layer.yml lists cargo. The build phase
that rebuilds fedora-builder locally does run, but parent-stage FROM
resolution happens before that stage exists, and podman pulls the stale
remote image.
Workaround until the generator is fixed:
ov image build <image> --cache=none # or equivalently --no-cache at the ov level
Both --cache=none and --no-cache short-circuit cacheArgs() in
ov/build.go:cacheArgs and do NOT pass --cache-from to podman, so
the broken resolution path never fires. --no-cache is ov-level only — it
does not pass --no-cache to podman, it just skips --cache-from.
Proper fix (not yet implemented): ov's generator should emit pinned
CalVer tags for builder FROM clauses (e.g.,
FROM ghcr.io/overthinkos/fedora-builder:2026.105.0128) or pass
--pull=never to podman so local tags aren't resolved from the remote.
Tracked as a follow-up.
See also: /ov-build:generate for the Containerfile generation path, /ov-core:update
for the --build flag that also picks up this caveat.
ov image build (like every build-mode command) resolves image.yml via os.Getwd(). Override with -C <dir> / --dir <dir> / OV_PROJECT_DIR=<dir> — honoured before Kong dispatch. See /ov-build:image "Project directory resolution" for the canonical reference and the ov mcp serve use case. Typical use: building from an ov mcp serve MCP tool where the container cwd doesn't hold the project.
ov image family siblings/ov-build:image -- Family overview + image.yml composition reference/ov-build:generate -- Containerfile generation (called internally; stale :latest FROM lives there)/ov-build:inspect -- Inspect resolved image config before building/ov-build:list -- Enumerate images, layers, build targets/ov-build:merge -- Post-build layer consolidation (runs inline after each build level)/ov-build:new -- Scaffold a new layer directory before adding to image.yml/ov-build:pull -- Pull prebuilt images; orthogonal to building (use for downstream deploy-mode commands)/ov-build:validate -- Validate image.yml + layers before building/ov-build:layer -- Layer definitions that get built/ov-build:eval -- Tests are embedded as org.overthinkos.eval OCI label at build time; LABEL-at-end optimization (see Cache Efficiency above) makes test edits cheap./ov-core:update -- ov update <image> --build invokes BuildCmd.Run and picks up the same --jobs cap and stale-:latest caveat/ov-advanced:vm -- Building bootc disk images (ov vm build)/ov-core:config -- Engine configuration/ov-advanced:enc -- Encrypted-volume restart path interacts with the --build flow (ov config mount short-circuit means builds can restart services without touching the keyring)MUST be invoked when the task involves building images, pushing to registries, build caches, or layer merging. Invoke this skill BEFORE reading source code or launching Explore agents.
Workflow position: Typically first in a lifecycle chain.
Next step: /ov-core:deploy (quadlet setup, tunnels) → /ov-core:service (start and manage).
/ov-dev:capabilities — OCI labels emitted during the build stage; CapabilityLabelMap completeness check/ov-build:eval 10 standards)Changes that touch this verb's output must reach a healthy deployment on a target explicitly marked disposable: true (see /ov-dev:disposable). Use ov update <name> to destroy + rebuild unattended on any disposable target. Never experiment on a non-disposable deploy — set up a disposable one first with ov deploy add <name> <ref> --disposable or mark a VM in vms.yml.
After committing the source-level fix, ov update the disposable target ONCE MORE from clean and re-run the full verification. A fix that passes only on a hand-patched target is not a real fix — it's a regression waiting for the next unrelated rebuild. Paste BOTH the exploratory-pass output and the fresh-rebuild-pass output into the conversation.
Unit tests + a clean compile are necessary but not sufficient. See CLAUDE.md R1–R10.
development
Claude Code multi-agent support in Overthink — sub-agents, dynamic workflows, and agent teams, and how each drives the existing `ov eval` disposable beds to test and verify. MUST be invoked before authoring or invoking an ov sub-agent / dynamic workflow / agent team, wiring agent-lifecycle hooks, or asking "which primitive should drive the R10 beds?".
tools
Mounts a virtiofs share tagged `workspace` at /workspace inside a VM guest via a systemd .mount unit. Use when a kind:vm entity shares a host directory into the guest and you need it auto-mounted (and re-mounted at every boot).
development
MUST be invoked before any work involving: the `kind: android` schema kind, a `target: android` deploy, the `apk:` layer package format (installing Android apps declaratively), AndroidDeployTarget, an in-pod emulator OR a remote/physical adb-endpoint device, or nested `pod → android` deployment. The first-class Android device + app surface that sits above `ov eval adb`/`appium`.
tools
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + image/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.