Ejection logic

1 min read
On trails:AGSAFEstep 5/7

Ejection logic is the agent’s escape hatch: a way to stop the loop or hand over when something is going wrong, written as ordinary code the model cannot talk its way past. A stop button is the user’s version of it.

Three triggers, user frustration, user termination and policy violation, feed a pre-check that runs every turn. If nothing triggers, the agent answers. If something does, the run ejects: it stops or hands over to a human. user frustration user termination policy violation Pre-check every turn clear triggered Agent answers Reply Eject stop or hand over Human
The pre-check runs before the model each turn.

Triggers

A pre-check filter runs before the model on every turn and looks for three things.

  • User frustration: the user asks for a person, or the conversation is going round in circles.
  • User termination: the user says stop or cancel.
  • Policy or safety violation: a guardrailLLM guardrailsChecks around the model, so a bad input or a bad output is caught by something that does not rely on the model behaving.Opens in a new tab flagged the request.

Any one of them ejects. The eject path ends the run, and for frustration or a violation it hands the conversation to a human along with what has happened so far.

In LangGraph

The pre-check is a conditional edge from START. It returns the name of the next node, so the model is never reached when it returns "eject".

Python
from typing import Literal, TypedDict from langgraph.graph import END, START, StateGraph  class State(TypedDict, total=False):    message: str    policy_flag: bool    reply: str  def eject_reason(state: State) -> str | None:    text = state["message"].lower().strip()    if text in {"stop", "cancel"}:        return "user_termination"    if "talk to a human" in text or "this is useless" in text:        return "user_frustration"    if state.get("policy_flag"):        return "policy_violation"    return None  def pre_check(state: State) -> Literal["eject", "agent"]:    return "eject" if eject_reason(state) else "agent"  graph = (    StateGraph(State)    .add_node("agent", lambda s: {"reply": "...the model answers..."})    .add_node("eject", lambda s: {"reply": f"Stopped ({eject_reason(s)}). A person takes over."})    .add_conditional_edges(START, pre_check)    .add_edge("agent", END)    .add_edge("eject", END)    .compile())

Stopping conditions

Ejection also covers runaway loops. Anthropic recommends stopping conditions such as a maximum number of iterations to keep an agent under control. LangChain has model and tool call limit middleware for this, with an exit behavior of 'end' for a graceful stop or 'error' to raise.

The catch

Keyword checks for frustration are crude. A classifier does it better but is probabilistic itself, so keep the stop and terminate triggers deterministic, and let the fuzzy one only add a handover.


Sources