Strategy¶
Built and verified
Strategy, its contracts, EngineContext (defined in coniti.broker;
Strategy and EngineContext are also exported from coniti) and the
default strategy (in coniti.impl.strategy) are built and tested, and the
Container has a strategy slot. The engine that calls the strategy is
described in engine.
Strategy¶
A Strategy decides how the system layer is used.
from coniti import EngineContext, Strategy
from coniti.broker import AdmitInput, AdmitOutput, RetrieveInput, RetrieveOutput
A strategy subclasses Strategy and implements one abstract method, _retrieve.
Callers use the public methods, which check the contracts around your code:
| Method | Behaviour |
|---|---|
admit(AdmitInput) |
Returns an AdmitOutput. Calls _admit, which admits everything unless you override it. |
retrieve(RetrieveInput) |
Returns a RetrieveOutput from _retrieve. |
Inside a strategy, self._context(inp) returns the input’s EngineContext as a
typed value.
| Contract | Fields |
|---|---|
AdmitInput |
note, vector, context |
AdmitOutput |
admit (default True) |
RetrieveInput |
query, query_vector (None when the query has no text), context |
RetrieveOutput |
hits, groups, raw (each structure’s output, by name) |
EngineContext |
embedder, memory, structures: how a strategy reaches the system |
Like every contract, they may be extended by a subclass, never replaced
(contracts). Strategies reach memory only through the context’s
MemoryBackend.
The context is runtime-only¶
context holds live objects (the embedder, the memory, the structures), so it
cannot be serialised. It is excluded from to_dict() and to_json() and
from the JSON Schema. The consequence:
- An input rebuilt with
from_dict/from_jsonhascontextset toNone. - Calling a strategy with such an input raises
ContractError(“needs an EngineContext”), instead of failing later with an obscure error. - Set
input.context = EngineContext(...)on the deserialised input before passing it on.
The default strategy¶
Kind default, class DefaultStrategy. It has no tuning params.
admit: admits every note.retrieve: calls each structure in order. The input it builds is the structure’s own declaredquery_input, filled from the query (vectorfromquery_vector, thetime_range,k, andanchor_idfromquery.near). Each structure’s output is stored inrawunder"<index>:<ClassName>"(for example"0:FlatStructure").hitsare the first non-empty hit list, andgroupsare collected from all structures. An error from a structure propagates.
from coniti import (
Container, ContinuityEngine, Embedder, EngineContext, MemoryBackend, Note,
Query, Strategy, Structure,
)
from coniti.broker import RetrieveInput
c = Container(search_cwd=False) # defaults: in_memory, local_hash, [flat], default
ContinuityEngine(container=c).ingest_many([
Note(id="n1", text="october product launch", timestamp=1.0),
Note(id="n2", text="october launch plan", timestamp=2.0),
Note(id="n3", text="apple pie recipe", timestamp=3.0),
])
embedder, memory = c.get(Embedder), c.get(MemoryBackend)
structures = c.get_all(Structure)
ctx = EngineContext(embedder=embedder, memory=memory, structures=structures)
q = Query(text="october launch", k=2)
out = c.get(Strategy).retrieve(
RetrieveInput(query=q, query_vector=embedder.vector(q.text), context=ctx)
)
[(h.id, round(h.score, 3)) for h in out.hits] # [('n1', 0.816), ('n2', 0.816)]
list(out.raw) # ['0:FlatStructure']
The Container provides the strategy like the other pieces; set it in
coniti.toml with [strategy] and kind = "default" (see container).
Engine¶
The ContinuityEngine that uses the strategy (ingest, ask, Answer) is built:
see engine.