# How Pi Web Handles Model Scoping with Minimatch Globs: A Deep Dive

> Discover how Pi Web leverages minimatch globs for efficient model scoping, filtering UI availability and agent sessions with automatic fallbacks for unmatched patterns.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-10

---

**Pi Web uses minimatch glob patterns (e.g., `anthropic/*:high`) to filter which language models appear in the UI and become available to new agent sessions, with automatic fallback to all models when patterns are empty or unmatched.**

Pi Web implements a robust model-scoping system that mirrors the Pi CLI's `--models` flag behavior. By leveraging **minimatch globs**, users can precisely control which provider/model combinations are visible and selectable. This article examines the complete implementation flow, from settings parsing through UI rendering to session initialization.

## The Core Scoping Pipeline in [`lib/model-scope.ts`](https://github.com/agegr/pi-web/blob/main/lib/model-scope.ts)

The heart of Pi Web's model scoping resides in [`lib/model-scope.ts`](https://github.com/agegr/pi-web/blob/main/lib/model-scope.ts). This module exports two critical functions: `resolveVisibleModels()` for filtering available models, and `selectInitialModelScope()` for choosing which model a new session should actually use.

### Reading and Normalizing `enabledModels`

The scoping process begins by retrieving user preferences from the settings store:

```typescript
// SettingsManager provides the raw pattern list
const enabledPatterns = services.settingsManager.getEnabledModels();

```

The `resolveVisibleModels()` function (lines 57-71) performs initial normalization:
- Trims empty strings from the pattern list
- Returns **all models** immediately if the list is empty (`modelRuntime.getAvailable()`)
- Delegates to the SDK helper for actual glob matching when patterns exist

### SDK-Powered Glob Matching with Diagnostics

When patterns are present, Pi Web calls `resolveModelScopeWithDiagnostics()` from the `@earendil-works/pi-coding-agent` SDK. This utility:

- Applies **minimatch** pattern matching against `provider/modelId` strings
- Performs fuzzy matching for non-glob entries
- Extracts optional `:thinkingLevel` suffixes (e.g., `:high`, `:low`)

The result includes an array of `ScopedModel` objects and diagnostic warnings. Pi Web then builds a `thinkingLevelPins` map (lines 85-90) so the UI can display pinned levels alongside model names.

### Graceful Fallback Behavior

Pi Web guarantees the UI never renders empty due to typos or stale configuration. When glob patterns resolve to zero matches, the system falls back to the complete model catalogue (lines 73-80):

```typescript
if (visible.length === 0) {
  // Fallback: user misconfigured or patterns became stale
  visible = await modelRuntime.getAvailable();
  warnings.push('No models matched your enabledModels patterns; showing all models.');
}

```

## Session Initialization in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)

New agent sessions inherit the same scoping rules. The `startRpcSession` flow in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) (lines 32-36) creates services, reads enabled patterns, and resolves the visible scope:

```typescript
const scope = await resolveVisibleModels(
  services.modelRuntime,
  services.settingsManager.getEnabledModels(), // minimatch globs here
);

```

### Selecting the Initial Model

With the scoped set determined, `selectInitialModelScope()` (lines 41-48) applies a three-tier selection strategy:

1. **Explicit request** — If the caller provided `initialModel` and it exists in the scoped set, use it
2. **Default model** — Otherwise check if `settingsManager.getDefaultModel()` falls within the scope
3. **First available** — Finally, default to the first model in the scoped list

The selected model's thinking level respects the pin unless the caller supplied an explicit `thinkingLevel` (lines 34-36):

```typescript
const initial = selectInitialModelScope(scope, {
  requestedModel: initialModel,
  defaultModel: { provider, modelId },
  thinkingLevel, // optional override
});

```

## API Endpoint for the Model Selector UI

The frontend model picker consumes the same scoping logic through [`app/api/models/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models/route.ts) (lines 42-48). This endpoint:

```typescript
const services = await createAgentSessionServices({ cwd, agentDir, ... });
const scope = await resolveVisibleModels(
  services.modelRuntime,
  services.settingsManager.getEnabledModels(),
);
const { visible, thinkingLevelPins, warnings } = scope;
return Response.json({ models: visible, thinkingLevelPins, warnings });

```

The response populates the sidebar model selector with scoped results, pinned thinking levels, and any resolver warnings for user feedback.

## Minimatch Glob Pattern Syntax

Pi Web supports expressive patterns compatible with the Pi CLI. Valid entries in `~/.pi/agent/settings.json` include:

| Pattern | Meaning |
|---------|---------|
| `anthropic/*:high` | All Anthropic models, default to high thinking level |
| `openai/gpt-4` | Specific model only |
| `my-gateway/*` | All models from a custom provider gateway |
| `*/*:low` | All models with low thinking level default |

The colon suffix is optional and instructs Pi Web to pin a thinking level for the matched models.

## Summary

- **[`lib/model-scope.ts`](https://github.com/agegr/pi-web/blob/main/lib/model-scope.ts)** implements `resolveVisibleModels()` and `selectInitialModelScope()` with minimatch glob support
- **Empty or unmatched patterns** trigger automatic fallback to all available models
- **SDK delegation** to `resolveModelScopeWithDiagnostics()` handles the actual matching and `:thinkingLevel` extraction
- **Session startup** in [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) reuses the same scoping flow for consistency
- **UI endpoint** at [`app/api/models/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models/route.ts) exposes scoped results to the frontend model selector

## Frequently Asked Questions

### What happens if my `enabledModels` patterns match nothing?

Pi Web detects zero matches and falls back to returning all available models, adding a warning to the response. This prevents the UI from becoming unusable due to configuration errors or stale patterns after provider updates.

### Can I specify different thinking levels for different model groups?

Yes. Append `:high`, `:medium`, or `:low` to any pattern. For example, `"anthropic/*:high"` pins high thinking for all Anthropic models while `"openai/*:low"` keeps OpenAI models economical. The resulting `thinkingLevelPins` map propagates these defaults to both UI and new sessions.

### How does Pi Web's scoping relate to the Pi CLI `--models` flag?

Both use identical minimatch glob syntax against `provider/modelId` strings. Patterns valid for `pi --models anthropic/*:high` work identically in Pi Web's `enabledModels` setting, ensuring consistent behavior across interfaces.

### Where does the actual glob matching implementation live?

The core matching logic resides in `@earendil-works/pi-coding-agent` (external SDK), specifically `resolveModelScopeWithDiagnostics()`. Pi Web's [`lib/model-scope.ts`](https://github.com/agegr/pi-web/blob/main/lib/model-scope.ts) orchestrates this utility, handles edge cases like empty inputs, and builds UI-friendly data structures from the results.