Skip to content

Rebuild the base image when the baked panopticon package is stale - #389

Merged
tildesrc merged 1 commit into
mainfrom
panopticon/base-image-staleness
Aug 24, 2026
Merged

Rebuild the base image when the baked panopticon package is stale#389
tildesrc merged 1 commit into
mainfrom
panopticon/base-image-staleness

Conversation

@tildesrc

Copy link
Copy Markdown
Contributor

Problem

A live task launched claude --model primary — the abstract model tier — instead of the resolved model (opus). The tier→model code was correct on main (#382/#383); the root cause was a stale container image.

The panopticon package runs inside the task container, baked into panopticon-base-<cli> from a wheel at build time — not mounted from the working tree. ImageBuilder.build_base_if_missing probed only presence (docker image inspect) and rebuilt only when the image was absentnever when it was stale. So once the base existed, every spawn reused the old baked package: the current control plane created tasks with starting_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 manual make 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.version label) and rebuild when the stamp is absent or differs from the installed panopticon.__version__, in addition to the absent-image case. The hot path stays one docker image inspect --format call (no rebuild when the stamp matches); a rebuild happens only on miss/mismatch. The stamp rides on the docker build argv, so the image name stays panopticon-base-<cli> (the rest of the system keys on that name) — only a label is added. The docker build invocation, previously duplicated between build_base and build_base_if_missing and now carrying the label at both, is factored into a shared _build_base helper.

Fail-loud model guard (backstop). resolve_model used _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 bare primary leak unvalidated. A new shared resolve_tier helper (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.5 today), 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 on container/ without a version bump. That case still needs make 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

  • Only the base image gets the explicit check — composed images (panopticon-<cli>-<workflow>-<repo>) recompose off the base and docker build re-runs the wheel layer when the base changes.
  • No change to the tier→model mappings or to which model primary resolves to.
  • No Dockerfile change (the label is a docker build flag).

`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
tildesrc force-pushed the panopticon/base-image-staleness branch from 0d8e928 to 7b0c903 Compare August 24, 2026 01:15
@tildesrc
tildesrc merged commit a32d469 into main Aug 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant