Skip to main content

cogno-soma

The reference orchestrator for the Cogno cognitive pipeline.

cogno-anima ships the cognitive stages (NOUMENO → NER → ID → EGO ⇄ SUPEREGO → Drift) as decoupled pieces — but deliberately not the glue that runs them end-to-end. cogno-soma is that glue, promoted to a shippable, infra-agnostic lib: the control flow, the EGO⇄SUPEREGO correction loop, the PII/scope/handoff gates, an interception seam (Hooks / StopPipeline) and atomicity callbacks.

It is the keystone that imports the stages and wires them — and only that. The host still injects the backends, the tool dispatcher and the persona's prompts; soma never touches a DB, an MCP client, billing, or a persona store.

NOUMENO → NER → ID → [PII gate] → [scope gate] → EGO ⇄ SUPEREGO(judge) → SUPEREGO(voice)

See it run — a complete agent in 15 minutes

The SECRETARY demo in cogno-praxis is soma doing its job end-to-end: the full cognitive loop over local Ollama, with a real scheduling vertical served as an MCP subprocess and the cognition visible stage by stage (--trace). Free, offline, any language in:

git clone https://github.com/sudoers-ai/cogno-praxis && cd cogno-praxis
python examples/secretary_demo.py --trace     # full install chain in the praxis README

Install

pip install cogno-soma          # pulls cogno-anima + cogno-synapse

The sibling libs are not on PyPI yet — install them from git first (cogno-homeo, then cogno-synapse, then cogno-engram, then cogno-anima); see .github/workflows/ci.yml. cogno-engram is in the chain because the anima's "did you mean…?" selector reads its tokenizer, and fails closed without it (#57).

Quick start — one turn

from cogno_soma import Pipeline, TurnConfig
from cogno_anima.types import PipelineContext
from cogno_synapse import OllamaBackend, OllamaEmbedder

gen = OllamaBackend(model="mistral:latest")        # NOUMENO/NER/scope/judge (JSON)
embedder = OllamaEmbedder(model="nomic-embed-text:latest")

pipe = Pipeline(prompts_dir=PROMPTS_DIR, embedder=embedder)
cfg = TurnConfig(
    gen_backend=gen, ego_backend=gen,
    ego_prompt=persona_system, scope_prompt=persona_scope,
    limits_prompt=persona_limits, voice_prompt=persona_voice,
)

ctx = PipelineContext(user_input="What's my balance?")
ctx = await pipe.run_turn(ctx, cfg, dispatcher=my_dispatcher)
print(ctx.superego_result.response)     # the final, voiced reply

The four prompt strings come from the host (e.g. resolved via cogno-persona's PersonaSelector). soma is prompt-agnostic — it does not depend on a persona store.

Multi-turn sessions

run_turn is single-turn and stateless (every cross-turn signal rides in ctx.metadata["id_state"]). SessionRunner threads that state — id_state, conversation history, NER carry-over — across a session, and is itself a thin, serializable holder so a multi-worker host reconstructs it per request:

from cogno_soma import SessionRunner

sess = SessionRunner(pipe, cfg, dispatcher_factory=build_dispatcher,
                     persona_id="FINANCE", state=load_state(session_id))

ctx = await sess.run("What's my balance?", memories=retrieved_facts)
save_state(session_id, sess.state)      # persist the snapshot for the next request

Only the current time-burst of the transcript reaches the model verbatim: at most CONTEXT_WINDOW_EXCHANGES (6) exchanges, none older than CONTEXT_WINDOW_GAP_SECONDS (4 h) before the turn. Both are exported from cogno_soma and are the defaults of SessionRunner(max_history=, burst_gap_seconds=), so a host that writes rows into the transcript itself reads them instead of copying the numbers (#56; docs/HOST_INTEGRATION.md §2).

run(..., split_context=True) hands the composed context over by PROVENANCE, on two carriers. ego_context keeps the [SOURCES] instruction (this library's own text). ego_context_untrusted gets the layers other people wrote: the conversation, the earlier-session summary, the memories and the graph facts. cogno-anima renders that second carrier inside a fence. Off by default: one block, byte for byte as before (docs/HOST_INTEGRATION.md §2).

Either way, the runner records which layers it composed on ctx.metadata["context_layers"]: slug, length and carrier per layer, never the text. The per-attempt ledger (ctx.metadata["judge_attempts"]) carries what each attempt's executor prompt held, as ego_prompt_blocks, ego_prompt_sha and ego_prompt_path (docs/HOST_INTEGRATION.md §2, §4.1).

Hooks — the interception seam

Memory injection, safety screens, auditing and atomicity plug in as optional Hooks fired around the stages (mirroring the parent's InterceptorChain). A hook may be sync or async, may mutate the context, or raise StopPipeline to halt the turn with a terminal response:

from cogno_soma import Hooks, StopPipeline

async def inject_memory(ctx):
    ctx.metadata["ego_context"] = await store.retrieve(ctx.user_input)

def crisis_screen(ctx):
    if is_crisis(ctx.noumeno.rewritten):
        raise StopPipeline(reason="human_handoff", response=CRISIS_MESSAGE, blocked=True)

cfg = TurnConfig(..., hooks=Hooks(
    after_ner=inject_memory,
    after_id=crisis_screen,
    on_rollback=lambda ctx: tx.rollback(),   # before each EGO retry
    on_commit=lambda ctx: outbox.flush(),    # once the judge approves
))

Fire order: before_turn → NOUMENO → after_noumeno → NER → after_ner → ID → after_id → gates → EGO⇄SUPEREGO (after_ego_attempt after each executor pass and before its judgement, on_rollback per retry, on_commit on approval) → after_ego → voice → after_superego → after_turn.

A proposal turn (the EGO held a call for the contact's "yes") skips the judge, because the action is incomplete on purpose. The exception is a held call that sends text to a person, which the host declares in mk.HELD_DELIVERED_TEXT: that text is final at the hold, so the turn is judged and rewritten like any other before it can be proposed (on a budget of one, a rejected held message still gets ONE recomposition, extra_pass: held_message_rejected) — and the «sim» that replays it is not judged a second time when the host stamps that approval in the confirmed row (PREJUDGED_TEXT_SHA; the ledger row says skipped: prejudged_replay; #55). See docs/HOST_INTEGRATION.md §4.2.

When the guard refuses, and when the budget is spent

  • A refusal the selector proves false is let through (#57). When the host injects BOTH TurnConfig.scope_selector_backend and TurnConfig.scope_options (a closed list it builds per turn), a turn the scope guard BLOCKED gets one strict call to cogno_anima.stages.scope_options. A covered pick means the refusal was false and the turn goes on to the EGO and the voice. Any other outcome keeps the refusal byte for byte, and the record on mk.SCOPE_OPTIONS_SELECTION is the host's to turn into its closed question. The call has its own ledger line, superego_select, in ctx.retry_metrics. Without both fields the selector is never called (docs/HOST_INTEGRATION.md §1).
  • Two rejections buy ONE more EGO pass (#54). With the correction budget spent, re-voicing cannot supply a missing tool call, so _owes_an_action ("you did not act") and _owes_a_read ("there is no X" over a source read the host declared in mk.SOURCE_READS and nobody called) each grant one more executor pass, under one shared ceiling (_ACTION_RETRY_CEILING, two passes in all). The rejected ledger entry that bought it says which, extra_pass = action_owed | read_owed (cogno_soma.EXTRA_PASSES). No declaration, no read pass (docs/HOST_INTEGRATION.md §4.3).

The turn the agent opens

A proactive turn has no utterance. What you hold is your OWN directive ("open the conversation with Ana, mention the checkup"), and running the two stages that exist to understand a person over it is not wasteful but wrong — five ways, each one a stage doing its job correctly on the wrong input:

stage what it does to your directive
NER (PII) classifies YOUR words; "checkup" reads as HEALTH_DATA → CRITICAL → the ID blocks the turn and the agent never opens
ID (goal) files the directive as the CONTACT's goal, so their first reply reads as a topic change
SUPEREGO (scope) judges whether your own sentence is on-topic for your deployment
NOUMENO (drift) measures every drift number for the turn against your prompt
NOUMENO (continuity) embeds your internal marker against the contact's last message to decide change_subject

So hand the two results in and the pipeline seats them — no LLM, no embedder, no tokens:

import dataclasses
from cogno_soma import opening_perception

cfg = dataclasses.replace(cfg, scope_prompt="", **opening_perception())
ctx.user_input = "[OPENING]"          # a marker, not words you put in the contact's mouth
ctx = await pipe.run_turn(ctx, cfg, dispatcher=dispatcher)

The EGO, the judge and the voice still run for real — the judge is what stops an opening from inventing the history it greets someone with — and the scope guard goes off because a guard whose input is your own directive can only ever block you.

TurnConfig.noumeno_result / intent_result are the general seam; opening_perception() is the one set of values this library ships for it. What is a decision comes from the result; what is a fact about the turn comes from the context — original is ctx.user_input, language is ctx.force_language, langue is inherited from the NOUMENO — so a stand-in can never claim the turn carried text nobody put in it.

Token accounting

Every LLM call a turn makes — NOUMENO, NER, the scope guard, each judge attempt, each EGO attempt of the correction loop, and the voice — lands on the returned ctx. soma drops nothing; read the totals and feed your meter:

ctx.total_tokens            # LLM + embedding tokens for the whole turn
ctx.total_llm_tokens        # prompt + completion (no embeddings)
ctx.total_embedding_tokens  # NOUMENO continuity + ID goal-similarity

soma preserves the counts but does not price them — metering is the host's (cogno-meter). A blocked / handoff turn still reports what it spent.

What stays at the host

Persona selection, metering/billing, the DB/MCP execution behind the dispatcher, RBAC, retrieval, the real human handoff and semantic cache — soma signals (stop_reason, needs_handoff), the host decides. See docs/HOST_INTEGRATION.md.

The Cogno ecosystem

cogno-soma is one organ of Cogno — a family of small, composable, Apache-2.0 libraries that together form a complete conversational-agent platform. Each library owns a single concern and stays infra-agnostic; a host assembles them into a running agent:

The Cogno ecosystem

The open-source libraries are the organs; the host is the body that joins them. Our reference host — cogno-host, with its cogno-ui dashboard — is the private product layer, but it holds no special powers: everything it does rides on the public seams documented in each library's docs/HOST_INTEGRATION.md, so you can assemble a body of your own.

Development

pip install -e ".[dev]"
pytest tests/unit -q            # fast, no network (fake stages)
pytest tests/integration -q     # real stages over Ollama, auto-skips if absent
ruff check cogno_soma tests && mypy cogno_soma

tests/unit refuses Ollama by construction: tests/unit/conftest.py installs tests/unit/_ollama_gate.py for the whole session, so a unit test that tries to open a connection to Ollama (port 11434, or the host:port of OLLAMA_BASE_URL/COGNO_OLLAMA_URL/OLLAMA_HOST) raises OllamaGateError and FAILS — even when the code under test swallowed the refusal. The local Ollama is the GPU serving live traffic; inject a stub (tests/conftest.py) (#58).

Apache-2.0.

Metadata

Release files for cogno-soma 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cogno-soma 0.1.5
File Size Uploaded Provenance
cogno_soma-0.1.5.tar.gz 72.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cogno-soma 0.1.5
File Interpreter ABI Platform
cogno_soma-0.1.5-py3-none-any.whl Python 3 none any Details

Total release size: 145.7 kB

Release files / cogno_soma-0.1.5.tar.gz

Download URL cogno_soma-0.1.5.tar.gz
Size 72.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1638bbc97d1711014529902bdcd82d2faeb9416e668845375001c75102170974
BLAKE2b-256 checksum
How to use checksums
71d859448a7cc34607d9f067ae1f17a40097de45ba60015f5ac05d012f3fbf46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / cogno_soma-0.1.5-py3-none-any.whl

Download URL cogno_soma-0.1.5-py3-none-any.whl
Size 72.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a484e993ed83ee40a42a9f20ba1f175ab317043fe6a8724bf4f63bf85f494ad1
BLAKE2b-256 checksum
How to use checksums
0db119f3c92ad82c8efdbb430f0c053aa9b056bba1d2fd9636c19271618366ab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for cogno-soma 0.1.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cogno_soma-0.1.5.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.5 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page