Summary
Add a long-running tsops dev <namespace> mode (Skaffold-style) and content-addressed image caching, so that:
- New PRs reach a usable preview environment in seconds, not minutes — even on cold CI.
- AI coding agents running in restricted sandboxes (Claude Code on the web, GitHub-hosted Codespaces, etc.) get a one-command preview-loop they can launch before they even open a PR.
- CI workflows like
worken-pr-preview.yml collapse onto the same code path as local dev, instead of reimplementing artifact-passing, digest-reuse, and namespace lifecycle in shell scripts per project.
Motivation
tsops already has most of the building blocks for an incremental, watch-driven workflow:
up <namespace> --apps-from-changes --base-ref — diff-driven app selection.
build --filter <ref> — incremental build by git ref.
core/dependencies/graph — app dependency graph.
core/operations/planner — diff-aware create/update/unchanged plan.
- Separated builder / deployer / cert-hook / db-hook operations.
What's missing for a fast PR-preview experience:
- No long-running loop. Every push triggers a full
up from scratch (clean checkout, clean BuildKit, full image build per included service). Even if only one file changed, all services in --include are rebuilt.
- No content-addressed image reuse. If
worken-front source didn't change between PR pushes, we still rebuild it; the registry tag is keyed by commit SHA, not by source-tree hash.
- No "attach" mode for CI. Workflows have to glue: build artifacts → upload → download → manually pass image digests via env. Each consumer reinvents this. (See e.g.
WORKEN_PREVIEW_IMAGE_DIGESTS_JSON in our worken-pr-preview.yml.)
- No file-sync. A one-character change in a TSX file rebuilds and redeploys the whole image, whereas a
kubectl cp + Next.js fast-refresh would take ~1s.
The agent-in-sandbox angle makes (1)–(3) acute: an agent's iteration loop currently is "edit → push → wait several minutes for CI workflow to build → fight basic-auth on the preview URL → discover the change". We'd like to compress that to "edit → preview reflects the change", with the bulk of the work happening before CI is even invoked.
Proposal
A. tsops dev <namespace> — long-running watch loop
tsops dev <namespace>
--var pr=<n> | --pr auto auto = resolve from `gh pr view` / current branch
--include <apps> initial filter; defaults to --apps-from-changes
--strategy <auto|sync|rebuild> per-event override
--debounce <ms> default 500
--port-forward auto kubectl port-forward per app
--once CI mode: up + first sync pass + exit 0
--attach skip up; only attach watcher to existing namespace
--no-build consume pre-built digests
--image-digests <json> { "<app>": "<repo>@sha256:..." }
-c, --config <path>
--dry-run
Loop semantics:
- Initial
up (or attach) using existing planner.
- Watcher (e.g.
chokidar) per app.build.context.
- Classifier maps each batch of events to an action:
- sync — file matches
app.dev.sync and not rebuildOn → tar-pipe via kubectl exec into the running pod (no sidecar required if the image has tar).
- rebuild — matches
rebuildOn (Dockerfile, lockfile, deps) → rebuild that single app + rolling-restart deployment.
- redeploy —
tsops.config.ts or k8s manifest changed → re-run planner, apply diff.
- db-migrate — matches
db-hook.watch (new field) → re-run db-hook.
B. app.dev config
defineConfig({
apps: {
'worken-front': {
build: { context: 'apps/worken-front', dockerfile: '...' },
dev: {
sync: [
{ from: 'apps/worken-front/src', to: '/app/apps/worken-front/src' },
{ from: 'apps/worken-front/public', to: '/app/apps/worken-front/public' },
],
rebuildOn: ['**/Dockerfile', '**/package.json', 'bun.lock'],
ignore: ['**/*.test.ts', '**/.next/**'],
command: ['bun', '--hot', 'run', 'dev'],
readinessProbe: { httpGet: { path: '/_health', port: 3000 } },
},
},
},
})
Pure addition — existing apps without dev keep current behavior.
C. Content-addressed image cache
In operations/builder:
computeSourceTreeHash(app) = stable hash over files in build.context minus .dockerignore, plus the app's slice of tsops.config.ts, plus the tsops version.
- Image gets two tags on push:
<sha> (current) and srctree-<hash> (new).
- Before building,
docker buildx imagetools inspect <repo>:srctree-<hash> — hit → return digest, skip build entirely.
- New flag
--no-cache to opt out.
This is the single biggest win for PR previews: a 6-service preview where only one service changed becomes 1 build + 5 cache-hits.
D. --no-build --image-digests for CI
Today downstream projects glue this in shell. Lifting it into the CLI gives:
- run: tsops dev preview --once --no-build --image-digests "$DIGESTS_JSON" --var pr="$PR"
…which replaces a multi-step build-artifact / verify-artifact / up dance.
Why "blazing fast" for new PRs
End-to-end timeline for a typical PR with this in place:
| Step |
Today |
With tsops dev + content-cache |
Agent edits one file in worken-front |
— |
dev watcher syncs file (~1s) |
| Agent pushes first commit |
n/a |
n/a |
CI up triggered on PR open |
full build × N services + deploy |
content-hash hits for unchanged services, rebuild only changed → digest already in registry → dev --once --no-build → ~30s deploy |
| Subsequent PR push |
full rebuild of --include set |
content-hash skips unchanged + sync for hot path |
The combination — content-hash cache plus --once --no-build — means CI does almost no work on the happy path. The agent or developer has effectively pre-built everything before CI ran.
Sandbox-friendly story for Claude / coding agents
Two scenarios, both improved:
Scenario 1 — agent has cluster credentials (rare). Run tsops dev preview --pr=auto in the sandbox; loop runs for the duration of the session.
Scenario 2 — agent has no cluster access (common). A SessionStart hook in the project triggers the trusted CI workflow with gh workflow run … on the agent's first commit, before the PR is even opened. Because that workflow now runs tsops dev preview --once --no-build --image-digests … against a content-addressed cache, by the time the agent opens the PR, the preview namespace is already up. Agent's iteration is bounded by git push latency, not by image build time.
This makes tsops directly useful as the substrate for "open a PR, get a working preview before the first review comment" — the experience Vercel/Netlify offer for static sites, but for full Kubernetes apps.
Non-goals
- Replacing Skaffold/Tilt for users who already have them.
- Hot-reloading compiled languages without language-specific support; sync is opt-in per app.
- Running the preview cluster itself; tsops still assumes a reachable cluster.
Trade-offs
- Content hash correctness. Stable hashing must respect
.dockerignore and pin the tsops version into the hash; otherwise a tsops upgrade silently invalidates caches. Acceptable: fail closed on hash mismatch, verbose --explain-cache.
- Sync requires
tar in the dev image. Documented. Distroless prod images aren't affected — sync is dev-mode-only.
dev.command override means the dev-mode container differs from prod. Same constraint as Skaffold; the alternative (separate dev Dockerfile) is also supported via build.dockerfile overrides.
- Watcher resource use on large monorepos. Mitigation:
ignore globs, default-derived from .gitignore + .dockerignore.
Suggested incremental PRs
feat(builder): content-addressed image cache via source-tree hash — standalone, immediate value, no API surface change beyond --no-cache.
feat(cli): tsops up --no-build --image-digests — unify what downstream projects already do in shell.
feat: tsops dev <namespace> — config schema, watcher, classifier, syncer, dev-loop. Largest PR; ships behind the new command, no behavior change for up/build/deploy.
feat(cli): tsops dev --once — final piece that lets CI consume the same code path.
Alignment with current best practices for agentic dev speed
A short cross-check against published guidance from Anthropic, Skaffold/Tilt, and BuildKit, with concrete refinements to the design above.
1. Fast feedback is the dominant lever
Anthropic's 2026 Agentic Coding Trends Report and CodeScene both single out the speed of build/test/tool feedback as the highest-impact factor in agent productivity — agents converge on working solutions only as fast as they can verify them. The Claude Code best-practices doc calls verification ("tests, screenshots, expected outputs the agent can check itself") the highest-leverage practice.
→ Refinement: in addition to the human-facing watch loop, emit a stable, machine-parseable status file ({ phase, ready, urls, lastSyncMs, lastError }) that an agent can poll without scraping logs. Pairs naturally with --once.
2. Sync-vs-rebuild classification matches Skaffold/Tilt consensus
The community has converged on the exact rule encoded in section B:
- Sync for interpreted code and static assets — pipe files into the running container, skip build/push/deploy.
- Rebuild only on dependency-manifest changes (Dockerfile, lockfile,
package.json).
- For Node/Bun: run
nodemon / bun --hot inside the container; never npm install on every change — gate it on package.json.
→ Refinement: default dev.ignore to .gitignore ∪ .dockerignore (Skaffold does this) so users don't have to re-list common ignores.
3. Content-addressed cache should sit on top of BuildKit, not replace it
The pattern in section C is well-established (the docker-image-context-hash GitHub Action does exactly this), and BuildKit's external cache backends (registry, gha, s3) are considered essential in CI because runners have no persistence between runs.
→ Refinements:
- Use BuildKit's
--cache-to type=registry,mode=max as the substrate for per-layer reuse, and treat the srctree-<hash> tag as a fast-path index on top of that — not a replacement.
- Pin both the tsops version and the BuildKit frontend version into the hash; otherwise a frontend bump silently invalidates caches.
- Ship
--explain-cache from day one so misses are debuggable.
4. --once / --no-build mirrors skaffold render + skaffold deploy --images
Splitting CI build from deploy with pre-built digests is a documented Skaffold pattern. Lifting it into tsops is consistent with industry practice and removes per-project shell glue.
5. Sandbox-agent guidance from Anthropic
The Claude Code best-practices and sandbox guides recommend:
- Run unsupervised agent loops inside Docker/sandbox isolation.
- Pre-warm preview environments via a
SessionStart hook so the preview is ready before the first review comment — exactly Scenario 2.
- Cap iterations (
maxTurns) and prefer preview-branch systems for review ergonomics.
→ Refinement: ship a documented SessionStart hook recipe in the tsops repo that runs gh workflow run … to kick off the preview before the agent opens the PR.
6. Spec-first / plan-mode caveat
Independent of tsops, the 2026 best-practice consensus is "spec-first, plan-mode, review every diff, ≥60% test coverage." A delivery-loop change like this lands its agent value only when those upstream practices are in place — worth a one-line note in the README so users don't expect speed to substitute for verification.
Net adjustments to fold into the PRs above
- PR 1 (content cache): layer on BuildKit registry/GHA cache; include BuildKit frontend version in the hash; add
--explain-cache.
- PR 3 (
tsops dev): default ignore from .gitignore ∪ .dockerignore; emit a structured status file the agent can poll.
- Docs: add a
SessionStart hook recipe for sandboxed agents; one-line note about spec-first being prerequisite.
References
Happy to prototype any of the four PRs against a fork if useful.
Summary
Add a long-running
tsops dev <namespace>mode (Skaffold-style) and content-addressed image caching, so that:worken-pr-preview.ymlcollapse onto the same code path as local dev, instead of reimplementing artifact-passing, digest-reuse, and namespace lifecycle in shell scripts per project.Motivation
tsopsalready has most of the building blocks for an incremental, watch-driven workflow:up <namespace> --apps-from-changes --base-ref— diff-driven app selection.build --filter <ref>— incremental build by git ref.core/dependencies/graph— app dependency graph.core/operations/planner— diff-aware create/update/unchanged plan.What's missing for a fast PR-preview experience:
upfrom scratch (clean checkout, clean BuildKit, full image build per included service). Even if only one file changed, all services in--includeare rebuilt.worken-frontsource didn't change between PR pushes, we still rebuild it; the registry tag is keyed by commit SHA, not by source-tree hash.WORKEN_PREVIEW_IMAGE_DIGESTS_JSONin ourworken-pr-preview.yml.)kubectl cp+ Next.js fast-refresh would take ~1s.The agent-in-sandbox angle makes (1)–(3) acute: an agent's iteration loop currently is "edit → push → wait several minutes for CI workflow to build → fight basic-auth on the preview URL → discover the change". We'd like to compress that to "edit → preview reflects the change", with the bulk of the work happening before CI is even invoked.
Proposal
A.
tsops dev <namespace>— long-running watch loopLoop semantics:
up(orattach) using existing planner.chokidar) perapp.build.context.app.dev.syncand notrebuildOn→ tar-pipe viakubectl execinto the running pod (no sidecar required if the image hastar).rebuildOn(Dockerfile, lockfile, deps) → rebuild that single app + rolling-restart deployment.tsops.config.tsor k8s manifest changed → re-run planner, apply diff.db-hook.watch(new field) → re-run db-hook.B.
app.devconfigPure addition — existing apps without
devkeep current behavior.C. Content-addressed image cache
In
operations/builder:computeSourceTreeHash(app)= stable hash over files inbuild.contextminus.dockerignore, plus the app's slice oftsops.config.ts, plus the tsops version.<sha>(current) andsrctree-<hash>(new).docker buildx imagetools inspect <repo>:srctree-<hash>— hit → return digest, skip build entirely.--no-cacheto opt out.This is the single biggest win for PR previews: a 6-service preview where only one service changed becomes 1 build + 5 cache-hits.
D.
--no-build --image-digestsfor CIToday downstream projects glue this in shell. Lifting it into the CLI gives:
…which replaces a multi-step build-artifact / verify-artifact / up dance.
Why "blazing fast" for new PRs
End-to-end timeline for a typical PR with this in place:
tsops dev+ content-cacheworken-frontdevwatcher syncs file (~1s)uptriggered on PR opendev --once --no-build→ ~30s deploy--includesetThe combination — content-hash cache plus
--once --no-build— means CI does almost no work on the happy path. The agent or developer has effectively pre-built everything before CI ran.Sandbox-friendly story for Claude / coding agents
Two scenarios, both improved:
Scenario 1 — agent has cluster credentials (rare). Run
tsops dev preview --pr=autoin the sandbox; loop runs for the duration of the session.Scenario 2 — agent has no cluster access (common). A
SessionStarthook in the project triggers the trusted CI workflow withgh workflow run …on the agent's first commit, before the PR is even opened. Because that workflow now runstsops dev preview --once --no-build --image-digests …against a content-addressed cache, by the time the agent opens the PR, the preview namespace is already up. Agent's iteration is bounded bygit pushlatency, not by image build time.This makes
tsopsdirectly useful as the substrate for "open a PR, get a working preview before the first review comment" — the experience Vercel/Netlify offer for static sites, but for full Kubernetes apps.Non-goals
Trade-offs
.dockerignoreand pin the tsops version into the hash; otherwise a tsops upgrade silently invalidates caches. Acceptable: fail closed on hash mismatch, verbose--explain-cache.tarin the dev image. Documented. Distroless prod images aren't affected — sync is dev-mode-only.dev.commandoverride means the dev-mode container differs from prod. Same constraint as Skaffold; the alternative (separate dev Dockerfile) is also supported viabuild.dockerfileoverrides.ignoreglobs, default-derived from.gitignore+.dockerignore.Suggested incremental PRs
feat(builder): content-addressed image cache via source-tree hash— standalone, immediate value, no API surface change beyond--no-cache.feat(cli): tsops up --no-build --image-digests— unify what downstream projects already do in shell.feat: tsops dev <namespace>— config schema, watcher, classifier, syncer, dev-loop. Largest PR; ships behind the new command, no behavior change forup/build/deploy.feat(cli): tsops dev --once— final piece that lets CI consume the same code path.Alignment with current best practices for agentic dev speed
A short cross-check against published guidance from Anthropic, Skaffold/Tilt, and BuildKit, with concrete refinements to the design above.
1. Fast feedback is the dominant lever
Anthropic's 2026 Agentic Coding Trends Report and CodeScene both single out the speed of build/test/tool feedback as the highest-impact factor in agent productivity — agents converge on working solutions only as fast as they can verify them. The Claude Code best-practices doc calls verification ("tests, screenshots, expected outputs the agent can check itself") the highest-leverage practice.
→ Refinement: in addition to the human-facing watch loop, emit a stable, machine-parseable status file (
{ phase, ready, urls, lastSyncMs, lastError }) that an agent can poll without scraping logs. Pairs naturally with--once.2. Sync-vs-rebuild classification matches Skaffold/Tilt consensus
The community has converged on the exact rule encoded in section B:
package.json).nodemon/bun --hotinside the container; nevernpm installon every change — gate it onpackage.json.→ Refinement: default
dev.ignoreto.gitignore ∪ .dockerignore(Skaffold does this) so users don't have to re-list common ignores.3. Content-addressed cache should sit on top of BuildKit, not replace it
The pattern in section C is well-established (the
docker-image-context-hashGitHub Action does exactly this), and BuildKit's external cache backends (registry,gha,s3) are considered essential in CI because runners have no persistence between runs.→ Refinements:
--cache-to type=registry,mode=maxas the substrate for per-layer reuse, and treat thesrctree-<hash>tag as a fast-path index on top of that — not a replacement.--explain-cachefrom day one so misses are debuggable.4.
--once/--no-buildmirrorsskaffold render+skaffold deploy --imagesSplitting CI build from deploy with pre-built digests is a documented Skaffold pattern. Lifting it into
tsopsis consistent with industry practice and removes per-project shell glue.5. Sandbox-agent guidance from Anthropic
The Claude Code best-practices and sandbox guides recommend:
SessionStarthook so the preview is ready before the first review comment — exactly Scenario 2.maxTurns) and prefer preview-branch systems for review ergonomics.→ Refinement: ship a documented
SessionStarthook recipe in thetsopsrepo that runsgh workflow run …to kick off the preview before the agent opens the PR.6. Spec-first / plan-mode caveat
Independent of
tsops, the 2026 best-practice consensus is "spec-first, plan-mode, review every diff, ≥60% test coverage." A delivery-loop change like this lands its agent value only when those upstream practices are in place — worth a one-line note in the README so users don't expect speed to substitute for verification.Net adjustments to fold into the PRs above
--explain-cache.tsops dev): defaultignorefrom.gitignore ∪ .dockerignore; emit a structured status file the agent can poll.SessionStarthook recipe for sandboxed agents; one-line note about spec-first being prerequisite.References
Happy to prototype any of the four PRs against a fork if useful.