Summary
22 notebook intros under docs/notebooks/ claim that "OCI Generative AI is the default; auto-detected from ~/.oci/config". In practice this is true only for the notebook harness (examples/config.py), and even there the detection is little more than a Path.exists() check that hard-codes the profile name DEFAULT. Users who follow the docs but construct OCIChatCompletionsModel(...) directly — i.e., the pattern shown in docs/how-to/oci-models.md — get Got profile=None, auth_type=None and are reasonably confused about why the advertised auto-detection didn't kick in.
What users actually hit
A reader who skims a notebook page sees "auto-detected from ~/.oci/config" and writes the obvious thing:
from locus.agent import Agent
from locus.models.providers.oci.openai_compat import OCIChatCompletionsModel
model = OCIChatCompletionsModel(model="<model>") # no profile=
agent = Agent(model=model)
This raises immediately at src/locus/models/providers/oci/openai_compat.py:304-313:
OCIChatCompletionsModel: specify exactly one auth mode...
Got profile=None, auth_type=None.
The fix is one kwarg (profile="DEFAULT"), but the docs gave the wrong mental model and the error doesn't point back at the doc claim.
Root cause
Two separate weaknesses, both worth fixing:
1. Notebook prose oversells the auto-detection
22 notebook docs (notebook_13, 35, 36, 46, 49, 50, 51, 55, 56, 57, 58, 62, 64, …) open with:
"OCI Generative AI is the default; auto-detected from ~/.oci/config"
The phrasing reads as a property of the SDK. It is in fact a property of examples/config.py's get_model() helper. Readers who copy OCIChatCompletionsModel(...) directly (as shown in docs/how-to/oci-models.md) get no such auto-detection.
2. The harness "auto-detection" is a file-exists check
From examples/config.py:221-235:
def _auto_detect_provider() -> str:
if os.environ.get("LOCUS_OCI_AUTH_TYPE") in ("instance_principal", "resource_principal"):
return "oci"
if os.environ.get("OCI_AUTH_TYPE") in ("instance_principal", "resource_principal"):
return "oci"
if (Path.home() / ".oci" / "config").exists():
return "oci"
return "mock"
It never parses the config. The profile name then comes from LOCUS_OCI_PROFILE → OCI_PROFILE → hard-coded "DEFAULT". Consequences:
- Users with multiple profiles and no
[DEFAULT] section get an obscure OCI SDK stack trace, because the harness happily picks provider="oci" and then asks the SDK to load a profile that doesn't exist.
- Users with a
[DEFAULT] that points at the wrong tenancy/region silently use the wrong account.
- There is no friendly error pointing the user at
OCI_PROFILE.
Proposed solution
1. Fix the harness detector (examples/config.py). Replace the file-exists check with real parsing:
- If
~/.oci/config exists, parse it (via oci.config.from_file or configparser).
[DEFAULT] present → use it.
- Exactly one non-default profile → use it.
- Multiple profiles, no
[DEFAULT], no OCI_PROFILE env var → raise a clear error:
"Found N profiles in ~/.oci/config (X, Y, Z) but no [DEFAULT] section and OCI_PROFILE is unset. Set export OCI_PROFILE=<name> to pick one."
- Workload-identity env vars still short-circuit to
oci as today.
2. Tighten the 22 notebook intros so the prose matches reality. Replace:
"OCI Generative AI is the default; auto-detected from ~/.oci/config"
with:
"OCI GenAI is the default — the notebook harness auto-selects it when ~/.oci/config exists. Set OCI_PROFILE to pick a non-default profile."
One mechanical pass across the 22 files.
Explicitly not proposing: changing OCIChatCompletionsModel.__init__ to default profile="DEFAULT" when both auth args are None. The constructor's strict contract is a feature — silently defaulting would mask "user has no OCI setup at all" (constructor succeeds, first API call fails with a worse error than today's clear validation). The notebook prose is the bug, not the constructor.
Optional follow-up: expose a public helper locus.models.providers.oci.autodetect_kwargs() returning a kwargs dict (or raising the same clear error). Lets power users do OCIChatCompletionsModel(model=..., **autodetect_kwargs()) without weakening the constructor's contract. Worth doing only if anyone asks for it after #1 and #2 ship.
Acceptance criteria
Summary
22 notebook intros under
docs/notebooks/claim that "OCI Generative AI is the default; auto-detected from~/.oci/config". In practice this is true only for the notebook harness (examples/config.py), and even there the detection is little more than aPath.exists()check that hard-codes the profile nameDEFAULT. Users who follow the docs but constructOCIChatCompletionsModel(...)directly — i.e., the pattern shown indocs/how-to/oci-models.md— getGot profile=None, auth_type=Noneand are reasonably confused about why the advertised auto-detection didn't kick in.What users actually hit
A reader who skims a notebook page sees "auto-detected from
~/.oci/config" and writes the obvious thing:This raises immediately at
src/locus/models/providers/oci/openai_compat.py:304-313:The fix is one kwarg (
profile="DEFAULT"), but the docs gave the wrong mental model and the error doesn't point back at the doc claim.Root cause
Two separate weaknesses, both worth fixing:
1. Notebook prose oversells the auto-detection
22 notebook docs (
notebook_13,35,36,46,49,50,51,55,56,57,58,62,64, …) open with:The phrasing reads as a property of the SDK. It is in fact a property of
examples/config.py'sget_model()helper. Readers who copyOCIChatCompletionsModel(...)directly (as shown indocs/how-to/oci-models.md) get no such auto-detection.2. The harness "auto-detection" is a file-exists check
From
examples/config.py:221-235:It never parses the config. The profile name then comes from
LOCUS_OCI_PROFILE→OCI_PROFILE→ hard-coded"DEFAULT". Consequences:[DEFAULT]section get an obscure OCI SDK stack trace, because the harness happily picksprovider="oci"and then asks the SDK to load a profile that doesn't exist.[DEFAULT]that points at the wrong tenancy/region silently use the wrong account.OCI_PROFILE.Proposed solution
1. Fix the harness detector (
examples/config.py). Replace the file-exists check with real parsing:~/.oci/configexists, parse it (viaoci.config.from_fileorconfigparser).[DEFAULT]present → use it.[DEFAULT], noOCI_PROFILEenv var → raise a clear error:ocias today.2. Tighten the 22 notebook intros so the prose matches reality. Replace:
with:
One mechanical pass across the 22 files.
Explicitly not proposing: changing
OCIChatCompletionsModel.__init__to defaultprofile="DEFAULT"when both auth args areNone. The constructor's strict contract is a feature — silently defaulting would mask "user has no OCI setup at all" (constructor succeeds, first API call fails with a worse error than today's clear validation). The notebook prose is the bug, not the constructor.Optional follow-up: expose a public helper
locus.models.providers.oci.autodetect_kwargs()returning a kwargs dict (or raising the same clear error). Lets power users doOCIChatCompletionsModel(model=..., **autodetect_kwargs())without weakening the constructor's contract. Worth doing only if anyone asks for it after #1 and #2 ship.Acceptance criteria
examples/config.py::_auto_detect_providerparses~/.oci/configand returns a usable provider+profile pair, or raises a clear error.~/.oci/configcontaining only non-default profiles gets a friendly error (not an OCI SDK trace) the first time they run a notebook without settingOCI_PROFILE.docs/how-to/oci-models.mdis unchanged (it already showsprofile="DEFAULT"explicitly and is correct).