Per-kind Model + Effort tiering Implementation Plan¶
For agentic workers: this plan is executed by subagents. REQUIRED SUB-SKILL: superpowers:subagent-driven-development
Goal: Attack axis 1 of the token-conservation spec (component 1). Add modelByKind/effortByKind map fields to the Project CR AgentSpec, resolve MODEL/EFFORT per Task kind in BuildPod (per-kind override else project-wide fallback), and carry the approved tier map in tatara-helmfile for both Projects. The approved tiers, keyed on the operator's real Task.Spec.Kind enum:
| Kind | Model | Effort |
|---|---|---|
| triageIssue | claude-sonnet-5 | low |
| review | claude-sonnet-5 | medium |
| brainstorm (healthCheck shares Kind=brainstorm, inherits) | claude-opus-4-8 | high |
| refine | claude-opus-4-8 | high |
| implement | claude-opus-4-8 | high |
| incident | claude-opus-4-8 | high |
| issueLifecycle | claude-opus-4-8 | high |
| selfImprove | claude-opus-4-8 | high |
This is xhigh -> high everywhere the kind stays Opus.
Architecture decision (KISS, applied in Task 5): rather than enumerate all eight kinds in effortByKind, we lower the project-wide agent.effort fallback from xhigh to high and only override the two Sonnet kinds. All Opus kinds (brainstorm/refine/implement/incident/issueLifecycle/selfImprove) then inherit high from the fallback; only triageIssue/review carry explicit overrides. Same for model: fallback stays claude-opus-4-8, modelByKind carries only the two Sonnet entries. Minimal map, no redundant high/opus repetition.
A/B RISK (plan comment, NOT a task): triageIssue and review are the only kinds dropping to Sonnet. review is the riskiest downgrade (it carries the adversarial-verify structure). Both are high-frequency, so the volume covered is large. The map is a pure tatara-helmfile values change, trivially reversible per kind: revert a single modelByKind/effortByKind entry to bump one kind back to Opus. Validate parity via the component-6 per-kind dashboards after rollout. No task here mitigates this; it is a post-deploy observation gated on component 6.
Architecture: AgentSpec gains two map[string]string CRD spec fields (CRD spec fields are typed API fields, exempt from the no-lists-in-values rule). BuildPod (internal/agent/pod.go) already branches per kind for tool profile (toolProfileForKind, pod.go:832) and skill profile (skillProfileForKind, pod.go:861); the new modelForKind/effortForKind helpers follow that exact pattern and wire into the MODEL/EFFORT env at pod.go:417-418. The Project CR spec is rendered by the tatara-project chart via toYaml .Values.project.spec (charts/tatara-project/templates/project.yaml:16), so map fields under project.spec.agent in helmfile values pass straight through to the CR.
Tech Stack: Go 1.26.3 (pinned in tatara-operator/go.mod), controller-gen v0.18.0 (CONTROLLER_GEN_VERSION in Makefile), controller-runtime CRD chart, helm/helmfile. Tests: go test table-driven with t.Run, stretchr/testify require. Repo tooling via mise (mise exec -- go test ./...).
Global Constraints¶
- Newest stable Go; pin the exact minor in go.mod. gofmt + golangci-lint must pass. Wrap errors with %w. Table-driven tests with t.Run.
- KISS. No tech debt; if complex, note rationale in MEMORY.md.
- JSON logs via stdlib log/slog. Expose /metrics Prometheus on every service. Log business actions at INFO with structured fields.
- Charts via 'helm create' then edited, never hand-rolled. Charts cluster-agnostic.
- values.yaml rule: NO plain ENVs, NO lists in values.yaml. camelCase scalar in values.yaml -> kebab-case key in ConfigMap/Secret -> workload consumes via envFrom. List-shaped data goes into a templated ConfigMap read at runtime. (Note: CRD spec fields are exempt from this - they are typed API fields, not helm values; a map[string]string on a CRD spec is fine.)
- Model IDs are authoritative literals: claude-opus-4-8 (Opus), claude-sonnet-5 (Sonnet). Effort enum: low|medium|high|xhigh|max.
- Deploy ONLY via tatara-helmfile GitOps: merge component repo main (CI builds+pushes image/chart), then a tatara-helmfile MR bumping BOTH the chart version AND the pinned image.tag for the release. Never kubectl set-image/patch to ship. Project CR value changes are tatara-helmfile values edits.
- Branch flow: worktree off main -> develop -> merge to component main -> deploy from main only.
Task 1: Add modelByKind/effortByKind map fields to AgentSpec + regenerate deepcopy + CRD manifest¶
Files: - Modify: tatara-operator/api/v1alpha1/project_types.go (AgentSpec, after Effort at :139) - Test (Create): tatara-operator/api/v1alpha1/agentspec_bykind_test.go - Regenerated (do not hand-edit): tatara-operator/api/v1alpha1/zz_generated.deepcopy.go (AgentSpec.DeepCopyInto at :16), tatara-operator/charts/tatara-operator/crd-bases/tatara.dev_projects.yaml (agent block at :46) - Unchanged (auto-picks-up new fields via Glob): tatara-operator/charts/tatara-operator/templates/crds.yaml
Interfaces: - Produces: AgentSpec.ModelByKind map[string]string (json modelByKind,omitempty), AgentSpec.EffortByKind map[string]string (json effortByKind,omitempty). - Consumed by: Task 2 helpers modelForKind/effortForKind.
Note on the CRD template: charts/tatara-operator/templates/crds.yaml globs crd-bases/tatara.dev_*.yaml (.Files.Glob "crd-bases/tatara.dev_*.yaml") and injects helm.sh/resource-policy: keep. It does not enumerate fields, so regenerating crd-bases/tatara.dev_projects.yaml via make manifests is the only CRD-template action needed - no hand-edit of crds.yaml. Per the [[operator-crd-templating-helm-adoption-2026-06-20]] memory, helm upgrade applies the templated CRDs, so the new fields reach the live CRD on deploy.
-
Step 1: Write the failing deepcopy-independence test. Create
tatara-operator/api/v1alpha1/agentspec_bykind_test.go:package v1alpha1 import "testing" // TestAgentSpecDeepCopy_ByKindMapsIndependent asserts the generated deepcopy // makes the by-kind maps independent (mutating the source after a DeepCopy // must not mutate the copy). This fails to COMPILE until the fields exist and // fails at runtime until controller-gen regenerates the map-copy loops. func TestAgentSpecDeepCopy_ByKindMapsIndependent(t *testing.T) { in := &AgentSpec{ Model: "claude-opus-4-8", Effort: "high", ModelByKind: map[string]string{"review": "claude-sonnet-5"}, EffortByKind: map[string]string{"review": "medium"}, } out := in.DeepCopy() in.ModelByKind["review"] = "mutated" in.EffortByKind["review"] = "mutated" if out.ModelByKind["review"] != "claude-sonnet-5" { t.Fatalf("ModelByKind not deep-copied: got %q", out.ModelByKind["review"]) } if out.EffortByKind["review"] != "medium" { t.Fatalf("EffortByKind not deep-copied: got %q", out.EffortByKind["review"]) } } -
Step 2: Run the test, expect a COMPILE failure.
Expected output contains: -
Step 3: Add the two map fields to
AgentSpec. Inapi/v1alpha1/project_types.go, immediately after theEffortfield (the block ending at line 139), insert:// ModelByKind overrides the project-wide Model per Task Kind. Keys are the // Task.Spec.Kind enum values (triageIssue, review, brainstorm, refine, // implement, incident, issueLifecycle, selfImprove); healthCheck shares // Kind=brainstorm so inherits the brainstorm entry. A missing or empty entry // falls back to Model. Values are authoritative model IDs (claude-opus-4-8, // claude-sonnet-5). // +optional ModelByKind map[string]string `json:"modelByKind,omitempty"` // EffortByKind overrides the project-wide Effort per Task Kind. Same keying as // ModelByKind; a missing or empty entry falls back to Effort. Values are the // effort enum (low|medium|high|xhigh|max). // +optional EffortByKind map[string]string `json:"effortByKind,omitempty"` -
Step 4: Regenerate deepcopy and CRD manifests.
This runs controller-gen (v0.18.0):generaterewritesapi/v1alpha1/zz_generated.deepcopy.go(adds map-copy loops for the two new maps insideAgentSpec.DeepCopyInto),manifestsrewritescharts/tatara-operator/crd-bases/tatara.dev_projects.yaml(addsmodelByKind/effortByKindadditionalProperties: {type: string}under theagentproperties). Confirm the generated deepcopy now contains a block like: and the CRD yaml agent block now has: -
Step 5: Run the test, expect PASS.
Expected:ok github.com/szymonrychu/tatara-operator/api/v1alpha1. -
Step 6: Verify the CRD chart-lint guard still passes (the crds.yaml inject depends on controller-gen output shape):
Expected: nochart-lint: expected 5 tatara.dev CRDs ...error; exit 0. -
Step 7: gofmt + commit.
mise exec -- gofmt -s -w api/v1alpha1/project_types.go api/v1alpha1/agentspec_bykind_test.go git add api/v1alpha1/project_types.go api/v1alpha1/agentspec_bykind_test.go api/v1alpha1/zz_generated.deepcopy.go charts/tatara-operator/crd-bases/tatara.dev_projects.yaml git commit -m "feat: add modelByKind/effortByKind to AgentSpec for per-kind tiering"
Task 2: modelForKind / effortForKind resolution helpers with table-driven tests¶
Files: - Modify: tatara-operator/internal/agent/pod.go (add helpers next to skillProfileForKind, after :882) - Test (Create): tatara-operator/internal/agent/pod_model_effort_test.go
Interfaces: - Consumes: AgentSpec.ModelByKind/EffortByKind (Task 1), project.Spec.Agent.Model/.Effort (existing, project_types.go:103,139). - Produces: modelForKind(project *tatarav1alpha1.Project, kind string) string, effortForKind(project *tatarav1alpha1.Project, kind string) string. Consumed by Task 3 (BuildPod wiring).
Resolution rule (mirrors the existing per-kind branch helpers' simplicity): a non-empty per-kind entry wins; otherwise the project-wide value. Indexing a nil map yields "", so no nil guard is needed - the != "" check handles both nil-map and set-empty. An empty-string override is treated as unset (defensive: a stray empty value in the CR should not blank out MODEL/EFFORT).
-
Step 1: Write the failing helper tests. Create
tatara-operator/internal/agent/pod_model_effort_test.go:package agent import ( "testing" tatarav1alpha1 "github.com/szymonrychu/tatara-operator/api/v1alpha1" ) func TestModelForKind(t *testing.T) { proj := &tatarav1alpha1.Project{} proj.Spec.Agent.Model = "claude-opus-4-8" proj.Spec.Agent.ModelByKind = map[string]string{ "review": "claude-sonnet-5", "triageIssue": "claude-sonnet-5", } cases := []struct { name, kind, want string }{ {"override present review", "review", "claude-sonnet-5"}, {"override present triage", "triageIssue", "claude-sonnet-5"}, {"override absent falls back", "implement", "claude-opus-4-8"}, {"unknown kind falls back", "bogus", "claude-opus-4-8"}, {"empty kind falls back", "", "claude-opus-4-8"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := modelForKind(proj, tc.kind); got != tc.want { t.Fatalf("modelForKind(%q) = %q, want %q", tc.kind, got, tc.want) } }) } } func TestModelForKind_NilAndEmptyOverride(t *testing.T) { proj := &tatarav1alpha1.Project{} proj.Spec.Agent.Model = "claude-opus-4-8" if got := modelForKind(proj, "review"); got != "claude-opus-4-8" { t.Fatalf("nil map: modelForKind = %q, want claude-opus-4-8", got) } proj.Spec.Agent.ModelByKind = map[string]string{"review": ""} if got := modelForKind(proj, "review"); got != "claude-opus-4-8" { t.Fatalf("empty override treated as set: modelForKind = %q, want claude-opus-4-8", got) } } func TestEffortForKind(t *testing.T) { proj := &tatarav1alpha1.Project{} proj.Spec.Agent.Effort = "high" proj.Spec.Agent.EffortByKind = map[string]string{ "review": "medium", "triageIssue": "low", } cases := []struct { name, kind, want string }{ {"override present review", "review", "medium"}, {"override present triage", "triageIssue", "low"}, {"override absent falls back", "implement", "high"}, {"unknown kind falls back", "bogus", "high"}, {"empty kind falls back", "", "high"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := effortForKind(proj, tc.kind); got != tc.want { t.Fatalf("effortForKind(%q) = %q, want %q", tc.kind, got, tc.want) } }) } } func TestEffortForKind_NilAndEmptyOverride(t *testing.T) { proj := &tatarav1alpha1.Project{} proj.Spec.Agent.Effort = "high" if got := effortForKind(proj, "review"); got != "high" { t.Fatalf("nil map: effortForKind = %q, want high", got) } proj.Spec.Agent.EffortByKind = map[string]string{"review": ""} if got := effortForKind(proj, "review"); got != "high" { t.Fatalf("empty override treated as set: effortForKind = %q, want high", got) } } -
Step 2: Run the tests, expect a COMPILE failure.
Expected output contains: -
Step 3: Implement the two helpers. In
internal/agent/pod.go, afterskillProfileForKind(the function ending at line 882), add:// modelForKind resolves the MODEL env for a Task Kind: a non-empty per-kind // override in AgentSpec.ModelByKind wins, else the project-wide Agent.Model. // Follows the toolProfileForKind/skillProfileForKind per-kind branch pattern. // A nil map or empty override value falls through to the project-wide model. func modelForKind(project *tatarav1alpha1.Project, kind string) string { if v := project.Spec.Agent.ModelByKind[kind]; v != "" { return v } return project.Spec.Agent.Model } // effortForKind resolves the EFFORT env for a Task Kind: a non-empty per-kind // override in AgentSpec.EffortByKind wins, else the project-wide Agent.Effort. func effortForKind(project *tatarav1alpha1.Project, kind string) string { if v := project.Spec.Agent.EffortByKind[kind]; v != "" { return v } return project.Spec.Agent.Effort } -
Step 4: Run the tests, expect PASS.
Expected:ok github.com/szymonrychu/tatara-operator/internal/agent. -
Step 5: gofmt + commit.
Task 3: Wire modelForKind/effortForKind into BuildPod's MODEL/EFFORT env¶
Files: - Modify: tatara-operator/internal/agent/pod.go:417-418 (MODEL/EFFORT env in BuildPod) - Test (Modify): tatara-operator/internal/agent/pod_model_effort_test.go (add TestBuildPod_ModelEffortByKind)
Interfaces: - Consumes: modelForKind/effortForKind (Task 2), BuildPod(project, repo, task, repos, memoryEndpoint, cfg) (pod.go:396), envValue test helper (pod_effort_test.go:10). - Produces: MODEL/EFFORT env resolved per task.Spec.Kind.
-
Step 1: Write the failing BuildPod wiring test. Append to
tatara-operator/internal/agent/pod_model_effort_test.go(add therequire,corev1,metav1imports to the file's import block):The file's import block becomes:func TestBuildPod_ModelEffortByKind(t *testing.T) { proj := &tatarav1alpha1.Project{ ObjectMeta: metav1.ObjectMeta{Name: "demo", Namespace: "tatara"}, Spec: tatarav1alpha1.ProjectSpec{ ScmSecretRef: "demo-scm", Agent: tatarav1alpha1.AgentSpec{ Model: "claude-opus-4-8", Effort: "high", Image: "wrapper:1", PermissionMode: "bypassPermissions", TurnTimeoutSeconds: 1800, ModelByKind: map[string]string{ "review": "claude-sonnet-5", "triageIssue": "claude-sonnet-5", }, EffortByKind: map[string]string{ "review": "medium", "triageIssue": "low", }, }, }, } repo := &tatarav1alpha1.Repository{ ObjectMeta: metav1.ObjectMeta{Name: "repo1", Namespace: "tatara"}, Spec: tatarav1alpha1.RepositorySpec{URL: "https://git/acme/repo1", DefaultBranch: "main"}, } cfg := PodConfig{ Namespace: "tatara", CallbackURL: "http://tatara-operator-internal.tatara.svc:8082", OIDCIssuer: "https://keycloak.tatara.svc/realms/master", AnthropicSecretName: "anthropic", CLIOIDCSecretName: "tatara-cli-oidc", OperatorURL: "http://tatara-operator.tatara.svc:8080", } cases := []struct { kind, wantModel, wantEffort string }{ {"review", "claude-sonnet-5", "medium"}, {"triageIssue", "claude-sonnet-5", "low"}, {"implement", "claude-opus-4-8", "high"}, {"brainstorm", "claude-opus-4-8", "high"}, } for _, tc := range cases { t.Run(tc.kind, func(t *testing.T) { task := &tatarav1alpha1.Task{ ObjectMeta: metav1.ObjectMeta{Name: "task-1", Namespace: "tatara", UID: "uid-1"}, Spec: tatarav1alpha1.TaskSpec{ProjectRef: "demo", RepositoryRef: "repo1", Goal: "g", Kind: tc.kind}, } env := BuildPod(proj, repo, task, nil, "http://mem.tatara.svc:8080", cfg).Spec.Containers[0].Env model, ok := envValue(env, "MODEL") require.True(t, ok, "MODEL env present for kind %q", tc.kind) require.Equal(t, tc.wantModel, model, "MODEL for kind %q", tc.kind) effort, ok := envValue(env, "EFFORT") require.True(t, ok, "EFFORT env present for kind %q", tc.kind) require.Equal(t, tc.wantEffort, effort, "EFFORT for kind %q", tc.kind) }) } }(import ( "testing" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" tatarav1alpha1 "github.com/szymonrychu/tatara-operator/api/v1alpha1" )corev1is used transitively by the shared test package; keep it only if the linter does not flag it unused - ifgoimports/golangci-lintreportscorev1unused in this file, drop that one import line.metav1andrequireare used directly here.) -
Step 2: Run the test, expect FAIL (wrong values, not compile).
Expected failure (BuildPod still reads the project-wide Model/Effort, so the Sonnet kinds get the Opus/high fallback): -
Step 3: Wire the helpers into BuildPod. In
with:internal/agent/pod.go, replace lines 417-418: -
Step 4: Run the new test + the existing effort test, expect PASS. The pre-existing
TestBuildPod_SetsEffortEnv(pod_effort_test.go) sets onlyAgent.Effortwith an empty Kind, soeffortForKindfalls back to the project value and it must still pass.Expected:mise exec -- go test ./internal/agent/ -run 'TestBuildPod_ModelEffortByKind|TestBuildPod_SetsEffortEnv'ok github.com/szymonrychu/tatara-operator/internal/agent. -
Step 5: Full operator test + lint sweep (nothing else regresses).
Expected: all packagesok, golangci-lint exit 0. -
Step 6: gofmt + commit.
-
Step 7: Request code review, fix critical/high findings, run pre-commit, then merge to operator main. Per the working agreement:
superpowers:requesting-code-review, fix critical/high,pre-commit run --all-files, then merge the worktree branch totatara-operatormain. CI on main builds+pushes the operator image (harbor.szymonrichert.pl/containers/tatara-operator:<shortSHA>) and packages thetatara-operator+tatara-projectcharts as0.0.0-g<shortSHA>. Record the resulting operator main short SHA - Task 5 needs it. Call it<newOperatorSHA>below.
Task 4: Carry the approved tier map in tatara-helmfile Project values (both Projects)¶
Files: - Modify: tatara-helmfile/values/project-tatara/common.yaml (project.spec.agent block, :17-23) - Modify: tatara-helmfile/values/project-infrastructure/common.yaml (project.spec.agent block, :17-23)
Interfaces: - Consumes: the modelByKind/effortByKind CRD fields (Task 1) via the tatara-project chart's toYaml .Values.project.spec (charts/tatara-project/templates/project.yaml:16). - Produces: Project CR spec.agent.modelByKind/effortByKind maps + the lowered project-wide effort: high fallback.
This repo has no Go/unit tests; verification is helmfile template rendering the CR and asserting the maps land. The value edit and the deploy pin-bump (Task 5) go in ONE tatara-helmfile MR (the CRD with the new fields must be live for the CR carrying the maps to validate; the pins move in lockstep).
-
Step 1: Edit
values/project-tatara/common.yamlagent block. Replace the current agent block (:17-23):with (fallback effort lowered xhigh -> high; only the two Sonnet kinds overridden - see the KISS decision at the top of this plan):agent: effort: xhigh image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:8f3d880 maxTurnsPerTask: 100 model: claude-opus-4-8 permissionMode: bypassPermissions turnTimeoutSeconds: 2700agent: # Per-kind tiering (token-conservation component 1): project-wide fallback # is Opus/high; only triageIssue+review drop to Sonnet with low/medium # effort. All other kinds (brainstorm, refine, implement, incident, # issueLifecycle, selfImprove; healthCheck shares Kind=brainstorm) inherit # the high fallback. xhigh->high everywhere it stays Opus. effort: high model: claude-opus-4-8 modelByKind: triageIssue: claude-sonnet-5 review: claude-sonnet-5 effortByKind: triageIssue: low review: medium image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:8f3d880 maxTurnsPerTask: 100 permissionMode: bypassPermissions turnTimeoutSeconds: 2700 -
Step 2: Edit
values/project-infrastructure/common.yamlagent block identically. Replace the current agent block (:17-23):with:agent: effort: xhigh image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:8f3d880 maxTurnsPerTask: 100 model: claude-opus-4-8 permissionMode: bypassPermissions turnTimeoutSeconds: 2700(Preserve the surrounding grafana/memory/scm blocks unchanged; only theagent: # Per-kind tiering (token-conservation component 1): project-wide fallback # is Opus/high; only triageIssue+review drop to Sonnet with low/medium # effort. All other kinds inherit the high fallback. xhigh->high where it # stays Opus. effort: high model: claude-opus-4-8 modelByKind: triageIssue: claude-sonnet-5 review: claude-sonnet-5 effortByKind: triageIssue: low review: medium image: harbor.szymonrichert.pl/containers/tatara-claude-code-wrapper:8f3d880 maxTurnsPerTask: 100 permissionMode: bypassPermissions turnTimeoutSeconds: 2700agent:sub-block is edited.) -
Step 3: Commit the value change (the pin-bump in Task 5 lands in the same MR; commit separately for a clean diff).
Task 5: Deploy bump - dual-pin operator chart + image.tag, in the MR carrying Task 4¶
Files: - Modify: tatara-helmfile/helmfile.yaml.gotmpl (three version: pins at :64, :83, :95) - Modify: tatara-helmfile/values/tatara-operator/common.yaml (image.tag at :6)
Interfaces: - Consumes: <newOperatorSHA> from Task 3 Step 7 (operator main merge), the Task 4 value edits (same MR). - Produces: a tatara-helmfile MR that, on the sticky helmfile diff, shows the operator Deployment image + CRD update and the two Project CR agent-map additions; auto-applies on merge.
Per [[tatara-helmfile-dual-chart-pin-and-cr-adoption-2026-06-22]] and [[tatara-operator-deploy-chart-version-and-image-tag]]: an operator deploy bumps BOTH the chart version pin AND the operator container image.tag; a chart-only bump leaves the old operator image running (so the new modelForKind code never executes). Both tatara-operator and the two tatara-project releases pin the same operator-published chart SHA and must move in lockstep (the CRD shipped by tatara-operator@<sha> must match the tatara-project chart the CRs render against - the helmfile comment at :54-59 stresses this).
-
Step 1: Bump the three chart pins to the new operator SHA. In
helmfile.yaml.gotmpl, change all three occurrences ofversion: 0.0.0-gfb8531a(lines 64, 83, 95 - thetatara-operator,project-tatara,project-infrastructurereleases) toversion: 0.0.0-g<newOperatorSHA>. -
Step 2: Bump the operator container image tag. In
to:values/tatara-operator/common.yaml, change: -
Step 3: Verify the CRs render with the new maps.
Expected: both renderedProjectmanifests contain, underspec.agent: (map key order in the render is not significant). -
Step 4: Verify the diff (before applying).
Expected: thetatara-operatorDeployment image changes...tatara-operator:fb8531a->...tatara-operator:<newOperatorSHA>, theprojects.tatara.devCRD gainsmodelByKind/effortByKindunderspec.properties.agent.properties, and bothProjectCRs gain thespec.agent.modelByKind/effortByKindmaps witheffort: high(wasxhigh). No unrelated release drift. -
Step 5: Commit and open the MR (auto-merge on pipeline success per the tatara-helmfile flow; the sticky diff is the review artifact).
Open the MR (or push to the flow that posts the stickygit add helmfile.yaml.gotmpl values/tatara-operator/common.yaml git commit -m "chore: deploy per-kind tiering - bump operator to <newOperatorSHA> + Project tier maps" git push -u origin <branch>helmfile diff); merge tomainauto-applies on the in-cluster ARC runner. -
Step 6: Post-apply verification (per
superpowers:verification-before-completion):Expected: operator image tagkubectl -n tatara get deploy tatara-operator -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' kubectl -n tatara get project tatara -o jsonpath='{.spec.agent.modelByKind}{"\n"}{.spec.agent.effortByKind}{"\n"}{.spec.agent.effort}{"\n"}' kubectl -n tatara get project infrastructure -o jsonpath='{.spec.agent.modelByKind}{"\n"}{.spec.agent.effortByKind}{"\n"}{.spec.agent.effort}{"\n"}'<newOperatorSHA>; both Projects report{"review":"claude-sonnet-5","triageIssue":"claude-sonnet-5"},{"review":"medium","triageIssue":"low"}, andeffort: high. Then confirm on the next scan cycle that a freshreviewpod carriesMODEL=claude-sonnet-5andEFFORT=mediumwhile animplementpod carriesMODEL=claude-opus-4-8andEFFORT=high: (verify the pod-label selector against the live wrapper pod labels; if the operator does not label pods by kind, inspect a review pod's env directly by name). Do NOT verify on transition-period/terminating pods per [[verify-fix-via-downstream-not-draining-pods]]. -
Step 7: Record the outcome in tatara-operator MEMORY.md (one dated line) noting component 1 shipped, the A/B risk on triageIssue/review, and that reverting a single
modelByKind/effortByKindentry in tatara-helmfile bumps a kind back to Opus. Sequence any CLAUDE.md/MEMORY.md policy edits at cutover per [[semver-push-cd-2026-06-28]], not mid-rollout.
Verification summary (whole plan)¶
- Task 1:
go test ./api/v1alpha1/green;make chart-lintgreen; CRD yaml carriesmodelByKind/effortByKind. - Task ⅔:
go test ./...+golangci-lint rungreen in tatara-operator;TestBuildPod_SetsEffortEnvstill passes (fallback path intact). - Task ⅘:
helmfile templateshows both Project CRs with the tier maps;helmfile diffshows operator image + CRD update; post-applykubectlconfirms live CR maps and a fresh review pod on Sonnet/medium, implement pod on Opus/high.