Spaces:
Running on Zero
X04 — Configuration
Spec version: v1.0 Depends on: stdlib only Depended on by: every module
1. Responsibility
Single source of runtime configuration. Loads from disk, validates, and exposes a typed Config object. No module reads environment variables, files, or CLI flags directly — they all read from a Config instance handed to them.
2. File layout
hearthnet/
├── config.py # implementation
└── constants.py # immutable numeric constants (from GLOSSARY.md §Defaults)
config.toml lives at <CONFIG>/config.toml (see GLOSSARY.md for path resolution).
3. The Config object
# hearthnet/config.py
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
@dataclass(frozen=True)
class IdentityConfig:
"""Where keys live, and whether to auto-generate if missing."""
keys_dir: Path # <DATA>/keys
auto_generate: bool = True
@dataclass(frozen=True)
class CommunityConfig:
"""Which community this node belongs to, and where its state lives."""
community_id: Optional[str] = None # None → must run `hearthnet init`
state_dir: Path = Path() # <DATA>/communities/<id>
@dataclass(frozen=True)
class TransportConfig:
host: str = "0.0.0.0"
port: int = 7080
tls_cert: Optional[Path] = None # None → self-signed
tls_key: Optional[Path] = None
@dataclass(frozen=True)
class DiscoveryConfig:
mdns_enabled: bool = True
udp_enabled: bool = True
udp_multicast_group: str = "239.255.42.42"
udp_port: int = 42424
relay_urls: list[str] = field(default_factory=list) # Phase 2
@dataclass(frozen=True)
class BusConfig:
prefer_local: bool = True
local_load_threshold: float = 0.80
@dataclass(frozen=True)
class LlmBackendConfig:
name: str # "llama_cpp" | "ollama" | "lmstudio" | "hf_api" | "anthropic_api"
url: Optional[str] = None
model: Optional[str] = None
api_key_env: Optional[str] = None # env var name; the actual key is never stored in config
@dataclass(frozen=True)
class LlmConfig:
backends: list[LlmBackendConfig] = field(default_factory=list)
@dataclass(frozen=True)
class EmbeddingConfig:
model: str = "BAAI/bge-small-en-v1.5"
device: str = "auto" # "cpu" | "cuda" | "auto"
@dataclass(frozen=True)
class RagConfig:
enabled: bool = True
corpora_dir: Path = Path() # <CACHE>/embeddings
@dataclass(frozen=True)
class FileConfig:
blobs_dir: Path = Path() # <DATA>/blobs
gc_threshold: float = 0.80
@dataclass(frozen=True)
class MarketConfig:
enabled: bool = True
default_ttl_seconds: int = 86400 * 7 # 7 days
max_ttl_seconds: int = 86400 * 30
@dataclass(frozen=True)
class ChatConfig:
enabled: bool = True
store_and_forward: bool = True
@dataclass(frozen=True)
class EmergencyConfig:
probe_targets: list[str] = field(default_factory=lambda: [
"1.1.1.1", "8.8.8.8", "cloudflare.com", "quad9.net"
])
@dataclass(frozen=True)
class UiConfig:
host: str = "127.0.0.1"
port: int = 7860
launch_browser: bool = True
@dataclass(frozen=True)
class ObservabilityConfig:
log_level: str = "info"
log_dir: Path = Path() # <LOG>
metrics_enabled: bool = True
otlp_endpoint: Optional[str] = None # Phase 2
@dataclass(frozen=True)
class Config:
identity: IdentityConfig
community: CommunityConfig
transport: TransportConfig
discovery: DiscoveryConfig
bus: BusConfig
llm: LlmConfig
embedding: EmbeddingConfig
rag: RagConfig
file: FileConfig
market: MarketConfig
chat: ChatConfig
emergency: EmergencyConfig
ui: UiConfig
observability: ObservabilityConfig
4. Public API
load(path: Path | None = None) -> Config
Loads from path if given, otherwise from the platform-standard location. Applies defaults for omitted sections. Validates and returns a frozen Config.
Raises:
ConfigError("invalid_toml")— TOML parse failureConfigError("invalid_field", field=...)— type or value validationConfigError("path_resolution")— XDG resolution failed (e.g. read-only filesystem)
default_config() -> Config
Returns a Config populated entirely from defaults. Used by tests and hearthnet init.
save(config: Config, path: Path | None = None) -> None
Serialises a Config to TOML and writes atomically (write to tempfile, rename). Used by hearthnet init.
resolve_paths(config: Config) -> Config
Resolves empty Path() fields to their canonical XDG locations. Called by load() automatically. Idempotent.
validate(config: Config) -> None
Cross-field validation (e.g. transport port not equal to udp port). Raises ConfigError on failure. Called by load().
ConfigError(Exception)
class ConfigError(Exception):
def __init__(self, code: str, **details):
self.code = code
self.details = details
super().__init__(f"{code}: {details}")
5. Default config.toml
[identity]
auto_generate = true
[community]
# community_id is set by `hearthnet init`
[transport]
host = "0.0.0.0"
port = 7080
[discovery]
mdns_enabled = true
udp_enabled = true
[bus]
prefer_local = true
local_load_threshold = 0.8
[[llm.backends]]
name = "lmstudio"
url = "http://192.168.188.25:1234"
model = "qwen2.5-7b-instruct"
[embedding]
model = "BAAI/bge-small-en-v1.5"
device = "auto"
[rag]
enabled = true
[file]
gc_threshold = 0.8
[market]
enabled = true
default_ttl_seconds = 604800
max_ttl_seconds = 2592000
[chat]
enabled = true
store_and_forward = true
[ui]
host = "127.0.0.1"
port = 7860
launch_browser = true
[observability]
log_level = "info"
metrics_enabled = true
6. Cross-cutting constants
hearthnet/constants.py holds the values from GLOSSARY.md §Defaults. These are NOT configurable. Examples:
MANIFEST_TTL_SECONDS = 30
MANIFEST_REPUBLISH_INTERVAL_SECONDS = 20
EMERGENCY_PROBE_INTERVAL_ONLINE = 10
EMERGENCY_PROBE_INTERVAL_OFFLINE = 2
STREAM_WINDOW_FRAMES = 16
CHUNK_SIZE_BYTES = 262144
HEALTH_WINDOW_CALLS = 100
HEALTH_QUARANTINE_THRESHOLD = 0.5
HEALTH_QUARANTINE_SECONDS = 60
# ... see GLOSSARY.md for the complete list
Rationale for non-configurability: these affect interop. A node tweaking MANIFEST_TTL_SECONDS will desync from the network.
7. Tests
test_default_config_round_trips—save(default_config()); load()returns equal configtest_invalid_toml_raises— malformed TOML →ConfigError("invalid_toml")test_missing_required_field_raises— community section withoutcommunity_idis OK (post-init); but other validations applytest_path_resolution_xdg— empty paths resolve to user_data_dir, etc.test_env_var_substitution—${ENV_VAR}in TOML strings is expanded byload()
8. References
- Constants list: GLOSSARY.md §Defaults
- Used by all modules; this is the universal entry point