PIXELBANKv9.1.0
Menu

Build a Terse, Honest Tool-Result Envelope

Problem Statement

Whatever a tool returns goes straight back into the context window, where it competes with everything else the agent needs to remember. Three rules govern the hand-back: be terse (fit a budget), be honest about errors (say what failed and whether retrying could help), and be structured (a fixed envelope shape the model can learn once).

Background

The envelope always has the same six keys, so the model never has to guess which fields exist:

| Key | Meaning | |---|---| | call_id | echoed from the request | | tool | echoed from the request | | status | "ok", "truncated" or "error" | | content | the rendered result, or the error line | | omitted | how many rows were dropped for budget | | retryable | whether a retry could plausibly succeed |

Error path. If result["error"] is present and truthy, the envelope is status="error", content = f"{type}: {message}", omitted=0, and retryable=True only when the error type is one of timeout, rate_limit, server_error, overloaded. A not_found or invalid_argument is the agent's fault - retrying it verbatim just burns a turn.

Success path. result["rows"] is a list of flat dicts. Render each row as its keys sorted ascending, joined as k=v with ,, and join rows with "; ". Then fill greedily: add whole rows in order while the running length (separators included) stays within char_budget, and stop at the first row that does not fit - do not skip it and try a smaller later row. Anything left over is counted in omitted; if omitted > 0 the status is "truncated" rather than "ok".

Your Task

Implement:

def summarize_tool_result(result, char_budget):
  • result: {"call_id": str, "tool": str, "rows": list[dict], "error": dict|None} - rows and error may be absent.
  • char_budget: max length of content on the success path.

Input/Output Format

Returns the six-key dict described above. content for a fully-truncated result is the empty string.

Sample

res = {"call_id": "t1", "tool": "sql", "rows": [{"b": 2, "a": 1}, {"a": 9, "b": 8}]}
print(summarize_tool_result(res, 8)["content"])   # a=1,b=2
print(summarize_tool_result(res, 8)["omitted"])   # 1
print(summarize_tool_result(res, 8)["status"])    # truncated

Row one renders as a=1,b=2 (7 chars, keys sorted). Adding row two would cost 2 more for "; " plus 7 more chars = 16 > 8, so it is dropped.

Example:

Input:
res = {'call_id': 't1', 'tool': 'sql', 'rows': [{'b': 2, 'a': 1}, {'a': 9, 'b': 8}]}
out = summarize_tool_result(res, 8)
print(out['content'], out['omitted'], out['status'])
Output:
a=1,b=2 1 truncated
Reasoning:

Keys are sorted, so the first row renders as 'a=1,b=2' - 7 characters, within the budget of 8. The second row would add '; ' (2) plus 'a=9,b=8' (7) for a total of 16, over budget, so it is omitted and the status downgrades to truncated.

Constraints:

  • char_budget >= 0; rows is at most 500 flat dicts of scalar values
  • Row keys are rendered in ascending sorted order, joined by , as k=v with str(value)
  • Rows are joined by "; ", and the separator counts against the budget
  • Greedy fill stops at the first row that does not fit - later, smaller rows are not backfilled
  • status is "error", else "truncated" when omitted > 0, else "ok"
  • Retryable error types: timeout, rate_limit, server_error, overloaded
  • The envelope always carries all six keys, on both the success and error paths
solution.py

Test Results

0/0
Run code to see test results.
Build a Terse, Honest Tool-Result Envelope - Medium | PixelBank