runSession method
Run a full session. Returns when one of the PRD §17 termination conditions fires:
- 50 turns OR 15 minutes wall-clock →
budget_exhausted - 3 consecutive failed turns →
harness_error agent_stuck - VM connection lost mid-turn →
harness_error connection_lost - malformed observation envelope →
harness_error observation_envelope_rejected - any unclassified escaping exception →
harness_error unclassified(the footer names it; the exception still propagates) - voluntary
core.done(reason)→done
On termination the trajectory writer is closed with the final
footer (idempotently). BindingNotInitializedError is raised by
LeonardSession.start() before runSession is
invoked, so it is never observed here.
Implementation
Future<SessionTermination> runSession() async {
_sessionStart = _clock();
SessionTermination? termination;
Object? escaped;
try {
while (true) {
if (_turnIndex >= _maxTurns ||
_clock().difference(_sessionStart!) >= _sessionBudget) {
termination = SessionTermination(SessionOutcome.budgetExhausted);
return termination;
}
if (_consecutiveFailedTurns >= _kMaxConsecutiveFailedTurns) {
termination = SessionTermination(
SessionOutcome.harnessError,
harnessError: HarnessError.agentStuck,
terminationDetail: _lastFailureDetail,
);
return termination;
}
if (_consecutiveTurnTimeouts >= _kMaxConsecutiveTurnTimeouts) {
termination = const SessionTermination(
SessionOutcome.budgetExhausted,
terminationDetail: 'inference_latency',
);
return termination;
}
try {
await runTurn();
if (_doneRequested) {
termination = SessionTermination(
SessionOutcome.done,
finalSummary: _doneReason,
);
return termination;
}
} on TurnFailure {
// counted by runTurn — loop continues.
} on VmServiceConnectionLost {
termination = const SessionTermination(
SessionOutcome.harnessError,
harnessError: HarnessError.connectionLost,
);
return termination;
} on ObservationEnvelopeError catch (e) {
termination = SessionTermination(
SessionOutcome.harnessError,
harnessError: HarnessError.observationEnvelopeRejected,
terminationDetail: 'envelope_keys=${e.keySummary}',
);
return termination;
}
}
} catch (error) {
// Nothing leaves runSession unclassified: record the escapee so the
// finally clause can NAME it in the footer, then propagate it to the
// caller exactly as before.
escaped = error;
rethrow;
} finally {
// Close writer with the appropriate footer (close() is idempotent, so
// duplicate calls are safe if termination is already set).
final SessionTermination t =
termination ??
SessionTermination(
SessionOutcome.harnessError,
harnessError: HarnessError.unclassified,
terminationDetail: describeThrowable(escaped),
);
await _writer.close(
SessionFooter(
outcome: t.outcome,
totalTurns: _turnIndex,
totalDurationMs: _sessionStart == null
? 0
: _clock().difference(_sessionStart!).inMilliseconds,
harnessError: t.harnessError?.wireName,
terminationDetail: t.terminationDetail,
),
);
}
}