Loop Entry Gates
Opening Explanation
This stage exists to do two things at the very top of every loop iteration: stamp the iteration with the current episode number, and decide whether the agent is still allowed to continue because its terminal is still alive. That sounds small, but it protects the rest of the loop from running on a dead terminal and gives later telemetry a stable, user-facing iteration count. It sits right at loop entry because both facts matter before anything else happens. The rest of the pipeline should only run if there is still a live tmux session (a terminal you can drive remotely). If that session is gone, there is nothing useful left to observe, plan, or act on.
Main Flow
- The loop first updates the episode counter.
self._n_episodes = episode + 1turns the internal zero-based loop index into a 1-based count people can read in logs and metadata. This stage does it early so the iteration is already stamped even if the loop stops immediately after. That gives downstream reporting a consistent “which attempt was this?” value. - Then it checks whether the terminal still exists.
is_session_alive()(asks whether the tmux session is still running) is the real gate here. Its job is not to produce LLM context or agent state. Its job is to answer a simple control question: can the agent safely keep going? - The result becomes a continue-or-exit decision.
If the session is alive, the loop continues to
stage-4.2 Proactive Summarize Probe. If it is dead, this stage logs that the session ended and terminates the loop/run from inside the loop body. No later stage in this iteration runs.
This is basically a guardrail. Without it, later stages would try to reason about or act on a terminal that no longer exists.
State Flow
- writes:
reg-n-episodes— written at the start of each iteration to store the 1-based episode count for user-facing telemetry; this stamp happens before the liveness gate so the attempted iteration is still counted consistently - reads: no tracked registers — this stage does not consume any tracked register as input
- clears: no tracked registers — this stage leaves all tracked state unchanged except for the episode-count write
- triggers downstream:
stage-4.2 Proactive Summarize Probe— continue to next stage if the tmux session is alive; otherwise terminate the loop/run and skip all downstream stages
Session liveness is checked from external runtime/session state, not from a tracked register, and it does not write any tracked register besides reg-n-episodes.
Pipeline Hand-Off
Upstream hands this stage a fresh loop iteration. This stage produces two outputs: an updated episode register for later metadata, and a control-flow decision about whether the loop may continue. If the terminal is alive, downstream stages inherit a stamped iteration and keep running; if not, no downstream stage runs at all.
TmuxSession.is_session_alivetmux_session.py:518–524 ↗
tmux session liveness probe
Stage context: This function supplies the loop-entry liveness check for stage [stage-4.1](stage-4.1.html).
Terminus2._run_agent_loopcalls it at the start of each iteration, after updating the user-facing episode counter, to decide whether the loop should continue. In this stage, it is the gate that can end the loop early when the backing tmux session no longer exists.
What this code does
TmuxSession.is_session_alive checks whether the tmux session named by self._session_name still exists in self.environment for self._user. It returns True when the probe command succeeds and False when it does not. The method does not change instance state; its only external action is invoking self.environment.exec(...) with a tmux existence check.
Interface · params / IO
async def is_session_alive(self) -> bool
- params:
self:?— boundTmuxSessioninstance that provides environment, session name, and user - reads:
self.environment,self._session_name,self._user - returns: A boolean:
Trueifself.environment.exec(...)reportsreturn_code == 0fortmux has-session -t <session>, otherwiseFalse. - effects: Runs
tmux has-session -t <session>throughself.environment.exec(...)asself._user
Execution flow
- Build the probe command from
self._session_nameastmux has-session -t <session>. - Call
await self.environment.exec(...)with that command anduser=self._user, and capture the result object inresult. - Convert the probe outcome to a boolean by testing
result.return_code == 0and return that value.
Source
async def is_session_alive(self) -> bool:
"""Check if the tmux session is still alive."""
result = await self.environment.exec(
command="tmux has-session -t {}".format(self._session_name),
user=self._user,
)
return result.return_code == 0
Non-obvious design decisions
- It treats tmux's exit status as the single source of truth by checking
result.return_code == 0. That avoids parsing command output, which would add format assumptions thattmux has-sessiondoes not require. - It delegates execution to
self.environment.exec(...)and passesuser=self._userinstead of running locally or assuming a default user. That keeps the liveness check aligned with the same execution context that owns the tmux session.
Relations
- Callers:
Terminus2._run_agent_loopat stage [stage-4.1](stage-4.1.html) uses the returned boolean as the loop-entry gate - Core callees:
self.environment.exec; external commandtmux has-session - Config / state sources:
self._session_namesupplies the tmux target name;self._userselects the execution user;self.environmentprovides the command-execution backend - Results to: Returns a boolean to
Terminus2._run_agent_loop; Feeds the stage-4.1 branch that logs "Session has ended" and returns from the loop onFalse
Terminus2._run_agent_loopterminus_2.py:1266–1287 ↗
loop setup and per-iteration liveness gate
Stage context: This region opens
Terminus2._run_agent_loopand performs the stage's two entry-gate duties before later loop work can proceed. It runs once at loop startup to validate required objects and reset run-level accounting onself._context, then runs at the top of each iteration to publish the 1-based episode counter and stop immediately if the tmux session is already gone. Its liveness check delegates to the siblingTmuxSession.is_session_alivetranslation.
What this code does
This region verifies that self._context and self._session are present, seeds the working prompt from initial_prompt, and resets aggregate token and cost fields on self._context. It then enters the episode loop, writes self._n_episodes as a 1-based counter for the current iteration, and probes self._session.is_session_alive(). If the session is dead at iteration start, it logs a debug message and returns from _run_agent_loop immediately.
Interface · params / IO
(self, initial_prompt: str) -> None
- params:
self:Terminus2— agent runner instance that owns context, session, counters, and logger;initial_prompt:str— starting prompt text copied into the loop's workingprompt - reads:
self._context,self._session,self._max_episodes,self.logger - returns: Returns
None; its real product is initialized run context plus either entry into the episode loop or an early loop exit when the session is dead. - effects: writes local
prompt = initial_promptfor later loop use; setsself._context.n_input_tokens = 0; setsself._context.n_output_tokens = 0; setsself._context.n_cache_tokens = 0; setsself._context.cost_usd = None; writesself._n_episodes = episode + 1each iteration; awaitsself._session.is_session_alive(); emitsself.logger.debug(...)before early return when the session is not alive; raisesRuntimeErrorifself._contextisNone; raisesRuntimeErrorifself._sessionisNone
Execution flow
- Check
self._context; if it isNone, raiseRuntimeErrorbecause the loop requires a bound run context. - Copy
initial_promptinto localprompt, then reset shared run totals onself._context:n_input_tokens,n_output_tokens,n_cache_tokens, andcost_usd. - Check
self._session; if it isNone, raiseRuntimeErrorbecause the loop cannot interact with the terminal session without it. - Start
for episode in range(self._max_episodes)and publish the user-facing iteration number withself._n_episodes = episode + 1. - At the top of each iteration, await
self._session.is_session_alive(); if it returnsFalse, logSession has ended, breaking out of agent loopandreturnfrom_run_agent_loop.
Source
if self._context is None:
raise RuntimeError("Agent context is not set. This should never happen.")
prompt = initial_prompt
self._context.n_input_tokens = 0
self._context.n_output_tokens = 0
self._context.n_cache_tokens = 0
self._context.cost_usd = None
if self._session is None:
raise RuntimeError("Session is not set. This should never happen.")
# Step ID offset accounts for initial steps in the trajectory:
# Step 1: system message (includes task instruction)
# Steps 2+: agent episodes (starting from episode 0)
for episode in range(self._max_episodes):
self._n_episodes = episode + 1
if not await self._session.is_session_alive():
self.logger.debug("Session has ended, breaking out of agent loop")
return
Non-obvious design decisions
- The region fails fast on missing
self._contextandself._sessionwithRuntimeErrorinstead of trying to recover. That keeps later loop code from running against impossible state and makes these invariants explicit. - It resets
self._contexttoken and cost aggregates here, at loop entry, so per-run accounting starts from a known baseline. Deferring that reset to later stages would mix setup concerns with per-episode work and risk carrying stale totals across runs. - It stores
self._n_episodesasepisode + 1, not the zero-based loop index. That matches user-facing telemetry expectations, while the loop still uses Python's natural zero-basedrange(self._max_episodes)internally. - It checks session liveness at the start of every iteration and returns immediately on failure. This centralizes the only non-completion early-exit path inside the loop; letting later stages discover a dead session would spread shutdown handling across unrelated code.
Relations
- Callers:
Terminus2.run, via its call intoTerminus2._run_agent_loop - Core callees:
TmuxSession.is_session_alive;self.logger.debug - Config / state sources:
initial_promptargument;self._context;self._session;self._max_episodes - Results to: later regions of
Terminus2._run_agent_loop, which consume localpromptand the initialized context totals; [stage-5](stage-5.html) metadata surfacing throughself._n_episodesandself._contextaggregate fields;Terminus2.runfinally-path, which still executes after this region returns early; debug logs that record session-ended loop termination - Related siblings:
TmuxSession.is_session_alivesupplies the session-exists probe used by this gate.
reg-n-episodes— set to 1-based count each iteration