Error Branch
Opening Explanation
This stage exists to catch a bad LLM reply before the agent acts on it. Sometimes the model returns output that cannot be parsed into the expected shape, so the system does not have a safe command list, plan, or handoff to trust. Instead of guessing, Terminus 2 stops the normal action path and turns the parse failure into a repair prompt for the next LLM turn. That is the whole job here: detect “we could not understand your answer,” record that failed turn in the trajectory, and ask for a corrected reply. It sits between “we got an LLM response” and “go run commands,” because this is the last safe place to prevent malformed output from becoming real terminal actions.
Main Flow
- The stage checks whether parser feedback says the last LLM answer was invalid.
In practice, it looks for an
"ERROR:"signal infeedback. That means the model answered, but not in a form the agent can safely use. - If there was a parse error, the stage rebuilds
promptinto a repair message.The new prompt basically says: your previous response had formatting or parsing problems, here is the error, please try again.
_get_error_response_type()(figures out the human-readable label for the expected response shape) is used so the retry message names the kind of answer the parser wanted. That gives the model a more precise correction target. - The stage still records this failed turn as part of the run history.
That matters because the agent should remember that the model did answer, even if the answer was unusable. The stored step uses the raw LLM text as the agent message, not a cleaned analysis or plan, because parsing never succeeded. It also attaches the repair prompt as an observation result, plus per-step metrics like token cache use and cost.
- Then the stage stops this iteration early.
It does not run commands. It does not check for completion. It does not enter the normal loop tail. The only next move is: go back around and ask the LLM again with the error-repair prompt.
- Why this design matters: it keeps malformed model output in the “conversation repair” lane instead of the “real-world action” lane.
Without this branch, the agent would either crash, lose the failure context, or worse, try to act on half-parsed output.
State Flow
- writes: 无 — this stage is not associated with any explicit skeleton register in the provided input
- reads: 无 — no explicit skeleton register is named for this stage in the provided input
- clears: 无 — no explicit skeleton register is cleared here in the provided input
- triggers downstream: stage-4.9 Command Execute → Observation → Trajectory Step — not triggered when parser feedback contains
"ERROR:"; this stagecontinues and skips command execution for the iteration
Pipeline Hand-Off
Upstream, this stage receives an LLM reply plus parser feedback saying that reply could not be understood. It produces a retry prompt and a recorded error step, then sends the loop back to another LLM attempt instead of handing anything to command execution.
Terminus2._run_agent_loopterminus_2.py:1384–1426 ↗
Parser-error repair prompt and trajectory logging
Stage context: This region handles the
if feedback and "ERROR:" in feedbackbranch insideTerminus2._run_agent_loop. It runs after an LLM response produced parser feedback, rebuildspromptfor a repair attempt, records the failed raw response as aStep, and ends this loop iteration withcontinue. This is the first translated region for this stage, so it stands alone here.
What this code does
When feedback contains "ERROR:", this branch replaces the next-iteration prompt with a parser-repair message that embeds feedback and the label from self._get_error_response_type(). It computes per-step token and cost deltas from chat.total_* counters and the tokens_before_* / cost_before snapshots, then appends one agent Step to self._trajectory_steps using the raw llm_response.content, a corrective ObservationResult(content=prompt), and Metrics built from the deltas and token/logprob fields on llm_response. It may also copy llm_response.model_name into self._last_response_model_name, and it returns no value because it exits this loop pass via continue.
Interface · params / IO
(self) -> None
- params:
self:Terminus2— Owns loop state, model defaults, and trajectory storage;feedback:?— Parser feedback string tested for"ERROR:";chat:?— Source of cumulative token and cost counters:total_cache_tokens,total_cost,total_input_tokens,total_output_tokens;tokens_before_cache:?— Earlier cache-token snapshot used to compute this step's delta;cost_before:?— Earlier cost snapshot used to compute this step's delta;tokens_before_input:?— Earlier prompt-token snapshot used to compute this step's delta;tokens_before_output:?— Earlier completion-token snapshot used to compute this step's delta;llm_response:?— Raw model response; supplies content, reasoning, model name, token ids, and logprobs - reads:
self._get_error_response_type,self._trajectory_steps,self._model_name - returns: None; the branch's product is a rewritten local
prompt, an optional write toself._last_response_model_name, and one appendedStepinself._trajectory_steps, thencontinueskips the rest of the current loop iteration. - effects: Writes local
promptto a parser-repair message; May writeself._last_response_model_namewhenllm_response.model_nameis truthy; Appends oneStep(...)toself._trajectory_steps; Callsdatetime.now(timezone.utc).isoformat()to stamp the recorded step; Ends the current loop iteration withcontinue
Execution flow
- Check
feedbackand enter this branch only when it is truthy and contains"ERROR:". - Rebuild local
promptfromfeedbackplusself._get_error_response_type(), using the fixed text"Previous response had parsing errors:"and"Please fix these issues and provide a proper ...". - Compute
cache_tokens_used = chat.total_cache_tokens - tokens_before_cacheandstep_cost = chat.total_cost - cost_before. - If
llm_response.model_nameis truthy, copy it intoself._last_response_model_name. - Append one
Steptoself._trajectory_stepswithstep_id=len(self._trajectory_steps) + 1,timestamp=datetime.now(timezone.utc).isoformat(),source="agent",model_name=llm_response.model_name or self._model_name,message=llm_response.content,reasoning_content=llm_response.reasoning_content, andobservation.results=[ObservationResult(content=prompt)]. - Build
Step.metricsfrom counter deltas and response metadata: prompt/completion token deltas fromchat.total_input_tokensandchat.total_output_tokens,cached_tokensonly whencache_tokens_used > 0,cost_usdonly whenstep_cost > 0, plusllm_response.prompt_token_ids,completion_token_ids, andlogprobs. - Execute
continueto skip the remainder of the current loop iteration.
Source
if feedback and "ERROR:" in feedback:
prompt = (
f"Previous response had parsing errors:\n{feedback}\n\n"
f"Please fix these issues and provide a proper "
f"{self._get_error_response_type()}."
)
# For error cases, we still want to record the step
# Use the raw response as the message since parsing failed
cache_tokens_used = chat.total_cache_tokens - tokens_before_cache
step_cost = chat.total_cost - cost_before
if llm_response.model_name:
self._last_response_model_name = llm_response.model_name
self._trajectory_steps.append(
Step(
step_id=len(self._trajectory_steps) + 1,
timestamp=datetime.now(timezone.utc).isoformat(),
source="agent",
model_name=llm_response.model_name or self._model_name,
message=llm_response.content,
reasoning_content=llm_response.reasoning_content,
observation=Observation(
results=[
ObservationResult(
content=prompt,
)
]
),
metrics=Metrics(
prompt_tokens=chat.total_input_tokens - tokens_before_input,
completion_tokens=chat.total_output_tokens
- tokens_before_output,
cached_tokens=cache_tokens_used
if cache_tokens_used > 0
else None,
cost_usd=step_cost if step_cost > 0 else None,
prompt_token_ids=llm_response.prompt_token_ids,
completion_token_ids=llm_response.completion_token_ids,
logprobs=llm_response.logprobs,
),
)
)
continue
Non-obvious design decisions
- The recorded
Step.messageusesllm_response.contentrather than any parsed structure. The nearby comment anchors this choice: parsing failed, so the branch preserves the raw model output in the trajectory. - The branch updates
self._last_response_model_nameonly whenllm_response.model_nameis truthy, but the storedStep.model_namestill falls back toself._model_name. That split avoids overwriting the remembered last-response model with an empty value while still guaranteeing a model name in the trajectory record. - The metrics writer suppresses non-positive
cached_tokensandcost_usdby storingNoneunless the computed delta is greater than zero. This keeps theMetricspayload from claiming cache use or cost for a step when the counter difference is zero or negative.
Relations
- Callers: Terminus2._run_agent_loop
- Core callees: self._get_error_response_type(); datetime.now(timezone.utc).isoformat(); Step(...); Observation(...); ObservationResult(...); Metrics(...)
- Config / state sources:
feedbackbranch condition;chat.total_cache_tokens,chat.total_cost,chat.total_input_tokens,chat.total_output_tokens;tokens_before_cache,cost_before,tokens_before_input,tokens_before_output;llm_response.model_name,llm_response.content,llm_response.reasoning_content;llm_response.prompt_token_ids,llm_response.completion_token_ids,llm_response.logprobs;self._model_namefallback - Results to: local
prompt;self._last_response_model_name;self._trajectory_steps; current loop control viacontinue
reg-trajectory-steps— append parser-error agent step with observation
Terminus2._get_error_response_typeterminus_2.py:475–487 ↗
Parser-name to error-label mapper
Stage context: This helper supplies the short response-type label used by the stage's parser-error path. Within the provided stage materials, the sibling entry for
Terminus2._run_agent_loopstates that the error branch callsself._get_error_response_type()while rebuilding the repair prompt after a parse failure.
What this code does
This function maps self._parser_name to the human-readable label used in error text. It returns "JSON response" for "json" and "response" for "xml". It does not write any state. If self._parser_name matches neither branch, it raises ValueError whose message includes the unsupported parser name and instructs the caller to use 'json' or 'xml'.
Interface · params / IO
(self) -> str
- params:
self:?— Instance providing_parser_name - reads:
self._parser_name - returns: A short error-message label:
"JSON response"whenself._parser_name == "json", or"response"whenself._parser_name == "xml"; otherwise it raisesValueErrorwith a message that embedsself._parser_nameand says to use'json'or'xml'.
Execution flow
- Check
self._parser_nameagainst the first supported value,"json", and return the literal label"JSON response"when it matches. - Otherwise check
self._parser_nameagainst the second supported value,"xml", and return the literal label"response"when it matches. - If neither condition matches, take the
elsebranch and raiseValueErrorwithf"Unknown parser_name: {self._parser_name}. Use 'json' or 'xml'.".
Source
def _get_error_response_type(self) -> str:
"""Return the response type name for error messages.
Examples: 'JSON response', 'response'
"""
if self._parser_name == "json":
return "JSON response"
elif self._parser_name == "xml":
return "response"
else:
raise ValueError(
f"Unknown parser_name: {self._parser_name}. Use 'json' or 'xml'."
)
Non-obvious design decisions
- The mapping is explicit and closed: the code recognizes only two parser names,
"json"and"xml", instead of falling back to a default label for unknown values. - The XML branch returns the generic string
"response"rather than a parser-specific label such as"XML response"; this wording is an observed code choice in theelif self._parser_name == "xml"branch. - The function fails fast on unsupported configuration. The
elsebranch raisesValueErrorimmediately, and its message both echoes the badself._parser_namevalue and names the allowed values'json'and'xml'.
Relations
- Callers: Terminus2._run_agent_loop
- Core callees: ValueError
- Config / state sources: self._parser_name
- Results to: Parser-error prompt text built in
Terminus2._run_agent_loop; Exception path for unsupported parser configuration - Related siblings: Terminus2._run_agent_loop: uses this label when the parser-error branch rebuilds the repair prompt