Skip to content

feat(retain): expose processed_content_tokens on RetainResult#1217

Merged
cdbartholomew merged 1 commit into
mainfrom
feat/retain-processed-content-tokens
Apr 22, 2026
Merged

feat(retain): expose processed_content_tokens on RetainResult#1217
cdbartholomew merged 1 commit into
mainfrom
feat/retain-processed-content-tokens

Conversation

@cdbartholomew

Copy link
Copy Markdown
Contributor

Summary

Delta retain (PR #701) already computes, at chunk-level granularity, which content on an upsert is new vs. a duplicate of existing chunks. This PR surfaces that signal to post-retain hooks so extensions can reason about "how much content actually went through the extraction pipeline" without re-implementing the dedup logic themselves.

New field on RetainResult:

processed_content_tokens: int | None = None

with these semantics:

Value Meaning
None Retain went through the full (non-delta) path, or has no dedup signal. Treat as "the full submitted payload was processed."
0 Submission matched prior content exactly (all chunks matched by content_hash). No chunks went through LLM extraction — metadata-only update.
N > 0 Only N tokens of content+context were actually re-extracted. Remaining chunks matched existing content_hash and were skipped.

Motivation

Clients that append to a growing document on each call (e.g. a session transcript that's upserted under a stable document_id as turns accumulate) already benefit from chunk-level dedup inside the retain pipeline — unchanged chunks don't re-enter LLM extraction. But the post-retain hook currently sees only the submitted content list, with no signal about how much of it was genuinely new.

Surfacing processed_content_tokens lets a metering or rate-limiting extension charge / account for the actually-new portion without shadowing the dedup state table.

Where it's populated

  • Streaming / full retain pathNone (no per-chunk dedup was performed).
  • _try_delta_retain "no changes" fallthrough (_delta_metadata_only) → 0.
  • _try_delta_retain partial-delta success → sum of count_tokens(content) + count_tokens(context) across the items built for extraction (delta_contents). Chunks matched by content_hash contribute zero.
  • Sub-batch aggregation (retain_batch_async when the payload is auto-chunked) → None if any sub-batch bypassed dedup, otherwise sum. Conservative so callers never undercount when only part of a large batch was eligible for delta processing.
  • Multi-document grouping inside retain_batch applies the same None-propagating merge rule.

Backward compatibility

  • RetainResult gains a new field with a None default — existing extensions compile and run unchanged.
  • The internal return signature of retain_batch / _retain_batch_async_internal changes from (unit_ids, usage) to (unit_ids, usage, processed_content_tokens). The public retain_batch_async return (and its return_usage=True form) is unchanged — the new signal is delivered only via the RetainResult hook.
  • No storage / schema changes. This is a surfacing-only change on top of PR feat(retain): delta retain — skip LLM for unchanged chunks on upsert #701's existing chunk content-hash state.

Test plan

  • Unit check on the None-propagating aggregation helper.
  • First retain to a new document_idprocessed_content_tokens is None.
  • Identical resubmit under same document_idprocessed_content_tokens == 0.
  • Appended-content retain → processed_content_tokens is either a positive value strictly less than submitted tokens, or None (if the chunker forced a full-retain fallback). Both are legitimate signals.
  • Retain without a document_idprocessed_content_tokens is None.
  • uv run ruff check clean.

Delta retain already knows, at chunk-level granularity, which content
was new vs unchanged on an upsert to an existing document_id. Surface
that signal to post-retain hooks so extensions can reason about "how
much content actually went through the extraction pipeline" without
re-implementing the dedup logic.

New field `RetainResult.processed_content_tokens: int | None`:
  * None — the retain went through the full (non-delta) path or has
           no dedup signal. Consumers should treat this as "the full
           submitted payload was processed."
  * 0    — the submission matched prior content exactly; no chunks
           went through extraction (metadata-only update).
  * N>0  — only N tokens of content+context were actually re-extracted.
           The remainder matched existing chunks by content_hash and
           was skipped.

Populated in three places:
  * Streaming / full retain path → None
  * `_try_delta_retain` no-changes fallthrough (`_delta_metadata_only`)
    → 0
  * `_try_delta_retain` partial-delta success → sum of
    count_tokens(content) + count_tokens(context) across the chunks
    built for extraction (delta_contents)

Sub-batch aggregation propagates None if any sub-batch bypassed dedup,
so callers never accidentally undercount when only part of a large
batch was eligible for delta processing.

Tests exercise the full path, unchanged-resubmit, appended-content,
and no-document-id cases plus a unit check on the aggregation helper.
@cdbartholomew
cdbartholomew force-pushed the feat/retain-processed-content-tokens branch from e9b7440 to 17b892e Compare April 22, 2026 21:09
@cdbartholomew
cdbartholomew merged commit 9c9d791 into main Apr 22, 2026
53 of 54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant