Content chunking
A chunk is one unit of semantic recall. When you query, Jennah ranks your agent's chunks by cosine distance and returns the nearest matches. How content is split into chunks directly affects recall precision and embedding limits.
The embedding input limit
When you commit a chunk with rawContent and no embedding, Jennah generates
the vector for you with an embedding model that accepts about 2048 tokens of
input.
If content exceeds this limit, the model embeds a prefix and stops. rawContent
is stored and returned in full on a match, but the vector only represents the
prefix up to the token limit. Text past the limit is not indexed in vector space.
Truncation reporting
Jennah reports truncation on commit receipts. Inspect commit receipts to verify that long inputs fit within embedding token limits.
Reading the truncation report
Every memory:commit receipt names the chunks whose embedding was truncated:
{
"commitTimestamp": "2026-07-30T09:12:44.113Z",
"vectorRows": "1",
"truncatedChunkIds": ["daily-2026-07-30-slack"]
}
An empty or absent truncatedChunkIds list means every chunk in that commit was embedded in full. Truncation is reported without failing the commit.
To fail the commit rather than storing a truncated embedding, set
rejectOnTruncation on the request:
The entire commit is then rejected with FAILED_PRECONDITION and nothing is written.
To audit chunks that are already stored, use memory:inspect (see
Reading a workspace back for paging a listing to the end):
{
"vectors": {
"chunks": [{
"chunkId": "daily-2026-07-30-slack",
"rawContent": "...",
"truncated": true,
"tokenCount": "9001"
}]
}
}
Unmeasured chunk truncation
Some chunks omit the truncated field. An absent field indicates unmeasured truncation state rather than false.
Re-committing the chunkId evaluates and reports truncation status.
Sizing a split
tokenCount is the token count of submitted content:
A chunk reporting tokenCount: 9001 needs roughly five pieces.
Character counts vs. tokens
Characters are an unreliable proxy for tokens. English averages roughly 4
characters per token (~8000 characters per chunk limit), whereas Japanese,
Chinese, and Korean average close to 1 token per character (~2000 characters
per chunk limit). Inspect tokenCount rather than relying on character length.
Chunk granularity and retrieval
Staying under token limits is required, but smaller chunks also improve retrieval precision:
- Split on meaning, not size. One chunk per section, topic, or exchange. A fixed-width window may split semantic context across chunks.
- Distill before storing. Summarize lengthy conversations or logs before committing to prevent low-value chunks from occupying query result slots.
- Keep pointers to large sources. Store concise summaries with external IDs or URIs to fetch raw content as needed.
- Retire chunks that stop being true. A stale chunk competes for the same result slots as the one that replaced it. See Temporal memory.
Structuring metadata vs. raw content
Including inline provenance tags (such as [#eng-billing 2026-07-30]) in rawContent
dilutes embedding similarity and prevents exact metadata filtering.
Use metadata instead. It is stored with the chunk and returned with every hit,
but it is not embedded - only rawContent is:
{
"vectors": [{
"chunkId": "daily-2026-07-30-slack",
"rawContent": "Alice fixed duplicate invoice lines in billing.",
"metadata": {
"employee": "alice",
"day": "2026-07-30",
"source": "slack"
}
}]
}
Then scope recall to a subset with filters:
{
"semantic": {
"queryText": "what did alice ship recently?",
"limit": 8,
"filters": [
{ "key": "employee", "value": "alice" },
{ "key": "source", "value": "slack" }
]
}
}
Filters combine with AND and are applied before ranking, so you get the
nearest limit chunks from among those that match - not a filter applied to an
already-ranked result set.
For complete metadata specifications and filter rules, see Metadata.
Automated chunking
While direct commits require callers to chunk content explicitly,
memory:form automates chunking for conversational turns by
extracting facts and writing each under a content-derived ID.
Use direct commits (memory:commit) when:
- Natural boundaries exist: Structured documents, section-based articles, or row-based records have clear structural boundaries.
- Low write latency is required: Direct commits execute in milliseconds, whereas formation takes seconds due to model evaluation.
- Explicit ID control is needed: Formation derives IDs from content hashes, whereas direct commits allow caller-specified chunk IDs.
- Exact content preservation is required: Formation extracts and summarizes salient facts; direct commits store payloads verbatim.
- Large tables or structured data: Direct commits allow fine-grained chunking per row or section rather than relying on prose extraction (Tables, code, and other structured content).
Use formation (memory:form) for unstructured conversations where semantic
boundaries must be inferred.
Field names and types
Responses use lowerCamelCase (truncatedChunkIds, tokenCount,
rawContent), while requests accept either camelCase or snake_case. The
API Reference lists fields in snake_case.
64-bit integers are encoded as JSON strings (such as "tokenCount": "9001" and
"vectorRows": "1").