# Model Scoping and Resolution Using Provider Patterns in pi-mono

> Discover model scoping and resolution in pi-mono using provider patterns. Learn how to achieve flexible LLM discovery with pattern matching and automatic fallback resolution across multiple vendors.

- Repository: [Mario Zechner/pi-mono](https://github.com/badlogic/pi-mono)
- Tags: deep-dive
- Published: 2026-02-15

---

**pi-mono implements a provider-centric registry in [`packages/coding-agent/src/core/model-resolver.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/model-resolver.ts) that enables flexible LLM discovery through pattern matching, glob syntax, and automatic fallback resolution across multiple vendors.**

The pi-mono codebase abstracts vendor-specific LLM implementations behind a unified resolution layer. By treating providers as namespaces and supporting expressive selection patterns, the system allows users to specify models via shorthand strings like `anthropic/claude-opus-4-6:high` or glob patterns like `google/*:low`. This article examines the core resolution algorithms, scoping mechanisms, and provider patterns that power this abstraction.

## Core Concepts of the Provider Pattern

The provider pattern in pi-mono centers on three architectural pillars: default model mappings, expressive pattern parsing, and thinking-level extraction. These components work together in [`model-resolver.ts`](https://github.com/badlogic/pi-mono/blob/main/model-resolver.ts) to transform user input into concrete `ScopedModel` instances.

### Default Model Per Provider

The system maintains a hard-coded fallback map called `defaultModelPerProvider` (lines 13-38) that supplies sensible defaults when users specify only a provider name. This ensures that `openai` alone resolves to a working model without requiring users to memorize exact model IDs.

When resolution occurs, the algorithm checks this map after attempting explicit pattern matches, creating a seamless fallback chain that prioritizes user intent while guaranteeing functionality.

### Pattern Parsing and Matching

The `parseModelPattern` function (lines 27-84) implements a sophisticated parser that handles provider-prefixed identifiers, glob wildcards, and nested colons within model IDs. The parser recognizes the `provider/model-id:thinking-level` structure while correctly handling edge cases like model IDs that contain colons.

Pattern matching occurs through the `tryMatchModel` helper (lines 62-94), which implements a four-tier fallback strategy:
1. Exact `provider/id` string equality
2. Case-insensitive ID equality
3. Substring matching against `id` or `name` fields
4. Glob pattern matching against `provider/id` or raw `id`

The system prefers alias models (those marked with `isAlias` or lacking date suffixes) over dated releases, ensuring users receive stable API versions by default.

### Thinking Level Extraction

The resolver extracts optional reasoning intensity via the suffix after the final colon, mapping strings like `low`, `medium`, `high`, or `off` to the `ThinkingLevel` enum. Invalid suffixes trigger warnings and are stripped rather than causing resolution failures, maintaining robustness against typos.

## The Model Resolution Pipeline

The resolution pipeline transforms abstract patterns into concrete model configurations through three primary entry points: scope resolution for configuration files, CLI resolution for command-line flags, and initial model selection for session startup.

### Resolving Model Scopes from Patterns

The `resolveModelScope` function (lines 95-132) serves as the main entry point for configuration-driven resolution. It accepts an array of pattern strings and a `ModelRegistry` instance, returning an array of `ScopedModel` objects.

The implementation performs several critical operations:
- Deduplicates models that match multiple patterns
- Validates that glob patterns resolve to at least one concrete model
- Associates thinking levels with specific model instances
- Filters the registry through `ModelRegistry.getAvailable()` to ensure API keys are present

This scoping mechanism enables complex configurations like `["anthropic/claude-opus-4-6:high", "google/*:low"]` to resolve to multiple concrete models with varying reasoning intensities.

### CLI-Specific Resolution

The `resolveCliModel` function (lines 66-124) handles command-line flag parsing with stricter validation than the general scoping logic. It processes `--provider` and `--model` arguments, applying the same pattern parsing but with explicit error handling for invalid thinking levels.

Unlike `resolveModelScope`, which silently ignores invalid suffixes, the CLI resolver treats malformed thinking levels as fatal errors, ensuring users receive immediate feedback on typos in interactive usage.

### Initial Model Selection

The `findInitialModel` function (lines 71-119) implements the session startup logic that determines which model to use when beginning a conversation. It establishes a hierarchy of configuration sources:

1. CLI arguments (`--provider`/`--model`)
2. Previously scoped models from configuration files
3. Saved default preferences
4. First available model with valid API credentials

This cascading priority ensures that explicit user overrides take precedence while maintaining sensible fallbacks for new sessions.

## Practical Implementation Examples

The following examples demonstrate how to interact with the resolution system in real-world scenarios.

### Resolving Configuration Patterns

This example shows how to resolve multiple model patterns from a configuration file:

```typescript
import { resolveModelScope } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";

async function getScopedModels() {
  const registry = new ModelRegistry();               // reads models.json
  const patterns = [
    "anthropic/claude-opus-4-6:high",   // explicit provider + thinking level
    "google/*:low",                     // glob across all Google models
    "*sonnet*:medium"                   // any provider, alias containing “sonnet”
  ];

  const scoped = await resolveModelScope(patterns, registry);
  console.log(scoped);
  // Returns: Array<{ model: Model, thinkingLevel?: ThinkingLevel }>
}

```

### CLI Argument Resolution

This example mirrors how the `pi` CLI handles `--provider` and `--model` flags:

```typescript
import { resolveCliModel } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";

function parseArgs() {
  const cliProvider = process.argv.includes("--provider")
    ? process.argv[process.argv.indexOf("--provider") + 1]
    : undefined;
  const cliModel = process.argv.includes("--model")
    ? process.argv[process.argv.indexOf("--model") + 1]
    : undefined;

  const result = resolveCliModel({
    cliProvider,
    cliModel,
    modelRegistry: new ModelRegistry(),
  });

  if (result.error) {
    console.error(result.error);
    process.exit(1);
  }
  console.log("Using", result.model?.provider + "/" + result.model?.id);
}

```

### Session Initialization

This example demonstrates the startup logic that selects the initial model for a coding session:

```typescript
import { findInitialModel } from "@mariozechner/pi-coding-agent";
import { ModelRegistry } from "@mariozechner/pi-ai";

async function startSession() {
  const registry = new ModelRegistry();

  const initial = await findInitialModel({
    cliProvider: undefined,
    cliModel: undefined,
    scopedModels: [],               // could be filled from a .pi file
    isContinuing: false,
    defaultProvider: "openai",
    defaultModelId: "gpt-5.1-codex",
    defaultThinkingLevel: "medium",
    modelRegistry: registry,
  });

  if (!initial.model) {
    console.error("No model could be selected.");
    process.exit(1);
  }
  console.log(`Starting with ${initial.model.provider}/${initial.model.id}`);
}

```

## Summary

- **Provider-centric architecture** – pi-mono organizes models by vendor in [`packages/coding-agent/src/core/model-resolver.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/model-resolver.ts), enabling shorthand notation like `anthropic/claude-opus-4-6:high`.
- **Hierarchical resolution** – The system cascades through exact matches, substring searches, and glob patterns, preferring stable aliases over dated releases.
- **Thinking level extraction** – Colon suffixes (`:high`, `:low`) are parsed as `ThinkingLevel` enums and attached to `ScopedModel` instances.
- **Three entry points** – `resolveModelScope` handles configuration files, `resolveCliModel` manages strict CLI validation, and `findInitialModel` orchestrates session startup priorities.
- **Vendor-agnostic abstraction** – New providers require only updates to `defaultModelPerProvider` and `ModelRegistry`, leaving resolution logic unchanged.

## Frequently Asked Questions

### How does pi-mono handle ambiguous model patterns like wildcards?

The `resolveModelScope` function in [`packages/coding-agent/src/core/model-resolver.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/model-resolver.ts) expands glob patterns such as `google/*` or `*sonnet*` against the available model registry. It validates that each pattern resolves to at least one concrete model, deduplicates results when multiple patterns match the same model, and returns an array of `ScopedModel` objects ready for use.

### What happens if I specify an invalid thinking level in a model string?

The behavior depends on the resolution context. When using `resolveModelScope` for configuration files, invalid suffixes after the colon trigger a warning and are stripped, allowing resolution to continue with the matched model but no thinking level. However, `resolveCliModel` treats invalid thinking levels as fatal errors, immediately returning an error object to prevent silent misconfiguration in interactive CLI usage.

### Can I use provider shorthand without specifying an exact model ID?

Yes. The `defaultModelPerProvider` map (lines 13-38) defines fallback models for every known provider. If you specify only a provider name like `anthropic` or `openai` without a model ID, the resolver automatically selects the default model for that vendor. This enables quick experimentation without requiring memorization of specific model identifiers.

### How does pi-mono prioritize which model to use when multiple sources are configured?

The `findInitialModel` function implements a strict priority hierarchy: command-line arguments (`--provider`/`--model`) take precedence, followed by models scoped from configuration files, then saved user defaults, and finally the first available model with valid API credentials. This cascading approach ensures explicit user intent always overrides implicit defaults while maintaining reliable fallbacks for new sessions.