CliSubprocessCore
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 raw execution substrate now lives in external_runtime_transport.
cli_subprocess_core consumes that package through one public placement seam:
execution_surface.
For downstream packages that still type against the historical module name,
CliSubprocessCore.ExecutionSurface remains available as a compatibility
facade over ExternalRuntimeTransport.ExecutionSurface.
What This Package Owns
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.
What This Package Does Not Own
cli_subprocess_core no longer owns the raw execution substrate modules. The
following are owned by external_runtime_transport:
ExternalRuntimeTransport.ExecutionSurfaceExternalRuntimeTransport.Transport- adapter registry and transport contracts
-
built-in
:local_subprocess,:ssh_exec, and:guest_bridgefamilies -
shared
ProcessExit,LineFraming, and transport result types
That separation keeps provider/runtime behavior in the core while leaving raw process placement reusable by non-CLI stacks.
The compatibility facade does not change that ownership boundary. Transport
validation, capabilities, and dispatch still live in
ExternalRuntimeTransport.ExecutionSurface.
Installation
def deps do
[
{:cli_subprocess_core, "~> 0.1.0"}
]
endQuick Start
Run 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)Execution Surface
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 or
ExternalRuntimeTransport.ExecutionSurface.to_map/1 to project the versioned
map form.
Documentation
guides/getting-started.mdfor the main public entrypoints.guides/external-runtime-transport.mdfor the shared placement seam.guides/execution-surface-compatibility.mdfor the compatibility facade exported for downstream packages.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.
Emergency Hardening Surfaces
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.