# How the Spend Tier System Routes Evaluations Across AI Providers in career-ops

> Discover how career-ops uses the spend tier system to route evaluations to appropriate AI providers based on your config profileyml settings. Optimize your AI costs.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: internals
- Published: 2026-08-28

---

**The spend tier system reads your `spend_tier` setting from [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) and translates it into a specific Claude model identifier via the `spend_tier_to_model` helper, automatically routing evaluations to budget-appropriate AI providers while allowing CLI overrides.**

The career-ops open-source framework simplifies AI-driven job offer evaluations by abstracting provider complexity into a declarative **spend tier** setting. Instead of manually selecting model names or managing provider-specific API configurations, you define your budget preference in a single YAML key. The system then automatically routes inference requests to the appropriate model across pipeline, batch, and CLI execution modes.

## How Spend Tier Routing Works

The routing mechanism operates through a four-stage pipeline that transforms a user-friendly configuration value into a concrete provider API call. This process is centralized in the batch runner and shared across all execution modes.

### Reading the Configuration

At the start of any evaluation run—whether in `pipeline`, `batch`, or CLI mode—the system invokes a configuration helper to parse [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml). According to the shared mode documentation in [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md), the helper searches for the `spend_tier` key. If the key is absent, the system defaults to `standard`.

In [`batch/batch-runner.sh`](https://github.com/santifer/career-ops/blob/main/batch/batch-runner.sh) (lines 351–409), the `read_spend_tier` function extracts this value once per run and exports it for downstream use. This ensures consistent model selection across an entire batch job, preventing mixed-tier evaluations within a single execution context.

### Validation and Fallback Logic

The system enforces a strict allowlist of tier values. As implemented in [`batch/batch-runner.sh`](https://github.com/santifer/career-ops/blob/main/batch/batch-runner.sh) (line 385), only three strings are accepted:

- `economy`
- `standard`
- `premium`

If the configuration contains an invalid value, the runner emits a warning and falls back to `standard`. This validation prevents provider errors from typos or unsupported tier names.

### Mapping Tiers to Provider Models

Once validated, the tier string passes through the `spend_tier_to_model` helper function defined in [`batch/batch-runner.sh`](https://github.com/santifer/career-ops/blob/main/batch/batch-runner.sh) (lines 392–408). This function maps abstract budget tiers to concrete model identifiers:

| `spend_tier` | Resolved Model |
|--------------|----------------|
| `economy` | `claude-haiku-4-5` |
| `standard` | `claude-sonnet-5` |
| `premium` | `claude-opus-5` |

This mapping provides a provider-agnostic interface; the user thinks in terms of cost ("economy"), while the system manages the specific Claude model name required by the API.

### Applying Models to Evaluation Scripts

The resolved model identifier becomes the `--model` argument passed to evaluation scripts (e.g., `node evaluate.mjs --model $RESOLVED_MODEL`). However, explicit CLI flags take precedence over tier-derived values. As verified in `test-all.mjs` (lines 12948–13037), if you provide `--model claude-opus-5` on the command line while your profile specifies `economy`, the CLI flag wins.

### Batch-Specific Pre-Screening

In batch mode, the spend tier gates an additional optimization step. As noted in `test-all.mjs` (line 12948), the system runs a cheap pre-screen using the tier-appropriate model to discard low-quality offers before spending credits on full evaluations. This ensures high-volume runs remain cost-effective even when processing thousands of job descriptions.

## Configuration and Usage Examples

Set your preferred tier in the profile configuration:

```yaml

# config/profile.yml

spend_tier: economy   # Uses claude-haiku-4-5 for high-volume, low-cost scanning

# spend_tier: standard  # Default: claude-sonnet-5 for balanced performance

# spend_tier: premium   # Uses claude-opus-5 for maximum evaluation depth

```

Run a standard evaluation—the tier determines the model automatically:

```bash
career-ops evaluate --job ./jds/example-job.md

# Verbose output confirms: Model: claude-haiku-4-5 (spend_tier=economy)

```

Override the tier for a single evaluation using the explicit model flag:

```bash
career-ops evaluate --job ./jds/example-job.md --model claude-opus-5

# Output shows: Model: claude-opus-5 (spend_tier=economy)  # CLI flag takes precedence

```

Execute a batch run where the runner reads the tier once and applies it to every job:

```bash
career-ops batch run ./batch/jobs/

# Internally executes: RESOLVED_MODEL=$(spend_tier_to_model $(read_spend_tier))

```

## Summary

- The **spend tier** is defined in [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) and defaults to `standard` if omitted.
- Valid tiers are `economy`, `standard`, and `premium`, mapping to `claude-haiku-4-5`, `claude-sonnet-5`, and `claude-opus-5` respectively.
- The [`batch/batch-runner.sh`](https://github.com/santifer/career-ops/blob/main/batch/batch-runner.sh) script handles validation, mapping, and application of the model identifier via the `spend_tier_to_model` helper.
- Explicit `--model` CLI arguments override the tier-derived model, as confirmed by the test suite in `test-all.mjs`.
- Batch mode uses the tier to gate a pre-screen step that filters low-quality offers before incurring full evaluation costs.

## Frequently Asked Questions

### What happens if I don't define a spend_tier in my profile?

If the `spend_tier` key is missing from [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), the system defaults to `standard`, which resolves to the `claude-sonnet-5` model. This fallback logic is documented in [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) and implemented in [`batch/batch-runner.sh`](https://github.com/santifer/career-ops/blob/main/batch/batch-runner.sh).

### Can I use a different model for a single evaluation without changing my profile?

Yes. You can override the tier-derived model by passing the `--model` flag directly to the CLI command (e.g., `--model claude-opus-5`). This explicit flag takes precedence over the `spend_tier` configuration for that specific execution.

### How do the three spend tiers differ in practice?

The **economy** tier uses `claude-haiku-4-5` for fast, low-cost scanning suitable for high-volume batches. The **standard** tier uses `claude-sonnet-5` for balanced speed and reasoning quality. The **premium** tier uses `claude-opus-5` for maximum capability when evaluating complex compensation packages or critical offers.

### Does the spend tier affect batch processing costs beyond the model choice?

Yes. In batch mode, the tier also controls a pre-screening step that evaluates offers with a cheaper model pass before committing to full evaluations. This additional gate, referenced in `test-all.mjs`, prevents wasting premium model credits on obviously low-quality job descriptions.