End a v2 conversation through the close pipeline (admin)
conv_* replacement for the retired POST /v1/runtime/end_session.
The legacy endpoint drove Runtime.end_session(triggered_by="admin"),
which under PersistStrategy.PER_TURN (every HTTP channel) reduced to the
same unified close pipeline SessionService.close_session now owns: flip
to CLOSED, stamp the meta close-out columns + retention, and enqueue the
post-session finalize chain (PII scrub → summarize + memory-sync). So this
is a straight call to close_session — there is no separate v2 Runtime
to construct.
The conversation id is taken from the PATH and org-scoped (cross-org /
missing → 404 inside close_session → get_session_for_org); the legacy
body’s session_id / end_user_id / agent_id / channel fields
are accepted for wire-compat but ignored. reason defaults to
AGENT_INITIATED (the same RESOLVED outcome category the legacy admin
close used when no explicit reason was supplied).
Response mirrors the legacy TurnResult shape (messages=[],
status="ended") — the admin UI ignores the body and only awaits the 200.
Authorizations
Path Parameters
Body
Admin-triggered runtime end-session.
Distinct from POST /v1/sessions/{id}/close which only flips the DB
status. This drives the full Runtime.end_session pipeline (AOP
cleanup, memory flush, OTel session.end span, triggered_by=admin).
session_id is required: Runtime.from_authenticated falls through
to get_or_create_active_session when it's missing, which would mint
a fresh ACTIVE session purely to end it on the same request. Requiring
the caller to name the target session prevents that ghost-session path
and forces a clear 422 instead of a misleading 200.
farewell_text is intentionally omitted: the runtime ignores it
under PersistStrategy.PER_TURN (used by every HTTP channel today),
so accepting it would silently drop the value. Callers that want a
user-visible farewell should send a final POST /v1/runtime/turn
with the goodbye message before calling this endpoint.
Response
Successful Response
Universal post-turn snapshot returned by Runtime.run_turn.
Every channel sees the same fields:
session_id: the session this turn ran against. Always populated when a realRuntimeproduced the result (the caller may have passedsession_id=Noneand asked the runtime to resolve / create the active session — this is how it learns which one was used). Optional in the type for the benefit of test fixtures that mintTurnResults by hand without a Runtime.messages: 1 message for API/voice; N (Plan 5) for SMS chunked.status: snapshot ofRuntime._statusat end-of-turn. The caller uses this to decide whether to callend_session.handoff: populated when AOP reached a HandoffNode. The dict shape mirrors the existingAOPExecutionResult.handoff_configfor forward-compat — Plan 3 does not re-type it.error: when AOP execution fails, this is theAOPExecutionResult.execution_errorstring the pipeline stamped (e.g."node_execution_failed: ValidationError: ...","routing_error: <node_id>","unknown_node_type"); falls back to the legacy"max_steps_exceeded"label only when the pipeline didn't attach a reason.repr(exc)for an uncaught exception in_execute.Noneon success / handoff / policy-violation.model_used/token_count/latency_ms: aggregate metrics surfaced for telemetry;Nonewhen the turn made no LLM call.
In-memory lifecycle status of a Runtime instance.
Distinct from ConversationSession.status (DB column). The DB status
is the durable end-state of a session row; this enum is the per-instance
live signal that channel runtimes consult after each run_turn to
decide whether to call end_session.
idle, running_turn, end_requested, end_processing, ended, error The resolved, ready-to-execute transfer the voice host renders after a turn.
Both authoring surfaces converge on this: the assistant transfer_call platform
tool (label → destination resolved from config) and the workflow HandoffNode
voice transfer (deterministic single target, or LLM-picked among many) both produce a
CallTransferIntent; the voice worker reads it via VoiceRuntime.pending_transfer
and issues the SIP REFER. connecting_text is the optional "one moment, connecting
you…" line spoken before the transfer.
Per-node keypad-collection tuning (authoring config + runtime directive).
All fields optional: an unset field means "leave the assistant-level default
in place" (the voice config's dtmf_* values). expects_input is the
intent flag — a node that explicitly expects keypad entry — used so the host
can apply the retune even when no numeric override differs from the default.
prefix overrides the turn-text label (collection mechanics + framing).