Quality-feedback loop (G4+G5) Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Instrument the Sonnet-review/triage downgrade with model-keyed quality-proxy metrics (G4) and close a self-tuning loop that auto-proposes a tier-revert MR on regression (G5).
Architecture: Operator-only signal capture - review verdict recorded at the write-back Approve/RequestChanges branch, implement CI recorded in handleMRCI, both tagged with the token metrics' existing model resolution. A handleGrafanaAlert branch turns a tier-quality alert into an incident Task scoped to tatara-helmfile with a tier-revert goal. tatara-observability adds dashboards + the alert rules.
Tech Stack: Go 1.x (tatara-operator), prometheus/client_golang, controller-runtime + envtest, Grafana-managed dashboards + alert rules (tatara-observability).
Global Constraints¶
- Repos: tatara-operator + tatara-observability. Branch off FRESH
origin/mainin each (external bots push). Worktree off main; never build/deploy from a worktree. - Newest stable Go; JSON logs via
slog; log every business action at INFO with structured fields. - Metric label sets EXACTLY:
operator_review_outcome_total{project,repo,model,verdict}(verdict in {approved, changes_requested});operator_review_findings_total{project,repo,model};operator_implement_ci_total{project,repo,model,result}(result in {pass, fail}). ReusetaskTokenLabels(task)for project/repo/model;model=task.Status.ResolvedModel. - Model IDs:
claude-opus-4-8,claude-sonnet-5(never invent). - Alert rules carry labels
homelab,system=tatara, plus the markertatara_tier_quality="true"andkind+model+project(the operator keys the tier-revert branch on the marker and reads the others). - TDD: failing test first. envtest via
KUBEBUILDER_ASSETS="$(go run sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.21 use 1.33.0 -p path)",GOFLAGS=-buildvcs=false.make manifests+make generateif CRD/markers change (none expected here). Pre-push hook runs fullmake test. - Commit messages + PR bodies end with
https://claude.ai/code/session_01GVnfZiAkBdLANE2n3uN9BK. - Deploy is a SEPARATE gated step (fleet burn) - do NOT deploy from this plan.
File Structure¶
tatara-operator - internal/obs/operator_metrics.go - 3 new CounterVec fields + register + public methods (Task 1). - internal/controller/writeback.go (~:1074/:1078) - record review outcome + findings at the Approve/RequestChanges branch (Task 2). - internal/controller/lifecycle.go (handleMRCI ~:1925) - record implement CI pass/fail (Task 3). - internal/incident/goal.go - new GoalTierRevert (Task 4). - internal/webhook/server.go (handleGrafanaAlert ~:832, createIncidentTask ~:885) - tier-quality branch (Task 4).
tatara-observability - Dashboards dir - a quality-feedback dashboard (Task 5). - Alert rules dir - tier-quality rules (Task 6).
Task 1: New quality metric families (operator)¶
Files: - Modify: internal/obs/operator_metrics.go - Test: internal/obs/operator_metrics_test.go
Interfaces: - Produces: (*OperatorMetrics).RecordReviewOutcome(project, repo, model, verdict string), (*OperatorMetrics).AddReviewFindings(project, repo, model string, n int), (*OperatorMetrics).RecordImplementCI(project, repo, model, result string). - Consumes: the existing OperatorMetrics struct + registration idiom (mirror taskTerminalTotal at :319 and its TaskTerminal method at :943).
- Step 1: Write failing test - assert a registered gathering exposes the three families after calling the methods:
func TestQualityMetrics_Emit(t *testing.T) {
m := obs.NewOperatorMetrics(prometheus.NewRegistry()) // use the ctor's registry param; if none, adapt to the existing NewOperatorMetrics signature
m.RecordReviewOutcome("tatara", "op", "claude-sonnet-5", "approved")
m.RecordReviewOutcome("tatara", "op", "claude-sonnet-5", "changes_requested")
m.AddReviewFindings("tatara", "op", "claude-sonnet-5", 3)
m.RecordImplementCI("tatara", "op", "claude-opus-4-8", "fail")
// gather + assert the 4 series exist with the right label values and counts
}
- Step 2: Run - fail (methods undefined).
- Step 3: Implement - add three CounterVec fields mirroring the
taskTerminalTotalidiom:
reviewOutcomeTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "operator_review_outcome_total",
Help: "Review tasks by verdict (approved|changes_requested), keyed by the model that ran the review.",
}, []string{"project", "repo", "model", "verdict"}),
reviewFindingsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "operator_review_findings_total",
Help: "Sum of review findings (suggestions/comments) per review, by model.",
}, []string{"project", "repo", "model"}),
implementCITotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "operator_implement_ci_total",
Help: "Implement-task PR CI conclusions (pass|fail), by model.",
}, []string{"project", "repo", "model", "result"}),
func (m *OperatorMetrics) RecordReviewOutcome(project, repo, model, verdict string) {
m.reviewOutcomeTotal.WithLabelValues(project, repo, model, verdict).Inc()
}
func (m *OperatorMetrics) AddReviewFindings(project, repo, model string, n int) {
if n > 0 { m.reviewFindingsTotal.WithLabelValues(project, repo, model).Add(float64(n)) }
}
func (m *OperatorMetrics) RecordImplementCI(project, repo, model, result string) {
m.implementCITotal.WithLabelValues(project, repo, model, result).Inc()
}
- Step 4: Run - pass.
- Step 5: Commit
feat: quality-proxy metric families (G4).
Task 2: Record review outcome at write-back (operator)¶
Files: - Modify: internal/controller/writeback.go (the Approve branch ~:1074, RequestChanges ~:1078) - Test: internal/controller/writeback_quality_test.go
Interfaces: - Consumes: Task 1 methods; taskTokenLabels(task) (turncallback.go:440 -> project, repo, kind, issue, model). - Produces: metric side-effect only.
Context: the write-back loop iterates review verdict objects v and calls writer.Approve(ctx, repo.Spec.URL, token, number, v.Body) (approved) or writer.RequestChanges(...) (changes_requested). The enclosing function has the task in scope (it writes back that task's result). The finding count is the number of suggestions/comments on v - read the exact field when implementing (e.g. len(v.Suggestions) or the review's comment slice); if no count field exists, record findings only for changes_requested as 1 and note it.
- Step 1: Write failing test - a review task written back via RequestChanges records
operator_review_outcome_total{verdict="changes_requested", model=<ResolvedModel>}+1 and findings by the count; an Approve recordsverdict="approved". Use a fakewriter+ a registry-backedOperatorMetrics, assert the series. - Step 2: Run - fail.
- Step 3: Implement - after a successful
Approve/RequestChangescall, record:
project, repo, _, _, model := taskTokenLabels(task)
if r.Metrics != nil {
r.Metrics.RecordReviewOutcome(project, repo, model, "approved") // or "changes_requested" in that branch
r.Metrics.AddReviewFindings(project, repo, model, findingCount) // findingCount from v
}
- Step 4: Run - pass.
- Step 5: Commit
feat: record review verdict + findings at write-back (G4).
Task 3: Record implement CI in handleMRCI (operator)¶
Files: - Modify: internal/controller/lifecycle.go (handleMRCI ~:1925, at the GetPRState CIStatus branch ~:1950) - Test: internal/controller/lifecycle_ci_metric_test.go
Interfaces: - Consumes: Task 1 RecordImplementCI; taskTokenLabels(task); existing st.CIStatus ("success"|"failure"|"pending"). - Produces: metric side-effect.
Context: handleMRCI reads st, _ := writer.GetPRState(...); st.CIStatus == "failure" re-enters Implement, "success" proceeds to Merge. Record the metric on those two terminal conclusions only (skip "pending"), once per conclusion (guard against re-recording on repeated reconciles of the same conclusion - only record on the state TRANSITION, e.g. when the branch is actually taken to advance the task, not on every poll).
- Step 1: Write failing test -
handleMRCIwith a fake writer returningCIStatus:"failure"recordsoperator_implement_ci_total{result="fail", model=<ResolvedModel>};"success"recordsresult="pass";"pending"records nothing. - Step 2: Run - fail.
- Step 3: Implement - in the success/failure branches:
project, repo, _, _, model := taskTokenLabels(task)
switch st.CIStatus {
case "failure":
if r.Metrics != nil { r.Metrics.RecordImplementCI(project, repo, model, "fail") }
// existing re-enter-Implement logic
case "success":
if r.Metrics != nil { r.Metrics.RecordImplementCI(project, repo, model, "pass") }
// existing proceed-to-Merge logic
}
- Step 4: Run - pass.
- Step 5: Commit
feat: record implement PR CI pass/fail (G4).
Task 4: Tier-revert incident goal + alert branch (operator, G5)¶
Files: - Modify: internal/incident/goal.go (add GoalTierRevert) - Modify: internal/webhook/server.go (createIncidentTask ~:885, uses alert.CommonLabels) - Test: internal/incident/goal_tier_revert_test.go, internal/webhook/grafana_tier_revert_test.go
Interfaces: - Produces: incident.GoalTierRevert(project, kind, model string) string. - Consumes: GrafanaAlert.CommonLabels map[string]string (grafana.go); the existing createIncidentTask -> queue.EnqueueEvent(... QueueClassAlert ...) path.
- Step 1: Write failing test (goal) -
GoalTierRevert("tatara", "review", "claude-sonnet-5")returns a goal that names the kind, the revert-to modelclaude-opus-4-8, and the exact pathvalues/project-tatara/common.yamlagent.modelByKind/agent.effortByKind, and instructs "open one MR, do not merge". - Step 2: Run - fail.
- Step 3: Implement
GoalTierRevert:
func GoalTierRevert(project, kind, model string) string {
return "A quality-proxy alert is FIRING: kind \"" + kind + "\" on model \"" + model +
"\" has regressed in project \"" + project + "\". Propose reverting this kind's tier: in " +
"tatara-helmfile values/project-" + project + "/common.yaml, set agent.modelByKind[" + kind +
"] back to claude-opus-4-8 and raise agent.effortByKind[" + kind + "] (to high). Open ONE MR " +
"against tatara-helmfile with only that change and a short rationale citing the alert. Do NOT merge."
}
- Step 4: Run - pass.
- Step 5: Write failing test (branch) - a firing alert with
CommonLabels{"tatara_tier_quality":"true","kind":"review","model":"claude-sonnet-5"}produces an incident QueuedEvent whoseGoal==GoalTierRevert(project,"review","claude-sonnet-5")and is scoped so tatara-helmfile is a target; an alert WITHOUT the marker uses the existingGoalProject. - Step 6: Run - fail.
- Step 7: Implement branch in
createIncidentTask:
var goal string
if alert.CommonLabels["tatara_tier_quality"] == "true" {
goal = incident.GoalTierRevert(proj.Name, alert.CommonLabels["kind"], alert.CommonLabels["model"])
} else {
goal = incident.GoalProject(alertCtx, slugs)
}
- Step 8: Run - pass (unit + the covering envtest if present).
- Step 9: Commit
feat: tier-quality alert -> tier-revert incident goal (G5).
Task 5: Quality-feedback dashboard (tatara-observability)¶
Files: - Create: a quality-feedback dashboard following the repo's existing dashboard structure (inspect the dashboards dir first; match the provisioning format the other tatara dashboards use). - Test: the repo's dashboard-validation path (JSON valid + panel-guard, mirror an existing dashboard test).
Panels (PromQL): - Review find-rate by model: sum by (model) (rate(operator_review_outcome_total{verdict="changes_requested"}[1h])) / sum by (model) (rate(operator_review_outcome_total[1h])). - Findings per review by model: sum by (model) (rate(operator_review_findings_total[1h])) / sum by (model) (rate(operator_review_outcome_total[1h])). - Implement CI-pass-rate by model: sum by (model) (rate(operator_implement_ci_total{result="pass"}[1h])) / sum by (model) (rate(operator_implement_ci_total[1h])).
- Step 1: Inspect the existing dashboard + its test/guard; copy the pattern.
- Step 2: Write the dashboard with the three panels above.
- Step 3: Validate (repo's dashboard test /
terraform validate/ JSON lint as the repo uses). - Step 4: Commit
feat: quality-feedback dashboard (G4).
Task 6: Tier-quality alert rules (tatara-observability, G5)¶
Files: - Create/Modify: the alert-rules definitions following the repo's existing Grafana-managed alert-rule format (inspect first; these went through the alerts-as-code migration - see MEMORY note grafana-alerting-terraform-broken-contactpoints). - Test: the repo's rule-validation path.
Rules (on the downgraded kinds; thresholds are INITIAL/rough - tune from the G4 baseline once data exists, per the build order): - Rubber-stamp: for model="claude-sonnet-5", sum(rate(operator_review_outcome_total{verdict="changes_requested"}[6h])) / sum(rate(operator_review_outcome_total{model="claude-sonnet-5"}[6h])) < 0.02 sustained AND a minimum review volume (avoid firing on 1-2 reviews) - i.e. find-rate near zero. - (Optional, once CI attribution is trusted) implement CI-pass-rate for merged work dropping below a floor.
Each rule MUST set labels: homelab, system=tatara, tatara_tier_quality="true", kind="review", model="claude-sonnet-5", project=<p>, and route to the project's existing Grafana webhook contact point.
- Step 1: Inspect the existing tatara alert rules + their validation; copy the label + routing pattern.
- Step 2: Write the rule(s) with a code comment that the thresholds are provisional pending the G4 baseline.
- Step 3: Validate (repo's rule check).
- Step 4: Commit
feat: tier-quality alert rules (G5).
Self-Review¶
- Spec coverage: G4 metrics (Task 1), review capture (Task 2), implement-CI (Task 3), dashboards (Task 5) = G4. Tier-revert goal + alert branch (Task 4), alert rules (Task 6) = G5. All spec sections covered.
- Build order: G4 code (1-3,5) is independent of G5 (4,6). G5's alert THRESHOLDS (Task 6) are marked provisional pending baseline - the code lands, numbers tune later. Consistent with the spec.
- Types:
RecordReviewOutcome/AddReviewFindings/RecordImplementCIandGoalTierRevertsignatures are used identically in the consuming tasks.taskTokenLabelsreturns(project, repo, kind, issue, model)- tasks read positions correctly. - Open detail flagged for implementer: the review finding-count field on the write-back verdict object
v(Task 2) - read the exact field; fall back to1for changes_requested if none. The implement-CI once-per-conclusion idempotency (Task 3) - co-locate with the existing phase transition.