README

colmugx/acp/runtime does not have a README file

#
RuntimeCancelOutboundHandler

type RuntimeCancelOutboundHandler = async (
RequestId
) -> Unit

#
RuntimeOutboundFailureHandler

type RuntimeOutboundFailureHandler = async (
RequestId
, RuntimeOutboundFailure) -> Unit

#
RuntimeError

pub(all) suberror RuntimeError {
InvalidOptions
ReaderFailed
ReaderEmptyChunk
EventQueueClosed
EventQueueBackpressure(limit~ : Int)
WriterQueueClosed
WriterQueueBackpressure(limit~ : Int)
WriterFailed
FramingFailed
JsonRpcDecodeFailed
JsonRpcEncodeFailed
ReducerFailed
TaskControlMissing
TaskRequestMismatch
HandlerFailed
NotificationFailed
ResponseHandlerFailed
OutboundCancelUnavailable
OutboundCancelFailed
OutboundCompletionFailed
CloseCleanupFailed(primary_kind~ : String, cleanup_phase~ : String)
}

Failures at the native runtime boundary.

These errors deliberately carry only failure categories. Peer payloads, prompts, files, tokens, and environment values never enter a trace or an error string produced by the runtime.

#
RuntimeHandlerPort

Protocol dispatch seams. They are values rather than globals so tests and composition roots can inject memory or faulting implementations.

#
RuntimeHandlerResult

pub(all) enum RuntimeHandlerResult {
HandlerSuccess(Json)
HandlerError(
JsonRpcError
)
}

Result returned by an inbound request handler. A handler failure is raised through the async port and becomes RuntimeError::HandlerFailed.

#
RuntimeOptions

pub(all) struct RuntimeOptions {
max_frame_bytes : Int
queue_capacity : Int
read_chunk_bytes : Int
}

Native runtime bounds. The event and writer queues are intentionally bounded so backpressure is observable instead of becoming unbounded heap growth.

#
RuntimeOutboundFailure

pub(all) struct RuntimeOutboundFailure {
reason : RuntimeOutboundFailureKind
}

Typed completion data for a locally initiated request. The reason is intentionally a category, never a peer payload or arbitrary exception.

#
RuntimeOutboundFailureKind

pub(all) enum RuntimeOutboundFailureKind {
Protocol
Framing
Reader
Writer
Handler
Notification
EndOfInput
EventQueue
Runtime
} derive(Eq,
Debug
)

Closed, finite categories for a locally initiated request completion. Arbitrary close strings never cross this boundary.

#
RuntimePorts

pub(all) struct RuntimePorts {
reader : RuntimeReaderPort
writer : RuntimeWriterPort
handlers : RuntimeHandlerPort
cancel_outbound : async (
RequestId
) -> Unit?
trace : (RuntimeTraceEvent) -> Unit
}

All per-connection runtime dependencies.

#
RuntimeProcessPorts

The spawned-agent handle handed back with client-side process ports. The ports value drives the single connection engine; child, child_stdin, and child_stdout expose the real moonbitlang/async process seams so a composition root can close the child's stdin (the ACP stdio shutdown signal), wait for the exit status, or cancel the child without any wrapper API duplicating the underlying library.

#
RuntimeReaderPort

pub(all) struct RuntimeReaderPort {
read : async (Int) -> Bytes?
}

Caller-owned asynchronous input seam. None is the only EOF signal.

#
RuntimeTraceEvent

pub(all) struct RuntimeTraceEvent {
direction : String
phase : String
request_id : String
method_name : String
error_kind : String
}

A redacted runtime trace record. Only routing metadata and a static failure category are exposed to the sink.

#
RuntimeWriterPort

pub(all) struct RuntimeWriterPort {
write : async (Bytes) -> Unit
}

Caller-owned asynchronous output seam. Each call receives one complete newline-delimited frame and must either write all bytes or raise.

#
runtime_default_options

fn runtime_default_options() -> RuntimeOptions

#
runtime_libc_write

fn runtime_libc_write(fd : Int, buffer : Bytes, count : Int) -> Int

Public wrapper over the raw diagnostics writer so the blackbox suite can prove the byte path against a real pipe. Not exported through the stable facade.

#
runtime_process_ports

async fn[G] runtime_process_ports(group :
TaskGroup
[G], handlers~ : RuntimeHandlerPort, command~ : String, args? : Array[String], extra_env? : Map[String, String], inherit_env? : Bool, cancel_outbound? : async (
RequestId
) -> Unit?, trace? : (RuntimeTraceEvent) -> Unit) -> RuntimeProcessPorts raise RuntimeError

Construct the client side of one ACP stdio connection over a spawned child process. The child's stdin is the writer target and its stdout is the reader source; the child's stderr is redirected to this process's real stderr so agent diagnostics stay diagnostics and stdout stays pure frames. Both pipe ends are raw, unbuffered @process handles, so every frame write is delivered to the operating system in that call — the pinned flush-per-write discipline, structurally identical to runtime_stdio_ports.

The child is spawned with no_wait = true inside the caller's task group: when the group terminates, the async process layer cancels its wait task, which gracefully terminates the child (then forcefully after its timeout) and reaps it. Child lifetime is therefore bound to the structured concurrency scope that owns the connection — no background reaper loop, no detached process. A well-behaved agent additionally observes the stdin EOF a composition sends through child_stdin.close() before teardown.

Fail-fast mapping (the closed stable RuntimeError set has no process category, so each failure keeps its precise kind in the trace, mirroring the documented closest-category mapping of the outbound channel): an empty command is rejected as InvalidOptions before any OS call; a failed stdout pipe is ReaderFailed and a failed stdin pipe is WriterFailed (the seam that could not be constructed); an OS spawn rejection — including a missing command — is InvalidOptions with trace kind process_spawn_failed, because the caller's process configuration was rejected before any I/O seam ever ran. Every failure is typed and accompanied by one trace event; nothing degrades silently.

#
runtime_request_id_text

fn runtime_request_id_text(id :
RequestId
) -> String

#
runtime_stderr_trace

fn runtime_stderr_trace(RuntimeTraceEvent) -> Unit

Default trace sink: one write(2) per event to the real standard error. Stdout is never touched here, so the protocol channel keeps carrying newline-delimited ACP frames only. The sink type (RuntimeTraceEvent) -> Unit has no failure channel: when the OS rejects a diagnostics write (for example a closed stderr under a daemon supervisor), the event is dropped without inventing a failure the trace contract cannot carry. Protocol I/O failures stay fully typed at the reader/writer seams.

#
runtime_stdio_ports

fn runtime_stdio_ports(trace? : (RuntimeTraceEvent) -> Unit) -> RuntimePorts

Serve the native ACP stdio boundary of this process. The reader reads the real stdin chunk-wise (None is EOF, non-empty chunks only, exactly the RuntimeReaderPort contract). The writer writes each complete newline-delimited frame to the real stdout in one Output::write call: @stdio.Output wraps the raw file descriptor with no userspace buffering (moonbitlang/async/src/stdio/stdio.mbt), so every write is handed to the operating system immediately — the pinned flush-per-write discipline holds structurally because no buffered writer layer exists on this path.

The handler seams exist only for engine runners with an outbound channel. response and outbound_failure are deliberate no-op observers: reply delivery to parked submitters is owned by the engine's outbound channel, never by these callbacks. The legacy dispatch seams (request, notification) abort fail-fast: these ports exist for owner-loop runners, and a legacy handler dispatch through them would silently answer nothing, so it must crash loudly instead.

Diagnostics go to the trace sink, defaulting to stderr. Nothing in this constructor writes to stdout, and it performs no I/O at all beyond binding the process file descriptors.

#
runtime_trace_event

fn runtime_trace_event(direction : String, phase : String, request_id : String, method_name : String, error_kind : String) -> RuntimeTraceEvent

#
runtime_trace_stderr_line

fn runtime_trace_stderr_line(event : RuntimeTraceEvent) -> String

Render one redacted trace event as exactly one diagnostics line ending in a single newline. The five trace fields keep their fixed order; every value is sanitized first. This renderer is the exact text the default stderr sink writes, which keeps the diagnostics format unit-testable.

#
runtime_validate_options

fn runtime_validate_options(options : RuntimeOptions) -> Unit raise RuntimeError