Modules · Assimilation
The summarizer.
The summarizer is an interposer on the Context Transfer Engine's task interface. It forwards every operation to the pool below it, and after a successful put whose tag and blob names match a configured rule, it sends the blob's bytes to a language model and stores the reply as a sibling blob named with a _label suffix. With no rules configured it is a pure passthrough.
- Module
- clio_cae_summarizer
- Pool ID
- 401.0 by convention
- Backends
- Ollama, OpenAI-compatible
- Source
- context-assimilation-engine/summarizer
What it summarizes
The summarizer watches PutBlob. When a write completes
successfully and a rule matches, it reads the written payload, runs a
prompt template over it, and stores the model's response as a new blob
called {blob_name}_label in the same tag, with the same
placement score as the original. Nothing else is summarized: reads,
tag creation, metadata operations and searches pass straight through.
Use it when you want a short natural-language description of each ingested document or dataset chunk to exist alongside the data, for example so that keyword search or an agent can find the data by what it says rather than by its name. The summary is written as an ordinary blob, so anything that can read blobs can read it.
Where it sits
The module sits between whatever addresses the CTE and the CTE core itself. Typical chains, from the top down:
- CAE core as the entry point, then the summarizer, then the CTE core.
- CAE core, summarizer, indexer, CTE core, as in the dispatch test configuration.
- A CTE client pointed directly at the summarizer pool.
Its compose key next_pool_id names the pool below. When
unset, the canonical CTE core pool is used. The summary blob is written
through a client addressed at that lower pool, so the write never
re-enters the summarizer. A rule whose blob pattern is .*
therefore does not summarize its own summaries.
Like the cache, replication and indexer interposers, the module is deliberately absent from the repository's module registry: regenerating its dispatch code would remove the default-case forwarding it depends on, so its method table is maintained by hand.
Composing it
This is the labeling test configuration. The CTE core comes first because an interposer's next pool must exist when its container is created.
compose:
- mod_name: clio_bdev
pool_name: "ram::chi_default_bdev"
pool_query: local
pool_id: "301.0"
bdev_type: ram
capacity: "256MB"
- mod_name: clio_cte_core
pool_name: cte_core
pool_query: local
pool_id: "513.0"
storage:
- path: "ram::cte_ram_tier1"
bdev_type: "ram"
capacity_limit: "128MB"
score: 1.0
dpe:
dpe_type: "max_bw"
- mod_name: clio_cae_summarizer
pool_name: clio_cae_summarizer
pool_query: local
pool_id: "401.0"
next_pool_id: "513.0"
label_endpoint: "http://127.0.0.1:11434"
label_prompts:
summarize: "Summarize the following text in one short sentence."
label_matches:
- tag_re: ".*\\.txt$"
blob_re: ".*"
model: "gemma3:1b"
prompt: "summarize"
context_length: 4096
- mod_name: clio_cae_core
pool_name: cae_labeler
pool_query: local
pool_id: "512.0"
next_pool_id: "401.0"Configuration
- next_pool_id
- Pool to forward to, as
"major.minor". Default: the canonical CTE core pool. - label_endpoint
- Base URL of the inference server. The module appends
/api/generatefor Ollama or/chat/completionsfor the OpenAI protocol. - label_api
ollama(default when empty) oropenai, case-insensitive. Any other spelling fails container creation with an error rather than falling back.- label_api_key_file, label_api_key_env
- Where the bearer token for the OpenAI protocol comes from: a file
(with
${VAR}expansion) or an environment variable. The file wins when readable. Both are re-read on every request, so a refreshed token needs no restart. Surrounding whitespace is stripped. The token itself is never placed in the compose file. If neither yields a token, requests are sent without an Authorization header and a warning is logged at creation. - label_prompts
- A map from prompt name to prompt text. The full model input is the prompt text, a blank line, then the payload chunk.
- label_matches
- A list of rules, checked in order; the first match wins. Each has
tag_re,blob_re,model,prompt(a key inlabel_prompts),context_length(default 4096) andnum_predict(default 0, no cap).
Rule regexes are matched with search semantics, so
.*\.txt matches any name ending in .txt
without anchors, and globs are not converted. An invalid regex disables
only that rule, with a warning, and matching continues with the next.
A rule naming a prompt that does not exist is skipped with a warning.
Backends
- Ollama. A POST to
/api/generatewithstream: false.context_lengthis sent asoptions.num_ctxandnum_predictasoptions.num_predict; a value of 0 omits the option and leaves Ollama's default. Theresponsefield is the summary. - OpenAI-compatible. A POST to
/chat/completionswith the prompt as a single user message andstream: false.num_predictis sent asmax_tokenswhen non-zero;context_lengthis not sent, because these servers fix the context window per model, but it still drives chunking below. The first choice's message content is the summary. This is the protocol hosted inference services speak, and also what a local vLLM or llama.cpp server exposes, usually without a token.
Both calls use libcurl with a 5 second connect timeout and a 120
second total timeout. Loopback addresses bypass any configured proxy,
and the environment's no_proxy list is honored in addition.
Any transport, HTTP or parse error returns no summary and is logged at
warning level.
Chunking
When context_length is positive, the payload is split so
each request fits the model's context. Three quarters of the token
budget is reserved for the payload and converted to bytes at about 3
bytes per token, less the prompt template's size. Each chunk is
summarized separately and the responses are concatenated with a blank
line between them. A chunk that fails is skipped; only when every chunk
fails is nothing stored. A context_length of 0 disables
chunking and sends the whole payload in one request.
Binary payloads tokenize closer to one byte per token, so the byte
estimate errs toward splitting more rather than overflowing. With
num_predict set, the final summary is roughly that many
tokens times the number of chunks.
Operations
- Create reads the compose keys, resolves the protocol and token source, and logs the number of rules.
- Destroy clears the tag-name cache.
- Monitor is the standard no-op status hook.
- PutBlob forwards first, then summarizes if the write succeeded, the write was not replica-addressed, and a rule matches.
- Every other CTE core verb (GetBlob, GetOrCreateTag, SemanticSearch, the metadata operations) is forwarded verbatim by the dispatch default.
The summarizer has no client of its own beyond pool creation. Programs talk to it with the ordinary CTE core client, pointed at its pool ID.
Behaviors worth knowing
- The user's write is never affected. The original put is forwarded before any model call, and its return code is what the caller sees. Every summarization failure is logged and swallowed.
- Tag names are resolved lazily. A put carries only the tag ID, so the module asks the pool below for the name on the local node and caches it. If the name cannot be resolved on that node, the rule's tag pattern is matched against an empty string.
- Inference is blocking. The HTTP call runs on the
worker that owns the put. A rule that matches a hot write path
serializes that path behind the model, so scope
tag_reandblob_renarrowly. - Summaries inherit placement. The label blob is written with the source blob's score, so it lands on the same kind of tier.
- The payload is copied. The blob bytes are copied out of shared memory into a private string before the model call, so the request does not share lifetime with the inbound buffer.
Current limits
- Chunks are summarized one after another on the calling worker; there is no dedicated inference worker pool.
- Only the two wire protocols above are supported, with one model per rule. There is no local in-process model.
- Configuration parsing is best-effort: a malformed value leaves the
default in place silently, apart from
label_api, which fails creation. - A put larger than the model's context produces a concatenation of per-chunk summaries, not a single summary of the whole.
- There is no re-summarization: an overwritten blob gets a new label only because the new put matches again, and a blob written before the rule existed is never labeled.
How it is tested
Tests live under context-assimilation-engine/test/unit.
The config-units and rules tests cover compose parsing, rule matching
and chunk-boundary arithmetic without a runtime. The label-client and
inference tests point the HTTP client at an in-process stub to check
both protocols, token resolution, the proxy bypass list and
partial-chunk failure. Runtime tests use the compose files in the same
directory: the dispatch test against a dead endpoint exercises the
match-and-forward path and the SemanticSearch forward, the store test
checks the label blob lands in the tag, and the labeling test runs
against a local Ollama instance with the configuration shown above.