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 determines recall precision.
Retrieval precision depends on two factors: 1. Embedding token limits: Content exceeding the model input limit is truncated during vector generation. 2. Chunk specificity: Well-focused chunks improve semantic similarity scoring when ranking top matches.
The embedding input limit
When you commit a chunk with rawContent and no embedding, Jennah generates
the vector for you with Vertex AI gemini-embedding-001, which accepts about
2048 tokens of input.
If your content is longer, the model embeds a prefix and stops. Your text is not
lost; rawContent is stored in full and returned in full on a match, but the vector only represents the beginning. Content beyond the token limit is not represented in the vector index and cannot match semantic search queries.
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
Chunks written before truncation tracking was enabled 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.
Don't estimate from character counts
Characters are a bad proxy for tokens, and how bad depends on the language.
English runs about 4 characters per token, so ~8000 characters is near the
limit. Japanese, Chinese, and Korean run closer to 1 token per character,
so a 2500-character Japanese chunk can already be over it. Read
tokenCount - don't guess from len(text).
Chunk granularity and retrieval strategy
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.
Note that metadata tags on chunks are not embedded into vector space; only rawContent is embedded.
A note on field names
Responses use lowerCamelCase (truncatedChunkIds, tokenCount,
rawContent), which is what the examples above show. Requests accept either
casing. The API Reference lists fields in
snake_case, so truncated_chunk_ids there is the same field as
truncatedChunkIds on the wire.
Note: 64-bit integers are represented as JSON strings (such as "tokenCount": "9001" and "vectorRows": "1").