symfonic.core.tool_offload_gate¶
tool_offload_gate ¶
v8.6.0 net-saving gate for large-tier tool-output offload.
This is the load-bearing economic piece of the size-tiered tool-output
policy (design: .claude/docs/2026-06-17-design-size-tiered-tool-
output-policy.md §3.3). It is what was MISSING in v7.12.0 — that
release fired offload on size alone with no net-saving check and lost
money (+94.8% cost, cache-hit 58% → 32% on Jarvio's A/B).
The gate fires offload for a large tool output only when it provably saves money::
fire_offload ⇔ reprefill_saving
− (stub_carry_cost + expected_recall_hop_cost) > 0
Every term is priced from the resolved model's MODEL_PRICING row
(src/symfonic/core/observability/pricing.py). For opus-4-6:
input = $5/M, output = $25/M.
Conservatism (so default-on is safe and the +94.8% failure mode cannot recur):
- A cheap pre-filter (
large_offload_threshold_tokens) gates out small outputs entirely — they are the ones likely to be recalled, and the recall hop dominates their tiny re-prefill saving (worked example B). The threshold sits well abovetool_result_compaction_size_chars. - The horizon
His an underestimate of remaining turns (fewer turns of saving → the gate does not over-fire). Per the locked product decision it is a fixed, configurableexpected_conversation_ depthprior — NOT the live iteration index. - The recall hop is priced as a single expected hop (recall is
on-demand, not per-turn), weighted by
p_recall. - An unknown model (no pricing row) cannot prove a saving → does not fire.
The module is intentionally a pure, side-effect-free function so it is
unit-testable in isolation (the worked examples A/B in the design doc
are reproduced as tests in tests/agent/test_tool_offload_gate.py).
OffloadGateDecision
dataclass
¶
OffloadGateDecision(
fire: bool,
net_saving_usd: float,
reprefill_saving_usd: float,
stub_carry_cost_usd: float,
expected_recall_hop_cost_usd: float,
reason: str,
)
Result of the gate: the fire/no-fire verdict plus the priced terms (so telemetry can record WHY it fired or did not).
OffloadGateInputs
dataclass
¶
OffloadGateInputs(
output_tokens: float,
horizon: int,
recall_probability: float,
model_name: str,
large_offload_threshold_tokens: int = DEFAULT_LARGE_OFFLOAD_THRESHOLD_TOKENS,
stub_tokens: int = DEFAULT_STUB_TOKENS,
recall_input_tokens: int = DEFAULT_RECALL_INPUT_TOKENS,
recall_output_tokens: int = DEFAULT_RECALL_OUTPUT_TOKENS,
)
Inputs to the net-saving gate. All economic; no I/O.
output_tokens is the size of the candidate tool output (chars /
4). horizon is the fixed expected_conversation_depth prior
(remaining turns the output would otherwise be carried).
recall_probability is the configurable p_recall prior.
model_name resolves the pricing row.
evaluate_offload_gate ¶
Decide whether a large tool output should be offloaded.
Returns an :class:OffloadGateDecision whose fire field is the
verdict. Pure function — no I/O, no mutation; safe to call on the
hot path and trivial to unit-test against the design's worked
examples.
Source code in src/symfonic/core/tool_offload_gate.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |