Qdrant supplies candidate ids and scores and nothing else. Mind::rank drops every hit the mind does not hold, then lets CultNet's selection decide membership over the whole mind with its keys narrowed to the candidates, so in_force and cited mean what they mean in query. Order is score then ordinal; the page is cut to the selection's limit and top_k; next is never set. A cursor with semantic, blank text and an out-of-range top_k are refused by name before anything is embedded. Mind::query loses its semantic parameter and the "index is not wired" refusal; the published request schema's description of SemanticQuery follows. The serve loop never waits on the embedder: a semantic query becomes a Search held beside its session, the worker embeds (behind the model's query instruction) and searches between batches, and the loop answers when the worker has or when ServeOptions::search_timeout passes. A worker that is Failing or Refused refuses at once, typed Unavailable naming its own error. VectorIndex gains search; Qdrant's adapter sends no filter. Model identity is re-read before every flush and before every search: a model re-pulled under the same dimensions but a new digest forgets the reconciliation instead of mixing vectors, and the next step rebuilds. An idle worker forgets the reconciliation every Backoff::recheck (60 s), so a lost collection is found without a write. Both go through Projector::reconcile, still the one judge. Also fixes a race in the worker loop found by the mutation runs: entries were taken from the inbox under one lock and published as wanted under another, so whoami could read a stale Current with nothing queued while the worker held them. Taking, absorbing and publishing are now one critical section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.5 KiB
Huginn
Huginn is the GameCult memory organ. It owns each agent instance's mind as typed state: what an instance remembers, which of that memory is admitted to steer its next action, and the provenance of every admitted claim.
Upstream: https://github.com/GameCult/Huginn.git
Authority
- An instance owns its mind; Huginn owns the state. Huginn is the single writer of an instance's memory documents. No other service, script, or agent writes them.
- Memory documents are CultCache state.
huginn-mindpersists them through CultLib's Rust CultCache into a redb store at<state_root>/minds/<instance>/mind.redb.huginn-daemonserves one such mind over CultNet RUDP: admission and the read side ridecultnet.operation_request.v0andcultnet.operation_response.v0, and the two wire schemas answer a schema catalog request. - Retrieval depends on Qdrant directly, with no second store and no second
writer.
huginn-daemonkeeps one collection per mind,huginn_mind_<instance>, as a projection of the mind: one worker thread embeds each indexable document through Ollama and writes its point, off the serving path, and a restart compares the mind with the collection and writes what is missing. An unreachable Qdrant or Ollama shows inwhoamias the index's status and is retried; admission never waits on it. The daemon requires--qdrant-url,--ollama-urland--embedding-model. Aquerycarryingsemanticis answered through the same selection as any other: the index supplies candidate ids and scores, and the mind keeps only the documents it holds and the selection admits (ask forin_forceto exclude resolved ones), ordered by score. It takes no cursor, and it is refusedUnavailablewhile the index is failing or refused. - Mind state is not version-controlled. It lives in Huginn's store, not in any repository.
Generic .cc inspection is not Huginn's job. CultCache Studio inspects and
edits .cc state. Huginn reads and writes minds.
Layout
A Cargo workspace of three crates:
crates/huginn-mind: storage, identity, admission, and queries and derived status over memory documents.crates/huginn-daemon: the CultNet surface. The socket, the sessions, the process and the operation envelope, and no rule.crates/eureka-state: a stub. It will carry typed state for the Eureka pipeline; today it holds none.
huginn-mind is live: it opens one instance's store (an owned redb CultCache
at <state_root>/minds/<instance>/mind.redb, locked for the mind's lifetime),
and refuses a store whose instance document names another instance, whose
epoch record is foreign, or whose types are not a mind's. It admits batches
of pipeline documents through one commit path: the leaf's bounds, formats and
keys, then the organ's cross-document rules (references, in-force status,
the resolution matrix, derived resolutions and stewardships), then one
compare-and-swap that lands the batch whole with a receipt naming the exact
bytes it read and wrote. An exact replay answers with the stored receipt.
It reads them back the same way: a document with the facts of its admission
joined from that receipt and its status derived at read time, and typed
queries over one mind. Status is never stored, and the views derive it
through the same rules admission does. Document shape and keys come from
epiphany-pipeline; the store is CultLib's Rust CultCache.
huginn-daemon opens one mind, binds one UDP socket in that order, and
answers every frame on the session it arrived on: one operation per Mind
method plus whoami, whose payload is the mind's own types as named
MessagePack. A refusal is an answer with a rejected status, never a
transport error; an envelope that does not decode is answered with a typed
failure and reaches no mind. The two schemas it publishes live in
schemas/cultnet/ and are pinned to their derivation by a test. There is no
index yet.
eureka-state is a stub; the campaign's cut map in
Epiphany/notes/eureka-pipeline-state-cut.md owns what each crate must do
next.
cargo check --workspace
cargo run -p huginn-daemon -- --state-root <abs> --instance <slug> --bind 127.0.0.1:17872
Persona
Huginn's legacy repo Persona lives under .voidbot/. Its state/huginn.cc
holds legacy void.* documents and its voice/identity.json names the
Persona. The two disagree about jurisdiction; that migration belongs to the
portable-Persona work, not to this workspace, and nothing here reads or writes
.voidbot/.