v7next F1: domain D12 - settings vocabulary split from tip bytes, launcher seam half landed

Module side: config.py (1598) hands its vocabulary to the five oracle leaves,
re-emitted from TIP bytes with the transplant proof green on every span:
settings_defaults (361; SHARED leaf emitted FULL from BOTH parents per the
ledger - config.py rows 840-854 + provider_models.py rows 3238-3241, final
--check against the concatenated parents), settings_scales (111), model_slots
(113), review_model_routes (131), runtime_limits (192); the facade shrinks
1598 -> 883 with the oracle's re-export import block. The drift-probe
falsified the reference as a copy source on 7 spans (SETTINGS_DEFAULTS,
RETIRED_SETTING_KEYS, ENDPOINT_AUTHORED_SETTINGS, OPENROUTER_REVIEW_DEFAULTS,
get_websearch_timeout_sec, get_search_code_wall_sec, get_max_subagent_depth)
plus one structural reshape: upstream's tuple statement also binds the
unrowed MAX_SUBAGENT_DEPTH_HARD_CAP, which rides into runtime_limits and is
re-exported (F5 row needed; the tool's undeclared-top-level gate has a
Tuple-target blind spot - span proofs green, documented). provider_models.py
is touched by span removal + the re-export import ONLY (D02 module). The
reference's D04 retirement of the SOFT/HARD timeout knobs is
upstream-diverged and NOT replayed.

launcher_onboarding.py lands the launcher HALF of the approved D03 settings
seam (rows 918-920): zero upstream drift since merge-base, reference bytes
verbatim - startup stops persisting the pre-server provider normalization;
the server-side mirror stays for the D11 lane. The config.py in-place seam
rows 914/916 (normalize_settings_raw / serialize_settings) are HOT-DEFERRED:
upstream rewrote the read path through the new settings_integrity module, so
a verbatim replay would revert it; their pin test_settings_read_seam.py
defers with the machinery.

Test side: test_config_extraction.py + test_settings_env_on_disk.py
transplanted (adaptations: the tuple-bound cap in the owner inventory, one
literal re-pinned to tip's ENDPOINT_AUTHORED_SETTINGS, the provider_models
clause narrowed to this tree - the no-config-import half returns with D02);
the onboarding pins follow the launcher delta (row 920 rename, wizard and
server_runtime launcher clauses; every server clause keeps tip bytes).
Lossless: 19==19 / 63==63 / 28==28 test functions on the touched suites, one
ledger-mandated rename. Two path-keyed mirrors updated (prompt-cache TTL
definition-sites, hotreload key-paths set).

CI-shape battery on the final tree: parallel 11807 tests 0 failed (3
skipped) rc=0; serial 618 tests 0 failed (9 skipped) rc=0; -m size_ratchet 5
passed (official manifest regeneration = byte no-op); ruff --select F clean;
HEAD held through every pytest run. Ledger corrections: entries 13-19 in
docs/v7next/LEDGER_CORRECTIONS.md.

(cherry picked from commit 3a6a0344eabb9b933352c5fc32cabb0a79b186e5)
This commit is contained in:
Ouroboros 2026-08-30 18:35:19 +00:00
parent 22c1473117
commit a44815215f
16 changed files with 1557 additions and 876 deletions

View file

@ -357,3 +357,85 @@ with evidence, found lane by lane. Applied to the campaign's carried ledger at F
facade for now), and the reference's extra HOT_CODE_PATHS row for
ouroboros/tools/extension_dispatch.py (nothing moved there on this tree —
adding it is an oracle delta beyond relocation parity).
## From the D12 lane (base d830cdba, 2026-08-30)
13. Split rows 855-867 (settings_scales), 868-879 (model_slots), 880-886
(review_model_routes) — RE-PROVEN against tip bytes: every span of the three
reference leaves is ast=tokens=bytes=True against
`git show HEAD:ouroboros/config.py` (drift-probe first, exit 0); the leaves
landed from tip bytes and differ from the reference only in BETWEEN-SPAN
comments upstream rewrote inside config.py (EFFORT_SCALE header now names
exact-route request-wire recovery; the PROMPT_CACHE_TTL comment rewrapped) —
carried from tip, since the span proof is blind to inter-span comment lines.
14. Shared-leaf rows 840-846/852-854 (config.py) + 3238-3241 (provider_models.py)
-> settings_defaults.py — BYTE-FALSIFIED as a copy source on 4 of 12 spans,
transform still valid: upstream rewrote SETTINGS_DEFAULTS (advisory slot is
the routed id `anthropic/claude-sonnet-5`, `CLAUDE_CODE_MODEL` retired,
MAX_SUBAGENT_DEPTH default 2->3, `OUROBOROS_SOFT/HARD_TIMEOUT_SEC` live
again with a display-only note, plus new PRESENCE/SUBAGENTS/CLAUDEXOR/
REVIEW_NATIVE_* keys), RETIRED_SETTING_KEYS (upstream itself retired only
PLAN_TASK_SWARM_HEARTBEAT_STALE_SEC and kept SOFT/HARD live — the
reference's D04 retirement of those two knobs is DIVERGENT-SUPERSEDED and
must be re-derived in its own return, not replayed), ENDPOINT_AUTHORED_
SETTINGS (+OUROBOROS_SUBAGENT_PRESET_RECEIPT) and OPENROUTER_REVIEW_DEFAULTS
(routed advisory id + comment). Leaf emitted FULL from BOTH parents (the
shared-leaf convention: drift-probe per parent separately; the final
transplant --check runs against the two parents concatenated into one
upstream source so every span is verified in a single exit-0 report).
provider_models.py was touched ONLY by span removal + the settings_defaults
re-export import; its call-time `from ouroboros.config import ...` imports
are tip truth (D02-owned) and stand.
15. Split rows 887-912 (runtime_limits) — 3 spans byte-falsified by upstream
drift (get_websearch_timeout_sec docstring; get_search_code_wall_sec now
routes through _clamped_number_setting; get_max_subagent_depth reads the
named cap), all re-emitted from tip bytes. STRUCTURAL: upstream reshaped
`MAX_ACTIVE_SUBAGENTS_HARD_CAP = 500` into the tuple statement
`MAX_ACTIVE_SUBAGENTS_HARD_CAP, MAX_SUBAGENT_DEPTH_HARD_CAP = 500, 10`;
the UNROWED twin (consumed by ouroboros/tools/control_delegation.py via
config) rides the rowed statement into runtime_limits and the facade
re-exports both — the carried ledger must mint its row at F5. Tool note:
the hardened --check flags this one statement as `assignment to <complex
target>` under undeclared_top_level even though BOTH bound names are
requested symbols (Tuple-target blind spot; every span proof in the same
report is green, leaf_invariants=[]) — the one lane gate that exits 2 with
a proven false-positive cause; the tool wants Tuple support at F5.
16. Rows 918-920 (launcher_onboarding, semantic delta D03/settings seam,
launcher half) — RE-PROVEN applicable and LANDED: the module is
byte-identical between tip and merge-base (zero upstream drift), so the
reference bytes apply verbatim; the pin renamed per row 920. The SERVER
half of the same seam (rows 1080-1081, server.py lifespan) is NOT landed —
server.py keeps the tip guarded write and its old pin; it returns with the
D11 lane. Two unrowed oracle test adaptations were mirrored because they
pin exactly this delta and go red without it: test_onboarding_wizard.py::
test_the_launcher_onboarding_module_authors_no_onboarding_settings
(reference bytes) and tests/test_server_runtime.py (launcher clause ->
`"save_settings(" not in launcher_host`; the server clause KEEPS the tip
guard-string assertion, diverging from the reference's both-sides form
until D11 lands).
17. Rows 913-917 (the rest of the D03 settings seam: config.py
normalize_settings_raw/serialize_settings, gateway/owner_settings digest +
locked update, packaged_cli writer) — HOT-DEFERRED: upstream rewrote
load_settings_lock_held's read path through the NEW post-cutoff
settings_integrity module (read_settings_json_verified /
SettingsIntegrityError raise-through), which the reference does not have;
replaying the reference seam verbatim would revert the integrity feature
(the re-prove-trap class, entry 3). The whole seam machinery re-derives
against tip bytes in its own return; its pin tests/test_settings_read_seam.py
(a DOMAIN_MAP D12 pin) defers WITH the machinery — not transplanted by this
lane.
18. Pin adaptations recorded: test_settings_env_on_disk.py re-pinned one
literal to tip bytes (ENDPOINT_AUTHORED_SETTINGS gains
OUROBOROS_SUBAGENT_PRESET_RECEIPT — same upstream train as entry 14);
test_config_extraction.py gains MAX_SUBAGENT_DEPTH_HARD_CAP in the owner
inventory, Tuple-target parsing in its _top_level_names helper, and a
narrowed provider_models clause (the reference's "no ouroboros.config
import anywhere" + top-level model_slots import clauses type against the
reference's D02 rework of provider_models and return with the D02 lane;
the surviving clauses pin no IMPORT-TIME config read and leaf-object
identity of both moved literals).
19. settings_integrity.py — NEW upstream module (post-cutoff, absent from the
reference and the merge base), already D12 in scripts/v7next_domains.toml;
no ledger rows; upstream bytes stand. Non-split D12 modules re-proven:
colab_bootstrap.py / onboarding_wizard.py / secret_masking.py /
update_channels.py byte-identical across tip==ref==merge-base;
settings_setup_contract.py / subscription_install_presets.py pure upstream
drift (ref==merge-base, zero v7 delta) — upstream bytes stand.

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,6 @@ import logging
from ouroboros.config import (
apply_settings_to_env as _apply_settings_to_env,
load_settings,
save_settings,
)
from ouroboros.server_runtime import apply_runtime_provider_defaults, has_startup_ready_provider
@ -28,20 +27,15 @@ def prepare_first_run_settings() -> tuple[dict, bool]:
launcher shows once it is healthy. The onboarding SURFACE is not rendered
here: the live server serves it (``present_first_run_onboarding``).
"""
settings, provider_defaults_changed, _provider_default_keys = apply_runtime_provider_defaults(load_settings())
# Persist the pre-server normalization ONLY for an install that already has a
# settings file. On a FRESH install this save would CREATE the file before the
# owner's first onboarding save, and every fresh-install proof is gated on
# that freshness — safety-light authorship, install-time agent presets (a
# local-first launch with LOCAL_MODEL_SOURCE in the environment reaches here
# with changed=True and would silently lose Light). Nothing is dropped: the
# completion save persists the same normalization, and the managed server
# keeps the mirror-image guard in its lifespan.
from ouroboros.config import SETTINGS_PATH as _settings_path
if provider_defaults_changed and _settings_path.exists():
# Owner-process boundary: a first-run/provider save may elevate runtime mode.
save_settings(settings, allow_elevation=True)
settings, _provider_defaults_changed, _provider_default_keys = apply_runtime_provider_defaults(load_settings())
# The normalization is APPLIED, not persisted. Startup is a read, and a read that
# rewrites the file it read is how a normalization becomes an owner decision: the
# fresh-install case already had to be carved out of this save (it would create
# settings.json before the owner's own onboarding write and lose safety-light
# authorship and the install-time agent presets), which is the same objection in a
# narrower dress. Nothing is dropped, because nothing here was the only place the
# normalization happens: every reader re-derives it (`/api/settings`, `/onboarding`,
# the onboarding host, the plan-review script), and the completion save persists it.
_apply_settings_to_env(settings)
return settings, not has_startup_ready_provider(settings)

113
ouroboros/model_slots.py Normal file
View file

@ -0,0 +1,113 @@
"""Ouroboros — model slot resolution.
The Main/Heavy/Light/Vision/Consciousness/deep-review slots and the ordered
cross-model fallback chain, resolved from the environment with the shipped
defaults as the floor, plus the rename-alias migration that keeps a slot the
owner customized under its former key. Imported by ``provider_models`` as well
as by ``config``, which is why it holds no settings-file knowledge.
"""
from __future__ import annotations
import os
from ouroboros.settings_defaults import SETTINGS_DEFAULTS
def _parse_model_list(value: str) -> list[str]:
return [item.strip() for item in str(value or "").split(",") if item.strip()]
def _main_model() -> str:
return (
str(os.environ.get("OUROBOROS_MODEL", "") or "").strip()
or str(SETTINGS_DEFAULTS["OUROBOROS_MODEL"])
)
def get_light_model() -> str:
"""Light slot; empty falls back to Main (heavy/consciousness stay empty->main)."""
return str(os.environ.get("OUROBOROS_MODEL_LIGHT", "") or "").strip() or _main_model()
def get_heavy_model() -> str:
"""Return the heavy (strong acting/coding) lane slot; empty falls back to
OUROBOROS_MODEL. Renamed from the legacy code slot."""
return str(os.environ.get("OUROBOROS_MODEL_HEAVY", "") or "").strip() or _main_model()
def get_vision_model() -> str:
"""Return the vision/caption model slot; empty falls back to OUROBOROS_MODEL."""
return str(os.environ.get("OUROBOROS_MODEL_VISION", "") or "").strip() or _main_model()
def get_image_input_mode() -> str:
raw = str(os.environ.get("OUROBOROS_IMAGE_INPUT_MODE", SETTINGS_DEFAULTS["OUROBOROS_IMAGE_INPUT_MODE"]) or "").strip().lower()
return raw if raw in {"auto", "caption", "inline", "off"} else "auto"
def parse_fallback_chain() -> list[str]:
"""Parse the raw ordered cross-model fallback chain — SSOT for every consumer
(resilience walk, pricing categorization, credentialed-model resolution).
Reads OUROBOROS_MODEL_FALLBACKS, then the legacy singular OUROBOROS_MODEL_FALLBACK
(env-only back-compat). No dedup, no active-model drop, and NO SETTINGS_DEFAULTS
injection: an EXPLICITLY empty Fallbacks slot means "no cross-model fallback". The
shipped default reaches a default install through apply_settings_to_env."""
raw = (
str(os.environ.get("OUROBOROS_MODEL_FALLBACKS", "") or "").strip()
or str(os.environ.get("OUROBOROS_MODEL_FALLBACK", "") or "").strip()
)
return [m.strip() for m in _parse_model_list(raw) if str(m or "").strip()]
def get_fallback_models(active_model: str = "") -> list[str]:
"""Return the ordered cross-model resilience CHAIN (deduped, with the active model
removed so a benchmark all-slots-one-model setup collapses the chain to a no-op)."""
out: list[str] = []
seen = set()
active = str(active_model or "").strip()
for m in parse_fallback_chain():
if m and m != active and m not in seen:
seen.add(m)
out.append(m)
return out
# v6.39 slot rename-alias migration (same shape as the retention-key rename):
# OUROBOROS_MODEL_CODE -> _HEAVY, USE_LOCAL_CODE -> USE_LOCAL_HEAVY,
# OUROBOROS_MODEL_FALLBACK -> _FALLBACKS.
_LEGACY_SLOT_RENAMES = (
("OUROBOROS_MODEL_CODE", "OUROBOROS_MODEL_HEAVY"),
("OUROBOROS_VISION_MODEL", "OUROBOROS_MODEL_VISION"),
("USE_LOCAL_CODE", "USE_LOCAL_HEAVY"),
("OUROBOROS_MODEL_FALLBACK", "OUROBOROS_MODEL_FALLBACKS"),
)
def migrate_legacy_slot_keys(settings: dict) -> dict:
"""In-place settings migration, applied BEFORE defaults are merged.
Preserves a stored value (never orphans an owner customization), then drops the legacy
key. Shared SSOT for every settings entry point (load_settings AND the Colab builder).
Order matters: the singular scope-review pin is promoted HERE, before ``SETTINGS_DEFAULTS``
supplies the plural that WINS in get_scope_review_models."""
for _old, _new in _LEGACY_SLOT_RENAMES:
if _new not in settings and _old in settings:
settings[_new] = settings[_old]
settings.pop(_old, None)
_pin = str(settings.get("OUROBOROS_SCOPE_REVIEW_MODEL") or "").strip()
if _pin and not str(settings.get("OUROBOROS_SCOPE_REVIEW_MODELS") or "").strip():
settings["OUROBOROS_SCOPE_REVIEW_MODELS"] = _pin
return settings
def get_consciousness_model() -> str:
"""Return the high-horizon background-consciousness model slot."""
return str(os.environ.get("OUROBOROS_MODEL_CONSCIOUSNESS", "") or "").strip() or _main_model()
def get_deep_self_review_model() -> str:
"""Return the configured deep self-review model slot."""
return (str(os.environ.get("OUROBOROS_MODEL_DEEP_SELF_REVIEW", "") or "").strip()
or str(SETTINGS_DEFAULTS["OUROBOROS_MODEL_DEEP_SELF_REVIEW"]))

View file

@ -7,6 +7,8 @@ from __future__ import annotations
import os
from ouroboros.settings_defaults import OPENROUTER_DEFAULTS, OPENROUTER_REVIEW_DEFAULTS # noqa: F401
# MiniMax exposes the same OpenAI-compatible API on two regional hosts. Keep the
# mapping centralized so transport, capability evidence, and settings diagnostics
# fingerprint the exact endpoint selected by the owner.
@ -297,32 +299,6 @@ def provider_credential_plan(
}
# Shipped router profile. Keeping the root-loop role policy beside the direct
# provider profiles gives onboarding, runtime defaults, and tests one vocabulary
# instead of repeating model ids across those surfaces.
OPENROUTER_DEFAULTS = {
"main": "google/gemini-3.7-flash",
"heavy": "",
"light": "openai/gpt-5.6-luna",
"vision": "",
"consciousness": "",
"fallback": "openai/gpt-5.6-luna",
"deep_self_review": "openai/gpt-5.6-sol-pro",
}
OPENROUTER_REVIEW_DEFAULTS = {
"triad": (
"google/gemini-3.7-flash",
"openai/gpt-5.6-terra",
"anthropic/claude-opus-5",
),
"scope": ("openai/gpt-5.6-terra",),
# Routed catalog id (the retired Claude-SDK spelling migrated same-model);
# without provider credentials the advisory gate records an audited bypass.
"advisory": "anthropic/claude-sonnet-5",
}
OPENAI_DIRECT_DEFAULTS = {
"main": "openai::gpt-5.6-terra",
"heavy": "",

View file

@ -0,0 +1,131 @@
"""Ouroboros — the reviewer model lists a review lane actually runs.
Triad and scope review each resolve a configured comma list into the models the
lane will call, honouring a local-only Main route and rewriting the list when the
install has exactly one direct provider credentialed. Also the reviewer-quorum
rule shared by every review family.
"""
from __future__ import annotations
import os
from ouroboros.model_slots import _main_model, _parse_model_list
from ouroboros.provider_models import (
compute_direct_review_models_fallback,
local_only_review_route_env,
migrate_model_value,
)
from ouroboros.settings_defaults import SETTINGS_DEFAULTS
_DIRECT_PROVIDER_REVIEW_RUNS = 3
def _exclusive_direct_remote_provider_env() -> str:
has_openrouter = bool(str(os.environ.get("OPENROUTER_API_KEY", "") or "").strip())
has_openai = bool(str(os.environ.get("OPENAI_API_KEY", "") or "").strip())
has_anthropic = bool(str(os.environ.get("ANTHROPIC_API_KEY", "") or "").strip())
has_minimax = bool(str(os.environ.get("MINIMAX_API_KEY", "") or "").strip())
has_legacy_base = bool(str(os.environ.get("OPENAI_BASE_URL", "") or "").strip())
has_compatible = bool(str(os.environ.get("OPENAI_COMPATIBLE_BASE_URL", "") or "").strip())
has_cloudru = bool(str(os.environ.get("CLOUDRU_FOUNDATION_MODELS_API_KEY", "") or "").strip())
has_gigachat = bool(str(os.environ.get("GIGACHAT_CREDENTIALS", "") or "").strip()) or (
bool(str(os.environ.get("GIGACHAT_USER", "") or "").strip())
and bool(str(os.environ.get("GIGACHAT_PASSWORD", "") or "").strip())
)
# OpenRouter / legacy OpenAI base / OpenAI-compatible all route through the
# OpenRouter-style stack, so their presence means "not an exclusive direct
# provider". Among the registered direct providers, return one only when
# exactly one is configured.
if has_openrouter or has_legacy_base or has_compatible:
return ""
direct = [name for name, present in (
("openai", has_openai), ("anthropic", has_anthropic), ("minimax", has_minimax),
("cloudru", has_cloudru), ("gigachat", has_gigachat),
) if present]
return direct[0] if len(direct) == 1 else ""
def direct_provider_review_models_fallback(provider: str) -> list[str]:
"""Return the exact review-models list a direct-provider fallback emits."""
if provider not in ("openai", "anthropic", "minimax", "cloudru", "gigachat"):
return []
main_model = str(
os.environ.get("OUROBOROS_MODEL", SETTINGS_DEFAULTS["OUROBOROS_MODEL"]) or ""
).strip()
main_model = migrate_model_value(provider, main_model)
user_light_raw = str(os.environ.get("OUROBOROS_MODEL_LIGHT", "") or "").strip()
return compute_direct_review_models_fallback(
provider,
main_model,
user_light_raw,
review_runs=_DIRECT_PROVIDER_REVIEW_RUNS,
)
def adaptive_quorum(n_slots: int) -> int:
"""Reviewer-quorum SSOT for an ARBITRARY configured slot count, reused by
triad/scope/plan/skill/acceptance review. One configured reviewer needs 1 (a loud
single_reviewer_no_diversity degraded mode), 2 need both, 3+ keep the classic 2-of-N
majority. DISTINCT from "configured >= quorum but fewer responded", which stays a loud
infra quorum FAILURE at the call site."""
return 2 if n_slots >= 3 else max(1, n_slots)
def get_review_models() -> list[str]:
"""Return the configured pre-commit review model list."""
default_str = SETTINGS_DEFAULTS["OUROBOROS_REVIEW_MODELS"]
models_str = os.environ.get("OUROBOROS_REVIEW_MODELS", default_str) or default_str
models = _parse_model_list(models_str)
models = [_main_model()] * max(1, len(models)) if local_only_review_route_env() else models
provider = _exclusive_direct_remote_provider_env()
if not provider:
return models
main_model = str(os.environ.get("OUROBOROS_MODEL", SETTINGS_DEFAULTS["OUROBOROS_MODEL"]) or "").strip()
main_model = migrate_model_value(provider, main_model)
provider_prefix = f"{provider}::"
if not main_model.startswith(provider_prefix):
return models
migrated = [migrate_model_value(provider, model) for model in models]
if not migrated or any(not model.startswith(provider_prefix) for model in migrated):
# Auto-expand to the [main]*N stochastic fallback ONLY when nothing usable is
# configured (empty, or foreign models in an exclusive direct-provider setup). An
# explicit provider-matching list is honored exactly, duplicates included.
return direct_provider_review_models_fallback(provider)
return migrated
def get_review_enforcement() -> str:
"""Return the configured pre-commit review enforcement mode."""
default_val = str(SETTINGS_DEFAULTS["OUROBOROS_REVIEW_ENFORCEMENT"])
raw = (os.environ.get("OUROBOROS_REVIEW_ENFORCEMENT", default_val) or default_val).strip().lower()
return raw if raw in {"advisory", "blocking"} else default_val
def get_scope_review_models() -> list[str]:
"""Return configured scope reviewer slots, preserving duplicate model IDs."""
default_str = str(SETTINGS_DEFAULTS["OUROBOROS_SCOPE_REVIEW_MODELS"])
raw = os.environ.get("OUROBOROS_SCOPE_REVIEW_MODELS", "") or ""
if not raw.strip():
raw = os.environ.get("OUROBOROS_SCOPE_REVIEW_MODEL", default_str) or default_str
models = _parse_model_list(raw)
singular = str(os.environ.get("OUROBOROS_SCOPE_REVIEW_MODEL", SETTINGS_DEFAULTS["OUROBOROS_SCOPE_REVIEW_MODEL"]) or "").strip()
if not models and singular:
models = [singular]
if not models:
models = _parse_model_list(default_str)
models = [_main_model()] * max(1, len(models)) if local_only_review_route_env() else models
provider = _exclusive_direct_remote_provider_env()
if not provider:
return models
migrated = [migrate_model_value(provider, model) for model in models]
provider_prefix = f"{provider}::"
if migrated and all(model.startswith(provider_prefix) for model in migrated):
return migrated
migrated_singular = migrate_model_value(provider, singular or SETTINGS_DEFAULTS["OUROBOROS_SCOPE_REVIEW_MODEL"])
if migrated_singular.startswith(provider_prefix):
return [migrated_singular]
fallback = direct_provider_review_models_fallback(provider)
return fallback[:1] if fallback else migrated

192
ouroboros/runtime_limits.py Normal file
View file

@ -0,0 +1,192 @@
"""Ouroboros — the numeric runtime knobs and their clamps.
Worker count, task liveness windows, per-call ceilings, reviewer and acceptance
budgets, subagent caps and delegation windows. Every one of them is an
environment-or-default scalar clamped into a documented band, so a typo falls
back to the shipped value instead of disabling a rail.
"""
from __future__ import annotations
import os
from typing import Optional
from ouroboros.settings_defaults import (
PACING_INTERVAL_DEFAULT_SEC,
SETTINGS_DEFAULTS,
SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC,
)
def _clamped_number_setting(key: str, *, low, high=float("inf"), cast=float):
"""Env-or-default numeric setting clamped to [low, high]; a typo falls back to the
shipped default. SSOT for the clamped scalar getters below — the seven of them were
byte-identical except for key, caster and bounds (P7 DRY)."""
try:
value = cast(os.environ.get(key, "") or SETTINGS_DEFAULTS[key])
except (TypeError, ValueError):
value = cast(SETTINGS_DEFAULTS[key])
return max(low, min(value, high))
def _bounded_positive_int_setting(key: str, *, default: int, hard_max: int, min_value: int = 1) -> int:
"""Bounded int setting; below ``min_value`` it is a typo and falls back to ``default``. Only
subagent depth passes 0 — there an explicit 0 is a real owner choice, not unset (owner Q26)."""
raw = os.environ.get(key, SETTINGS_DEFAULTS.get(key, default))
try:
parsed = int(raw)
except (TypeError, ValueError):
parsed = default
if parsed < min_value:
parsed = default
return max(min_value, min(parsed, hard_max))
def get_max_workers() -> int:
return _clamped_number_setting("OUROBOROS_MAX_WORKERS", low=1, cast=int)
def get_task_idle_timeout_sec() -> int:
"""Idle window before a task is eligible for an activity-based stop: it has made
no REAL progress (its own last_progress_at) AND has no progressing subtree for
this long. The periodic 30s process heartbeat is liveness, NOT progress."""
return _clamped_number_setting("OUROBOROS_TASK_IDLE_TIMEOUT_SEC", low=60, cast=int)
def get_task_abs_ceiling_sec() -> int:
"""Absolute wall-clock backstop per task, independent of activity — the only hard
time axis (budget/cost is the other, separate hard axis). A productively-waiting
orchestrator survives to this ceiling instead of a flat 1800s wall-clock kill."""
return _clamped_number_setting("OUROBOROS_TASK_ABS_CEILING_SEC", low=300, cast=int)
def get_per_call_timeout_ceiling_sec() -> int:
"""SSOT ceiling for an explicit per-call run_command/run_script timeout_sec
(and the outer tool-execution cap that accommodates it)."""
return _clamped_number_setting("OUROBOROS_PER_CALL_TIMEOUT_CEILING_SEC", low=1, cast=int)
def get_restart_drain_max_sec() -> int:
return _clamped_number_setting(
"OUROBOROS_RESTART_DRAIN_MAX_SEC", low=0, cast=lambda v: int(float(v)))
def get_safety_max_tokens() -> int:
"""Output-token budget for safety-supervisor LLM calls (parse-bug fix)."""
return _clamped_number_setting("OUROBOROS_SAFETY_MAX_TOKENS", low=256, high=16384, cast=int)
def get_safety_call_timeout_sec() -> float:
"""Transport timeout for safety-supervisor LLM calls (prevents indefinite hang)."""
return _clamped_number_setting("OUROBOROS_SAFETY_CALL_TIMEOUT_SEC", low=5.0, high=600.0)
def get_websearch_timeout_sec() -> float:
"""Per-attempt transport timeout for provider-backed web_search calls."""
return _clamped_number_setting("OUROBOROS_WEBSEARCH_TIMEOUT_SEC", low=30.0, high=3600.0)
def get_llm_transport_read_timeout_sec() -> float:
"""Default httpx read/write timeout for no_proxy LLM clients (v6.54.3, D).
The DEAD-SOCKET bound, not a latency target; explicit per-call timeouts win."""
return _clamped_number_setting("OUROBOROS_LLM_TRANSPORT_READ_TIMEOUT_SEC", low=60.0, high=7200.0)
def get_acceptance_review_est_sec() -> float:
"""Estimated duration of one acceptance review/improvement pass (v6.54.4)."""
return _clamped_number_setting("OUROBOROS_ACCEPTANCE_REVIEW_EST_SEC", low=10.0, high=3600.0)
def get_acceptance_reserve_pct() -> int:
"""Default finalization-reserve percentage of the total budget (v6.54.4)."""
return _clamped_number_setting("OUROBOROS_ACCEPTANCE_RESERVE_PCT", low=0, high=50, cast=int)
def get_plan_task_deadline_min_sec() -> float:
"""Minimum useful deadline-scaled planning-swarm window (v6.54.3, 1.5)."""
return _clamped_number_setting("OUROBOROS_PLAN_TASK_DEADLINE_MIN_SEC", low=30.0, high=3600.0)
def get_vision_caption_timeout_sec() -> int:
return _clamped_number_setting("OUROBOROS_VISION_CAPTION_TIMEOUT_SEC", low=1, cast=int)
def get_pacing_interval_sec(settings: Optional[dict] = None) -> int:
"""Intrinsic self-pacing checkpoint cadence in seconds (0 disables)."""
raw = os.environ.get("OUROBOROS_PACING_INTERVAL_SEC")
if raw is None and isinstance(settings, dict):
raw = settings.get("OUROBOROS_PACING_INTERVAL_SEC")
try:
parsed = int(raw)
except (TypeError, ValueError):
parsed = int(PACING_INTERVAL_DEFAULT_SEC)
return max(0, parsed)
def get_supervisor_liveness_deadline_sec(settings: Optional[dict] = None) -> int:
"""Supervisor-loop stall deadline in seconds (0 disables the watchdog)."""
raw = os.environ.get("OUROBOROS_SUPERVISOR_LIVENESS_DEADLINE_SEC")
if raw is None and isinstance(settings, dict):
raw = settings.get("OUROBOROS_SUPERVISOR_LIVENESS_DEADLINE_SEC")
try:
parsed = int(raw)
except (TypeError, ValueError):
parsed = int(SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC)
return max(0, parsed)
def get_post_task_evolution_budget_usd() -> float:
"""Optional per-window USD budget for post-task evolution (0 = use the
existing EVOLUTION_BUDGET_RESERVE / TOTAL_BUDGET gating only)."""
return _clamped_number_setting("OUROBOROS_POST_TASK_EVOLUTION_BUDGET_USD", low=0.0)
# Per-root active-child ceiling (v6.82: 50->500) and absolute host-visible nesting ceiling, used by supervisor gates and ARCHITECTURE §7.
MAX_ACTIVE_SUBAGENTS_HARD_CAP, MAX_SUBAGENT_DEPTH_HARD_CAP = 500, 10
def get_max_active_subagents_per_root() -> int:
return _bounded_positive_int_setting(
"OUROBOROS_MAX_ACTIVE_SUBAGENTS_PER_ROOT",
default=int(SETTINGS_DEFAULTS["OUROBOROS_MAX_ACTIVE_SUBAGENTS_PER_ROOT"]),
hard_max=MAX_ACTIVE_SUBAGENTS_HARD_CAP,
)
def get_max_subagent_depth() -> int:
"""Structural nesting cap; 0 = NO delegation at all (every child refused, root tasks still
run). Before v6.79.0 a configured 0 was silently rewritten to 2, so "no-swarm" delegated."""
return _bounded_positive_int_setting(
"OUROBOROS_MAX_SUBAGENT_DEPTH",
default=int(SETTINGS_DEFAULTS["OUROBOROS_MAX_SUBAGENT_DEPTH"]),
hard_max=MAX_SUBAGENT_DEPTH_HARD_CAP,
min_value=0,
)
# delegate_wait's ToolEntry per-call timeout (above it a configured ceiling buys a
# KILLED call, not a longer wait; pinned by test) and the hard max WINDOW per call
# (F5): 1800 < 2100 (kill) < 2400 (lease) — decoupled, a raised timeout never widens it.
DELEGATE_WAIT_CEILING_SEC = 2100
DELEGATE_WAIT_WINDOW_MAX_SEC = 1800
def get_delegate_wait_max_sec() -> int:
"""delegate_wait window ceiling: the setting NARROWS, never widens past 1800."""
return _clamped_number_setting(
"OUROBOROS_DELEGATE_WAIT_MAX_SEC", low=1, high=DELEGATE_WAIT_WINDOW_MAX_SEC, cast=int)
def get_delegate_wait_sec() -> int:
"""Default WINDOW one ``delegate_wait`` call holds — not a quiet cutoff: the
wait holds, returns its advances, and bounds the nanny's mailbox absence."""
return _clamped_number_setting(
"OUROBOROS_DELEGATE_WAIT_SEC", low=1, high=get_delegate_wait_max_sec(), cast=int)
def get_search_code_wall_sec() -> float:
"""Total wall-clock budget (seconds) for ONE search_code call — bounds both the rg
directory walk and the batched rg loop so a scan over a very large root cannot run
unbounded. Env/setting: ``OUROBOROS_SEARCH_CODE_WALL_SEC`` (floored at 5s)."""
return _clamped_number_setting("OUROBOROS_SEARCH_CODE_WALL_SEC", low=5.0)

View file

@ -0,0 +1,361 @@
"""Ouroboros — the settings vocabulary.
What settings exist, the values a fresh install ships, which keys a release has
retired, and which keys never travel between disk and the environment. Data and
derivations only: nothing here reads or writes settings.json.
"""
from __future__ import annotations
from ouroboros.update_channels import UPDATE_SETTINGS_DEFAULTS
FINALIZATION_GRACE_DEFAULT_SEC = 120
# Owner finalization outer cap starts at the stop request; grace starts at control delivery
# (the loop's mailbox drain). No summary by this cap -> honest custody cancel.
OWNER_STOP_OUTER_CAP_SEC = 600
# Cadence for intrinsic self-pacing checkpoints when a task has NO deadline_at (headless benchmark runs). Advisory only — surfaces elapsed/rounds/cost for self-pacing; 0 disables.
PACING_INTERVAL_DEFAULT_SEC = 600
# Supervisor-loop liveness deadline (WS3, v6.34.0): a watchdog thread flags the main supervisor loop STALLED if it has not ticked within this many seconds (healthy tick ~0.5s, real wedges only). 0 disables.
SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC = 90
# Shipped router profile. Keeping the root-loop role policy beside the direct
# provider profiles gives onboarding, runtime defaults, and tests one vocabulary
# instead of repeating model ids across those surfaces.
OPENROUTER_DEFAULTS = {
"main": "google/gemini-3.7-flash",
"heavy": "",
"light": "openai/gpt-5.6-luna",
"vision": "",
"consciousness": "",
"fallback": "openai/gpt-5.6-luna",
"deep_self_review": "openai/gpt-5.6-sol-pro",
}
OPENROUTER_REVIEW_DEFAULTS = {
"triad": (
"google/gemini-3.7-flash",
"openai/gpt-5.6-terra",
"anthropic/claude-opus-5",
),
"scope": ("openai/gpt-5.6-terra",),
# Routed catalog id (the retired Claude-SDK spelling migrated same-model);
# without provider credentials the advisory gate records an audited bypass.
"advisory": "anthropic/claude-sonnet-5",
}
# Settings defaults
SETTINGS_DEFAULTS = {**UPDATE_SETTINGS_DEFAULTS,
"OPENROUTER_API_KEY": "",
"OPENAI_API_KEY": "",
"OPENAI_BASE_URL": "",
"OPENAI_COMPATIBLE_API_KEY": "",
"OPENAI_COMPATIBLE_BASE_URL": "",
"CLOUDRU_FOUNDATION_MODELS_API_KEY": "",
"CLOUDRU_FOUNDATION_MODELS_BASE_URL": "https://foundation-models.api.cloud.ru/v1",
"GIGACHAT_CREDENTIALS": "",
"GIGACHAT_USER": "",
"GIGACHAT_PASSWORD": "",
"GIGACHAT_SCOPE": "GIGACHAT_API_PERS",
"GIGACHAT_BASE_URL": "https://api.giga.chat/v1",
"GIGACHAT_VERIFY_SSL_CERTS": "true",
"GIGACHAT_PROFANITY_CHECK": "",
"ANTHROPIC_API_KEY": "",
"MINIMAX_API_KEY": "",
"MINIMAX_REGION": "",
"OUROBOROS_NETWORK_PASSWORD": "",
"OUROBOROS_SERVER_HOST": "127.0.0.1",
"OUROBOROS_HOST_SERVICE_PORT": 8767,
"OUROBOROS_MODEL": OPENROUTER_DEFAULTS["main"],
# Worker lanes; empty means "use OUROBOROS_MODEL" (one model by default, per-lane
# override optional). HEAVY = mutative first-level subagents; LIGHT = auto/deep bulk.
"OUROBOROS_MODEL_HEAVY": OPENROUTER_DEFAULTS["heavy"],
"OUROBOROS_MODEL_LIGHT": OPENROUTER_DEFAULTS["light"],
"OUROBOROS_MODEL_VISION": OPENROUTER_DEFAULTS["vision"],
"OUROBOROS_IMAGE_INPUT_MODE": "auto",
# Background consciousness is a high-horizon loop, not a cheap helper lane.
"OUROBOROS_MODEL_CONSCIOUSNESS": OPENROUTER_DEFAULTS["consciousness"],
# Cross-model resilience CHAIN (comma-separated, ordered). A single model is a
# 1-element chain; empty disables cross-model fallback. Resilience slot — keeps a
# real default, unlike the worker lanes. (Renamed from the singular MODEL_FALLBACK.)
"OUROBOROS_MODEL_FALLBACKS": OPENROUTER_DEFAULTS["fallback"],
"OUROBOROS_MODEL_DEEP_SELF_REVIEW": OPENROUTER_DEFAULTS["deep_self_review"],
"OUROBOROS_MAX_WORKERS": 10, "OUROBOROS_PRESENCE_MAX_ACTIVE": 2,
"OUROBOROS_MAX_ACTIVE_SUBAGENTS_PER_ROOT": 6,
"OUROBOROS_MAX_SUBAGENT_DEPTH": 3,
# Mutative ("acting") subagents master toggle. Empty = follow runtime mode
# (ON in advanced/pro, OFF in light); explicit true/false overrides. Owner-
# controlled; light-mode self-repo writes stay blocked by the sandbox.
"OUROBOROS_ALLOW_MUTATIVE_SUBAGENTS": "",
# Acting self_worktree base location + durable genesis projects root (both
# outside repo/ and data/). genesis projects are durable and never GC'd.
"OUROBOROS_SUBAGENT_WORKTREE_ROOT": "",
"OUROBOROS_SUBAGENT_PROJECTS_ROOT": "",
"OUROBOROS_DELIVERABLES_ROOT": "",
# Unified age-based GC retention (days) for ALL disposable runtime artifacts:
# subagent worktrees, headless/direct task drives, and leftover service logs.
# Single owner-facing knob (math SSOT in ouroboros/retention.py); deprecated
# per-subsystem keys are migrated to this on settings load.
"OUROBOROS_GC_RETENTION_DAYS": 7,
"TOTAL_BUDGET": 200.0,
"OUROBOROS_PER_TASK_COST_USD": 50.0,
# cloud.ru catalog prices are RUB per 1M while the budget is USD. No implicit
# exchange rate: the owner must explicitly configure the divisor.
"OUROBOROS_RUB_USD_RATE": "",
# Live-pricing (OpenRouter + cloud.ru catalog) refetch interval; prices/FX drift.
"OUROBOROS_PRICING_TTL_SEC": 21600,
# Main-loop round ceiling (was an inline literal in loop.py — hot-reloadable now).
"OUROBOROS_MAX_ROUNDS": 200,
# Same-model attempt budget for TRANSIENT provider failure classes
# (finish_reason=null, 429/5xx/overloaded); floored at the caller's base
# retry budget. Permanent classes fail fast regardless.
"OUROBOROS_TRANSIENT_RETRY_MAX": 6,
# #4 self-DoS guard: max concurrent provider calls per (model, use_local) route; excess
# worker threads wait (deadline-bounded) instead of storming one model's rate limit. <=0
# disables. Default-on, fail-soft (see ouroboros/model_concurrency.py).
"OUROBOROS_MODEL_MAX_CONCURRENCY": 3,
# Hard ceiling (seconds) a provider call waits for a concurrency slot when the task has
# NO deadline; past it the call proceeds WITHOUT a slot (never blocks forever). SSOT here.
"OUROBOROS_MODEL_SLOT_MAX_WAIT_SEC": 180,
# Project-naming LIGHT-call waits (v6.40): the provider-call transport timeout and the
# gateway's hard wait for the inline turn-into-project name. SSOT here (not magic numbers
# in project_naming.py) per DEVELOPMENT "Timeout & Wait Control".
"OUROBOROS_PROJECT_NAMING_TIMEOUT_SEC": 60,
"OUROBOROS_PROJECT_NAMING_ASYNC_TIMEOUT_SEC": 8,
# Skill lifecycle lane deadline (wedged-job loud-failure bound).
"OUROBOROS_SKILL_LIFECYCLE_TIMEOUT_SEC": 1800,
"OUROBOROS_CLAUDEXOR_HARNESS_INSTALL_TIMEOUT_SEC": 300,
"OUROBOROS_SOFT_TIMEOUT_SEC": 600,
# NOTE: OUROBOROS_HARD_TIMEOUT_SEC no longer terminates tasks — the flat wall-clock
# kill was replaced by the activity model below (idle + subtree-liveness, abs ceiling).
# It survives only as a soft-warning/status display input; runtime is governed by
# OUROBOROS_TASK_IDLE_TIMEOUT_SEC and OUROBOROS_TASK_ABS_CEILING_SEC.
"OUROBOROS_HARD_TIMEOUT_SEC": 1800,
# Activity-based liveness (replaces flat wall-clock as the primary stop):
# idle window = no real progress AND no progressing subtree; abs ceiling = the
# unconditional per-task backstop (budget/cost stays a separate hard axis).
"OUROBOROS_TASK_IDLE_TIMEOUT_SEC": 900,
"OUROBOROS_TASK_ABS_CEILING_SEC": 21600,
"OUROBOROS_PER_CALL_TIMEOUT_CEILING_SEC": 1800,
"OUROBOROS_FINALIZATION_GRACE_SEC": FINALIZATION_GRACE_DEFAULT_SEC,
"OUROBOROS_SUPERVISOR_LIVENESS_DEADLINE_SEC": SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC,
"OUROBOROS_PACING_INTERVAL_SEC": PACING_INTERVAL_DEFAULT_SEC,
"OUROBOROS_TOOL_TIMEOUT_SEC": 600,
"OUROBOROS_VISION_CAPTION_TIMEOUT_SEC": 90,
"OUROBOROS_BG_MAX_ROUNDS": 10,
"OUROBOROS_BG_WAKEUP_MIN": 30,
"OUROBOROS_BG_WAKEUP_MAX": 7200,
# Post-task self-evolution envelope (V4). Owner-enabled capability whose
# CONTENT stays LLM-first; default OFF. When enabled, after a qualifying task
# the worker may promote one high-value code-class backlog item into the
# existing (gated) evolution campaign. Cadence: off | llm | every_n:<k>.
"OUROBOROS_POST_TASK_EVOLUTION": "false",
"OUROBOROS_POST_TASK_EVOLUTION_CADENCE": "llm",
"OUROBOROS_POST_TASK_EVOLUTION_BUDGET_USD": 0.0,
# Optional owner steer appended to each evolution cycle's objective (never
# overrides the LLM-first promotion). Empty = pure LLM choice.
"OUROBOROS_EVOLUTION_PERSISTENT_OBJECTIVE": "",
"OUROBOROS_WEBSEARCH_MODEL": "gpt-5.2",
# web_search backend pin: auto (default OpenAI-first cascade) | ddgs (pure
# retrieval, no second LLM — for fixed-model runs) | openai | openrouter | anthropic.
"OUROBOROS_WEBSEARCH_BACKEND": "auto",
# Main-loop OpenRouter server web-search tool. Off by default: provider-
# specific capability, not a core provider-independence requirement.
"OUROBOROS_MAIN_WEB_SEARCH": "off",
"OUROBOROS_MAIN_WEB_SEARCH_ENGINE": "auto",
"OUROBOROS_MAIN_WEB_SEARCH_MAX_TOTAL_RESULTS": 10,
# OpenRouter provider routing: "" (off) | resilience (same-model failover, cache-warm)
# | repro (pin, no failover — fixed-model runs) | a raw JSON `provider` object.
"OUROBOROS_OR_PROVIDER": "",
# search_code total wall-clock budget (seconds) bounding the rg walk + the fallback walk.
"OUROBOROS_SEARCH_CODE_WALL_SEC": "45",
# NOTE: OUROBOROS_OBSERVABILITY_KEEP_RAW (writes UNREDACTED secret-bearing payloads to
# disk) is intentionally NOT a settings/UI carrier — it is an env-only operator debug
# override so a self-change or non-owner save can never enable secret logging.
# Generative context-window probe machinery: when enabled AND a caller passes
# allow_generative=True, confirms a route's >=1M window from a FREE over-window
# reject; *_CHARS sizes the padding. Since the settings-time Max gate retirement
# no production surface passes allow_generative=True (dormant; kept for tests
# and future explicit owner probes).
"OUROBOROS_GENERATIVE_PROBE": "1",
"OUROBOROS_GENERATIVE_PROBE_CHARS": "5000000",
# Pre-commit review: comma-separated provider-tagged model list
"OUROBOROS_REVIEW_MODELS": ",".join(OPENROUTER_REVIEW_DEFAULTS["triad"]),
"OUROBOROS_REVIEWER_SLOTS": "", # structured slot SSOT (reviewer_slot_config.py); "" = legacy comma keys
"OUROBOROS_SUBAGENTS": "", # configured task-actor SSOT; "" = bounded legacy/undecided read
# INSTALL-TIME facts: the agent-preset generation this install received, and WHEN onboarding last completed
# (recorded on EVERY completion). Endpoint-authored and disk-only — see ENDPOINT_AUTHORED_SETTINGS.
"OUROBOROS_SUBSCRIPTION_PRESET_VERSION": "",
"OUROBOROS_SUBAGENT_PRESET_RECEIPT": "",
"OUROBOROS_ONBOARDING_COMPLETED_AT": "",
# Pre-commit review enforcement: advisory | blocking
"OUROBOROS_REVIEW_ENFORCEMENT": "advisory",
# Native tool-round reviewer episode caps (review_native_episode.py owns
# the getters); both fail CLOSED — typed refusal, never compaction/resume.
"OUROBOROS_REVIEW_NATIVE_MAX_ROUNDS": "16",
"OUROBOROS_REVIEW_NATIVE_MAX_TRANSCRIPT_CHARS": "900000",
# Auto-grant reviewed-skill requests by default; grants stay bound to the
# reviewed content hash and editing a skill still invalidates them.
"OUROBOROS_AUTO_GRANT_REVIEWED_SKILLS": "true",
# Launcher-seeded native skills carry a hash-pinned native-trust review
# verdict (the payload bytes shipped through the repo commit gate); the
# zero-grant ones also auto-enable. Editing the payload still goes stale.
# Owner opt-out: set to false to keep manual review for native seeds.
"OUROBOROS_TRUST_NATIVE_SEEDED_SKILLS": "true",
# Agent-requested restarts drain running tasks first: while any RUNNING
# task still heartbeats, the restart waits up to this many seconds before
# proceeding fail-closed (0 = no drain, restart immediately).
"OUROBOROS_RESTART_DRAIN_MAX_SEC": 120,
# Runtime mode: light | advanced | pro; pro still requires review gates.
"OUROBOROS_RUNTIME_MODE": "advanced",
# Context mode: low | max. Owner-only working-context size profile. max = full always-on docs +
# current memory granularity; low = ARCHITECTURE as a navigation map + deeper memory consolidation,
# sized for ~200k / local models. Cognitive-horizon knob (BIBLE P1): the agent cannot lower it
# (owner-only), and it never changes model / reasoning-effort / output-token budgets.
"OUROBOROS_CONTEXT_MODE": "max",
# One-window compatibility tombstone for the retired persistent auto-Low mechanism.
# It never sizes or routes context and no runtime writer may set it true. An explicit
# false still distinguishes owner-authored Low from a bare forwarded env Low for P3.
"OUROBOROS_CONTEXT_MODE_AUTO_LOW": "false",
# Optional extra user-managed skills checkout; Ouroboros never clones/pulls it.
"OUROBOROS_SKILLS_REPO_PATH": "",
"OUROBOROS_CLAWHUB_REGISTRY_URL": "https://clawhub.ai/api/v1",
"OUROBOROS_HUB_CATALOG_URL": "https://raw.githubusercontent.com/razzant/OuroborosHub/main/catalog.json",
"MCP_ENABLED": False,
"MCP_SERVERS": [],
"MCP_TOOL_TIMEOUT_SEC": 60,
# Scope review: one or more reviewer slots; enforcement follows OUROBOROS_REVIEW_ENFORCEMENT.
"OUROBOROS_SCOPE_REVIEW_MODELS": ",".join(OPENROUTER_REVIEW_DEFAULTS["scope"]),
"OUROBOROS_SCOPE_REVIEW_MODEL": OPENROUTER_REVIEW_DEFAULTS["scope"][0],
# DEPRECATED, enforcement-inert (v6.80.0): stored, owner-only (dedicated audited endpoint), but
# NOTHING consults it — whether the BIBLE P3 blocking scope review applies follows owner-only
# OUROBOROS_CONTEXT_MODE. Degraded opt-in key: removed.
"OUROBOROS_SCOPE_REVIEW_FLOOR": "blocking_1m",
"OUROBOROS_TASK_REVIEW_MODE": "auto",
# LLM safety-supervisor coverage (owner-only, like runtime/context mode):
# full (shipped default; fail-closed fallbacks land here; a FRESH wizard authors
# "light") — LLM check on POLICY_CHECK + conditional shell.
# light — LLM check ONLY on POLICY_CHECK integration tools; POLICY_CHECK_CONDITIONAL
# shell/verify fall to the deterministic whitelist + registry guards (no LLM).
# off — no LLM safety calls at all; the deterministic registry sandbox, protected-path
# policy and light-mode guards STAY ON. Every non-full mode audits durably.
"OUROBOROS_SAFETY_MODE": "full",
# Safety-supervisor LLM call shaping (v6.54.3 parse-bug fix): a tight output
# budget + no reasoning keeps the light model from spending its whole budget on
# hidden reasoning and returning a 1-token/empty body that fails JSON parse and
# then fail-closed blocks a benign command. Registered numeric SSOT (no inline literals).
"OUROBOROS_SAFETY_MAX_TOKENS": 2000,
"OUROBOROS_SAFETY_CALL_TIMEOUT_SEC": 60,
# v6.54.3 transport-timeout SSOT (deadline package D). web_search: 480 is one
# provider-attempt bound; the ToolEntry envelope derives the configured paid
# cascade. LLM no_proxy: 2700 leaves room for long silent reasoning without
# pinning a worker on a dead socket.
"OUROBOROS_WEBSEARCH_TIMEOUT_SEC": 480,
"OUROBOROS_LLM_TRANSPORT_READ_TIMEOUT_SEC": 2700,
# v6.54.3 (1.5): plan_task deadline scaling. With a task deadline the planning swarm's
# wait ceiling is min(configured ceiling, remaining/4); below this floor plan_task SKIPS
# with a typed reason + telemetry rather than eat the tail of the budget.
"OUROBOROS_PLAN_TASK_DEADLINE_MIN_SEC": 300,
# Acceptance-review budget layer (task_pacing SSOT). The first final review
# reserves at least 200s; later passes use max(this floor, 1.5×timing EWMA).
"OUROBOROS_ACCEPTANCE_REVIEW_EST_SEC": 200,
# Shared paid-review-cycle cap (SSOT + per-gate meaning: ouroboros/review_cycles.py):
# STRING "N"|"unlimited": plan review, acceptance (passes = cycles - 1), commit gate and skill review (paid cycles per root task / manual snapshot); identical material is never re-reviewed for pay on any gate.
"OUROBOROS_REVIEW_MAX_CYCLES": "2",
"OUROBOROS_ACCEPTANCE_RESERVE_PCT": 5,
# Prompt-cache TTL, one honest GLOBAL override (owner decision 2026-08-08, batch #2 Q2=A): applied to
# EVERY cache_control breakpoint on the Anthropic-normalizing family — main loop, review lanes, safety
# supervisor alike — at the ONE send-time finalizer (llm._normalize_payload_cache_ttl). 'default' = bare
# markers (provider default 5m tier); '5m'/'1h' = the explicit Anthropic ephemeral tiers ('1h' bills cache
# writes at the documented 2x-vs-1.25x ratio). Non-Anthropic wire formats are a NO-OP by construction
# (Gemini documents no ttl field — the v5.30.0 outage class).
"OUROBOROS_PROMPT_CACHE_TTL": "1h",
# Reasoning effort per task type: none | low | medium | high
"OUROBOROS_EFFORT_TASK": "medium",
"OUROBOROS_EFFORT_EVOLUTION": "high",
"OUROBOROS_EFFORT_REVIEW": "high",
"OUROBOROS_EFFORT_SCOPE_REVIEW": "high",
"OUROBOROS_EFFORT_DEEP_SELF_REVIEW": "high",
"OUROBOROS_EFFORT_CONSCIOUSNESS": "high",
"OUROBOROS_RETURN_REASONING": True,
"OUROBOROS_REASONING_SUMMARY": "auto",
"GITHUB_TOKEN": "",
"GITHUB_REPO": "",
# Local model (llama-cpp-python server)
"LOCAL_MODEL_SOURCE": "",
"LOCAL_MODEL_FILENAME": "",
"LOCAL_MODEL_PORT": 8766,
"LOCAL_MODEL_N_GPU_LAYERS": 0,
"LOCAL_MODEL_CONTEXT_LENGTH": 16384,
"LOCAL_MODEL_CHAT_FORMAT": "",
"USE_LOCAL_MAIN": False,
"USE_LOCAL_HEAVY": False,
"USE_LOCAL_LIGHT": False,
"USE_LOCAL_CONSCIOUSNESS": False,
"USE_LOCAL_FALLBACK": False,
"OUROBOROS_FILE_BROWSER_DEFAULT": "",
# 429-aware cross-model fallback: process-local cooldown for transiently failing
# models (429/5xx/overloaded), passive heal-back. Owner-tunable; default-on, fail-soft.
"OUROBOROS_FALLBACK_COOLDOWN_ENABLED": True,
"OUROBOROS_FALLBACK_COOLDOWN_SEC": 120,
"OUROBOROS_FALLBACK_ATTEMPTS_PER_MODEL": 1,
# Delegated subagents. NARROW key, read ONLY by the subagent scheduler; deliberately absent from
# provider_models.MODEL_SETTING_KEYS (see ARCHITECTURE "Delegated subagents"). Empty = delegation off AND
# undecided (Settings' Subagents section offers the connected-subscription default); the literal `off` =
# delegation off because the owner said so. Wait keys bound the nanny's QUIET wait only.
"OUROBOROS_SUBAGENT_HARNESS": "",
# Optional Delegation account pin (D-U5): a credential-profile id sent as `credentialProfileId`; empty = engine
# rotation pool (D28; presets never author it). Read ONLY by get_subagent_harness -> DelegationRoute.profile_id.
"OUROBOROS_SUBAGENT_PROFILE": "",
"OUROBOROS_DELEGATE_WAIT_SEC": 120,
"OUROBOROS_DELEGATE_WAIT_MAX_SEC": 1800,
}
# Setting keys a release DELETED. `load_settings` keeps unrecognized keys so a rename never destroys
# an owner customization — which would otherwise leave a removed key living in data/settings.json
# forever, still served by GET /api/settings. Retiring a key is a decision; its ghost is not.
RETIRED_SETTING_KEYS: tuple[str, ...] = (
# v6.87.7: the depth cap conflated how DEEP delegation nests with how STRONG a descendant is.
"OUROBOROS_SUBAGENT_CAPABILITY_DEPTH_LIMIT",
# knobs are retired (the review-cycle cap OUROBOROS_REVIEW_MAX_CYCLES bounds plan review).
"OUROBOROS_ACCEPTANCE_MAX_IMPROVEMENT_PASSES",
"OUROBOROS_PLAN_TASK_SWARM_TIMEOUT_SEC",
"OUROBOROS_PLAN_TASK_SWARM_MAX_WAIT_SEC",
"OUROBOROS_PLAN_TASK_SWARM_HEARTBEAT_STALE_SEC",
)
# The same keys from the other side: load_settings overlays env onto disk-ABSENT keys, so without this an
# ordinary load->save round-trip in a process whose env says low/off would launder that value onto disk
# unauthorised — or, once the guard reads disk, raise a PermissionError nobody authored. Owner endpoints
# write BOTH disk and env, so the owner path is unaffected.
_DISK_AUTHORED_SETTINGS = ("OUROBOROS_CONTEXT_MODE", "OUROBOROS_CONTEXT_MODE_AUTO_LOW", "OUROBOROS_SAFETY_MODE")
# ENDPOINT-AUTHORED, DISK-ONLY: install-time facts POST /api/onboarding/complete alone writes. The ratchets above are
# disk-authored yet DO project once the file carries them; these never leave disk in EITHER direction — an env timestamp alone closed the onboarding window on a fresh install, and an env marker was then persisted by a save.
ENDPOINT_AUTHORED_SETTINGS = frozenset({"OUROBOROS_SUBSCRIPTION_PRESET_VERSION", "OUROBOROS_SUBAGENT_PRESET_RECEIPT", "OUROBOROS_ONBOARDING_COMPLETED_AT"})
# Settings keys deliberately NOT projected into the environment. Everything else in SETTINGS_DEFAULTS IS
# exported, by derivation rather than a parallel hand-kept list: such a list drifts silently and the failure
# is invisible — settings accept the key, the UI shows it saved, and the consumer goes on reading os.environ
# and falling back to its hardcoded constant (OUROBOROS_SKILL_LIFECYCLE_TIMEOUT_SEC sat like that behind a
# hardcoded 1800). Deriving makes export the DEFAULT for a new key and an exclusion a decision written here.
SETTINGS_KEYS_NOT_EXPORTED_TO_ENV = frozenset({
# Structured list value: `str(value)` is a Python repr no reader parses back, and every consumer already reads
# it from the settings dict (mcp_client.parse_servers, gateway.mcp), never from the environment.
"MCP_SERVERS",
# ENV IS THE AUTHORITY for the bind host, not settings. `ouroboros server --host 0.0.0.0` puts the choice in
# the environment, and both consumers (server.main, server_control.restart_current_process) deliberately read
# env BEFORE settings. Exporting this key stamped the settings value — usually the shipped 127.0.0.1 default,
# which no owner authored — back over that environment, so the operator's LAN-reachable server silently became
# loopback at the first self-restart. A default standing in for an absent key is not a decision.
"OUROBOROS_SERVER_HOST",
}) | ENDPOINT_AUTHORED_SETTINGS # disk-only in BOTH directions (never read from env, never exported to it)
def settings_env_keys() -> list:
"""Settings keys projected into os.environ, derived from SETTINGS_DEFAULTS."""
return [k for k in SETTINGS_DEFAULTS if k not in SETTINGS_KEYS_NOT_EXPORTED_TO_ENV]

View file

@ -0,0 +1,111 @@
"""Ouroboros — the closed scales a settings value is clamped to.
Reasoning effort, prompt-cache tier, runtime mode and safety-supervisor coverage
are ordered or enumerated vocabularies. Each one is defined once here, with the
clamp that turns any caller-supplied or environment-supplied text into a member
of it, so an unknown value can never reach a consumer.
"""
from __future__ import annotations
import os
from typing import Any
from ouroboros.settings_defaults import SETTINGS_DEFAULTS
# v6.57.0 — EFFORT_SCALE: ORDERED reasoning-effort SSOT (low→high), the single place a tier
# is defined (settings, llm.py builder, switch_model enum, subagent lanes). Exact-route
# request-wire recovery, not legacy model-global evidence, owns provider adaptation.
EFFORT_SCALE: tuple[str, ...] = ("none", "minimal", "low", "medium", "high", "xhigh", "max")
def effort_rank(value: str) -> int:
"""Index of an effort in EFFORT_SCALE (−1 if unknown). Strength-ordering SSOT."""
v = str(value or "").strip().lower()
return EFFORT_SCALE.index(v) if v in EFFORT_SCALE else -1
def clamp_effort_to(value: str, ceiling: str) -> str:
"""Clamp ``value`` down to ``ceiling`` on EFFORT_SCALE; unknown inputs pass through."""
vi, ci = effort_rank(value), effort_rank(ceiling)
return ceiling if (vi >= 0 and ci >= 0 and vi > ci) else str(value or "").strip().lower()
def effort_one_step_down(value: str) -> str:
"""Next-lower effort on EFFORT_SCALE (reject-and-retry walk); floors at `none`."""
idx = effort_rank(value)
return EFFORT_SCALE[idx - 1] if idx > 0 else ("none" if idx == 0 else "medium")
def resolve_effort(task_type: str) -> str:
"""Return the configured reasoning effort for the given task type."""
t = (task_type or "").lower().strip()
if t == "evolution":
key = "OUROBOROS_EFFORT_EVOLUTION"
default = "high"
elif t == "review":
key = "OUROBOROS_EFFORT_REVIEW"
default = "high"
elif t == "deep_self_review":
key = "OUROBOROS_EFFORT_DEEP_SELF_REVIEW"
default = "high"
elif t in ("scope_review", "scope-review"):
key = "OUROBOROS_EFFORT_SCOPE_REVIEW"
default = "high"
elif t == "consciousness":
key = "OUROBOROS_EFFORT_CONSCIOUSNESS"
default = "high"
else:
# Legacy INITIAL_REASONING_EFFORT is retired; use EFFORT_TASK.
key = "OUROBOROS_EFFORT_TASK"
default = "medium"
raw = os.environ.get(key, default)
return raw if raw in EFFORT_SCALE else default
# Prompt-cache TTL scale (owner decision 2026-08-08): 'default' = bare markers (provider default tier), '5m'/'1h' =
# the two documented Anthropic ephemeral tiers. Deliberately NO 'auto' (dead until an adaptive design exists) and NO '24h' (Anthropic would clamp it — a value that mostly lies).
PROMPT_CACHE_TTL_SCALE: tuple[str, ...] = ("default", "5m", "1h")
def resolve_prompt_cache_ttl() -> str:
"""The owner-configured global prompt-cache TTL ('default' | '5m' | '1h').
Validated like ``resolve_effort``: an unknown value falls back to the shipped default.
Consumed ONLY by the finalizer (``llm.LLMClient._normalize_payload_cache_ttl``), by
``review_helpers.cached_prompt_blocks`` (its marker gets stamped to the same value anyway),
and by ``usage_accounting._reservation_cost`` as the payload-free admission fallback
(payload-carrying sites use the finalizer's applied TTL) — never by per-builder marking
sites (docs/DEVELOPMENT.md cache-friendliness invariant)."""
default = str(SETTINGS_DEFAULTS["OUROBOROS_PROMPT_CACHE_TTL"])
raw = str(os.environ.get("OUROBOROS_PROMPT_CACHE_TTL", default) or "").strip().lower()
return raw if raw in PROMPT_CACHE_TTL_SCALE else default
# Runtime mode and review enforcement are separate axes.
VALID_RUNTIME_MODES = ("light", "advanced", "pro")
# Lower rank = stricter scope. ``save_settings`` refuses agent self-elevation.
_RUNTIME_MODE_RANK = {"light": 0, "advanced": 1, "pro": 2}
def normalize_runtime_mode(value: Any) -> str:
"""Clamp caller-supplied runtime mode to the canonical closed enum."""
default_val = str(SETTINGS_DEFAULTS["OUROBOROS_RUNTIME_MODE"])
text = str(value or "").strip().lower()
return text if text in VALID_RUNTIME_MODES else default_val
VALID_SAFETY_MODES = ("full", "light", "off")
def normalize_safety_mode(value: Any) -> str:
"""Clamp caller-supplied safety mode to the closed enum (full / light / off)."""
default_val = str(SETTINGS_DEFAULTS["OUROBOROS_SAFETY_MODE"])
text = str(value or "").strip().lower()
return text if text in VALID_SAFETY_MODES else default_val
_SAFETY_MODE_RANK = {"full": 2, "light": 1, "off": 0}

View file

@ -0,0 +1,218 @@
"""Structural contracts for the semantic-no-op settings-configuration extraction."""
from __future__ import annotations
import ast
import pathlib
from ouroboros import (
config,
model_slots,
provider_models,
review_model_routes,
runtime_limits,
settings_defaults,
settings_scales,
)
REPO = pathlib.Path(__file__).parents[1]
PACKAGE = REPO / "ouroboros"
_LEAVES = (settings_defaults, settings_scales, model_slots, review_model_routes, runtime_limits)
_MOVED_OWNERS = {
"ENDPOINT_AUTHORED_SETTINGS": settings_defaults,
# v6.104.0 upstream: the OpenRouter shipped-model defaults arrive in the
# vocabulary leaf the v7 split created for exactly this class of fact.
"OPENROUTER_DEFAULTS": settings_defaults,
"OPENROUTER_REVIEW_DEFAULTS": settings_defaults,
"FINALIZATION_GRACE_DEFAULT_SEC": settings_defaults,
"OWNER_STOP_OUTER_CAP_SEC": settings_defaults,
"PACING_INTERVAL_DEFAULT_SEC": settings_defaults,
"RETIRED_SETTING_KEYS": settings_defaults,
"SETTINGS_DEFAULTS": settings_defaults,
"SETTINGS_KEYS_NOT_EXPORTED_TO_ENV": settings_defaults,
"SUPERVISOR_LIVENESS_DEADLINE_DEFAULT_SEC": settings_defaults,
"_DISK_AUTHORED_SETTINGS": settings_defaults,
"settings_env_keys": settings_defaults,
"EFFORT_SCALE": settings_scales,
"PROMPT_CACHE_TTL_SCALE": settings_scales,
"VALID_RUNTIME_MODES": settings_scales,
"VALID_SAFETY_MODES": settings_scales,
"_RUNTIME_MODE_RANK": settings_scales,
"_SAFETY_MODE_RANK": settings_scales,
"clamp_effort_to": settings_scales,
"effort_one_step_down": settings_scales,
"effort_rank": settings_scales,
"normalize_runtime_mode": settings_scales,
"normalize_safety_mode": settings_scales,
"resolve_effort": settings_scales,
"resolve_prompt_cache_ttl": settings_scales,
"_LEGACY_SLOT_RENAMES": model_slots,
"_main_model": model_slots,
"_parse_model_list": model_slots,
"get_consciousness_model": model_slots,
"get_deep_self_review_model": model_slots,
"get_fallback_models": model_slots,
"get_heavy_model": model_slots,
"get_image_input_mode": model_slots,
"get_light_model": model_slots,
"get_vision_model": model_slots,
"migrate_legacy_slot_keys": model_slots,
"parse_fallback_chain": model_slots,
"_DIRECT_PROVIDER_REVIEW_RUNS": review_model_routes,
"_exclusive_direct_remote_provider_env": review_model_routes,
"adaptive_quorum": review_model_routes,
"direct_provider_review_models_fallback": review_model_routes,
"get_review_enforcement": review_model_routes,
"get_review_models": review_model_routes,
"get_scope_review_models": review_model_routes,
"DELEGATE_WAIT_CEILING_SEC": runtime_limits,
"DELEGATE_WAIT_WINDOW_MAX_SEC": runtime_limits,
"MAX_ACTIVE_SUBAGENTS_HARD_CAP": runtime_limits,
# Upstream reshaped the hard-cap assignment into a tuple that also binds the
# nesting ceiling (`MAX_ACTIVE_..., MAX_SUBAGENT_DEPTH_... = 500, 10`), so the
# unrowed twin rides the rowed statement into the same owner leaf.
"MAX_SUBAGENT_DEPTH_HARD_CAP": runtime_limits,
"_bounded_positive_int_setting": runtime_limits,
"_clamped_number_setting": runtime_limits,
"get_acceptance_reserve_pct": runtime_limits,
"get_acceptance_review_est_sec": runtime_limits,
"get_delegate_wait_max_sec": runtime_limits,
"get_delegate_wait_sec": runtime_limits,
"get_llm_transport_read_timeout_sec": runtime_limits,
"get_max_active_subagents_per_root": runtime_limits,
"get_max_subagent_depth": runtime_limits,
"get_max_workers": runtime_limits,
"get_pacing_interval_sec": runtime_limits,
"get_per_call_timeout_ceiling_sec": runtime_limits,
"get_plan_task_deadline_min_sec": runtime_limits,
"get_post_task_evolution_budget_usd": runtime_limits,
"get_restart_drain_max_sec": runtime_limits,
"get_safety_call_timeout_sec": runtime_limits,
"get_safety_max_tokens": runtime_limits,
"get_search_code_wall_sec": runtime_limits,
"get_supervisor_liveness_deadline_sec": runtime_limits,
"get_task_abs_ceiling_sec": runtime_limits,
"get_task_idle_timeout_sec": runtime_limits,
"get_vision_caption_timeout_sec": runtime_limits,
"get_websearch_timeout_sec": runtime_limits,
}
# The settings-file lifecycle, the path roots and the owner-only ratchets stay with the
# parent: every one of them reads or writes ``config.SETTINGS_PATH``/``config.DATA_DIR``,
# or the in-process boot runtime-mode pin, which a leaf could only see through a
# back-edge into its own parent.
_PARENT_RETAINED = (
"SETTINGS_PATH", "DATA_DIR", "APP_ROOT", "REPO_DIR", "PID_FILE", "PORT_FILE", "HOME",
"_BOOT_RUNTIME_MODE", "_guard_live_settings_write", "_settings_file_value",
"_settings_flag_enabled", "_settings_lock_path", "_acquire_settings_lock",
"_release_settings_lock", "_coerce_setting_value", "load_settings",
"load_settings_lock_held", "save_settings", "prepare_settings_for_persist",
"apply_settings_to_env", "get_runtime_mode", "get_safety_mode", "get_context_mode",
"get_owner_context_mode", "initialize_runtime_mode_baseline",
"_guard_context_mode_lowering", "_guard_safety_mode_lowering",
)
def _top_level_names(path: pathlib.Path) -> set[str]:
names: set[str] = set()
for node in ast.parse(path.read_text(encoding="utf-8")).body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
names.add(node.name)
elif isinstance(node, ast.Assign):
names.update(t.id for t in node.targets if isinstance(t, ast.Name))
# Upstream binds the two subagent hard caps in one tuple statement.
for target in node.targets:
if isinstance(target, ast.Tuple):
names.update(e.id for e in target.elts if isinstance(e, ast.Name))
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
names.add(node.target.id)
return names
def test_settings_leaves_never_import_their_parent():
for module in _LEAVES:
tree = ast.parse(pathlib.Path(module.__file__).read_text(encoding="utf-8"))
assert not any(
isinstance(node, ast.ImportFrom) and node.module == "ouroboros.config"
for node in ast.walk(tree)
), module.__name__
assert not any(
isinstance(node, ast.Import)
and any(alias.name == "ouroboros.config" for alias in node.names)
for node in ast.walk(tree)
), module.__name__
def test_provider_models_reads_the_shared_defaults_leaf():
"""The shipped router profiles moved below ``provider_models``, which now
re-exports them from the settings-vocabulary leaf at import time — no lazy
config import is needed for the literals, and the historical identities
(``provider_models.OPENROUTER_DEFAULTS``) are the leaf's own objects.
NOTE (v7next D12 lane): the reference goes further — no ``ouroboros.config``
import anywhere in ``provider_models`` and a top-level ``ouroboros.model_slots``
import replacing the call-time fallback-chain import. Those clauses type
against the reference's D02 rework of ``provider_models`` and return with the
D02 lane; this tree keeps the upstream call-time imports, which resolve
through the config facade to the same leaf objects."""
tree = ast.parse(pathlib.Path(provider_models.__file__).read_text(encoding="utf-8"))
top_level = {
node.module
for node in tree.body
if isinstance(node, ast.ImportFrom) and node.module
}
assert "ouroboros.settings_defaults" in top_level
# No module-level (import-time) config read: the remaining config imports in
# provider_models are call-time only, so the leaf split cannot deadlock boot.
assert not any(
isinstance(node, ast.ImportFrom) and node.module == "ouroboros.config"
for node in tree.body
)
assert provider_models.OPENROUTER_DEFAULTS is settings_defaults.OPENROUTER_DEFAULTS
assert (provider_models.OPENROUTER_REVIEW_DEFAULTS
is settings_defaults.OPENROUTER_REVIEW_DEFAULTS)
def test_config_facade_reexports_every_moved_identity():
"""``config`` keeps the exact objects, so every existing importer and every
``monkeypatch.setattr(config, ...)`` consumer sees no identity change."""
for name, owner in _MOVED_OWNERS.items():
assert hasattr(config, name), name
assert getattr(config, name) is getattr(owner, name), name
owned = {name for module in _LEAVES for name in vars(module)}
assert set(_MOVED_OWNERS) <= owned
def test_settings_file_lifecycle_and_path_roots_stay_with_the_parent():
parent_names = _top_level_names(pathlib.Path(config.__file__))
assert set(_PARENT_RETAINED) <= parent_names
for module in _LEAVES:
leaf_names = _top_level_names(pathlib.Path(module.__file__))
assert not (leaf_names & set(_PARENT_RETAINED)), module.__name__
def test_settings_extraction_owner_inventory_is_exact():
"""Every moved name is owned by exactly one leaf, and no leaf grew a name the
parent never had (a new symbol would be a redesign, not an extraction)."""
seen: dict[str, str] = {}
for module in _LEAVES:
for name in _top_level_names(pathlib.Path(module.__file__)):
assert name not in seen, f"{name} owned by {seen.get(name)} and {module.__name__}"
seen[name] = module.__name__
assert name in _MOVED_OWNERS, f"{module.__name__} owns an unmapped name: {name}"
assert set(seen) == set(_MOVED_OWNERS)
def test_settings_extraction_size_bounds_have_meaningful_headroom():
counts = {
module.__name__: len(
pathlib.Path(module.__file__).read_text(encoding="utf-8").splitlines()
)
for module in (config, *_LEAVES)
}
assert counts["ouroboros.config"] <= 1000
assert all(count <= 1000 for count in counts.values())
assert 250 <= counts["ouroboros.settings_defaults"] <= 500
assert (PACKAGE / "config.py").is_file()

View file

@ -313,37 +313,45 @@ def test_the_launcher_never_authors_settings_during_first_run(monkeypatch, tmp_p
assert not (tmp_path / "settings.json").exists()
def test_pre_server_normalization_never_creates_the_settings_file(monkeypatch, tmp_path):
"""The launcher normalizes provider defaults before starting the server, but
on a FRESH install it must not persist them: creating settings.json here
would destroy the freshness every install-time proof is gated on."""
def test_pre_server_normalization_never_writes_the_settings_file(monkeypatch, tmp_path):
"""The launcher normalizes provider defaults before starting the server and
persists NONE of it — on a fresh install OR on an existing one.
The fresh-install half was always the rule (creating settings.json here would
destroy the freshness every install-time proof is gated on); the existing-install
half is the same objection without the carve-out. Startup is a read, and a read
that rewrites the file it read turns a normalization into an owner decision.
Nothing is lost: the normalization is applied to the environment here and
re-derived by every reader, and the completion save persists it."""
from ouroboros import config as cfg
from ouroboros import launcher_onboarding
monkeypatch.setattr(cfg, "SETTINGS_PATH", tmp_path / "settings.json")
monkeypatch.setattr(cfg, "DATA_DIR", tmp_path)
monkeypatch.setattr(launcher_onboarding, "load_settings", lambda: {})
monkeypatch.setattr(launcher_onboarding, "_apply_settings_to_env", lambda settings: None)
applied: list = []
monkeypatch.setattr(launcher_onboarding, "_apply_settings_to_env", applied.append)
monkeypatch.setattr(
launcher_onboarding,
"apply_runtime_provider_defaults",
lambda settings: (dict(settings), True, ["OUROBOROS_MODEL_LIGHT"]),
)
saved: list = []
monkeypatch.setattr(
launcher_onboarding, "save_settings", lambda settings, **kwargs: saved.append(settings)
assert not hasattr(launcher_onboarding, "save_settings"), (
"the launcher bound a settings writer again"
)
_settings, onboarding_required = launcher_onboarding.prepare_first_run_settings()
assert onboarding_required is True
assert saved == []
assert len(applied) == 1, "the normalization must still reach the environment"
assert not (tmp_path / "settings.json").exists()
# An install that ALREADY has a settings file still persists normalization.
# An install that ALREADY has a settings file is not a licence to rewrite it.
(tmp_path / "settings.json").write_text("{}", encoding="utf-8")
before = (tmp_path / "settings.json").read_bytes()
launcher_onboarding.prepare_first_run_settings()
assert len(saved) == 1
assert (tmp_path / "settings.json").read_bytes() == before
assert len(applied) == 2
def test_server_boot_normalization_carries_the_same_guard():

View file

@ -1017,22 +1017,19 @@ def test_the_launcher_onboarding_module_authors_no_onboarding_settings():
default. That callback is gone: every host completes through
`POST /api/onboarding/complete`, which authors the default itself.
What is worth pinning now is the inverse — the module keeps ONLY its
pre-server normalization writer and hands the setup window a lifecycle
bridge with no persistence at all. `save_settings` stays bound because
`prepare_first_run_settings` still uses it for an install that already has a
settings file."""
What is worth pinning now is the inverse, and it has since gone all the way:
the module persists NOTHING. Its pre-server normalization is applied to the
process environment and re-derived by every reader, so `save_settings` is no
longer bound at all and the setup window gets a lifecycle bridge with no
persistence."""
from ouroboros import launcher_onboarding
assert callable(getattr(launcher_onboarding, "save_settings", None))
assert getattr(launcher_onboarding, "save_settings", None) is None
source = pathlib.Path(launcher_onboarding.__file__).read_text(encoding="utf-8")
assert "def save_wizard" not in source
assert "onboarding_safety_default" not in source
assert "prepare_onboarding_settings" not in source
# The only remaining write is the pre-server normalization, and only for an
# install whose settings file already exists.
assert source.count("save_settings(") == 1
assert "if provider_defaults_changed and _settings_path.exists():" in source
assert "save_settings(" not in source
def test_wizard_rejects_a_newly_typed_short_key():

View file

@ -488,10 +488,13 @@ def test_global_ttl_docstrings_name_every_consumer():
repo = pathlib.Path(__file__).resolve().parents[1]
call = re.compile(r"resolve_prompt_cache_ttl\(\)")
# The definition site is not a consumer: `settings_scales.py` owns the setting's
# closed scale and `config.py` re-exports it as the settings import surface.
definition_sites = {"config.py", "settings_scales.py"}
consumers = sorted(
p.relative_to(repo).as_posix()
for p in (repo / "ouroboros").rglob("*.py")
if p.name != "config.py" and call.search(p.read_text(encoding="utf-8"))
if p.name not in definition_sites and call.search(p.read_text(encoding="utf-8"))
)
assert consumers == [
"ouroboros/llm.py",

View file

@ -851,12 +851,14 @@ def test_a_fresh_local_first_install_still_authors_safety_light(tmp_path, monkey
assert (changed and settings_path.exists()), "an existing install still persists it"
assert wizard_authors_safety_light() is False
# ...and both pre-onboarding normalizers really implement that decision.
# The server joins the launcher here: it now starts BEFORE first-run
# onboarding, so its own boot normalization could create the file just as
# easily (behavioural coverage: tests/test_onboarding_host.py).
# ...and the pre-onboarding normalizers implement at least that decision.
# The launcher has since gone further and persists nothing at all, which
# satisfies the fresh-install rule by construction rather than by a
# carve-out that has to be got right; the server still carries the guarded
# write and joins it when its own lane lands the retirement (behavioural
# coverage: tests/test_onboarding_host.py).
repo = cfg.pathlib.Path(__file__).parent.parent
launcher_host = (repo / "ouroboros" / "launcher_onboarding.py").read_text(encoding="utf-8")
server_src = (repo / "server.py").read_text(encoding="utf-8")
assert "if provider_defaults_changed and _settings_path.exists():" in launcher_host
assert "save_settings(" not in launcher_host
assert "if provider_defaults_changed and _settings_path.exists():" in server_src

View file

@ -409,6 +409,7 @@ def test_auto_low_source_inventory_has_no_true_writer_or_ghost_reader():
key_paths = {path for path, text in sources.items() if key in text}
assert key_paths == {
"ouroboros/config.py",
"ouroboros/settings_defaults.py",
"ouroboros/context_mode_compat.py",
"ouroboros/gateway/owner_settings.py",
"ouroboros/gateway/settings.py",

View file

@ -0,0 +1,207 @@
"""Which environment values may become file content, and which never may.
Settings travel in both directions: `apply_settings_to_env` projects the document
into `os.environ` so subprocesses inherit it, and `load_settings` overlays the
environment onto keys the file does not mention. That overlay is what lets a
benchmark or an operator forward a value for one run without editing anyone's
settings; it is also how a forwarded value can end up PERSISTED as an owner
decision by an unrelated save. Owner decision (spec 4.3.7, answer A): the current
split stands, and these tests are the record of it.
The split has three parts:
1. **Alias keys are never written.** A key that exists only as backwards
compatibility for the environment (`OUROBOROS_MODEL_FALLBACK`, singular) is
read where it is read and is not part of the settings vocabulary, so no save
can put it on disk under either name.
2. **A canonical env value may be pinned by an EXPLICIT write.** `load_settings`
returns it, and a caller that deliberately saves what it loaded persists it.
That is the owner's escape hatch and stays available.
3. **...except for the keys that are disk-authored, where silence stays silence.**
`_DISK_AUTHORED_SETTINGS` (the two context-mode keys and the safety mode) and
`ENDPOINT_AUTHORED_SETTINGS` (the install-time facts) are ratchet or provenance
surfaces: an environment value there is not an owner decision, so it is neither
read into the document nor projected back out of one the file does not carry.
`_settings_file_value` reads DISK ONLY for the same reason — a ratchet whose
"previous value" came from the environment would let any subprocess open the
gate by exporting the value it wants to move away from.
"""
from __future__ import annotations
import json
import os
import pytest
@pytest.fixture
def isolated_settings(tmp_path, monkeypatch):
from ouroboros import config as cfg
data_dir = tmp_path / "data"
data_dir.mkdir()
settings_path = data_dir / "settings.json"
monkeypatch.setattr(cfg, "DATA_DIR", data_dir, raising=True)
monkeypatch.setattr(cfg, "SETTINGS_PATH", settings_path, raising=True)
for key in cfg.SETTINGS_DEFAULTS:
monkeypatch.delenv(key, raising=False)
monkeypatch.delenv("OUROBOROS_MODEL_FALLBACK", raising=False)
cfg.reset_runtime_mode_baseline_for_tests()
yield settings_path
cfg.reset_runtime_mode_baseline_for_tests()
def test_the_singular_fallback_alias_is_read_from_env_and_never_reaches_disk(
isolated_settings, monkeypatch):
"""The env-only alias: a live benchmark contract on the read side, invisible on
the write side. It is not in the settings vocabulary, so no save can persist it,
and it does not silently become a value for the canonical plural key either."""
from ouroboros import config as cfg
monkeypatch.setenv("OUROBOROS_MODEL_FALLBACK", "bench/only-chain")
assert "OUROBOROS_MODEL_FALLBACK" not in cfg.SETTINGS_DEFAULTS
assert "OUROBOROS_MODEL_FALLBACK" not in cfg.settings_env_keys()
assert cfg.parse_fallback_chain() == ["bench/only-chain"], "the read-side alias is live"
loaded = cfg.load_settings()
assert "OUROBOROS_MODEL_FALLBACK" not in loaded
assert loaded["OUROBOROS_MODEL_FALLBACKS"] == (
cfg.SETTINGS_DEFAULTS["OUROBOROS_MODEL_FALLBACKS"]), "the alias leaked into the slot"
cfg.save_settings(loaded)
stored = json.loads(isolated_settings.read_text(encoding="utf-8"))
assert "OUROBOROS_MODEL_FALLBACK" not in stored
def test_a_forwarded_canonical_value_is_read_and_can_be_pinned_by_an_explicit_write(
isolated_settings, monkeypatch):
"""The owner's escape hatch, and the reason a forwarded value is not simply
ignored: it applies for the run, and a deliberate save makes it durable."""
from ouroboros import config as cfg
monkeypatch.setenv("OUROBOROS_MODEL", "forwarded/main")
monkeypatch.setenv("OUROBOROS_MAX_ROUNDS", "17")
loaded = cfg.load_settings()
assert loaded["OUROBOROS_MODEL"] == "forwarded/main"
assert loaded["OUROBOROS_MAX_ROUNDS"] == 17
assert not isolated_settings.exists(), "reading a forwarded value pinned it"
cfg.save_settings(loaded)
stored = json.loads(isolated_settings.read_text(encoding="utf-8"))
assert stored["OUROBOROS_MODEL"] == "forwarded/main"
assert stored["OUROBOROS_MAX_ROUNDS"] == 17
def test_a_stored_value_wins_over_the_environment_for_an_ordinary_key(
isolated_settings, monkeypatch):
"""The overlay fills SILENCE, it does not override the owner's file."""
from ouroboros import config as cfg
isolated_settings.write_text(json.dumps({"OUROBOROS_MODEL": "owner/choice"}), encoding="utf-8")
monkeypatch.setenv("OUROBOROS_MODEL", "forwarded/main")
assert cfg.load_settings()["OUROBOROS_MODEL"] == "owner/choice"
@pytest.mark.parametrize("key,env_value", [
("OUROBOROS_CONTEXT_MODE", "low"),
("OUROBOROS_CONTEXT_MODE_AUTO_LOW", "false"),
("OUROBOROS_SAFETY_MODE", "off"),
])
def test_a_disk_authored_key_is_never_read_out_of_the_environment(
isolated_settings, monkeypatch, key, env_value):
"""These three are ratchets. An environment value is not authorship, so the
document never picks one up — otherwise an ordinary load/save round-trip in a
process whose environment says low/off would launder that value onto disk."""
from ouroboros import config as cfg
assert key in cfg._DISK_AUTHORED_SETTINGS
monkeypatch.setenv(key, env_value)
loaded = cfg.load_settings()
assert loaded[key] == cfg.SETTINGS_DEFAULTS[key], "an env ratchet value reached the document"
cfg.save_settings(loaded)
stored = json.loads(isolated_settings.read_text(encoding="utf-8"))
assert key not in stored, "silence did not stay silence"
def test_a_disk_authored_key_is_not_projected_back_out_of_a_silent_file(
isolated_settings, monkeypatch):
"""The mirror direction: projecting a default the file never carried would
clobber a legitimately forwarded value (a benchmark runner has no settings.json
at all), so an unauthored key is left exactly as the environment has it."""
from ouroboros import config as cfg
monkeypatch.setenv("OUROBOROS_CONTEXT_MODE", "low")
monkeypatch.setenv("OUROBOROS_SAFETY_MODE", "off")
cfg.apply_settings_to_env(dict(cfg.SETTINGS_DEFAULTS))
assert os.environ["OUROBOROS_CONTEXT_MODE"] == "low"
assert os.environ["OUROBOROS_SAFETY_MODE"] == "off"
# ...and once the FILE carries the key, the file is the authority again.
isolated_settings.write_text(json.dumps({"OUROBOROS_CONTEXT_MODE": "max"}), encoding="utf-8")
cfg.apply_settings_to_env(dict(cfg.SETTINGS_DEFAULTS, OUROBOROS_CONTEXT_MODE="max"))
assert os.environ["OUROBOROS_CONTEXT_MODE"] == "max"
def test_install_time_facts_are_disk_only_in_both_directions(isolated_settings, monkeypatch):
"""`ENDPOINT_AUTHORED_SETTINGS` is stricter than the ratchets: those project
once the file carries them, these never leave disk at all. An environment
timestamp alone once closed the onboarding window on a fresh install."""
from ouroboros import config as cfg
for key in cfg.ENDPOINT_AUTHORED_SETTINGS:
monkeypatch.setenv(key, "2020-01-01T00:00:00Z")
assert key not in cfg.settings_env_keys()
loaded = cfg.load_settings()
for key in cfg.ENDPOINT_AUTHORED_SETTINGS:
assert loaded[key] == cfg.SETTINGS_DEFAULTS[key]
isolated_settings.write_text(
json.dumps({key: "2026-01-01T00:00:00Z" for key in cfg.ENDPOINT_AUTHORED_SETTINGS}),
encoding="utf-8")
cfg.apply_settings_to_env(cfg.load_settings())
for key in cfg.ENDPOINT_AUTHORED_SETTINGS:
assert os.environ[key] == "2020-01-01T00:00:00Z", "a disk-only fact was projected"
def test_the_ratchet_previous_value_is_read_from_disk_only(isolated_settings, monkeypatch):
"""`_settings_file_value` is the ratchet's memory. Reading the environment there
would turn ``max -> low`` into ``low -> low`` and open the gate for any process
that can export a variable."""
from ouroboros import config as cfg
isolated_settings.write_text(json.dumps({"OUROBOROS_CONTEXT_MODE": "max"}), encoding="utf-8")
monkeypatch.setenv("OUROBOROS_CONTEXT_MODE", "low")
assert cfg._settings_file_value("OUROBOROS_CONTEXT_MODE", "max") == "max"
with pytest.raises(PermissionError, match="OUROBOROS_CONTEXT_MODE lowering refused"):
cfg.save_settings({"OUROBOROS_CONTEXT_MODE": "low"})
# A key the file does not carry answers the fail-closed default, never the env.
monkeypatch.setenv("OUROBOROS_SAFETY_MODE", "off")
assert cfg._settings_file_value("OUROBOROS_SAFETY_MODE", "full") == "full"
def test_the_exemption_sets_are_exactly_the_declared_ones():
"""A structural pin so the two exemptions cannot grow or shrink unnoticed: each
is a decision about who may author a value, not a convenience list."""
from ouroboros import config as cfg
assert cfg._DISK_AUTHORED_SETTINGS == (
"OUROBOROS_CONTEXT_MODE", "OUROBOROS_CONTEXT_MODE_AUTO_LOW", "OUROBOROS_SAFETY_MODE")
assert cfg.ENDPOINT_AUTHORED_SETTINGS == frozenset(
{"OUROBOROS_SUBSCRIPTION_PRESET_VERSION", "OUROBOROS_SUBAGENT_PRESET_RECEIPT",
"OUROBOROS_ONBOARDING_COMPLETED_AT"})
assert cfg.ENDPOINT_AUTHORED_SETTINGS <= cfg.SETTINGS_KEYS_NOT_EXPORTED_TO_ENV
# The exported set is DERIVED, never hand-kept: a new key exports by default and
# an exclusion is a decision written into the one list.
assert set(cfg.settings_env_keys()) == (
set(cfg.SETTINGS_DEFAULTS) - set(cfg.SETTINGS_KEYS_NOT_EXPORTED_TO_ENV))