Finish the dev-base image split: fan-out tier DAG, per-tier CI jobs, persistent build cache #494
No reviewers
Labels
No labels
burndown-2026-06
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/agentic-os!494
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-491"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
closes #491
ward.workflow: pull-request-and-merge
What this does
Finishes the dev-base tier split along all three axes from the issue.
1. Tier DAG - fan-out/fan-in, not a chain.
lang-node,lang-go,lang-dotnet, andopsare now siblings building directly oncore, each carrying only its own toolchain. The composed tiers graft what they need with multi-stageCOPY --from:agent(onops) grafts the Node toolchain,full(onagent) grafts Go and dotnet, sofullkeeps everything it had. Node moves to a self-contained/usr/local/nodeprefix (on core'sPATH) to make its graft one directory copy; Go and dotnet already lived in graftable prefixes. The wiring is data (TierSpec.base_tier/graft_tiersinagentic_os/dev_base.py), emitted asBASE_IMAGE/<TIER>_IMAGEbuild-args.The full restructure path was taken, not the incremental keep-the-chain path: the graft shape is the standard multi-stage answer and the plan/tests hold it together, so stopping at split-jobs-on-a-chain would have left the ops/agent tiers still hostage to a dotnet flake.
2. CI - one job per tier.
publish-dev-basebecomes seven jobs riding the newactions/publish-dev-base-tiercomposite, withneeds:carrying the real DAG (core-> {lang-node,lang-go,lang-dotnet,ops} ->agent->full). Each tier gets its own logs, timeout, retry (rerun-failed-jobsreruns one tier), and its own verify (tag + aliasimagetools inspect, folded into the build script). Alang-dotnetflake now costslang-dotnetandfullonly -opsandagentpublish anyway. The tag-cuttingreleasejob needspublish-full, so the public tag still lands only after the whole family (decoupling that further stays with #490).3. Cache - persistent and loud. The
aosbuilderbuildx builder is reused: created only when absent (create race between sibling jobs absorbed), recreated only when it genuinely fails to bootstrap. Its layer cache lives in the long-lived runner dind, so warm runs stop cold-rebuilding under qemu - the oldrm -fbootstrap threw that cache away every run. Thetype=registrycache keepsignore-error=true(a registry hiccup must not fail a good push), but the script now probes the buildcache manifest after each pushed tier and emits::warning::when the write did not land, so a permanently-cold cache is observed instead of inferred. Model documented indocs/dev-base-build-cache.md.Wall-clock evidence
Before (Forgejo actions API,
publish-dev-basetasks since 2026-07-10): 47 attempts, 5 successes. Warm-registry-cache successes ran 2.9-6.1 min; cold runs (cache wiped or registry cache-write silently failing) ran into the shared 120-minute family timeout, and run 1127's lang-dotnet push failure at position 4/7 droppedops/agent/fullentirely (the aos#490 lost release).After: cannot be measured until this lands and the promote -> release pipeline publishes with it - the numbers to record on the first post-merge release run are the per-tier job durations (expect the sibling rank to overlap, runner capacity permitting) and the second, warm run's family wall-clock (expect minutes, not a cold rebuild, since the builder now survives). Recording those on the issue after the first two release runs is the follow-up; the mechanism (builder reuse + cache probe) is in the diff and the probe makes any remaining coldness visible in the job logs rather than needing inference.
Verification
uv run pytest- 466 passed (new tests: graft plan entries, topological TIER_SPECS, Dockerfile graft stages, single-tier--tierbuild args, cache-probe warning).pre-commit run --all-files- clean.scripts/dev-base-build.py ... planderives exactly the intended DAG (core root; four siblings on core; agent = ops + LANG_NODE_IMAGE; full = agent + LANG_GO_IMAGE + LANG_DOTNET_IMAGE).