Design tiered dev-base images so AOS CI stops rebuilding every tool for every change #377

Closed
opened 2026-07-09 16:44:31 +00:00 by coilyco-ops · 3 comments
Member

Problem

The dev-base image has become both the agent parity substrate and a fleet-wide tool warehouse. That keeps warded containers and app CI aligned, but it also makes AOS CI fragile and slow: every dev-base publish rebuilds language runtimes, agent harnesses, ops CLIs, gate tools, Docker/Tailscale clients, ward, and the substrate seed in one multi-arch Dockerfile.

Recent additions (gh, helm, kubectl, yq, .NET, golangci-lint, trufflehog, kdlfmt, git-lfs, substrate seed, etc.) are useful, but the combined image is now heavy enough that one flaky upstream download or smoke check blocks the whole release.

Design target

Keep the core invariant from docs/ci-in-dev-base.md: app CI and headless agents run in pinned images that match the world they actually execute in. Do not go back to ad hoc per-job installs.

Explore a fan-out / fan-in image model by conceptual group, for example:

  • dev-base-core - Ubuntu base, certificates, git, shellcheck, Python/uv/pre-commit, ward, minimal catalog gate dependencies.
  • dev-base-language or per-language tiers - Node, Go, .NET, Rust/wasm if accepted later.
  • dev-base-ops - aws, gh, helm, kubectl, yq, docker client, tailscale client.
  • dev-base-agent - Claude, Codex, Goose, mcporter, agent self-name/statusline assets, substrate seed.
  • dev-base-full - fan-in image used by general warded containers when they need the complete battery set.
  • Optional app CI images that pin only the groups an app needs, while preserving the same ward exec entrypoint contract.

The exact names are open. The important part is to reduce rebuild blast radius and let CI validate independent conceptual groups before building the full fan-in image.

Questions to answer

  • Which layers are true base invariants versus convenience tools?
  • Which image should warded default to?
  • Should app workflows pin dev-base-full forever, or should repo classes move to narrower images (ops, language, core)?
  • How should docker/dev-base/ci-pinned-tag.txt evolve if there are multiple images? One tag for all images from the same release, or per-image pins?
  • How should scripts/dep-bump.py and tests model grouped ARG ownership without reintroducing drift?
  • Can release.yml build independent group targets in parallel and only fan in when prerequisites pass?
  • What is the migration path that does not break current app CI consumers?

Acceptance

  • Produce a concrete design document under docs/ with the image taxonomy, dependency graph, pinning model, and migration plan.
  • Update docs/dev-base-image.md, docs/ci-in-dev-base.md, and docs/FEATURES.md if the design changes the public contract.
  • File follow-up implementation issues from the design, split into small slices.
  • Do not implement the Dockerfile split in this issue unless the design is trivial and clearly safe.
## Problem The dev-base image has become both the agent parity substrate and a fleet-wide tool warehouse. That keeps `warded` containers and app CI aligned, but it also makes AOS CI fragile and slow: every dev-base publish rebuilds language runtimes, agent harnesses, ops CLIs, gate tools, Docker/Tailscale clients, ward, and the substrate seed in one multi-arch Dockerfile. Recent additions (`gh`, `helm`, `kubectl`, `yq`, .NET, golangci-lint, trufflehog, kdlfmt, git-lfs, substrate seed, etc.) are useful, but the combined image is now heavy enough that one flaky upstream download or smoke check blocks the whole release. ## Design target Keep the core invariant from `docs/ci-in-dev-base.md`: app CI and headless agents run in pinned images that match the world they actually execute in. Do not go back to ad hoc per-job installs. Explore a fan-out / fan-in image model by conceptual group, for example: * `dev-base-core` - Ubuntu base, certificates, git, shellcheck, Python/uv/pre-commit, ward, minimal catalog gate dependencies. * `dev-base-language` or per-language tiers - Node, Go, .NET, Rust/wasm if accepted later. * `dev-base-ops` - aws, gh, helm, kubectl, yq, docker client, tailscale client. * `dev-base-agent` - Claude, Codex, Goose, mcporter, agent self-name/statusline assets, substrate seed. * `dev-base-full` - fan-in image used by general `warded` containers when they need the complete battery set. * Optional app CI images that pin only the groups an app needs, while preserving the same `ward exec` entrypoint contract. The exact names are open. The important part is to reduce rebuild blast radius and let CI validate independent conceptual groups before building the full fan-in image. ## Questions to answer * Which layers are true base invariants versus convenience tools? * Which image should `warded` default to? * Should app workflows pin `dev-base-full` forever, or should repo classes move to narrower images (`ops`, `language`, `core`)? * How should `docker/dev-base/ci-pinned-tag.txt` evolve if there are multiple images? One tag for all images from the same release, or per-image pins? * How should `scripts/dep-bump.py` and tests model grouped ARG ownership without reintroducing drift? * Can release.yml build independent group targets in parallel and only fan in when prerequisites pass? * What is the migration path that does not break current app CI consumers? ## Acceptance * Produce a concrete design document under `docs/` with the image taxonomy, dependency graph, pinning model, and migration plan. * Update `docs/dev-base-image.md`, `docs/ci-in-dev-base.md`, and `docs/FEATURES.md` if the design changes the public contract. * File follow-up implementation issues from the design, split into small slices. * Do not implement the Dockerfile split in this issue unless the design is trivial and clearly safe.
Author
Member

🔎 ward agent advisor

ward agent advisor --harness codex ran a one-shot standard research pass on this question:

Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue.


Advisory recommendation

The split is worth doing, but the safest design is one source Dockerfile with named multi-stage targets, not several independent Dockerfiles. That preserves the current parity contract while reducing rebuild blast radius.

The key recommendation is:

  • Keep dev-base-full as the default warded image.
  • Introduce tiered images as explicit published targets built from the same source and the same release tag.
  • Do not move app CI to narrower images until warded can select the same image class for that repo, otherwise the repo breaks the current guarantee from docs/ci-in-dev-base.md.

Proposed image taxonomy

Recommended taxonomy:

  • dev-base-core - Ubuntu base, certs, curl/wget/jq/rg, git/git-lfs, openssh-client, shellcheck, python3, uv, managed Python installs, pre-commit, build-essential, ward runtime contract, minimal catalog dependencies.
  • dev-base-lang-node - Node/npm. This is its own language tier, but dev-base-agent depends on it because Claude and MCPorter need Node at runtime.
  • dev-base-lang-go - Go toolchain for repos that actually build/test Go inside CI.
  • dev-base-lang-dotnet - .NET SDK + ICU.
  • dev-base-ops - aws, gh, helm, kubectl, yq, docker client, tailscale client.
  • dev-base-agent - Claude, Codex, Goose, MCPorter, self-name/statusline assets, substrate seed. This should be built on top of core plus lang-node.
  • dev-base-full - fan-in image for general warded use. Depends on core + agent + ops + lang-go + lang-dotnet.

I would avoid a single broad dev-base-language tier. The issue is specifically rebuild blast radius. Per-language tiers keep a .NET bump from rebuilding Go consumers, and vice versa.

What is a true invariant vs a convenience tool

True base invariants for the current contract:

  • The OS/runtime substrate.
  • Python/uv/pre-commit because the catalog and ward exec flow assume them.
  • ward itself because it is the execution contract, not an optional tool.
  • Minimal shell/git/fetch tooling.

Not true base invariants, but still first-class shared tiers:

  • Go.
  • .NET.
  • Ops CLIs.
  • Agent CLIs.
  • Substrate seed and statusline/self-name assets.

One subtle point: Go is not a runtime invariant today, only a build-time need for producing ward inside the image. If the Dockerfile moves to multi-stage, ward can be built in a builder stage and copied into runtime targets. That means Go no longer has to live in core unless a target explicitly wants it.

Which image should warded default to

warded should default to dev-base-full.

Reasoning:

  • The current social contract is broad compatibility for arbitrary repos and issue work.
  • warded is used for headless implementation, not only CI.
  • Regressing into per-run missing-tool installs would recreate the exact drift/problem the current docs were written to stop.

Narrower defaults only make sense once ward has an explicit, repo-level image profile mechanism and the repo has proven it does not need the omitted tiers.

Should app workflows pin full forever

Not forever, but yes for now.

Recommended policy:

  • Phase 1 - all app workflows stay on dev-base-full.
  • Phase 2 - after ward can choose an image class per repo, specific repo classes may move to narrower images.
  • Phase 3 - infrastructure rollout templates the correct image ref per repo class.

This matters because the current invariant is not merely "CI uses some pinned image." It is "CI uses the same world headless agents execute in." If an app pins ops or core while warded still lands changes in full, parity is weakened.

So the narrower-image rule should be:

  • A repo may pin a narrower image only if the corresponding headless lane for that repo also runs in that same image class.

Pinning model

Use one shared release tag across all images from the same repo release, not per-image version lines.

Recommendation:

  • Keep the repo release semantics exactly as they are today: one source commit, one release tag like vX.Y.Z.
  • Publish multiple image names under that same tag.
  • Replace the single-line docker/dev-base/ci-pinned-tag.txt with a small lock/manifest file that maps logical image classes to full image refs.

For example, conceptually:

  • full -> forgejo.../agentic-os-dev-base-full:vX.Y.Z
  • core -> forgejo.../agentic-os-dev-base-core:vX.Y.Z
  • ops -> forgejo.../agentic-os-dev-base-ops:vX.Y.Z

That keeps rollout simple. Infrastructure still templates a literal image ref into app CI. The only change is that the source of truth becomes a manifest instead of one line.

I would not use separate independently-bumped version streams per image. They would complicate docs, rollout, release recovery, and parity reasoning with little benefit.

dep-bump.py and ARG ownership

The drift risk is real if the split turns into many files with repeated ARGs.

Recommended model:

  • Keep one canonical ARG defaults block.
  • Keep one ownership manifest in code or adjacent data that says which target/group consumes each ARG.
  • Keep one resolver per ARG, exactly like today.
  • Build many targets from the same source, rather than copying install logic into multiple Dockerfiles.

Concrete advice:

  • Prefer one multi-stage docker/dev-base/Dockerfile with named targets.
  • Extend scripts/dep-bump.py with target/group metadata, but keep ARG parsing single-source.
  • Add tests that assert:
  • every managed ARG belongs to exactly one ownership group,
  • every non-opt-out ARG has a resolver,
  • every target references only declared ARGs,
  • every published image target is represented in the lock/manifest.

That keeps the existing good property of dep-bump.py: a single tool bump is still one auditable commit.

Can release.yml parallelize this

Yes. That is one of the main wins.

Recommended dependency graph:

  • Build core first.
  • Build lang-node, lang-go, lang-dotnet, ops, and agent in parallel once core is available.
  • Build full only after its prerequisites pass.

The workflow can do this with either a matrix or docker buildx bake. The important behavior is:

  • target-local failures fail only that target and any downstream dependents,
  • full does not run if a prerequisite tier is red,
  • smoke checks are per target, not only on the final fan-in image.

This is a direct answer to the fragility called out in the issue: a flaky ops download should not block a successful core or language-tier publish.

Migration path

Recommended migration path:

  1. Refactor to a multi-target Dockerfile without changing the public default. Keep the current published image ref as the full contract.
  2. Teach release CI to publish the extra image targets under the same repo release tag.
  3. Introduce a manifest/lock file for image refs, but keep full as the default rollout value.
  4. Keep all current app CI consumers on full initially. No behavior change yet.
  5. Add ward-side image profile selection so a repo can declare or inherit full, ops, core, etc.
  6. Pilot one or two low-risk repos onto a narrower image class through infrastructure rollout.
  7. Only then update docs to present narrower app classes as supported, not merely experimental.

That path avoids breaking existing CI consumers and avoids a period where CI and headless agents silently diverge.

Main risks and open questions

  • Biggest risk: moving app CI to narrower images before ward can use the same class for that repo. That breaks the parity contract.
  • Design risk: multiple Dockerfiles would reintroduce drift and duplicated install logic. Avoid that.
  • Release naming question: whether Forgejo registry naming is cleaner as separate image names or one image with suffixed tags. I would favor separate image names with the same release tag because it keeps the tag semantic clean.
  • Agent tier boundary: Node belongs to language in principle, but agent CLIs require it. That is fine if agent explicitly depends on lang-node.
  • Substrate/self-name churn: changes to the seed or statusline still rebuild agent and full. That is acceptable because those are genuinely agent-facing concerns.
  • Go placement: if runtime Go is not actually needed by most repos, moving it out of core is a meaningful size and blast-radius win.

Suggested decisions for this issue

If this issue wants concrete calls rather than more exploration, my recommendation is:

  • Adopt multi-stage, single-source Docker build definitions.
  • Publish core / per-language / ops / agent / full targets.
  • Keep full as the warded default.
  • Keep app CI on full until ward supports matching image classes per repo.
  • Replace ci-pinned-tag.txt with a shared-tag image lock manifest.
  • Extend dep-bump.py to understand group ownership, but preserve one ARG owner and one bump commit per ARG.
  • Parallelize release.yml by target, with fan-in only after prerequisites pass.

That gives the repo the split it wants without giving up the strongest part of the current design, which is that CI and agent execution stay pinned to the same declared world.


Researched and posted automatically by ward agent advisor --harness codex (ward#179). This is one-shot research, not a carried change - verify before acting on it.

— Codex, via ward agent

### 🔎 ward agent advisor `ward agent advisor --harness codex` ran a one-shot **standard** research pass on this question: > Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue. --- ## Advisory recommendation The split is worth doing, but the safest design is **one source Dockerfile with named multi-stage targets**, not several independent Dockerfiles. That preserves the current parity contract while reducing rebuild blast radius. The key recommendation is: - Keep **`dev-base-full`** as the default `warded` image. - Introduce **tiered images as explicit published targets** built from the same source and the same release tag. - Do **not** move app CI to narrower images until `warded` can select the same image class for that repo, otherwise the repo breaks the current guarantee from `docs/ci-in-dev-base.md`. ## Proposed image taxonomy Recommended taxonomy: - **`dev-base-core`** - Ubuntu base, certs, curl/wget/jq/rg, git/git-lfs, openssh-client, shellcheck, python3, uv, managed Python installs, pre-commit, build-essential, ward runtime contract, minimal catalog dependencies. - **`dev-base-lang-node`** - Node/npm. This is its own language tier, but `dev-base-agent` depends on it because Claude and MCPorter need Node at runtime. - **`dev-base-lang-go`** - Go toolchain for repos that actually build/test Go inside CI. - **`dev-base-lang-dotnet`** - .NET SDK + ICU. - **`dev-base-ops`** - aws, gh, helm, kubectl, yq, docker client, tailscale client. - **`dev-base-agent`** - Claude, Codex, Goose, MCPorter, self-name/statusline assets, substrate seed. This should be built on top of `core` plus `lang-node`. - **`dev-base-full`** - fan-in image for general `warded` use. Depends on `core + agent + ops + lang-go + lang-dotnet`. I would avoid a single broad `dev-base-language` tier. The issue is specifically rebuild blast radius. Per-language tiers keep a .NET bump from rebuilding Go consumers, and vice versa. ## What is a true invariant vs a convenience tool True base invariants for the current contract: - The OS/runtime substrate. - Python/uv/pre-commit because the catalog and `ward exec` flow assume them. - `ward` itself because it is the execution contract, not an optional tool. - Minimal shell/git/fetch tooling. Not true base invariants, but still first-class shared tiers: - Go. - .NET. - Ops CLIs. - Agent CLIs. - Substrate seed and statusline/self-name assets. One subtle point: **Go is not a runtime invariant today, only a build-time need for producing `ward` inside the image**. If the Dockerfile moves to multi-stage, `ward` can be built in a builder stage and copied into runtime targets. That means Go no longer has to live in `core` unless a target explicitly wants it. ## Which image should `warded` default to `warded` should default to **`dev-base-full`**. Reasoning: - The current social contract is broad compatibility for arbitrary repos and issue work. - `warded` is used for headless implementation, not only CI. - Regressing into per-run missing-tool installs would recreate the exact drift/problem the current docs were written to stop. Narrower defaults only make sense once ward has an explicit, repo-level image profile mechanism and the repo has proven it does not need the omitted tiers. ## Should app workflows pin `full` forever Not forever, but **yes for now**. Recommended policy: - **Phase 1** - all app workflows stay on `dev-base-full`. - **Phase 2** - after ward can choose an image class per repo, specific repo classes may move to narrower images. - **Phase 3** - infrastructure rollout templates the correct image ref per repo class. This matters because the current invariant is not merely "CI uses some pinned image." It is "CI uses the same world headless agents execute in." If an app pins `ops` or `core` while `warded` still lands changes in `full`, parity is weakened. So the narrower-image rule should be: - A repo may pin a narrower image **only if** the corresponding headless lane for that repo also runs in that same image class. ## Pinning model Use **one shared release tag across all images from the same repo release**, not per-image version lines. Recommendation: - Keep the repo release semantics exactly as they are today: one source commit, one release tag like `vX.Y.Z`. - Publish multiple image names under that same tag. - Replace the single-line `docker/dev-base/ci-pinned-tag.txt` with a small lock/manifest file that maps logical image classes to full image refs. For example, conceptually: - `full -> forgejo.../agentic-os-dev-base-full:vX.Y.Z` - `core -> forgejo.../agentic-os-dev-base-core:vX.Y.Z` - `ops -> forgejo.../agentic-os-dev-base-ops:vX.Y.Z` That keeps rollout simple. Infrastructure still templates a literal image ref into app CI. The only change is that the source of truth becomes a manifest instead of one line. I would **not** use separate independently-bumped version streams per image. They would complicate docs, rollout, release recovery, and parity reasoning with little benefit. ## `dep-bump.py` and ARG ownership The drift risk is real if the split turns into many files with repeated ARGs. Recommended model: - Keep **one canonical ARG defaults block**. - Keep **one ownership manifest** in code or adjacent data that says which target/group consumes each ARG. - Keep **one resolver per ARG**, exactly like today. - Build many targets from the same source, rather than copying install logic into multiple Dockerfiles. Concrete advice: - Prefer one multi-stage `docker/dev-base/Dockerfile` with named targets. - Extend `scripts/dep-bump.py` with target/group metadata, but keep ARG parsing single-source. - Add tests that assert: - every managed ARG belongs to exactly one ownership group, - every non-opt-out ARG has a resolver, - every target references only declared ARGs, - every published image target is represented in the lock/manifest. That keeps the existing good property of `dep-bump.py`: a single tool bump is still one auditable commit. ## Can `release.yml` parallelize this Yes. That is one of the main wins. Recommended dependency graph: - Build `core` first. - Build `lang-node`, `lang-go`, `lang-dotnet`, `ops`, and `agent` in parallel once `core` is available. - Build `full` only after its prerequisites pass. The workflow can do this with either a matrix or `docker buildx bake`. The important behavior is: - target-local failures fail only that target and any downstream dependents, - `full` does not run if a prerequisite tier is red, - smoke checks are per target, not only on the final fan-in image. This is a direct answer to the fragility called out in the issue: a flaky ops download should not block a successful `core` or language-tier publish. ## Migration path Recommended migration path: 1. **Refactor to a multi-target Dockerfile without changing the public default.** Keep the current published image ref as the `full` contract. 2. **Teach release CI to publish the extra image targets** under the same repo release tag. 3. **Introduce a manifest/lock file** for image refs, but keep `full` as the default rollout value. 4. **Keep all current app CI consumers on `full` initially.** No behavior change yet. 5. **Add ward-side image profile selection** so a repo can declare or inherit `full`, `ops`, `core`, etc. 6. **Pilot one or two low-risk repos** onto a narrower image class through infrastructure rollout. 7. **Only then update docs to present narrower app classes as supported**, not merely experimental. That path avoids breaking existing CI consumers and avoids a period where CI and headless agents silently diverge. ## Main risks and open questions - **Biggest risk:** moving app CI to narrower images before ward can use the same class for that repo. That breaks the parity contract. - **Design risk:** multiple Dockerfiles would reintroduce drift and duplicated install logic. Avoid that. - **Release naming question:** whether Forgejo registry naming is cleaner as separate image names or one image with suffixed tags. I would favor separate image names with the same release tag because it keeps the tag semantic clean. - **Agent tier boundary:** Node belongs to language in principle, but agent CLIs require it. That is fine if `agent` explicitly depends on `lang-node`. - **Substrate/self-name churn:** changes to the seed or statusline still rebuild `agent` and `full`. That is acceptable because those are genuinely agent-facing concerns. - **Go placement:** if runtime Go is not actually needed by most repos, moving it out of `core` is a meaningful size and blast-radius win. ## Suggested decisions for this issue If this issue wants concrete calls rather than more exploration, my recommendation is: - Adopt **multi-stage, single-source Docker build definitions**. - Publish **`core` / per-language / `ops` / `agent` / `full`** targets. - Keep **`full` as the `warded` default**. - Keep **app CI on `full` until ward supports matching image classes per repo**. - Replace `ci-pinned-tag.txt` with a **shared-tag image lock manifest**. - Extend `dep-bump.py` to understand **group ownership**, but preserve **one ARG owner and one bump commit per ARG**. - Parallelize `release.yml` by target, with **fan-in only after prerequisites pass**. That gives the repo the split it wants without giving up the strongest part of the current design, which is that CI and agent execution stay pinned to the same declared world. --- Researched and posted automatically by `ward agent advisor --harness codex` (ward#179). This is one-shot research, not a carried change - verify before acting on it. <!-- ward-agent-reply --> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

WARD-RESERVATION: held 🔒

reservation details

Holder: container engineer-codex-agentic-os-377 on host kais-macbook-pro-2.local.

Reserved by ward agent --harness codex (reserved 2026-07-09T19:47:19Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (1h TTL). --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/agentic-os#377 · branch issue-377 · harness codex · workflow direct-to-main
  • Run: engineer-codex-agentic-os-377 · ward v0.493.0 · dispatched 2026-07-09T19:47:19Z
  • Comment thread: 1 included in the pre-flight read, 0 stripped (ward's own automated comments).

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.493.0).

— Codex, via ward agent

<!-- ward-agent-reservation --> WARD-RESERVATION: held 🔒 <details><summary>reservation details</summary> Holder: container `engineer-codex-agentic-os-377` on host `kais-macbook-pro-2.local`. Reserved by `ward agent --harness codex` (reserved 2026-07-09T19:47:19Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (1h TTL). `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh**. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/agentic-os#377` · branch `issue-377` · harness `codex` · workflow `direct-to-main` - **Run:** `engineer-codex-agentic-os-377` · ward `v0.493.0` · dispatched `2026-07-09T19:47:19Z` - **Comment thread:** 1 included in the pre-flight read, 0 stripped (ward's own automated comments). - included: @coilyco-ops (2026-07-09T16:46:49Z) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.493.0). </details> </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

WARD-OUTCOME: done

details workflow: direct-to-main; review summary: skipped, review gate skipped by ~/.ward/config.yaml default felt: tight after the docs caps forced a second pass. confidence: high surprise: the existing advisor already matched the final design, so the work was mostly compression and packaging. follow-ups: #385, #386, #387, #388
WARD-OUTCOME: done ✅ <details><summary>details</summary> workflow: direct-to-main; review summary: skipped, review gate skipped by ~/.ward/config.yaml default felt: tight after the docs caps forced a second pass. confidence: high surprise: the existing advisor already matched the final design, so the work was mostly compression and packaging. follow-ups: #385, #386, #387, #388 </details>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#377
No description provided.