Detached runs, streams, and replay
What you will learn
This notebook demonstrates the supervised run lifecycle. A detached run is
owned by Jido.Harness rather than this Livebook cell, so later cells can inspect
or reattach to it by run_id.
By the end you will have:
- started and inspected one detached run;
- consumed its pull-based cursor stream;
- replayed bounded event pages;
- awaited its normalized terminal result;
- seen how cancellation and pruning fit the lifecycle.
Read the detached-runs guide and retention guide for the corresponding API contracts.
Provider run cells may consume API or subscription usage.
Install the local package
Livebook Desktop may not inherit the PATH from your interactive shell. Add
the common CLI installation directories before resolving a provider. Extend
extra_cli_paths when needed.
extra_cli_paths = [
Path.join(System.user_home!(), ".local/bin"),
Path.join(System.user_home!(), ".local/share/mise/shims"),
Path.join(System.user_home!(), ".bun/bin"),
"/opt/homebrew/bin",
"/usr/local/bin"
]
path =
(extra_cli_paths ++ String.split(System.get_env("PATH", ""), ":", trim: true))
|> Enum.uniq()
|> Enum.join(":")
System.put_env("PATH", path)
Mix.install([
{:jido_harness, path: Path.expand("..", __DIR__)}
])
defmodule JidoHarnessLivebook do
def completed!({:ok, %{status: :completed} = result}), do: result
def completed!({:ok, result}) do
raise result.error || "provider run finished with status #{inspect(result.status)}"
end
def completed!({:error, %_{} = error}), do: raise(error)
def completed!({:error, error}), do: raise("provider run failed: #{inspect(error)}")
end
Start a detached run
provider = :codex
cwd = Path.expand("..", __DIR__)
{:ok, provider_status} = Jido.Harness.status(provider)
unless Jido.Harness.ProviderStatus.ready?(provider_status) do
raise """
#{provider} is not ready in this Livebook runtime.
executable: #{inspect(provider_status.executable)}
error: #{inspect(provider_status.error)}
PATH: #{System.get_env("PATH")}
"""
end
{:ok, run_id} =
Jido.Harness.Run.start(provider, %{
prompt: "Reply with exactly: detached-run-ready",
cwd: cwd,
runtime_timeout_ms: 300_000,
metadata: %{source: "detached-runs-livebook"}
})
run_id
The snapshot is redacted and safe to use for lifecycle decisions. Its output cursor tells consumers how many sequenced events have been retained so far.
Jido.Harness.Run.info(run_id)
Attach to the event stream
The stream polls cursor-addressed retention rather than receiving an unbounded producer mailbox. Evaluating the next cell waits until the run reaches a terminal state.
{:ok, event_stream} = Jido.Harness.Run.stream(run_id, poll_interval_ms: 25)
events = Enum.to_list(event_stream)
Enum.map(events, fn event ->
%{
sequence: event.sequence,
type: event.type,
turn_id: event.turn_id,
text: event.payload["text"]
}
end)
Await and replay
Awaiting a finished run returns immediately. Replay can start at any cursor and is the durable source of truth when a result’s bounded text tail is truncated.
result =
run_id
|> Jido.Harness.Run.await(300_000)
|> JidoHarnessLivebook.completed!()
%{
status: result.status,
text: result.text,
text_truncated?: result.text_truncated?,
usage: result.usage
}
{:ok, first_page} = Jido.Harness.Run.replay(run_id, cursor: 0, limit: 5)
next_cursor = case List.last(first_page) do
nil -> 0
event -> event.sequence
end
{:ok, second_page} = Jido.Harness.Run.replay(run_id, cursor: next_cursor, limit: 5)
%{
first_page: Enum.map(first_page, &{&1.sequence, &1.type}),
second_page: Enum.map(second_page, &{&1.sequence, &1.type}),
next_cursor: next_cursor
}
Cancellation and pruning
The following cell is intentionally disabled. Set exercise_cancellation? to
true to start a second live request and request cancellation. Cancellation is
cooperative at the provider boundary and always produces one normalized
terminal result.
exercise_cancellation? = false
if exercise_cancellation? do
{:ok, cancellable_id} =
Jido.Harness.Run.start(provider, %{
prompt: "Wait briefly, then reply with exactly: cancellation-missed",
cwd: cwd
})
:ok = Jido.Harness.Run.cancel(cancellable_id)
Jido.Harness.Run.await(cancellable_id, 30_000)
else
:skipped
end
Terminal runs remain available for the configured retention period. Pruning is explicit when the caller no longer needs the journal.
Jido.Harness.Run.prune(run_id)