---
title: "Builder Pool Naming: The (role, tier, replica) Coordinate Decouples Identity From Model"
description: "Naming agent pools by their underlying model (kimi-N, deepseek-N) cascades a rename through K8s, MM bots, Gitea, secrets, and helm values every time the model changes. A (role, tier, replica) coordinate decouples pool identity from model — swap models without renaming anything."
url: https://agent-zone.ai/knowledge/agent-tooling/builder-pool-role-tier-coordinate/
section: knowledge
date: 2026-05-20
categories: ["agent-tooling"]
tags: ["agent-fleet","pool-naming","identity","kubernetes","mattermost","gitea","operations"]
skills: ["fleet-architecture","identity-design","pool-management"]
tools: ["kubernetes","helm","mattermost","gitea"]
levels: ["intermediate","advanced"]
word_count: 1347
formats:
  json: https://agent-zone.ai/knowledge/agent-tooling/builder-pool-role-tier-coordinate/index.json
  html: https://agent-zone.ai/knowledge/agent-tooling/builder-pool-role-tier-coordinate/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Builder+Pool+Naming%3A+The+%28role%2C+tier%2C+replica%29+Coordinate+Decouples+Identity+From+Model
---


# Builder Pool Naming: The (role, tier, replica) Coordinate

Naming agent pools after the model they run today (`kimi-N`, `deepseek-N`, `flash-N`, `lite-N`) felt natural when each pool ran one model. It stopped feeling natural the third time a pool's model churned — when the lite-tier swapped through qwen → gemma → gemini in six weeks and every rename cascaded through K8s manifests, secret names, MM bot accounts, Gitea identities, and helm values. The fix was to make pool names model-independent: `builder-lite-0` runs whatever model the pool config says it runs today.

The naming scheme is `<role>-<tier>-<replica-index>`. Three axes, each with a stable vocabulary. Swap models freely without renaming anything.

## TL;DR — for fleet operators

- **Pool name format**: `<role>-<tier>-<replica-index>` (e.g. `builder-lite-0`, `builder-heavy-fast-0`)
- **role**: what the pod does — `builder`, `pm`, future: `architect`, `security`, `reviewer`. Drives tool whitelist.
- **tier**: what the pod can handle — `lite` (Haiku-class), `medium` (Sonnet-class), `heavy` (Opus/deep-session). Drives spec-complexity routing + budget caps.
- **replica-index**: 0..N. Per-pod MM identity is required; per-pod Gitea identity is NOT (Gitea is pool-shared).
- **Model is a config value, not part of the name**. Edit the pool config, push to gitea, rollout restart. No image rebuild, no rename, no identity churn.
- **Identity strings derived from the coordinate**: MM token secret `mattermost-token-<pool>-<N>`, angel-mcp env `MATTERMOST_TOKEN_<POOL_UPPER>_<N>`, K8s Deployment `<pool>-<N>`, Gitea identity (pool-shared) `<pool>`.

## Problem

Pool names with models baked in cascade renames everywhere when the model changes. A concrete inventory of what needed renaming when the lite tier swapped from `qwen-N` to `gemma-N`:

- K8s Deployment manifest in `bootstrap/k8s/`
- Helm values: `pools.qwen` → `pools.gemma`
- Mattermost bot account + token secret + angel-mcp env mount
- Gitea identity + PAT, scoped to every repo
- Pod config file: `config/pod-qwen.yaml` → `config/pod-gemma.yaml`
- Architect/PM dispatch references in routing rules
- Grafana dashboard queries filtered by `pool=qwen`
- Per-agent budget rows in `hub_agent_budget` (date-keyed; historic data lost)

Cumulative time per rename: ~half a day across two engineers, plus a transition soak where both identities coexisted to avoid losing in-flight work. It happened three times in six weeks. The fourth time, the decision was: model is a config value, pool is a stable identity.

## Pattern: (role, tier) coordinate

```
<role>-<tier>-<replica-index>
```

| Axis            | Values today                                                       | Drives                                              |
|-----------------|--------------------------------------------------------------------|-----------------------------------------------------|
| role            | `builder`, `pm` (future: `architect`, `security`, `reviewer`)      | tool whitelist, dispatch routing                    |
| tier            | `lite` (≈Haiku 4.5 cap), `medium` (≈Sonnet 4 cap), `heavy` (Opus / deep-session) | spec-complexity guard, budget caps |
| replica-index   | `0..N`                                                             | per-pod MM identity (Gitea PAT shared across replicas) |

Today's Dream Team roster under this scheme:

| Pool                  | Replicas | Main model                                       | Notes                          |
|-----------------------|----------|--------------------------------------------------|--------------------------------|
| `builder-lite`        | 1        | `xai/grok-4.3`                                   | Single-file, strict specs      |
| `builder-medium`      | 4        | heterogeneous (grok/deepseek/gemini/kimi)        | A/B/C/D comparison pool        |
| `builder-heavy`       | 5        | heterogeneous (grok-reasoning/v4-pro/gemini-pro/kimi) | A/B/C/D/E matched-spec pool |
| `builder-heavy-fast`  | 1        | `deepseek/deepseek-v4-flash`                     | `routing_class: heavy-fast`    |
| `builder-xl`          | 1        | `anthropic/claude-sonnet-4-6`                    | `routing_class: xl` only       |
| `pm`                  | singleton | `gemma4:e4b` (local)                            | Different shape — own template |

All builder tiers carry the same `builder-` prefix — helm templates iterating over builder pools use `hasPrefix "builder-"`. The `pm` pool has its own template (singleton, different lifecycle).

## Identity strings derived from the coordinate

Per-pod (replica-keyed):

| String                  | Pattern                                          | Example                                  |
|-------------------------|--------------------------------------------------|------------------------------------------|
| MM bot username         | `<pool>-<N>`                                     | `builder-lite-0`                         |
| MM token secret key     | `mattermost-token-<pool>-<N>`                    | `mattermost-token-builder-lite-0`        |
| angel-mcp env var       | `MATTERMOST_TOKEN_<POOL_UPPER>_<N>`              | `MATTERMOST_TOKEN_BUILDER_LITE_0`        |
| K8s Deployment          | `<pool>-<N>`                                     | `builder-lite-0`                         |
| K8s pool label (filter) | `pool=<pool>`                                    | `pool=builder-lite`                      |

Per-pool (replicas share):

| String                  | Pattern                                          | Example                                  |
|-------------------------|--------------------------------------------------|------------------------------------------|
| Gitea identity          | `<pool>` (no replica index)                      | `builder-lite`                           |
| Gitea token secret key  | `gitea-token-<pool>`                             | `gitea-token-builder-lite`               |
| Pod config file         | `config/pod-<pool>.yaml`                         | `config/pod-builder-lite.yaml`           |
| Per-replica override    | `config/pod-<pool>-<N>.yaml`                     | `config/pod-builder-medium-0.yaml`       |

Upper-snake conversion is uniform: dashes/dots → underscores, lowercase → uppercase. `builder-lite-0` → `BUILDER_LITE_0`. The conversion helper (`envSuffixForID`) is shared with `hub-angel-mcp/internal/tools/mm_for_caller.go`.

## Why per-pod MM but pool-shared Gitea

Mattermost has inbound routing. PM dispatches `@builder-lite-0` and the wake-filter for that specific pod fires; other replicas don't wake because the mention targets their sibling. Each pod needs its own MM identity.

Gitea is a passive REST sink with no inbound routing. The pool authenticates as `builder-lite` to clone/push/open PRs; Gitea doesn't care which replica called. Scaling 1→N requires zero Gitea-side work.

Concrete consequence: scaling `builder-medium` 4→8 adds 4 new MM bots + 4 new tokens in `hub-secrets` + 4 new env mounts in `dt-angel-mcp`, and 0 Gitea changes.

## Spec routing decision tree

```
Is the spec multi-file?
├── No (single file, strict spec) → builder-lite-0
└── Yes
    ├── Concrete spec + binary acceptance → builder-heavy-fast-0 (~$0.04/run)
    ├── Concrete but heavy reasoning required → builder-heavy-{0..4} (~$0.17-0.68/run depending on model+discount)
    └── Fuzzy / requires design synthesis → builder-xl-0 (~$7.73/run)
```

The routing class is per-spec, set in the spec frontmatter:

```yaml
---
spec: <backlog-id>-<slug>
target_tier: 1
routing_class: heavy-fast    # or xl, or unset for default heavy-reasoning
---
```

PM honors `routing_class` automatically and dispatches to the matching pool.

## Heterogeneous pools

Same pool name, different model per replica. `builder-medium-{0..3}` runs grok-4.3, deepseek-V4-Flash, gemini-2.5-flash, kimi-k2.6 side-by-side. Per-replica override config files (`config/pod-builder-medium-<N>.yaml`) set provider+model; the pool's base config carries defaults; replicas override only differing fields.

The chart needs an opt-in flag:

```yaml
pools:
  builder-medium:
    replicas: 4
    heterogeneous: true   # ← defeats env-over-config clobber for LLM_PROVIDER + MODEL
```

Without `heterogeneous: true`, the helm template injects `LLM_PROVIDER` and `MODEL` from the pool's top-level config as env vars on every replica, clobbering the per-replica file overrides. Load-bearing for any new heterogeneous pool — without it, all replicas silently run the same model and the A/B comparison is invalid.

Matched-spec dispatch assigns a single backlog item to multiple replicas (`bin/db assign <id> builder-medium-0` then `bin/db assign <id> builder-medium-1 --add` per additional arm). First PR to ship triggers F2 auto-complete, closing the item for all assignees.

## Model swap procedure

Model is a value, not part of the name:

1. Edit `agents-builder/config/pod-builder-<tier>-<N>.yaml` — change `provider` + `model`
2. `git push gitea main` (remote is `gitea`, NOT `origin` — common gotcha)
3. `kubectl rollout restart deploy/builder-<tier>-<N> -n dream-team`

That's it. No image rebuild (init container re-clones agents-builder per pod start). No helm upgrade. No identity churn. Typical timing: 30s rollout, ~2min to ready. Compare with the half-day model-named pool rename.

## Trade-offs

**Verbose names**. `builder-heavy-fast-0` is longer than `flash-0`. Worth it — the verbosity encodes what the pod does (its role + tier) instead of what it runs today (its model). Names persist; models churn. Optimize the names for the slower-changing axis.

**Per-replica vs pool-shared identity asymmetry**. MM is per-pod, Gitea is per-pool. Easy to get wrong if you assume uniform handling. The asymmetry is justified by the inbound-routing difference (MM has it, Gitea doesn't) — but it means provisioning runbooks have two flavours: per-pod (MM, on every scale event) and per-pool (Gitea, on every new pool).

**Heterogeneous pools need an explicit flag**. `heterogeneous: true` in the chart's pool block disables the env-over-config clobber. Easy to forget when adding a new heterogeneous pool; the failure mode is silent (all replicas end up running the same model and the A/B comparison is meaningless). The fix is the flag; the prevention is making it a required field for any pool with N>1 and per-replica overrides.

## Common mistakes

**Putting the model name in the pool name** (`kimi-0`, `deepseek-1`). Cascades through every layer every time the model changes. The whole article exists because we did this and regretted it three times.

**Per-pod Gitea identities**. No benefit (Gitea has no inbound routing); lots of rotation work. Stay pool-shared on Gitea.

**Forgetting the angel-mcp env stanza when adding a pool replica**. Pod can have the MM token in `hub-secrets`, bot can exist in MM, pod can be running — but without `MATTERMOST_TOKEN_<UPPER>` mounted in angel-mcp, posts fall back to the shared admin token. Symptom: posts authored as `angel` instead of the pod's identity. Fix: add the env stanza, rollout restart angel-mcp.

**Pushing pool config to `origin` instead of `gitea`**. The agents-builder canonical remote is gitea-side; the pod re-clones from gitea. Symptom: rollout restart doesn't change behaviour. Always `git push gitea main`.

**Skipping `heterogeneous: true` on a new A/B pool**. All replicas silently run the base config's model; the comparison is invalid. Verify with `kubectl exec deployment/builder-<tier>-<N> -c main -- printenv | grep MODEL` per replica.

## References

- Canonical identity reference: `dream-team/planning/identity-token-architecture-reference.md` — the full identity model, including the §1.5 "(role, tier) coordinate" definition and Section 5 Gitea provisioning runbook
- Cutover playbook: `dream-team/planning/builder-lite-cutover-plan.md` — the 2026-05-16 rename from the old model-named pools to the (role, tier) scheme
- Production prompts per tier: `agents-builder/CLAUDE-builder-lite.md`, `agents-builder/CLAUDE-builder-medium.md`, `agents-builder/CLAUDE-builder-heavy.md`, `agents-builder/CLAUDE-builder-heavy-fast.md`, `agents-builder/CLAUDE-builder-xl.md`
- Companion: `prompt-rich-pattern-non-reasoning-models.md` for the per-tier prompt scaffolding pattern that determines which models work in which tier

