Skip to content

core.models

core.models

Zone A — model serving (BUILD-SPEC §5, §7).

Two lifecycles kept separate: the model lifecycle (pull/update + the two-slot loader) lives here; the agent lifecycle (the factory + registry) arrives in Phase 5. Nothing is baked into Ollama — personas and params are injected at request time.

TwoSlotLoader dataclass

config instance-attribute

client instance-attribute

registry instance-attribute

last_load_seconds = 0.0 class-attribute instance-attribute

resident_models()

resident_gb()

ensure(name, *, warm=True)

Make name resident, swapping/evicting as the two-slot rules require. Refuses (raises MemoryCeilingError) before touching Ollama if it would breach the ceiling.

ensure_tier(tier, *, warm=True)

ensure_pinned(*, warm=True)

Message

Bases: TypedDict

One chat turn, Ollama chat-API shaped. Deliberately duplicated from core.constitution.Message (structurally identical, so mypy treats them as interchangeable) to keep this client standalone; runtime-identical to the plain dict it replaced.

role instance-attribute

content instance-attribute

OllamaClient dataclass

config instance-attribute

version()

list_models()

Names of models available on disk (pullable -> resident).

ps()

Names of models currently loaded (resident in memory).

load(model, *, num_ctx=None, keep_alive='30m')

Warm a model into memory without generating. An empty /api/generate with keep_alive loads it; num_ctx sets the load-time window (changing it reloads).

unload(model)

Evict a model now (keep_alive=0).

embed(model, inputs, *, keep_alive=None)

Batch-embed inputs. Returns one vector per input, order preserved.

chat(model, messages, *, num_ctx=None, temperature=None, keep_alive=None, think=None)

Single-shot, non-streaming chat. Returns the assistant text.

OllamaError

Bases: RuntimeError

Any failure talking to the local Ollama server.

MemoryCeilingError

Bases: RuntimeError

Raised when a requested load would breach the two-slot / usable-RAM budget (Invariant 8). The scheduler refuses breaching work rather than crashing.

Registry dataclass

config instance-attribute

pinned property

by_name(name)

by_tier(tier)

ModelServer dataclass

config instance-attribute

client instance-attribute

loader instance-attribute

version()

ensure_pinned(*, warm=True)

chat(tier, messages, *, think=None, temperature=None)

get_registry()

build_model_server(config=None)

loader

Two-slot model loader (BUILD-SPEC §5).

The model lifecycle's executor: it loads, swaps, and evicts weights while enforcing the hardware ceiling (Invariant 8). The router decides tier/window; this code does the load — model advises, code acts.

Two slots, never more: * Slot 1 — the pinned tiny model (router + watchdog), kept warm indefinitely. * Slot 2 — a single swappable worker. Loading a worker evicts the prior worker. A stretch model that declares evicts_pinned also evicts the pinned model and runs as the sole resident for its duration (the documented §5 tradeoff).

The ceiling is checked BEFORE any Ollama call, so breaching work is refused, not half-applied. The warm flag lets the eviction/accounting logic be unit-tested without a live server.

TwoSlotLoader dataclass

config instance-attribute
client instance-attribute
registry instance-attribute
last_load_seconds = 0.0 class-attribute instance-attribute
resident_models()
resident_gb()
ensure(name, *, warm=True)

Make name resident, swapping/evicting as the two-slot rules require. Refuses (raises MemoryCeilingError) before touching Ollama if it would breach the ceiling.

ensure_tier(tier, *, warm=True)
ensure_pinned(*, warm=True)

ollama_client

Thin HTTP client for the LOCAL Ollama server (BUILD-SPEC §7).

Stdlib-only by design: the sealed core must not import a network-capable third-party package (CONVENTIONS). urllib is network-capable, but every request here targets the loopback Ollama endpoint, and the egress guard (core.sealing) permits exactly that and blocks everything else. Personas and per-call parameters are injected at REQUEST time via this API — never baked into a Modelfile (CONVENTIONS / BUILD-SPEC §5).

Message

Bases: TypedDict

One chat turn, Ollama chat-API shaped. Deliberately duplicated from core.constitution.Message (structurally identical, so mypy treats them as interchangeable) to keep this client standalone; runtime-identical to the plain dict it replaced.

role instance-attribute
content instance-attribute

OllamaError

Bases: RuntimeError

Any failure talking to the local Ollama server.

OllamaClient dataclass

config instance-attribute
version()
list_models()

Names of models available on disk (pullable -> resident).

ps()

Names of models currently loaded (resident in memory).

load(model, *, num_ctx=None, keep_alive='30m')

Warm a model into memory without generating. An empty /api/generate with keep_alive loads it; num_ctx sets the load-time window (changing it reloads).

unload(model)

Evict a model now (keep_alive=0).

embed(model, inputs, *, keep_alive=None)

Batch-embed inputs. Returns one vector per input, order preserved.

chat(model, messages, *, num_ctx=None, temperature=None, keep_alive=None, think=None)

Single-shot, non-streaming chat. Returns the assistant text.

registry

Model registry + memory-ceiling accounting (BUILD-SPEC §5).

Agents are not models. This is the model lifecycle's reference data: the configured lineup keyed by tier/name, plus the rule for what may be resident at once. The router decides which tier to use; this code only describes and accounts for the weights.

MemoryCeilingError

Bases: RuntimeError

Raised when a requested load would breach the two-slot / usable-RAM budget (Invariant 8). The scheduler refuses breaching work rather than crashing.

Registry dataclass

config instance-attribute
pinned property
by_name(name)
by_tier(tier)

get_registry()

server

ModelServer — the facade agents use to talk to local models.

Combines the registry, the two-slot loader, and the Ollama client so callers say "chat at the synthesis tier" and the right model is made resident first (model advises, code acts). Persona/params are passed through at request time.

ModelServer dataclass

config instance-attribute
client instance-attribute
loader instance-attribute
version()
ensure_pinned(*, warm=True)
chat(tier, messages, *, think=None, temperature=None)

build_model_server(config=None)