Environment Setup
Opening Explanation
This stage exists to turn a configured trial into a live terminal the agent can actually use. Upstream, the agent already knows its fixed settings: pane size, extra environment variables, default user, and whether terminal recording should be enabled. But none of that matters until there is a real shell running inside the trial environment. That is the job here.
Environment Setup owns one thing: creating and starting the tmux session (a terminal you can drive remotely) that the rest of the agent will talk to. It sits between static setup and active execution. Without it, the next stage would have plans and prompts but no terminal to observe, type into, or record.
Main Flow
Terminus2.setup()(prepare this trial’s live terminal) is called once for the trial.- Its purpose is not to make decisions.
- Its job is to bind the agent to the specific
environmentit was given for this run.
- Inside that,
TmuxSession.__init__()(create a remote-controlled terminal wrapper) is used to package together:- the target trial environment
- terminal size
- extra environment variables
- default user
- optional asciinema recording paths Asciinema is a terminal recording format, so this is where recording gets attached if the trial asked for it.
- Then
TmuxSession.start()(actually launch the terminal session) makes that wrapper real.- This is the moment the agent goes from “configured” to “able to interact.”
- After this, later stages can send commands, read screen output, and treat the terminal as the agent’s workspace.
- This stage stops there on purpose.
- It does not run the task.
- It does not inspect output.
- It only guarantees: when
run()begins, a usable terminal already exists.
State Flow
- writes: 无
- reads: 无
- clears: 无
- triggers downstream:
stage-3 Run Onset— after the tmux session has been created and started successfully
Pipeline Hand-Off
Upstream gives this stage frozen configuration from stage-1 plus the concrete trial environment selected by the Harbor orchestrator. This stage produces a live tmux-backed terminal session, which stage-3 can immediately enter and use as the execution surface for the agent loop.
Terminus2.setupterminus_2.py:357–376 ↗
Initialize and start the trial tmux session
Stage context: This stage creates the terminal session that the rest of the trial will use.
Terminus2.setupruns once per trial after the Harbor orchestrator has provided a concreteenvironmentand beforerun()begins. In this stage, it is the entry point that materializes constructor-time terminal settings into a liveTmuxSessionbound to the trial paths.
What this code does
Terminus2.setup builds a TmuxSession for the supplied environment, optionally wiring in asciinema recording paths when _record_terminal_session is enabled. It reads terminal sizing and environment configuration from instance state, stores the created session in self._session, and then starts that session asynchronously. The method returns None; its real product is a running tmux-backed terminal bound to the trial environment.
Interface · params / IO
(self, environment: BaseEnvironment) -> None
- params:
self:?— TheTerminus2instance that owns configuration and receives the created session.;environment:BaseEnvironment— The trial environment that providestrial_pathsanddefault_userfor the session. - reads:
self._record_terminal_session,self._tmux_pane_width,self._tmux_pane_height,self._extra_env - returns: Returns
None; it writesself._sessionand starts the tmux session. - effects: Writes
self._sessionwith a newTmuxSessioninstance; Starts the session by awaitingself._session.start(); Usesenvironment.trial_paths.agent_dirto choose a local recording destination when recording is enabled
Execution flow
- Check
self._record_terminal_sessionto decide whether to derive asciinema paths or leave both recording path arguments asNone. - When recording is enabled, build
local_recording_pathfromenvironment.trial_paths.agent_dir / "recording.cast"andremote_recording_pathfromEnvironmentPaths.agent_dir / "recording.cast". - Create
TmuxSession(...)with the session name fromself.name(), the providedenvironment, a fixed pane log path atEnvironmentPaths.agent_dir / "terminus_2.pane", the two recording-path arguments, pane dimensions fromself._tmux_pane_widthandself._tmux_pane_height, extra environment fromself._extra_env, and the user fromenvironment.default_user. - Store that
TmuxSessioninself._sessionand awaitself._session.start()so the terminal is live before later stages use it.
Source
async def setup(self, environment: BaseEnvironment) -> None:
if self._record_terminal_session:
local_recording_path = environment.trial_paths.agent_dir / "recording.cast"
remote_recording_path = EnvironmentPaths.agent_dir / "recording.cast"
else:
local_recording_path = None
remote_recording_path = None
self._session = TmuxSession(
session_name=self.name(),
environment=environment,
logging_path=EnvironmentPaths.agent_dir / "terminus_2.pane",
local_asciinema_recording_path=local_recording_path,
remote_asciinema_recording_path=remote_recording_path,
pane_width=self._tmux_pane_width,
pane_height=self._tmux_pane_height,
extra_env=self._extra_env,
user=environment.default_user,
)
await self._session.start()
Non-obvious design decisions
- It always constructs
TmuxSessionwithlocal_asciinema_recording_pathandremote_asciinema_recording_path, and disables recording by passingNonefor both. This keeps session construction on one path instead of splitting into separate constructor call shapes. - It derives two recording paths from different roots:
environment.trial_paths.agent_dirfor the local artifact location andEnvironmentPaths.agent_dirfor the in-environment location. That separation lets the session know both where the cast should exist inside the environment and where the harness expects to collect it outside. - It takes the session user from
environment.default_userinstead of a cached self field. That choice binds the shell identity to the concrete trial environment, while pane size andextra_envstay fixed from constructor configuration.
Relations
- Callers: Harbor-orchestrated trial setup flow that invokes
Terminus2.setup(environment)beforerun() - Core callees:
TmuxSession;TmuxSession.start;self.name - Config / state sources:
self._record_terminal_session;self._tmux_pane_width;self._tmux_pane_height;self._extra_env;environment.trial_paths.agent_dir;environment.default_user;EnvironmentPaths.agent_dir - Results to:
self._sessionfor later trial interaction; the live tmux terminal used by laterrun()logic; optional asciinema output atrecording.cast; pane log output atEnvironmentPaths.agent_dir / "terminus_2.pane"
TmuxSession.__init__tmux_session.py:28–57 ↗
Initialize tmux session configuration and recording state
Stage context: This constructor prepares the
TmuxSessionobject that stage-2 will later start for a trial environment.Terminus2.setupinvokes it while assembling the session described in the sibling entry, then hands the initialized object off toTmuxSession.start. In this stage, its job is to freeze session configuration, validate pane sizing, and seed bookkeeping fields needed by later setup and teardown paths.
What this code does
TmuxSession.__init__ captures the session name, bound environment, logging and optional asciinema paths, pane size, extra environment variables, and optional user into instance state. It validates pane_width and pane_height by coercing them through int(...) and rejecting non-positive values with ValueError. It returns None; the real result is a fully initialized TmuxSession object with recording flags, marker storage, and buffer cache set to known defaults.
Interface · params / IO
(self, session_name: str, environment: BaseEnvironment, logging_path: Path | PurePosixPath, local_asciinema_recording_path: Path | None, remote_asciinema_recording_path: Path | PurePosixPath | None, pane_width: int = 200, pane_height: int = 50, extra_env: dict[str, str] | None = None, user: str | int | None = None)
- params:
session_name:str— tmux session identifier stored in_session_name;environment:BaseEnvironment— trial environment bound toself.environment;logging_path:Path | PurePosixPath— path for terminal logging stored in_logging_path;local_asciinema_recording_path:Path | None— host-side recording destination stored in_local_asciinema_recording_path;remote_asciinema_recording_path:Path | PurePosixPath | None— environment-side recording destination stored in_remote_asciinema_recording_path;pane_width:int— requested tmux pane width, validated then stored in_pane_width;pane_height:int— requested tmux pane height, validated then stored in_pane_height;extra_env:dict[str, str] | None— extra environment variables, normalized into_extra_env;user:str | int | None— default execution user stored in_user - returns: Returns
None; its real product is initialized instance state for laterTmuxSession.start/stopwork. - effects: writes
self._pane_width; writesself._pane_height; writesself._logging_path; writesself._local_asciinema_recording_path; writesself._remote_asciinema_recording_path; writesself._session_name; writesself._logger; writesself._previous_buffer; writesself._disable_recording; writesself.environment; writesself._markers; writesself._extra_env; writesself._user; raisesValueErrorfor non-integer or non-positive pane dimensions
Execution flow
- It coerces
pane_widthandpane_heightthroughint(...)and raisesValueError("pane_width and pane_height must be valid integers.")if either conversion fails. - It rejects zero or negative dimensions by checking
_pane_width <= 0 or _pane_height <= 0and raisingValueError("pane_width and pane_height must be positive integers."). - It stores the session-level configuration from the arguments into
_logging_path,_local_asciinema_recording_path,_remote_asciinema_recording_path,_session_name,environment, and_user. - It seeds runtime helpers and bookkeeping with
_logger = logger,_previous_buffer = None,_disable_recording = False,_markers = [], and_extra_env = extra_env or {}.
Source
def __init__(
self,
session_name: str,
environment: BaseEnvironment,
logging_path: Path | PurePosixPath,
local_asciinema_recording_path: Path | None,
remote_asciinema_recording_path: Path | PurePosixPath | None,
pane_width: int = 160,
pane_height: int = 40,
extra_env: dict[str, str] | None = None,
user: str | int | None = None,
):
try:
self._pane_width = int(pane_width)
self._pane_height = int(pane_height)
except (ValueError, TypeError):
raise ValueError("pane_width and pane_height must be valid integers.")
if self._pane_width <= 0 or self._pane_height <= 0:
raise ValueError("pane_width and pane_height must be positive integers.")
self._logging_path = logging_path
self._local_asciinema_recording_path = local_asciinema_recording_path
self._remote_asciinema_recording_path = remote_asciinema_recording_path
self._session_name = session_name
self._logger = logger
self._previous_buffer: str | None = None
self._disable_recording = False
self.environment = environment
self._markers: list[tuple[float, str]] = []
self._extra_env: dict[str, str] = extra_env or {}
self._user = user
Non-obvious design decisions
- It validates pane size at construction time, not when tmux starts. That makes bad
pane_widthandpane_heightfail immediately inTerminus2.setupinstead of surfacing later during session startup. - It runs both size inputs through
int(...)before storing them. This accepts integer-like inputs while still rejecting unusable values through theexcept (ValueError, TypeError)path. - It normalizes
extra_envwithextra_env or {}so later code can treat_extra_envas a dictionary without repeatedNonechecks. The trade-off is that an explicitly empty dict andNonebecome equivalent. - It initializes recording-related state even when the recording paths may be
None. Keeping_disable_recording,_markers, and_previous_bufferpresent from the start gives later methods one consistent object shape.
Relations
- Callers:
Terminus2.setup; stage-2 environment setup flow that constructs the per-trial terminal session - Core callees:
int;ValueError - Config / state sources:
Terminus2.setuppassessession_name,environment, and recording paths; stage-1-crystallized pane sizing feedspane_widthandpane_height(defaulting now to200x50when omitted); stage-1-crystallized environment overrides feedextra_env; stage-1-crystallized default user feedsuser - Results to:
TmuxSession.startconsumes_session_name, pane sizing, paths, and environment binding;TmuxSession.stopcan consume_markersand recording flags later in the trial lifecycle; later buffer-handling code can read and update_previous_buffer;Terminus2.setupstores the constructed object inself._sessionand starts it - Related siblings:
Terminus2.setupbuilds this object and then starts it
reg-asciinema-markers— initialize empty marker list for later recording
TmuxSession.starttmux_session.py:429–470 ↗
boot tmux session and optional terminal recording
Stage context: This method performs the concrete startup work for the
TmuxSessionthatTerminus2.setupcreated earlier in stage-2.Terminus2.setupcalls it once per trial, afterTmuxSession.__init__has frozen session configuration such as_user, pane sizing, and recording paths. Within this stage, it is the step that turns stored configuration into a live remote tmux shell, with optional asciinema capture enabled inside that shell.
What this code does
TmuxSession.start starts the remote tmux session represented by this object, using self.environment to run commands as self._user. It first ensures tmux is available, then launches the session, tries to raise tmux scrollback history, and, when _remote_asciinema_recording_path is set, starts an asciinema rec --stdin process in the pane and uploads a timestamp helper script. It returns None; its real result is a prepared remote terminal session, or a RuntimeError if session startup itself fails.
Interface · params / IO
(self) -> None
- params:
self:TmuxSession— session object holding the boundenvironment, startup command, logging, user, and optional recording paths - reads:
self.environment,self._tmux_start_session,self._user,self._logger,self._remote_asciinema_recording_path,self._GET_ASCIINEMA_TIMESTAMP_SCRIPT_HOST_PATH,self.GET_ASCIINEMA_TIMESTAMP_SCRIPT_CONTAINER_PATH - returns:
None; the real product is a running remote tmux session with enlarged history when possible and optional asciinema recording setup. - effects: calls
self._attempt_tmux_installation()before startup; executes the tmux start command in the remoteenvironment; raisesRuntimeErrorif the tmux start command returns a non-zeroreturn_code; executestmux set-option -g history-limit 10000000in the remoteenvironment; logs a warning through_loggerif the history-limit command fails; when_remote_asciinema_recording_pathis set, sends keys to the tmux pane to startasciinema rec --stdin ...; when_remote_asciinema_recording_pathis set, sendsclearto the tmux pane; when_remote_asciinema_recording_pathis set, uploads the asciinema timestamp helper script into the remote environment
Execution flow
- It calls
_attempt_tmux_installation()to make sure tmux is present before trying to open the session. - It runs
self._tmux_start_sessionthroughself.environment.exec(..., user=self._user)and treats any non-zeroreturn_codeas fatal by raisingRuntimeErrorwithstart_session_result.stderr. - It then asks tmux to use a large scrollback buffer by executing
tmux set-option -g history-limit 10000000asself._user. - If that history-limit command fails, it logs a warning with
_logger.warning(...)and continues instead of aborting startup. - If
_remote_asciinema_recording_pathis set, it starts asciinema inside the tmux pane by callingsend_keys(...)withasciinema rec --stdin ...followed byEnter, usingmin_timeout_sec=1.0to give recording startup time. - Still under the recording branch, it clears the pane with another
send_keys(...)call so the captured shell starts from a clean screen. - Finally, under the same recording condition, it uploads the helper script from
_GET_ASCIINEMA_TIMESTAMP_SCRIPT_HOST_PATHtoGET_ASCIINEMA_TIMESTAMP_SCRIPT_CONTAINER_PATHin the remote environment.
Source
async def start(self) -> None:
await self._attempt_tmux_installation()
start_session_result = await self.environment.exec(
command=self._tmux_start_session, user=self._user
)
if start_session_result.return_code != 0:
raise RuntimeError(
f"Failed to start tmux session. Error: {start_session_result.stderr}"
)
history_limit = 10_000_000
command = f"tmux set-option -g history-limit {history_limit}"
set_history_result = await self.environment.exec(
command=command, user=self._user
)
if set_history_result.return_code != 0:
self._logger.warning(
"Failed to increase tmux history-limit: %s",
(set_history_result.stderr or "").strip(),
)
if self._remote_asciinema_recording_path:
self._logger.debug("Starting recording.")
await self.send_keys(
keys=[
f"asciinema rec --stdin {self._remote_asciinema_recording_path}",
"Enter",
],
min_timeout_sec=1.0,
)
await self.send_keys(
keys=[
"clear",
"Enter",
],
)
if self._remote_asciinema_recording_path:
await self.environment.upload_file(
source_path=self._GET_ASCIINEMA_TIMESTAMP_SCRIPT_HOST_PATH,
target_path=str(self.GET_ASCIINEMA_TIMESTAMP_SCRIPT_CONTAINER_PATH),
)
Non-obvious design decisions
- It makes tmux session creation a hard requirement but treats scrollback tuning as optional. The branch on
start_session_result.return_coderaises immediately, while the branch onset_history_result.return_codeonly logs, so the code preserves a usable shell even when tmux refuses the larger history setting. - It gates all recording work on
_remote_asciinema_recording_pathinstead of a separate flag. That keeps recording setup tied to the presence of a concrete destination path; without that path, startingasciinema recwould not have enough information to produce a file. - It starts recording from inside the tmux pane with
send_keys(...)rather than as an out-of-band process throughenvironment.exec(...). That choice keeps the recorder attached to the same interactive shell stream that the agent will use, which is necessary for--stdincapture. - It sends
clearright after launching asciinema so the recording begins from a clean terminal view instead of including startup noise. The alternative would preserve whatever shell text preceded recording, which would make the capture harder to read. - It uploads the timestamp helper script only when recording is enabled. That avoids modifying the remote environment with recording-specific files during non-recorded trials.
Relations
- Callers:
Terminus2.setup; stage-2 environment setup flow that starts the per-trialTmuxSession - Core callees:
TmuxSession._attempt_tmux_installation;environment.exec;TmuxSession.send_keys;environment.upload_file;_logger.warning;_logger.debug - Config / state sources:
TmuxSession.__init__populates_tmux_start_session;TmuxSession.__init__stores the boundenvironment;TmuxSession.__init__stores_user;TmuxSession.__init__stores_remote_asciinema_recording_path;Terminus2.setupdecides whether recording paths are passed intoTmuxSession - Results to: the remote trial environment now has a live tmux session; later terminal I/O methods operate against the started pane; optional asciinema output is written to
_remote_asciinema_recording_path; the uploaded timestamp helper script is available for later recording-related use;TmuxSession.stopin stage-6 can later shut down the session and finalize recording artifacts - Related siblings:
Terminus2.setupcreates theTmuxSessionand awaits this method;TmuxSession.__init__validates and stores the startup configuration that this method consumes