Toolnexus.Builtin.BackgroundShells
Elixir · package toolnexus · SPEC §4A
bg = Toolnexus.Builtin.BackgroundShells.new()Toolnexus.Builtin.tools(%{background: bg, kill_grace_ms: 2000})# bash gains "run_in_background"; adds bash_output %{id} and kill_shell %{id}
Toolnexus.Builtin.BackgroundShells.list(bg) # [%{id, running, killed, exit_code, ...}]Toolnexus.Builtin.BackgroundShells.kill(bg, id) # :ok | {:error, reason}Toolnexus.Builtin.BackgroundShells.close_all(bg) # :ok — call on shutdownOpt-in background shells (elixir/lib/toolnexus/builtin.ex). With background: set, bash
accepts run_in_background: true and returns at once with a background id in
metadata.backgroundId (no timeout applies). bash_output %{id} returns the output produced
since the last read plus [status: running|killed|exited with code N]; kill_shell %{id} stops
the whole process group (SIGTERM, kill_grace_ms, SIGKILL). The host owns the registry and calls
close_all/1 on shutdown. A shell killed by a signal it did not get from kill_shell reports
exited with code -1; because the BEAM cannot tell a signal from an exit status, a command that
itself exits 129..159 also reads as -1. POSIX only.
The same builtins config turns on the other opt-in tools: web_search: fn query, limit -> {:ok, [%{title, url, snippet}]} | {:error, reason} end adds websearch (the library ships no
provider), and notebook_edit: true adds notebook_edit for .ipynb cells.
When to use it
Section titled “When to use it”- Dev servers, watchers, long builds — start them, keep working, read their output later.
- A coding agent that must not block a turn on
mix test --covertaking minutes.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. Off by default
Section titled “1. Off by default”names = Enum.map(Toolnexus.Builtin.tools(%{}), & &1.name)false = Enum.any?(~w(bash_output kill_shell websearch notebook_edit), &(&1 in names))IO.puts("ok: no background tools unless configured")2. Start, read, finish
Section titled “2. Start, read, finish”alias Toolnexus.Builtin.BackgroundShells
bg = BackgroundShells.new()tools = Map.new(Toolnexus.Builtin.tools(%{background: bg, kill_grace_ms: 100}), &{&1.name, &1})
r = tools["bash"].execute.(%{"command" => "echo one; sleep 0.2; echo two", "run_in_background" => true}, nil)id = r.metadata.backgroundId
out = Enum.reduce_while(1..100, "", fn _, acc -> o = tools["bash_output"].execute.(%{"id" => id}, nil) acc = acc <> o.output if o.metadata.status == "exited", do: {:halt, acc}, else: (Process.sleep(50) && {:cont, acc}) end)
true = out =~ "one" and out =~ "two" and out =~ "[status: exited with code 0]":ok = BackgroundShells.close_all(bg)IO.puts("ok: #{id} exited 0")3. Kill a stubborn job, then websearch over a host provider
Section titled “3. Kill a stubborn job, then websearch over a host provider”alias Toolnexus.Builtin.BackgroundShells
bg = BackgroundShells.new()cfg = %{background: bg, kill_grace_ms: 100, web_search: fn q, _n -> {:ok, [%{title: "Result for #{q}", url: "https://a.example", snippet: "s"}]} end}tools = Map.new(Toolnexus.Builtin.tools(cfg), &{&1.name, &1})
r = tools["bash"].execute.(%{"command" => "trap '' TERM; sleep 30 & wait", "run_in_background" => true}, nil)id = r.metadata.backgroundIdProcess.sleep(150)k = tools["kill_shell"].execute.(%{"id" => id}, nil)true = k.metadata.killed"killed" = tools["bash_output"].execute.(%{"id" => id}, nil).metadata.status:ok = BackgroundShells.close_all(bg)
"1. Result for elixir\n https://a.example\n s" = tools["websearch"].execute.(%{"query" => "elixir"}, nil).outputIO.puts("ok: killed #{id}; websearch answered")See also
Section titled “See also”Toolnexus.Builtin.load— Pick which built-in file/shell/web tools are in play, by name or by source toggle.Toolnexus.Builtin.tools— Construct every built-in tool directly, outside a toolkit.