<p align="center">
<img src="assets/cli_subprocess_core.svg" alt="CliSubprocessCore logo" width="200" />
</p>
# CliSubprocessCore
<p align="center">
<a href="https://github.com/nshkrdotcom/cli_subprocess_core">
<img src="https://img.shields.io/badge/github-nshkrdotcom%2Fcli__subprocess__core-24292e.svg" alt="GitHub" />
</a>
<a href="LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" />
</a>
</p>
`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, Cursor, Amp, and Antigravity.
The covered one-shot local process lane and the local session-bearing process
lane run on the single `execution_plane` package. `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`.
Downstream provider SDKs get this default local CLI execution path by depending
on `cli_subprocess_core`; they do not need to declare Execution Plane packages
manually for ordinary subprocess use. The separate
`execution_plane_jsonrpc` and `execution_plane_process` source components are
not public dependencies: core, JSON-RPC, and process runtime modules ship in
the one generated `execution_plane` package.
For downstream packages that still type against the historical module name,
`CliSubprocessCore.ExecutionSurface` remains available as a compatibility
facade over `ExecutionPlane.Process.Transport.Surface`.
## What This Package Owns
- `CliSubprocessCore.Command` for provider-aware one-shot CLI execution.
- `CliSubprocessCore.RawSession` for provider-agnostic long-lived raw sessions.
- `CliSubprocessCore.Session` for normalized provider sessions and event fanout.
- `CliSubprocessCore.Channel`, `CliSubprocessCore.ProtocolSession`, and
`CliSubprocessCore.JSONRPC` for framed or protocol-driven CLI interactions.
- `CliSubprocessCore.ProviderProfile`, `CliSubprocessCore.ProviderRegistry`,
and `CliSubprocessCore.ProviderProfiles.*` for provider-specific command
planning and parsing.
- `CliSubprocessCore.Event`, `CliSubprocessCore.Payload.*`, and
`CliSubprocessCore.Runtime` for the shared runtime vocabulary.
- `CliSubprocessCore.Tool.*` for serializable tool descriptors, requests, and
responses that contain no executable host callbacks.
- `CliSubprocessCore.ModelRegistry`, `CliSubprocessCore.ModelInput`, and
related catalog helpers for centralized model policy.
### Registry ownership and hex publish-ordering
The shared model catalog (`priv/models/*.json`) ships from this package.
Downstream consumers (`claude_agent_sdk`, `agent_session_manager`) resolve
their model lineup from whichever copy of this package their build uses —
the workspace sibling for `:path` dependencies, the published package for
hex consumers. The release order is `execution_plane`, then
`cli_subprocess_core`, then the provider/ASM consumers, so every published
version sees both its lower runtime and the current catalog. Consumers
switching between `:path` and `:github`/`:hex`
resolution should prune any previously fetched `deps/cli_subprocess_core`
copy — a stale fetched catalog would otherwise shadow the live one in
isolated builds.
## What This Package Does Not Own
`cli_subprocess_core` no longer owns the lower process substrate.
For the covered runtime slice:
- `execution_plane` owns execution-surface validation, capability lookup,
lower transport dispatch, and the local/non-local raw process substrate
- `cli_subprocess_core` owns provider planning, normalized command/session
APIs, and event projection above that lower owner
## Installation
```elixir
def deps do
[
{:cli_subprocess_core, "~> 0.2.0"}
]
end
```
## Quick Start
Run a provider-aware one-shot command:
```elixir
{: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:
```elixir
{: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:
```elixir
{: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:
```elixir
ref = make_ref()
{:ok, _session, info} =
CliSubprocessCore.Session.start_session(
provider: :antigravity,
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_version`
- `surface_kind`
- `transport_options`
- `target_id`
- `lease_ref`
- `surface_ref`
- `boundary_class`
- `observability`
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` with direct lower-lane-owner
provenance. That provenance is an honest standalone lane-owner claim; it is not
node-admitted Citadel governance. Non-local command placement and the
session-bearing APIs resolve through `ExecutionPlane.Process.Transport`.
## Governed Launch Authority
Standalone provider calls still honor the normal provider CLI path env, local
`PATH`, known home locations, and provider-local config behavior. Governed
calls are separate. Pass `:governed_authority` to `CliSubprocessCore.Command`
when a higher authority materializer has already selected the command, cwd,
env, target, config root, auth root, base URL, and cleanup posture for one
effect.
Governed launch requires `clear_env?: true` and rejects unmanaged caller
smuggling through `:command`, `:executable`, `:command_spec`, provider CLI path
keys, `:cwd`, `:env`, config roots, auth roots, base URLs, and model payload
env overrides. Provider CLI resolution also bypasses ambient provider CLI env,
`PATH`, npx, known home locations, and version-manager env while governed.
Materialized authority values are carried as refs and redacted shape evidence;
raw env values remain in the bounded child process launch only.
## Documentation
- `guides/getting-started.md` for the main public entrypoints.
- `guides/execution-surface-compatibility.md` for the compatibility facade
exported for downstream packages.
- `guides/recovery-envelope.md` for the shared failure-normalization contract
consumed by higher runtimes.
- `guides/command-api.md`, `guides/channel-api.md`, and `guides/session-api.md`
for the primary runtime APIs.
- `guides/raw-transport.md` and `guides/shutdown-and-timeouts.md` for the
transport boundary surfaced through `RawSession`.
- `guides/developer-guide-adding-transports.md` for the ownership rule after
extraction.
- `examples/README.md` for 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`, and
`buffer_overflow_mode`
- the common capability vocabulary now has a stable place for session-history, resume, pause, and
intervention support
- tool capability metadata separates normalized `tool_use`/`tool_result` observation from
host-executable tools and provider-native tool controls
- 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.
## License
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.