Skip to content

(MOT-4325) feat(context-manager): named-part counting and an assemble breakdown - #683

Merged
rohitg00 merged 4 commits into
mainfrom
feat/context-manager-count-parts
Aug 6, 2026
Merged

(MOT-4325) feat(context-manager): named-part counting and an assemble breakdown#683
rohitg00 merged 4 commits into
mainfrom
feat/context-manager-count-parts

Conversation

@rohitg00

@rohitg00 rohitg00 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

Two additive surfaces on context-manager so callers can render a context breakdown without re-implementing the estimator or making extra round trips.

  • context::count-tokens gains an optional parts map of named texts, counted individually and returned as by_part without joining the total, plus a tools_tokens field reporting the tools share of the existing total.
  • context::assemble gains a breakdown response object: system_prompt_tokens (summary section included), tools_tokens, per-role message tokens, and the estimator that produced them. Computed from the size memo the pipeline already maintains, so the numbers reconcile with token_count by construction.

Why

The harness assembles a context every generation and previously discarded every per-category number. The console context meter (follow-up PRs on MOT-4326 and MOT-4327) needs those categories from the one call that already happens per step.

Testing

  • 88 BDD scenarios (3 new: parts never join the total, tools share reported separately, breakdown reconciles), 97 unit tests, all passing.
  • Wire-schema goldens regenerated for both functions.
  • cargo fmt, clippy with -D warnings clean.

Part of MOT-4324. No behavior change for existing callers; both responses are additive.

Summary by CodeRabbit

  • New Features

    • Added detailed token breakdowns for assembled context, including system prompts, tools, per-role totals, and estimator type.
    • Added support for independently estimating named text parts, with per-part results returned separately from totals.
    • Tool token usage is now reported separately while remaining included in overall token counts.
  • Tests

    • Added coverage for token breakdowns, named parts, tool counts, estimator reporting, and response validation.

…re in count-tokens

context::count-tokens gains an optional parts map of named texts,
counted individually with the request's estimator and returned as
by_part without joining the total, plus a tools_tokens field that
reports the tools share of the existing total. Callers dissecting a
context window (system prompt segments vs tool schemas) no longer
re-implement the estimator or make one call per part.
…m assemble

context::assemble now returns breakdown alongside the totals it already
maintains: system prompt tokens (summary section included), tools
tokens, per-role message tokens, and the estimator that produced them.
Callers rendering a context meter get the categories from the one call
that already happens per generation instead of a second count round
trip.
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview Aug 6, 2026 11:54am
workers-tech-spec Ready Ready Preview Aug 6, 2026 11:54am

Request Review

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 54 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The context manager now reports token-estimation details for assembled responses and token-count requests. It adds per-role, tool, system-prompt, and named-part totals, estimator names, wire schemas, and acceptance coverage.

Changes

Token estimation telemetry

Layer / File(s) Summary
Estimation types and assemble breakdown
context-manager/src/types.rs, context-manager/src/core/estimate.rs, context-manager/src/functions/assemble.rs
context::assemble now returns system-prompt, tool, per-role, and estimator data. Role totals use precomputed message sizes.
Count-tokens parts and tool totals
context-manager/src/functions/count_tokens.rs, context-manager/tests/steps/*
context::count-tokens accepts named text parts and returns per-part estimates and separate tool totals.
Wire schemas and acceptance coverage
context-manager/tests/golden/schemas/*, context-manager/tests/features/*
The schemas define the new response fields. Acceptance scenarios validate assemble and count-tokens breakdowns.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • iii-hq/workers#495: Both changes modify token accounting in context::assemble and context::count-tokens.
  • iii-hq/workers#685: Both changes add token-counting infrastructure for context management.
  • iii-hq/workers#686: This change adds token-breakdown fields and types used by related context accounting.

Suggested reviewers: andersonleal, ytallo

Poem

A rabbit counts each tool and part,
And maps each role with careful art.
Prompt and totals now appear,
Estimator names are clear.
Schemas hop in line without fear.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies both main changes: named-part token counting and the assemble breakdown.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/context-manager-count-parts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@context-manager/src/functions/assemble.rs`:
- Around line 116-129: Expose request overhead in AssembleBreakdown by adding
request_overhead_tokens: u64 and populating it from request_overhead_tokens in
the assemble result construction; update
context-manager/src/functions/assemble.rs lines 116-129 and 360-368 accordingly.
Regenerate the required schema field in
context-manager/tests/golden/schemas/context.assemble.json lines 932-965, and
update context-manager/tests/features/assemble.feature lines 327-338 to send
nonzero overhead and assert the returned breakdown value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d52f6fb8-385d-4591-86e0-dda2d5dc14eb

📥 Commits

Reviewing files that changed from the base of the PR and between 17210c2 and 83fd29c.

📒 Files selected for processing (8)
  • context-manager/src/functions/assemble.rs
  • context-manager/src/functions/count_tokens.rs
  • context-manager/tests/features/assemble.feature
  • context-manager/tests/features/count_tokens.feature
  • context-manager/tests/golden/schemas/context.assemble.json
  • context-manager/tests/golden/schemas/context.count-tokens.json
  • context-manager/tests/steps/call_steps.rs
  • context-manager/tests/steps/common_steps.rs

Comment on lines +116 to +129
/// Where the returned `token_count` sits, by category — the same sums the
/// pipeline already maintains, exposed so callers can render a context
/// breakdown without re-counting: `token_count` equals the by_role sum plus
/// `system_prompt_tokens`, `tools_tokens`, and the request overhead.
#[derive(Debug, Serialize, JsonSchema)]
pub struct AssembleBreakdown {
/// Estimated tokens of the returned system prompt (any compaction
/// summary section included).
pub system_prompt_tokens: u64,
/// Estimated tokens of the invocation schemas.
pub tools_tokens: u64,
/// The returned messages' tokens by role.
pub by_role: ByRoleTokens,
pub estimator: EstimatorName,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Expose request overhead in AssembleBreakdown.

token_count includes request_overhead_tokens, but breakdown does not return it. A caller cannot reconcile the breakdown with token_count when it sends a nonzero overhead.

  • context-manager/src/functions/assemble.rs#L116-L129: Add request_overhead_tokens: u64 to AssembleBreakdown.
  • context-manager/src/functions/assemble.rs#L360-L368: Set the field from request_overhead_tokens.
  • context-manager/tests/golden/schemas/context.assemble.json#L932-L965: Regenerate the schema with the required field.
  • context-manager/tests/features/assemble.feature#L327-L338: Send a nonzero overhead and assert the returned breakdown value.
Proposed fix
 pub struct AssembleBreakdown {
     pub system_prompt_tokens: u64,
     pub tools_tokens: u64,
+    pub request_overhead_tokens: u64,
     pub by_role: ByRoleTokens,
     pub estimator: EstimatorName,
 }

 breakdown: AssembleBreakdown {
     system_prompt_tokens: prompt_tokens,
     tools_tokens: tool_tokens,
+    request_overhead_tokens,
     by_role,
     estimator: match estimator.kind() {
📍 Affects 3 files
  • context-manager/src/functions/assemble.rs#L116-L129 (this comment)
  • context-manager/src/functions/assemble.rs#L360-L368
  • context-manager/tests/golden/schemas/context.assemble.json#L932-L965
  • context-manager/tests/features/assemble.feature#L327-L338
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@context-manager/src/functions/assemble.rs` around lines 116 - 129, Expose
request overhead in AssembleBreakdown by adding request_overhead_tokens: u64 and
populating it from request_overhead_tokens in the assemble result construction;
update context-manager/src/functions/assemble.rs lines 116-129 and 360-368
accordingly. Regenerate the required schema field in
context-manager/tests/golden/schemas/context.assemble.json lines 932-965, and
update context-manager/tests/features/assemble.feature lines 327-338 to send
nonzero overhead and assert the returned breakdown value.

@rohitg00
rohitg00 requested review from andersonleal and ytallo August 3, 2026 17:38
… and estimator name

Review cleanups, wire-identical (goldens unchanged). ByRoleTokens and
EstimatorName move to types.rs with From impls from the core ByRole and
EstimatorKind, deleting two hand-written conversions and the dead
as_str. The by-role accumulation the breakdown added lives once in
core::estimate::by_role_from_sizes over the memoized sizes, shared by
assemble.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
context-manager/src/core/estimate.rs (1)

213-239: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert exact role totals using independent sizes.

Lines 231-237 derive sizes from est.message, so a regression that re-estimates messages can still pass. The aggregate assertions also do not verify each role mapping. Use distinct fixed sizes and assert every ByRole field.

Proposed test strengthening
-    let est = HeuristicEstimator;
-    let sizes: Vec<u64> = messages.iter().map(|m| est.message(m)).collect();
+    let sizes = vec![11_u64, 22, 33, 44];
     let by_role = by_role_from_sizes(&messages, &sizes);
-    assert!(by_role.user > 0 && by_role.assistant > 0);
-    assert!(by_role.function_result > 0 && by_role.custom > 0);
-    assert_eq!(
-        by_role.user + by_role.assistant + by_role.function_result + by_role.custom,
-        sizes.iter().sum::<u64>()
-    );
+    assert_eq!(by_role.user, 11);
+    assert_eq!(by_role.assistant, 22);
+    assert_eq!(by_role.function_result, 33);
+    assert_eq!(by_role.custom, 44);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@context-manager/src/core/estimate.rs` around lines 213 - 239, Strengthen
by_role_from_sizes_partitions_every_role by replacing estimator-derived sizes
with distinct fixed values, then assert each ByRole field exactly matches the
corresponding role’s size and that the total remains correct. Keep the existing
four-role message fixture and call by_role_from_sizes directly so the test
detects incorrect role mapping or message re-estimation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@context-manager/src/core/estimate.rs`:
- Around line 108-122: The by_role_from_sizes function currently zips messages
and sizes, silently truncating mismatched inputs. Enforce that messages.len()
equals sizes.len() before calculating the breakdown, using the function’s
existing return contract to assert or return an error, and preserve the per-role
accumulation for valid inputs.

---

Nitpick comments:
In `@context-manager/src/core/estimate.rs`:
- Around line 213-239: Strengthen by_role_from_sizes_partitions_every_role by
replacing estimator-derived sizes with distinct fixed values, then assert each
ByRole field exactly matches the corresponding role’s size and that the total
remains correct. Keep the existing four-role message fixture and call
by_role_from_sizes directly so the test detects incorrect role mapping or
message re-estimation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e54244ae-38eb-48e3-a729-f1774eacf5c5

📥 Commits

Reviewing files that changed from the base of the PR and between 5b0ffa9 and 324ceb8.

📒 Files selected for processing (10)
  • context-manager/src/core/estimate.rs
  • context-manager/src/functions/assemble.rs
  • context-manager/src/functions/count_tokens.rs
  • context-manager/src/types.rs
  • context-manager/tests/features/assemble.feature
  • context-manager/tests/features/count_tokens.feature
  • context-manager/tests/golden/schemas/context.assemble.json
  • context-manager/tests/golden/schemas/context.count-tokens.json
  • context-manager/tests/steps/call_steps.rs
  • context-manager/tests/steps/common_steps.rs
🚧 Files skipped from review as they are similar to previous changes (9)
  • context-manager/tests/features/count_tokens.feature
  • context-manager/tests/steps/common_steps.rs
  • context-manager/tests/features/assemble.feature
  • context-manager/src/types.rs
  • context-manager/tests/golden/schemas/context.assemble.json
  • context-manager/src/functions/assemble.rs
  • context-manager/tests/steps/call_steps.rs
  • context-manager/tests/golden/schemas/context.count-tokens.json
  • context-manager/src/functions/count_tokens.rs

Comment on lines +108 to +122
/// Per-role breakdown from already-computed message sizes (`assemble`'s
/// memoized `sizes`), so callers with a size memo don't re-estimate.
pub fn by_role_from_sizes(messages: &[AgentMessage], sizes: &[u64]) -> ByRole {
let mut by_role = ByRole::default();
for (message, size) in messages.iter().zip(sizes) {
match message.role() {
Role::User => by_role.user += size,
Role::Assistant => by_role.assistant += size,
Role::FunctionResult => by_role.function_result += size,
Role::Custom => by_role.custom += size,
}
}
by_role
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject mismatched message and size slices before zipping.

Line 112 truncates to the shorter slice. If sizes drifts from messages, the breakdown silently omits entries and can no longer reconcile with token_count. Enforce equal lengths, or return an error when callers control these inputs.

Proposed invariant check
 pub fn by_role_from_sizes(messages: &[AgentMessage], sizes: &[u64]) -> ByRole {
+    assert_eq!(
+        messages.len(),
+        sizes.len(),
+        "messages and sizes must have equal lengths"
+    );
     let mut by_role = ByRole::default();
-    for (message, size) in messages.iter().zip(sizes) {
+    for (message, size) in messages.iter().zip(sizes.iter()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Per-role breakdown from already-computed message sizes (`assemble`'s
/// memoized `sizes`), so callers with a size memo don't re-estimate.
pub fn by_role_from_sizes(messages: &[AgentMessage], sizes: &[u64]) -> ByRole {
let mut by_role = ByRole::default();
for (message, size) in messages.iter().zip(sizes) {
match message.role() {
Role::User => by_role.user += size,
Role::Assistant => by_role.assistant += size,
Role::FunctionResult => by_role.function_result += size,
Role::Custom => by_role.custom += size,
}
}
by_role
}
/// Per-role breakdown from already-computed message sizes (`assemble`'s
/// memoized `sizes`), so callers with a size memo don't re-estimate.
pub fn by_role_from_sizes(messages: &[AgentMessage], sizes: &[u64]) -> ByRole {
assert_eq!(
messages.len(),
sizes.len(),
"messages and sizes must have equal lengths"
);
let mut by_role = ByRole::default();
for (message, size) in messages.iter().zip(sizes.iter()) {
match message.role() {
Role::User => by_role.user += size,
Role::Assistant => by_role.assistant += size,
Role::FunctionResult => by_role.function_result += size,
Role::Custom => by_role.custom += size,
}
}
by_role
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@context-manager/src/core/estimate.rs` around lines 108 - 122, The
by_role_from_sizes function currently zips messages and sizes, silently
truncating mismatched inputs. Enforce that messages.len() equals sizes.len()
before calculating the breakdown, using the function’s existing return contract
to assert or return an error, and preserve the per-role accumulation for valid
inputs.

@rohitg00
rohitg00 merged commit b62a368 into main Aug 6, 2026
18 checks passed
@rohitg00
rohitg00 deleted the feat/context-manager-count-parts branch August 6, 2026 12:42
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