Pinocchio Profile Resolution

How Pinocchio builds hidden base settings and merges engine profiles without contaminating the underlying baseline.

Sections

Terminology & Glossary
📖 Documentation
Navigation
54 sectionsv0.1
📄 Pinocchio Profile Resolution — glaze help profile-resolution-runtime-switching
profile-resolution-runtime-switching

Pinocchio Profile Resolution

How Pinocchio builds hidden base settings and merges engine profiles without contaminating the underlying baseline.

Topicpinocchioprofilesbootstrapcliwebchatruntimeconfigurationpinocchioweb-chatconfig-fileprofileprofile-registries

Pinocchio Profile Resolution

What This Page Covers

This page explains the lifecycle that sits between Geppetto's profile model and Pinocchio's runtime behavior.

The core idea is simple, but the implementation has two different "base settings" paths that are easy to confuse:

  • a hidden base reconstructed from shared Geppetto sections plus config, environment, and defaults
  • a profile-free base recovered from already parsed values by stripping parse steps whose source is profiles

You need this page when you are trying to understand:

  • what BaseInferenceSettings really means
  • why FinalInferenceSettings is separate
  • how profile resolution avoids contaminating the baseline
  • why cross-profile settings such as ai-client.* belong in the baseline rather than in profiles
  • how local project config files such as .pinocchio.yml participate in bootstrap
  • how to inspect parsed field history to see which config layer won

Mental Model

Pinocchio treats profile resolution as:

baseline + profile overlay = active runtime settings

The baseline is app-owned.

The profile overlay is Geppetto-owned.

The active runtime settings are what the current engine is actually built from.

Pinocchio keeps baseline reconstruction separate from profile overlay merging so selected profiles do not become the new baseline for future command resolution.

The Three Settings States

1. Hidden base inference settings

This is the internal baseline reconstructed through profilebootstrap.ResolveBaseInferenceSettings(...).

It comes from:

  • shared Geppetto sections
  • config files
  • environment variables
  • defaults

Hidden base config layers

Pinocchio now supports a layered config plan instead of only a single implicitly discovered config file.

The standard low-to-high precedence order is:

system -> user -> repo -> cwd -> explicit

In concrete terms, Pinocchio can load from:

  1. /etc/pinocchio/config.yaml
  2. $HOME/.pinocchio/config.yaml
  3. ${XDG_CONFIG_HOME}/pinocchio/config.yaml
  4. .pinocchio.yml at the git repository root
  5. .pinocchio.override.yml at the git repository root
  6. .pinocchio.yml in the current working directory
  7. .pinocchio.override.yml in the current working directory
  8. --config-file <path>

Later layers win.

That means:

  • repo-local config can override user config
  • repo-local .pinocchio.override.yml can override committed repo .pinocchio.yml
  • cwd-local config can override repo-local config
  • cwd-local .pinocchio.override.yml can override committed cwd .pinocchio.yml
  • explicit --config-file can override everything else

This layered path is implemented through Glazed config-plan primitives and consumed by Geppetto bootstrap.

See:

  • pinocchio/pkg/cmds/profilebootstrap/engine_settings.go
  • geppetto/pkg/cli/bootstrap/engine_settings.go

This path is especially important for commands such as web-chat, which intentionally expose a narrower visible CLI but still need a full AI baseline.

2. Profile-free base recovered from parsed values

This is the baseline Pinocchio can recover from already parsed command values by removing parse steps whose source is profiles.

It comes from:

  • the actual parsed command values
  • minus profile-derived parse steps

See:

  • pinocchio/pkg/cmds/profile_base_settings.go

This path is especially important for commands that already parsed their real flag surface and still need a clean profile-free baseline.

3. Final inference settings

This is the result of merging the selected engine-profile overlay onto one of those bases:

final = merge(base, resolved_profile.inference_settings)

This is the object used to build engines.

See:

  • geppetto/pkg/engineprofiles/inference_settings_merge.go
  • pinocchio/pkg/cmds/cmd.go

Data Ownership Diagram

Geppetto-owned profile overlay
  - resolved engine-profile inference settings
  - model/provider defaults

Pinocchio-owned baseline
  - config, env, defaults
  - command-level non-profile flags
  - shared transport settings such as ai-client.*

Runtime-owned active state
  - merged final InferenceSettings
  - selected profile metadata
  - live session builder / engine

The ownership rule behind this diagram is the main architectural takeaway:

  • if a setting should survive profile changes, it belongs in the baseline
  • if a setting describes model/profile behavior, it can belong in the profile overlay

Startup Resolution Flow

The standard command path in pinocchio/pkg/cmds/cmd.go uses both a directly decoded settings object and a profile-safe baseline.

Sequence sketch:

Glazed middleware parses values
  -> stepSettings from parsed values
  -> baseSettingsFromParsedValuesWithBase(...)
  -> profilebootstrap.ResolveCLIEngineSettingsFromBase(...)
  -> BaseInferenceSettings
  -> FinalInferenceSettings
  -> engine factory

The important subtlety is that stepSettings can already reflect profile effects. That makes it useful for ordinary command execution but unsafe as a clean baseline unless profile-derived values are removed first.

Why Stripping profiles Parse Steps Matters

Pinocchio stores parse provenance in values.Values. Each field keeps a log of where its value came from.

baseSettingsFromParsedValuesWithBase(...) walks those logs and keeps the last non-profile parse step for each field. That means:

  • CLI flags still count
  • config/env/defaults still count
  • profile middleware contributions are removed

Conceptually:

parsed values
  - remove source == "profiles"
  = profile-free parsed baseline

That is the key trick that lets Pinocchio recover the original launch-time settings instead of treating the selected profile as part of the baseline.

Config Provenance In Parsed Field History

The parsed field history is now also the main debugging surface for layered config resolution.

Config-derived parse steps carry metadata such as:

  • config_file
  • config_index
  • config_layer
  • config_source_name
  • config_source_kind

That means you can inspect parsed fields or inference debug output and answer questions like:

  • did this value come from user config or repo config?
  • did cwd-local config override the git-root file?
  • did an explicit --config-file win last?

A simplified example looks like this:

profile.active:
  value: explicit-profile
  log:
    - source: config
      value: repo-profile
      metadata:
        config_layer: repo
        config_source_name: git-root-local-profile
    - source: config
      value: cwd-profile
      metadata:
        config_layer: cwd
        config_source_name: cwd-local-profile
    - source: config
      value: explicit-profile
      metadata:
        config_layer: explicit
        config_source_name: explicit-config-file

This provenance is especially useful when reviewing bug reports or unexpected profile selection in nested repositories.

Why This Matters For ai-client

ai-client settings are cross-profile transport settings.

That means they are baseline settings, not profile settings.

Examples:

  • timeout
  • organization
  • user agent
  • proxy configuration

If you put these settings in engine profiles, you are mixing operator/app infrastructure into profile overlays. If you keep them in the baseline, they naturally survive profile changes and stay consistent across runtime switches.

web-chat Specific Caveat

web-chat intentionally does not mount the full Geppetto sections on its public CLI. Its command surface currently exposes profile-selection controls plus redis, rather than the entire shared runtime flag surface.

See:

  • pinocchio/cmd/web-chat/main.go

It still builds a hidden base through ResolveBaseInferenceSettings(...), so shared baseline fields can still reach it through config and environment.

But there is an important consequence:

  • simply adding an ai-client section to the web-chat command description would not be enough by itself to make new CLI flags effective if the runtime continues to rely only on the hidden-base path that rebuilds from env/config/defaults.

If web-chat ever wants explicit cross-profile ai-client CLI flags, it will need both:

  1. a public ai-client section on the command
  2. a base-resolution path that preserves those parsed CLI values when constructing the runtime baseline

App-Local Repository Config Is A Separate Path

One subtlety that often confuses contributors is that Pinocchio's command repositories are not part of the shared Geppetto bootstrap sections.

The top-level config key:

  • repositories

is intentionally excluded by pinocchio/pkg/cmds/profilebootstrap/configFileMapper(...) before the shared bootstrap middleware sees the config file.

That means there are two startup flows running side by side:

shared bootstrap path
  config/env/defaults -> section values -> profile selection -> registry chain -> inference settings

root CLI repository path
  resolved config files -> top-level repositories[] -> prompt directories -> command discovery

The repository path currently lives in:

  • pinocchio/cmd/pinocchio/main.go

More specifically, loadRepositoriesFromConfig() does the following:

  1. calls profilebootstrap.ResolveCLIConfigFiles(nil) so repository discovery uses the same config-file plan as the shared bootstrap path
  2. reads every resolved config file in that returned order
  3. extracts the raw top-level repositories list from each file
  4. de-duplicates exact repeated repository strings
  5. appends $HOME/.pinocchio/prompts
  6. mounts only directories that exist

This is important for architecture discussions because it explains why repositories should not be treated like a normal shared section:

  • it is Pinocchio application metadata, not Geppetto runtime/profile data
  • but it still follows the same config-file discovery stack so operator expectations stay consistent

So the clean split is:

  • Geppetto/shared bootstrap owns profile/config/runtime resolution
  • Pinocchio root startup owns repository harvesting and command discovery

Listing Loaded Profiles

Use pinocchio profiles list to inspect the profile registry chain with explicit table headers:

pinocchio profiles list \
  --profile-registries ./profiles.yaml

The default table includes columns for selection/default status, registry slug, profile slug, effective engine/API values, reasoning effort, and description. The registry column is important: if it says default, that means the profile lives in the default registry; it is not itself the default-profile marker.

Use --verbosity detailed to see raw profile overrides and effective inherited values:

pinocchio profiles list \
  --profile-registries ./profiles.yaml \
  --profile child \
  --verbosity detailed

Detailed output includes fields such as:

  • override_paths
  • override_chat_engine
  • override_inference_reasoning_effort
  • effective_chat_engine
  • effective_chat_api_type
  • effective_reasoning_effort

Use pinocchio profiles show when you want one profile's raw overrides and resolved effective settings without repeating full details for every profile row:

pinocchio profiles show child \
  --profile-registries ./profiles.yaml \
  --verbosity full \
  --output json

The profile argument can be profile or registry/profile. If omitted, profiles show uses the selected/default profile.

Use normal Glazed output flags for machine-readable output:

pinocchio profiles list \
  --profile-registries ./profiles.yaml \
  --verbosity full \
  --output json

Pinocchio routes these commands through its application-specific profile bootstrap path, so local .pinocchio.yml inline profiles are included alongside imported Geppetto registries.

Troubleshooting

ProblemCauseSolution
Switching profiles leaks values from the previous profileThe runtime reused active settings instead of a preserved baselineRe-merge from the preserved base every time
A shared setting disappears after profile changesThe setting was treated like profile data instead of baseline dataMove it into the shared baseline section and preserve it in base reconstruction
web-chat sees config/env settings but not equivalent CLI settingsHidden base reconstruction currently rebuilds from env/config/defaults, not full parsed CLI valuesAdd a parsed-values-aware base path if widening web-chat CLI surface
A contributor puts transport config into engine profilesOwnership boundary between baseline and overlay is unclearTreat ai-client.* and similar operator settings as baseline-only
Repository changes in one config file do not behave like profile overridesrepositories is loaded as Pinocchio-local top-level app metadata across all resolved config files, not as a shared section mergeInspect cmd/pinocchio/main.go and the resolved config-file stack, not just profile bootstrap

See Also