cli_subprocess_core is the shared runtime for provider-facing CLIs. It owns
provider profile resolution, normalized command/session APIs, event and payload
shaping, model policy helpers, and the built-in first-party profiles for
Claude, Codex, Gemini, and Amp.
The covered one-shot local process lane and the local session-bearing process
lane now run on execution_plane. cli_subprocess_core keeps one public
placement seam, execution_surface, while the shared lower substrate for
local and non-local runtime execution now lives in execution_plane.
For downstream packages that still type against the historical module name,
CliSubprocessCore.ExecutionSurface remains available as a compatibility
facade over ExecutionPlane.Process.Transport.Surface.
CliSubprocessCore.Commandfor provider-aware one-shot CLI execution.CliSubprocessCore.RawSessionfor provider-agnostic long-lived raw sessions.CliSubprocessCore.Sessionfor normalized provider sessions and event fanout.CliSubprocessCore.Channel,CliSubprocessCore.ProtocolSession, andCliSubprocessCore.JSONRPCfor framed or protocol-driven CLI interactions.CliSubprocessCore.ProviderProfile,CliSubprocessCore.ProviderRegistry, andCliSubprocessCore.ProviderProfiles.*for provider-specific command planning and parsing.CliSubprocessCore.Event,CliSubprocessCore.Payload.*, andCliSubprocessCore.Runtimefor the shared runtime vocabulary.CliSubprocessCore.ModelRegistry,CliSubprocessCore.ModelInput, and related catalog helpers for centralized model policy.
cli_subprocess_core no longer owns the lower process substrate.
For the covered runtime slice:
execution_planeowns execution-surface validation, capability lookup, lower transport dispatch, and the local/non-local raw process substratecli_subprocess_coreowns provider planning, normalized command/session APIs, and event projection above that lower owner
def deps do
[
{:cli_subprocess_core, "~> 0.1.0"}
]
endRun a provider-aware one-shot command:
{:ok, result} =
CliSubprocessCore.Command.run(
provider: :claude,
prompt: "Summarize this repository"
)
IO.inspect(result.output)Move that command onto SSH through the generic placement seam:
{:ok, result} =
CliSubprocessCore.Command.run(
provider: :codex,
prompt: "Review the latest diff",
execution_surface: [
surface_kind: :ssh_exec,
transport_options: [
destination: "buildbox.example",
ssh_user: "deploy"
]
]
)Use RawSession when you need exact-byte ownership and normalized collection:
{:ok, session} =
CliSubprocessCore.RawSession.start("sh", ["-c", "cat"], stdin?: true)
:ok = CliSubprocessCore.RawSession.send_input(session, "alpha")
:ok = CliSubprocessCore.RawSession.close_input(session)
{:ok, result} = CliSubprocessCore.RawSession.collect(session, 5_000)
IO.inspect({result.stdout, result.exit.code})Use Session when you want normalized provider events:
ref = make_ref()
{:ok, _session, info} =
CliSubprocessCore.Session.start_session(
provider: :gemini,
prompt: "Hello from the shared runtime",
subscriber: {self(), ref}
)
IO.inspect(info.delivery)cli_subprocess_core keeps the public placement seam intentionally narrow. The
only public way to choose where a command runs is one execution_surface
value.
That contract carries:
contract_versionsurface_kindtransport_optionstarget_idlease_refsurface_refboundary_classobservability
It does not expose adapter module names. Public callers do not choose
LocalSubprocess, SSHExec, or GuestBridge directly.
Callers may supply that value either as:
execution_surface: [...]execution_surface: %{...}execution_surface: %CliSubprocessCore.ExecutionSurface{}
The first two are the preferred long-term shapes. The struct form remains for downstream compatibility.
When that surface needs to cross a boundary, use
CliSubprocessCore.ExecutionSurface.to_map/1 to project the versioned map
form.
For CliSubprocessCore.Command.run/1,2, surface_kind: :local_subprocess
now emits ProcessExecutionIntent.v1 and delegates the covered minimal one-shot
lane to ExecutionPlane.Process.run/2. Non-local command placement and the
session-bearing APIs resolve through ExecutionPlane.Process.Transport.
guides/getting-started.mdfor the main public entrypoints.guides/execution-surface-compatibility.mdfor the compatibility facade exported for downstream packages.guides/recovery-envelope.mdfor the shared failure-normalization contract consumed by higher runtimes.guides/command-api.md,guides/channel-api.md, andguides/session-api.mdfor the primary runtime APIs.guides/raw-transport.mdandguides/shutdown-and-timeouts.mdfor the transport boundary surfaced throughRawSession.guides/developer-guide-adding-transports.mdfor the ownership rule after extraction.examples/README.mdfor runnable examples.
cli_subprocess_core now preserves the transport hardening controls that matter to upper layers
instead of flattening them away inside provider defaults.
- shared provider-profile transport options retain
max_buffer_size,oversize_line_chunk_bytes,max_recoverable_line_bytes,oversize_line_mode, andbuffer_overflow_mode - the common capability vocabulary now has a stable place for session-history, resume, pause, and intervention support
- higher layers can reason about fatal data-loss boundaries without re-inventing transport-specific heuristics
This repo is still not a retry engine. It is the boundary that keeps subprocess and provider profiles honest about what can be recovered and what must fail.
This project is licensed under the MIT License. See LICENSE for details.