Skip to main content
Several observers can watch the same run at once — an idle watchdog, a run-budget timer, an external /stop, a provider client — and RunTerminal folds their racing signals into one authoritative outcome that only ever refines toward stronger attribution.
RunTerminal is a third, lower-level primitive — distinct from RunOutcome (host-facing terminal for a single run) and AgentRunOutcome (typed validation/handoff status). Reach for it in gateway/ledger scenarios where several observers watch the same run and you need one sticky, order-independent outcome.

Quick Start

1

Simplest merge (no current)

With no recorded outcome, the observation becomes authoritative.
2

Deliberate cancel is never downgraded

A user’s /stop wins over a late provider error, whatever the arrival order.
3

Round-trip through a durable ledger

Persist the outcome typed, not as a free string — then rehydrate it.

How It Works

Each observer reports a RunTerminal to merge_run_terminal, which keeps refining one authoritative outcome; collapse projects it to a RunStatus for downstream consumers.

Precedence & Stickiness

merge_run_terminal picks a winner by two rules: a stronger kind always beats a weaker one, and among equal kinds a sticky source beats a non-sticky one. Kind precedence ladder — a later, weaker observation never overwrites a stronger one. Sticky sources — a deliberate cancel, a hard run-budget timeout, and a supersede are intentional terminals; once recorded, authoritative.

Configuration Options

RunTerminal is a frozen dataclass. Every field: Methods: The source vocabulary and which sources are sticky: Full API surface lives in the auto-generated SDK reference for praisonaiagents.run_outcome.

Which source to pick

Pick the source that names the observer that ended the run.

Common Patterns

Gateway race — an idle timeout arrives, then the user cancels; the cancel wins:
Order-independent merge — the same signals yield the same result regardless of arrival order:
Durable ledger persistence — typed, not a free string:

Best Practices

Replacing record.terminal with the last observation is the exact bug this contract fixes. Always call merge_run_terminal(record.terminal, observed) so a late provider error can never overwrite a deliberate cancel or a hard timeout.
Store outcome.to_dict() in your durable ledger and rehydrate with RunTerminal.from_dict(...). The free-string form is the ambiguity this contract removes — keep the outcome typed end to end.
Keep RunTerminal internally and call collapse() only when you must emit a legacy RunStatus — a chat reaction, an exit code, an HTTP status. Collapsing early throws away the source attribution.
Once a terminal is sticky, its outcome cannot change. Check is_sticky(outcome) and stop watching the run — further observations can only be discarded anyway.

Run Outcome

The host-facing RunOutcome for a single run — related, but a different primitive with a reason field.

Agent Run Outcomes

The typed AgentRunOutcome status for validation and handoff results — related, but a different primitive.