Rebuild the base image when the baked panopticon package is stale - #389
Merged
Conversation
`build_base_if_missing` probed only *presence* (`docker image inspect`), so once `panopticon-base-<cli>` existed every spawn reused whatever `panopticon` wheel it was baked from — indefinitely. That's why a live task launched `--model primary` (the abstract tier) instead of the resolved `opus`: the control plane had advanced to tier resolution (#382/#383) while the stale container ran pre-resolution code that forwarded the tier straight to `--model`. Stamp the base image with the package version it was built from (`org.panopticon.version` label) and rebuild when the stamp is absent or differs from the installed `panopticon.__version__` — still one `docker image inspect --format` on the hot path, a rebuild only on miss/mismatch. The shared `docker build` invocation (now carrying the label + build-arg at both call sites) is factored into `_build_base`. Add a defense-in-depth guard: `resolve_tier` (shared by both CLI adapters) raises on a **reserved** tier name absent from the adapter's map instead of silently passing it to `--model`, while concrete model ids still pass through. So even a stale image now fails loud rather than launching the wrong model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tildesrc
force-pushed
the
panopticon/base-image-staleness
branch
from
August 24, 2026 01:15
0d8e928 to
7b0c903
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A live task launched
claude --model primary— the abstract model tier — instead of the resolved model (opus). The tier→model code was correct onmain(#382/#383); the root cause was a stale container image.The
panopticonpackage runs inside the task container, baked intopanopticon-base-<cli>from a wheel at build time — not mounted from the working tree.ImageBuilder.build_base_if_missingprobed only presence (docker image inspect) and rebuilt only when the image was absent — never when it was stale. So once the base existed, every spawn reused the old baked package: the current control plane created tasks withstarting_model="primary", while the stale container ran pre-resolution code that forwarded the tier straight to--model.Left unfixed this recurs on every container-side change (
container/, the CLI adapters, hooks) — each silently runs stale until a manualmake clean && make build.Fix
Version-stamped staleness check (the real fix). Stamp the base image with the package version it was built from (
org.panopticon.versionlabel) and rebuild when the stamp is absent or differs from the installedpanopticon.__version__, in addition to the absent-image case. The hot path stays onedocker image inspect --formatcall (no rebuild when the stamp matches); a rebuild happens only on miss/mismatch. The stamp rides on thedocker buildargv, so the image name stayspanopticon-base-<cli>(the rest of the system keys on that name) — only a label is added. Thedocker buildinvocation, previously duplicated betweenbuild_baseandbuild_base_if_missingand now carrying the label at both, is factored into a shared_build_basehelper.Fail-loud model guard (backstop).
resolve_modelused_MODEL_TIERS.get(tier, tier)— the passthrough is intentional so a concrete model id (claude-opus-4-8) still works, but it also let a bareprimaryleak unvalidated. A new sharedresolve_tierhelper (used by both the claude and codex adapters) raises when a value is a reserved tier name (core.models.MODEL_TIERS) absent from the adapter's map, while concrete model ids still pass through. So even a stale image now fails loud in the pane rather than launching the wrong model silently.Version-granularity caveat
panopticon.__version__is bumped manually (0.0.5today), so two working trees can share a version — a pure version-label check catches the reported bug (the version did advance across the tier-resolution PRs) and the common upgrade case, but not local dev iterating oncontainer/without a version bump. That case still needsmake clean && make build. Chosen deliberately over a content-hash label to keep this simple; the fail-loud guard covers the model-specific failure mode regardless.Scope
panopticon-<cli>-<workflow>-<repo>) recompose off the base anddocker buildre-runs the wheel layer when the base changes.primaryresolves to.docker buildflag).