Output Length Limiting
Opening Explanation
This stage exists to stop raw terminal text from overwhelming the LLM. A terminal can easily produce huge output: long logs, file dumps, test failures, or commands that hang and keep printing. If all of that is passed through unchanged, the agent wastes context window, pays more, and can bury the few lines that actually matter. Output Length Limiting owns one simple job: cap terminal output before it becomes model input. It is not a standalone phase of the loop. Instead, it sits anywhere terminal text is about to cross the boundary into the LLM, including initial terminal capture, normal observations, warning paths, and timeout messages. Its value is consistency: every path enforces the same size limit and uses the same truncation signal.
Main Flow
- Terminal-producing parts of the system gather text from a tmux session (a terminal you can drive remotely) or from command execution paths.
- Before that text is shown to the LLM,
_limit_output_length()(trim terminal text to a safe byte size) is called. - The function keeps the output within a fixed byte budget and adds a
[OUTPUT TRUNCATED]marker if cutting was needed. - The result is a smaller, predictable piece of text that still tells the model two important things:
- here is the most relevant output we can fit
- more existed, but it was cut
- This is used in several places on purpose. The system does not want one observation path to be safe while another accidentally sends a giant blob into the prompt.
- If this helper did not exist, stage-3, stage-4.9, and timeout formatting would each need their own ad hoc limit logic. That would be easy to get wrong and hard to keep consistent.
State Flow
- writes:
无— this helper does not write any explicit register in the provided skeleton - reads:
无— no explicit register is listed for this crosscut helper - clears:
无— it does not clear agent state - triggers downstream:
stage-3 / stage-4.9 / subsys-tmux consumers— runs whenever terminal output is about to be embedded into LLM-facing text
Pipeline Hand-Off
Upstream stages produce raw terminal output, often from command execution or terminal-state capture. This helper turns that raw text into bounded, LLM-safe text, which downstream observation-building and prompt-formatting code can include without risking prompt bloat or inconsistent truncation behavior.
Terminus2._limit_output_lengthterminus_2.py:533–572 ↗
UTF-8 byte-based middle truncation helper
Stage context: This function is a small utility in the owning stage's output-length limiting concern. The body itself only transforms the
outputargument under amax_bytesthreshold and does not show its call sites; it contributes the stage's truncation behavior by returning either the original text or a middle-omitted variant.
What this code does
_limit_output_length takes a text string output and a byte budget max_bytes, then returns either the original string or a shortened string with a truncation notice inserted between preserved beginning and ending portions. It measures size with output.encode("utf-8"), not with Python character count. It reads self.max_output_bytes when max_bytes is None and writes no state. The max_bytes value bounds the preserved byte slices taken from the original output, not necessarily the byte length of the final returned string after the notice and newline separators are added.
Interface · params / IO
(self, output: str, max_bytes: int | None = None) -> str
- params:
output:str— text to return unchanged or truncate;max_bytes:int | None— byte budget used for the kept prefix and suffix slices, defaulting toself.max_output_byteswhenNone - returns: A
str: either the originaloutputiflen(output.encode("utf-8")) <= max_bytes, or a new string made from a decoded leading slice, a notice line, and a decoded trailing slice.
Execution flow
- It first replaces
Nonemax_byteswithself.max_output_bytes. - It encodes
outputto UTF-8 for the size check and returnsoutputimmediately whenlen(output.encode("utf-8")) <= max_bytes. - On the truncation path, it sets
portion_size = max_bytes // 2, then encodesoutputagain intooutput_bytesfor byte-accurate slicing. - It takes the first
portion_sizebytes and the lastportion_sizebytes fromoutput_bytes, then decodes each slice witherrors="ignore"intofirst_portionandlast_portion. - It computes
omitted_bytesaslen(output_bytes) - len(first_portion.encode("utf-8")) - len(last_portion.encode("utf-8")), so the count reflects the re-encoded decodable bytes actually kept after boundary cuts. - It returns a formatted string that concatenates
first_portion, a newline-delimited notice containingmax_bytesandomitted_bytes, andlast_portion.
Source
def _limit_output_length(self, output: str, max_bytes: int = 10000) -> str:
"""
Limit output to specified byte length, keeping first and last portions.
Args:
output: The terminal output to potentially truncate
max_bytes: Maximum allowed bytes (default 10000)
Returns:
str: Original output if under limit, or truncated with middle omitted
"""
if len(output.encode("utf-8")) <= max_bytes:
return output
# Calculate portions (half each for first and last)
portion_size = max_bytes // 2
# Convert to bytes for accurate splitting
output_bytes = output.encode("utf-8")
# Get first portion
first_portion = output_bytes[:portion_size].decode("utf-8", errors="ignore")
# Get last portion
last_portion = output_bytes[-portion_size:].decode("utf-8", errors="ignore")
# Calculate omitted bytes
omitted_bytes = (
len(output_bytes)
- len(first_portion.encode("utf-8"))
- len(last_portion.encode("utf-8"))
)
return (
f"{first_portion}\n[... output limited to {max_bytes} bytes; "
f"{omitted_bytes} interior bytes omitted ...]\n{last_portion}"
)
Non-obvious design decisions
- The function uses
output.encode("utf-8")for both the initial size test and the truncation path, so all limits and slices are based on UTF-8 bytes rather than Python characters. That choice keeps the measurement aligned with the byte-orientedmax_bytesparameter visible in the code. - It preserves approximately half the budget from the front and half from the back by setting
portion_size = max_bytes // 2. This favors showing both the start and end of the original byte stream instead of keeping only a prefix or only a suffix. - It decodes sliced byte ranges with
errors="ignore", which deliberately drops incomplete multibyte characters at slice boundaries instead of raising a decode error. The trade-off is visible in the lateromitted_bytescalculation: because it subtractslen(first_portion.encode("utf-8"))andlen(last_portion.encode("utf-8")), ignored boundary bytes count as omitted even though they were inside the raw slices. - The code does not enforce
max_byteson the final returned string.max_byteslimits only the preserved original byte portions; the inserted notice text and newline separators can make the returned string longer thanmax_bytes.
Relations
- Callers: unknown from this function body; external code calls this helper; owning stage utility path for output-length limiting; methods that need a shortened
strbefore passing text onward; anyTerminus2method that suppliesoutputand optionalmax_bytes - Core callees: read of
self.max_output_byteswhenmax_bytes is None;str.encodewith"utf-8"; byte-slice operations onoutput_bytes;bytes.decodewitherrors="ignore"; formatted string construction for the truncation notice - Config / state sources: argument
output; argumentmax_bytes;self.max_output_byteswhenmax_bytesisNone; localportion_size = max_bytes // 2; derived localoutput_bytes = output.encode("utf-8") - Results to: the caller as the function's return value; either the unchanged
outputbranch result; or the synthesized truncated string with notice; downstream consumers chosen by the caller, not shown here