Skip to content

feat(billing-cost-management-mcp-server): bound SQL-offload handle - #4790

Open
Comusus wants to merge 1 commit into
awslabs:mainfrom
Comusus:feat/bcm-bound-offload-handle
Open

Comusus wants to merge 1 commit into
awslabs:mainfrom
Comusus:feat/bcm-bound-offload-handle

Conversation

@Comusus

@Comusus Comusus commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes

Summary

Changes

When a tool result crosses the session-SQL threshold, convert_api_response_to_table stores it in a table and returns a handle: table name, schema, row count and a preview of the first rows. The handle had no size bound. It listed every column name and copied up to five preview rows in full, so a result that is wide rather than tall (many flattened columns), or one with long text cells, put most of the payload back on the handle that was meant to replace it.

This PR bounds the handle. Stored values are not changed.

  • utilities/sql_utils.py
    • Preview: capped at 4 KB (PREVIEW_BUDGET_BYTES). String cells longer than 200 characters (PREVIEW_CELL_MAX_CHARS) are shortened with a marker giving the full length: ... [N chars total, query the table for the full value]. Non-text cells such as BLOBs are shortened through their text form the same way, so one large cell cannot cost the whole row.
    • Whole rows only: each row is costed in full and kept only if all of it fits, so every preview row has every column. The one exception is a first row that alone exceeds the budget: its leading cells are kept up to the budget, so the preview of a very wide table is not empty.
    • Schema: at most 60 columns (SCHEMA_COLUMN_LIMIT) within 4 KB (SCHEMA_BUDGET_BYTES).
    • Flags: preview_truncated: true and schema_column_count (the full column count) appear only when a bound applied. They are set after caller metadata, so a keyword argument with the same name cannot replace them.
    • The limits sit in the existing "Constants for SQL conversion" block.
  • Tests: new tests/utilities/test_sql_utils_bounded_handle.py (21 tests). The end-to-end cases reopen the session database and check that the stored table still has every column and the full cell values. They cover long cells, BLOB cells, wide records, a tall preview, the exact-fit boundary, empty input, a first row wider than the budget, an unserializable cell, caller metadata that tries to set the flags, and a small result whose handle is unchanged.
  • CHANGELOG: one line under Unreleased / Changed.

User experience

Before: an offloaded handle contains every column name and up to five full preview rows, however wide or long they are.

After: a handle that fits within the bounds is identical to today. A larger handle is capped at a few KB. The agent sees preview_truncated / schema_column_count when something was left out, and reads the rest from the table with session-sql, for example:

SELECT name FROM pragma_table_info('<table_name>');
SELECT <column> FROM <table_name> LIMIT 10;

Checklist

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented

Is this a breaking change? (Y/N) N. Handles within the bounds are unchanged, and the stored tables are unchanged. Only oversized handles are shortened, and they are marked when they are.

RFC issue number:

Checklist:

  • Migration process documented
  • Implement warnings (if it can live side by side)

Test plan

  • uv run --frozen pytest --cov --cov-branch: 1,940 passed; every new line in sql_utils.py covered
  • uv run --frozen pyright: 0 errors
  • uv run --frozen ruff check and ruff format --check: clean
  • pre-commit, except gitleaks and check-license-header, which could not install on our network (both run in CI)

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.

An offloaded response is moved into session SQL so the tool result stays
small, but the handle still carried every column name and up to five
unbounded preview rows. A response that is wide rather than tall, or one
with long text cells, put most of the payload back on the handle.

Cap the preview at 4 KB, shortening string cells (and the text form of
other cells such as BLOBs) over 200 characters with a marker giving the
full length, and cap the schema at 60 columns within 4 KB. The preview
keeps whole rows only, so every row it returns has every column; a first
row wider than the budget alone is kept up to the budget so the preview
is not empty. preview_truncated and schema_column_count (the full column
count) appear only when a bound applied, and are set after caller
metadata so a keyword cannot replace them. Stored values are unchanged,
and handles within the bounds are identical to before.
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.21429% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.55%. Comparing base (20c5c13) to head (e8da49c).

Files with missing lines Patch % Lines
..._cost_management_mcp_server/utilities/sql_utils.py 98.21% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4790   +/-   ##
=======================================
  Coverage   93.55%   93.55%           
=======================================
  Files        1068     1068           
  Lines       92344    92394   +50     
  Branches    15025    15035   +10     
=======================================
+ Hits        86389    86438   +49     
  Misses       3562     3562           
- Partials     2393     2394    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

2 participants