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.