Source code for mlparty.models

"""Core ontology (DESIGN.md §3): fixed spine, extensible ribs.

Nodes serialize to JSON documents; the journal holds them as the source of
truth and the SQLite index stores the same document for querying. Custom node
types fall back to `CustomNode` and inherit every common field.
"""
from __future__ import annotations

from datetime import UTC, datetime
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator

SCHEMA_VERSION = 1

CORE_EDGE_TYPES = frozenset({
    "derives-from", "supersedes", "compares-to", "produces",
    "uses-data", "part-of", "confirms", "refutes", "duplicate-of",
})

SUGGESTED_FAILURE_CLASSES = ("crash", "oom", "diverged", "wrong-result", "env", "cancelled")


def utcnow() -> datetime:
    return datetime.now(UTC)


[docs] class Edge(BaseModel): src: str dst: str type: str note: str | None = None created_by: str = "unknown" created_at: datetime = Field(default_factory=utcnow) @field_validator("type") @classmethod def _controlled_vocabulary(cls, v: str) -> str: if v in CORE_EDGE_TYPES or v.startswith("x-"): return v raise ValueError( f"edge type {v!r} not in controlled vocabulary {sorted(CORE_EDGE_TYPES)}; " "custom edge types must use the 'x-' prefix" )
[docs] class Abstract(BaseModel): purpose: str hypothesis: str method: str | None = None
[docs] class Result(BaseModel): summary: str verdict: Literal["confirmed", "refuted", "inconclusive"] metrics: dict[str, float] = Field(default_factory=dict) metrics_note: str | None = None surprises: str | None = None
[docs] class Failure(BaseModel): what_failed: str failure_class: str | None = None why: str | None = None traceback: str | None = None
class CommitRef(BaseModel): repo: str commit_sha: str tree_sha: str
[docs] class SnapshotReport(BaseModel): included_files: int = 0 included_bytes: int = 0 # What actually went in (capped; included_files keeps the exact count). # Without this the report can say what it left out but not what it took — # the only question that matters when checking for a leak. included: list[str] = Field(default_factory=list) source_mode: str | None = None # 'git' | 'walk' | None (no source captured) excluded: list[str] = Field(default_factory=list) skipped_for_size: list[str] = Field(default_factory=list) redacted_keys: list[str] = Field(default_factory=list) note: str | None = None
class ProjectGitRef(BaseModel): remote: str | None = None head: str | None = None branch: str | None = None dirty: bool | None = None
[docs] class Invocation(BaseModel): argv: list[str] = Field(default_factory=list) cwd: str | None = None entrypoint: str | None = None env: dict[str, str] = Field(default_factory=dict) captured_by: str | None = None # 'client' | 'agent'
[docs] class DataRef(BaseModel): uri: str fingerprint: str | None = None role: str | None = None
class GpuInfo(BaseModel): name: str | None = None vram_mb: int | None = None driver: str | None = None
[docs] class Hardware(BaseModel): host: str | None = None platform: str | None = None cpu: str | None = None ram_gb: float | None = None gpus: list[GpuInfo] = Field(default_factory=list) captured_by: str | None = None # 'start' (launcher's view) | 'attach' (compute host)
[docs] class ArtifactRef(BaseModel): sha256: str size_bytes: int media_type: str | None = None original_path: str note: str | None = None
class Annotation(BaseModel): text: str created_by: str = "unknown" created_at: datetime = Field(default_factory=utcnow) class NodeBase(BaseModel): id: str type: str title: str slug: str = "" tags: list[str] = Field(default_factory=list) annotations: list[Annotation] = Field(default_factory=list) created_by: str = "unknown" created_at: datetime = Field(default_factory=utcnow) updated_at: datetime = Field(default_factory=utcnow) schema_version: int = SCHEMA_VERSION
[docs] class ProjectNode(NodeBase): type: Literal["project"] = "project" description: str | None = None
[docs] class ExperimentNode(NodeBase): type: Literal["experiment"] = "experiment" project_id: str description: str | None = None # cross-run artifacts (experiment-level boards, summary reports) — mirrors # RunNode.artifacts; boards stay artifacts, never nodes (DESIGN.md §11.1) artifacts: list[ArtifactRef] = Field(default_factory=list)
[docs] class NoteNode(NodeBase): type: Literal["note"] = "note" kind: Literal["feedback", "reference", "insight"] = "insight" body: str = ""
[docs] class RunNode(NodeBase): type: Literal["run"] = "run" experiment_id: str status: Literal["open", "finalized", "failed", "abandoned"] = "open" provenance: Literal["live", "retro"] = "live" abstract: Abstract parameters: dict[str, Any] = Field(default_factory=dict) params_hash: str = "" result: Result | None = None reproduce: str | None = None failure: Failure | None = None # repro tuple — auto-captured at run.start for live runs code_ref: CommitRef | None = None snapshot_report: SnapshotReport | None = None project_git: ProjectGitRef | None = None invocation: Invocation | None = None env_lock_ref: str | None = None # runtime env lock, captured by attach() on the compute host (CAS-stored); # env_lock_ref stays the launcher's view inside the snapshot commit env_lock_runtime: ArtifactRef | None = None data_refs: list[DataRef] = Field(default_factory=list) seed: int | None = None hardware: Hardware | None = None started_at: datetime = Field(default_factory=utcnow) ended_at: datetime | None = None metrics_summary: dict[str, float] = Field(default_factory=dict) artifacts: list[ArtifactRef] = Field(default_factory=list)
class CustomNode(NodeBase): model_config = ConfigDict(extra="allow") body: str | None = None _NODE_CLASSES: dict[str, type[NodeBase]] = { "project": ProjectNode, "experiment": ExperimentNode, "run": RunNode, "note": NoteNode, } Node = ProjectNode | ExperimentNode | RunNode | NoteNode | CustomNode def parse_node(data: dict[str, Any]) -> NodeBase: cls = _NODE_CLASSES.get(data.get("type", ""), CustomNode) return cls.model_validate(data) def dump_node(node: NodeBase) -> dict[str, Any]: return node.model_dump(mode="json")