Skip to content

interlens.stop

interlens.stop

AnyStopCondition

AnyStopCondition(conditions: list[StopCondition])

Bases: StopCondition

Fires when any member condition fires. run(until=[...]) wraps a list in this.

Source code in src/interlens/stop/stop_condition.py
def __init__(self, conditions: list[StopCondition]):
	self.conditions = list(conditions)

ElapsedTimeStopCondition

ElapsedTimeStopCondition(seconds: float)

Bases: StopCondition

Stop once seconds of wall-clock have elapsed since the run started (monotonic clock).

Source code in src/interlens/stop/conditions.py
def __init__(self, seconds: float):
	self.seconds = seconds
	self.start = None

StopCondition

Bases: ABC

A stateful predicate that ends a Conversation.run early.

Each condition is stateful (tracks its own counters) and is checked after every committed turn via should_stop(conversation, last_message). reset() clears state and is called at the start of each run (and branches get fresh copies), so one instance can be reused across runs without leaking state.

A condition may also cap the next generation via turn_cap (e.g. a token budget shrinks the last turn so it lands exactly on budget) and may be installed ambiently as a context manager (with TokenBudget(...): conv.rollout(...) applies it to every conversation in the block). New conditions subclass this without any change to run.

reset

reset() -> None

Clear any accumulated state. Default no-op for stateless conditions.

Source code in src/interlens/stop/stop_condition.py
def reset(self) -> None:
	"""Clear any accumulated state. Default no-op for stateless conditions."""

turn_cap

turn_cap(conversation: 'Conversation') -> int | None

An upper bound on the NEXT turn's generated tokens, or None for no cap. The run loop passes it as max_new_tokens (bounded by the speaker's own cap), so a budget condition can prevent both overshoot and a single turn from consuming the whole allowance. Default: no cap.

Source code in src/interlens/stop/stop_condition.py
def turn_cap(self, conversation: "Conversation") -> int | None:
	"""An upper bound on the NEXT turn's generated tokens, or ``None`` for no cap. The run loop passes it as
	``max_new_tokens`` (bounded by the speaker's own cap), so a budget condition can prevent both overshoot and a
	single turn from consuming the whole allowance. Default: no cap."""
	return None

StopStringCondition

StopStringCondition(strings)

Bases: StopCondition

Stop when a committed message's visible content contains any of the given strings (a done-signal).

Source code in src/interlens/stop/conditions.py
def __init__(self, strings):
	self.strings = [strings] if isinstance(strings, str) else list(strings)

TokenBudget

TokenBudget(
    per_conversation: int | None = None,
    per_turn: int | None = None,
)

Bases: StopCondition

A per-conversation compute budget — the matched-compute primitive for fair solo-vs-pair comparisons.

per_conversation stops a conversation once ITS OWN cumulative generated tokens reach the budget, and per_turn caps each individual turn so the allowance is spread across real conversation turns rather than consumed by one monologue. Both are enforced via turn_cap too: the run loop shrinks the next generation to min(speaker cap, per_turn, per_conversation - spent), so the budget is respected without overshoot.

The budget is per-conversation, not a shared pool. Spend is read from the conversation's own transcript (metadata['n_tokens']), so the condition is stateless — in a rollout of N copies, each copy independently gets the full budget. Cheap to count (never re-tokenizes) and trivially picklable, so it works installed directly (run_until=TokenBudget(...)) or ambiently (with TokenBudget(per_conversation=200): conv.rollout()).

Source code in src/interlens/stop/conditions.py
def __init__(self, per_conversation: int | None = None, per_turn: int | None = None):
	if per_conversation is None and per_turn is None:
		raise ValueError("TokenBudget needs at least one of per_conversation= or per_turn=")
	self.per_conversation = per_conversation
	self.per_turn = per_turn

TokenStopCondition

TokenStopCondition(max_tokens: int)

Bases: StopCondition

Stop once the total generated tokens across turns reach max_tokens.

The per-turn count comes from Message.metadata['n_tokens'], which ModelParticipant.generate records — so the source of truth is defined, not guessed. Turns without a count (e.g. seeded messages) contribute 0.

Source code in src/interlens/stop/conditions.py
def __init__(self, max_tokens: int):
	self.max_tokens = max_tokens
	self.total = 0

TurnStopCondition

TurnStopCondition(max_turns: int)

Bases: StopCondition

Stop after max_turns committed turns.

Source code in src/interlens/stop/conditions.py
def __init__(self, max_turns: int):
	self.max_turns = max_turns
	self.count = 0

active_stop_conditions

active_stop_conditions() -> tuple['StopCondition', ...]

The stop conditions currently installed by enclosing with condition: blocks (outermost first).

Source code in src/interlens/stop/stop_condition.py
def active_stop_conditions() -> tuple["StopCondition", ...]:
	"""The stop conditions currently installed by enclosing ``with condition:`` blocks (outermost first)."""
	return _ambient.get()