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

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

The heart of Pi Web's model scoping resides in 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:

// 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):

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

New agent sessions inherit the same scoping rules. The startRpcSession flow in lib/rpc-manager.ts (lines 32-36) creates services, reads enabled patterns, and resolves the visible scope:

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):

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 (lines 42-48). This endpoint:

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 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 reuses the same scoping flow for consistency
  • UI endpoint at 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 orchestrates this utility, handles edge cases like empty inputs, and builds UI-friendly data structures from the results.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →