Files
ely-eye/backend/src/ely_eye/config.py
T
ZacharyZhang-NY 9145a23f18 Require a token for writes and quarantine what nobody vouches for
Sixteen mutating routes now refuse a caller who cannot present the token the
server started with; queries stay open, because a question is not a write. The
environment variable is ELY_EYE_AUTH_TOKEN, following the repo's env_prefix
convention — the shorter name I reached for first returned 403 where the smoke
expected 401, and the server printed the reason.

Words from a stranger are quarantined on the way in: an HTTP upload is
third_party by definition, and a sentence carrying an instruction-shaped phrase
is quarantined whoever wrote it. A quarantined atom stays findable and stays out
of the compiled context until a second, live, unquarantined source agrees with
its rarest words. Corroboration is computed at read time because it is a
property of the corpus now, not of the moment a memory was written; it is
term-level, and the docstring says so.

forget now erases the source row and the compiled contexts beside the atoms, so
a forgotten memory leaves nothing that can still speak. The semantic cache key
carries a store revision, which fixes a real staleness: a context compiled
before new evidence arrived kept its old quarantine decision forever.

bench.audit --product still pointed at evals.py, which the p1-6 split moved into
backend/src/ely_eye/proofs/. It failed fast, but the anti-self-justification
rule had quietly stopped auditing the product; it now walks the package, and a
test pins that the package exists and holds no re-roll.

Gates: pytest 306 passed; ruff clean; mypy cold cache 24 errors in 9 files, the
recorded baseline, net zero; pyright 0 errors on the 23 backend files and the 2
bench files; go build/vet/test and gofmt clean; npm run build 221.39 kB with
eslint exit 0; bench/filesize_check clean; bench.audit --product clean over 31
harness files; scripts/mcp_write_smoke.sh PASS with the gate live across two
servers and the MCP child.
2026-10-08 23:05:15 -04:00

95 lines
2.8 KiB
Python

from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from typing import Literal
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="ELY_EYE_", env_file=".env", extra="ignore")
home: Path = Field(default_factory=lambda: Path.cwd() / ".ely_eye")
default_holder: str = "local"
auth_token: str | None = None
allowed_origins: list[str] = Field(
default_factory=lambda: ["http://localhost:5173", "http://127.0.0.1:5173"]
)
model_id: str = "Qwen/Qwen3.5-9B"
model_dir: Path | None = None
kv_cache_dtype: str = "auto"
planner_model_id: str = "Qwen/Qwen3.5-0.8B"
embedding_model_id: str = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
embedding_model_dir: Path | None = None
clip_model_dir: Path | None = None
vlm_model_dir: Path | None = None
vlm_max_new_tokens: int = 96
asr_model_dir: Path | None = None
adapter_dir: Path | None = None
runtime_backend: Literal["sglang", "transformers"] = "transformers"
sglang_base_url: str = "http://127.0.0.1:8000/v1"
sglang_api_key: str = "ely-eye-local"
ui_dir: Path | None = None
wsl_distro: str = "Ubuntu"
dense_embeddings_enabled: bool = False
live_context_tokens: int = 262_144
extreme_context_tokens: int = 1_010_000
library_target_tokens: int = 100_000_000
upload_max_bytes: int = 20 * 1024 * 1024 * 1024
video_sample_seconds: float = 5.0
max_text_file_bytes: int = 10 * 1024 * 1024
retrieval_top_k: int = 24
max_new_tokens: int = 512
temperature: float = 0.1
train_max_length: int = 8192
train_gradient_accumulation_steps: int = 8
train_learning_rate: float = 2e-4
@property
def data_dir(self) -> Path:
return self.home / "data"
@property
def object_dir(self) -> Path:
return self.home / "objects"
@property
def cartridge_dir(self) -> Path:
return self.home / "cartridges"
@property
def cache_dir(self) -> Path:
return self.home / "cache"
@property
def training_dir(self) -> Path:
return self.home / "training"
@property
def adapters_dir(self) -> Path:
return self.home / "adapters"
@property
def db_path(self) -> Path:
return self.data_dir / "ely_eye.sqlite"
def ensure_dirs(self) -> None:
for path in (
self.data_dir,
self.object_dir,
self.cartridge_dir,
self.cache_dir,
self.training_dir,
self.adapters_dir,
):
path.mkdir(parents=True, exist_ok=True)
@lru_cache(maxsize=1)
def get_settings() -> Settings:
settings = Settings()
settings.ensure_dirs()
return settings