Bounded handoffs are the narrow public delegation capability for Scoria. Use them when your Phoenix app needs one role to hand a bounded slice of work to another role without turning Scoria into a general-purpose agent platform.
Use this guide with Default Runtime, Golden Path, and the glossary.
Start with the default runtime capability. It proves identity-aware durable runs, approvals, and reviewer traces with $ mix test.adoption. Add bounded handoffs only when the same durable run needs narrow same-run delegation, host-controlled scoped context, and reviewer-visible delegated lineage. The default runtime path is identity -> start -> inspect -> resume.
What this capability does
- starts one durable run through the public
Scoriafacade - records one explicit delegated handoff with inspectable lineage
- keeps the scoped context narrow and host-controlled
- creates a queued child step for the delegated role
- leaves the same run visible at
/scoria/workflows/:run_id
Host and Scoria ownership boundary
The host app owns identity, escalation policy, prompt or draft selection, and scoped-context selection.
Scoria owns durable run creation, scoped-context validation, queued delegated child creation, and curated readback through Scoria.get_run_detail/1.
Scoria does not copy hidden transcript, provider session, socket assigns, cookies, headers, or secrets into the handoff.
Core contract
Use Scoria.start_handoff_run/3 when you already know:
root_role_id: the root role that is delegating- the delegated role argument: the role that should own the child step
delegated_kind: the child step kind that host handlers should executehandoff_input: the exact host-supplied work brief Scoria should persistscoped_context: the exact scoped context slice that is safe to pass down
The host app passes these fields explicitly. Scoria does not fill in hidden handoff defaults.
identity =
Scoria.identity(%{
actor_id: current_user.id,
tenant_id: current_account.id,
session_id: get_session(conn, :assistant_session_id)
})
{:ok, started} =
Scoria.start_handoff_run(identity, "critic",
root_role_id: "planner",
delegated_kind: "review",
handoff_input: %{"brief" => "Review the draft answer for policy and accuracy"},
scoped_context: %{
"task" => "policy-and-accuracy review",
"draft_answer" => draft_answer
},
handlers: %{"review" => {MyApp.RuntimeHandlers, :review}}
)If the delegated role should receive no extra context, scoped_context: %{} is a valid explicit choice.
projected_context: remains accepted as a legacy 0.1.x compatibility alias. New public examples should use scoped_context:.
What gets persisted
Scoria records:
- the root run with canonical actor, tenant, and session identity
- a root
handoffstep - a durable handoff row showing the delegated role, delegated kind, and handoff input
- a queued child step with the delegated role and delegated kind
The child step stays under the same durable run. Root ownership does not transfer.
Safety rule: scoped context must stay narrow
Scoped context is for the bounded slice only. Do not pass broad runtime state through the public handoff capability.
Broad runtime-state keys are rejected explicitly, including:
transcriptmessageshistoryprovider_sessionsessionheaderssecretssocket_state
Narrow host-controlled slices such as %{"task" => "review"} and scoped_context: %{} remain valid.
Rejected scoped context returns the existing runtime error before Scoria creates the delegated run:
assert {:error, :unsafe_projected_context} =
Scoria.start_handoff_run(identity, "critic",
root_role_id: "planner",
delegated_kind: "review",
handoff_input: %{"brief" => "Review the draft answer for policy and accuracy"},
scoped_context: %{"request_headers" => %{"authorization" => "secret"}}
)Scoria rejects the call with {:error, :unsafe_projected_context} before creating a durable delegated run.
Inspecting delegated lineage
After Scoria.start_handoff_run/3:
{:ok, detail} = Scoria.get_run_detail(started.run_id)
delegated = detail.delegated_handoffsdetail.delegated_handoffs exposes the delegated role, delegated kind, handoff input, bounded scoped context, and the parent/child same-run lineage needed to inspect the bounded capability without reading raw workflow tables.
Open:
/scoria/workflows/:run_idThe workflow page keeps the topology-first tree and selected-step rail, and includes a run-level Delegated Trace section for the curated handoff story under the same durable run.
Runtime-to-handoff verifier
After the default runtime capability is proven with $ mix test.adoption, use this bounded escalation verification suite:
mix test.runtime_to_handoff
The verifier exercises the same delegated readback path in this guide: inspect Scoria.get_run_detail/1, confirm delegated_handoffs, and cross-check /scoria/workflows/:run_id.
This is one canonical verification suite for the bounded handoff capability; do not replace it with raw workflow-table assertions.
When to use this
Use bounded handoffs when:
- one role needs a second role to review, classify, summarize, or critique
- the delegated role only needs a small scoped context slice
- the reviewer trace should show delegated lineage clearly
Do not use this capability to build a broad autonomous multi-agent platform. Keep the contract narrow and explicit.
Remaining adoption gap
No remaining adopter-facing gap is required for closeout of the runtime-first bounded handoff capability. Richer notebook-style delegated forensics remain deferred follow-up work only if real reviewer confusion appears after the current Scoria.get_run_detail/1 and /scoria/workflows/:run_id surfaces prove insufficient.