WitRPC's transports already cover communication between processes; the OutWit.InterProcess extension adds what IPC scenarios need on top of that: process management. It implements a Host/Agent model, where a main application (the host) launches, monitors, and shuts down child processes (agents), and talks to each agent through an ordinary WitRPC proxy. The programming model does not change: define an interface, implement it in the agent, call it from the host.

Why run components in separate processes

Crash isolation. An agent that crashes takes only itself down. The host detects the loss and can relaunch, while the main application keeps running. This is the standard pattern for wrapping unstable or third-party code.

Mixed platforms and runtimes. A 64-bit host can offload work to a 32-bit agent that wraps a legacy library, or to an agent built against a different runtime version. Each process keeps its own world.

Privilege separation. An agent can run with fewer rights than the host, containing the damage a compromised component could do.

Plugin architectures. Modules loaded as agent processes can be started, stopped, and replaced without restarting the application, and cannot corrupt the host's memory.

Packages

Package Side Purpose
OutWit.InterProcess shared Model types: AgentStartupParameters, IAgent<TService>
OutWit.InterProcess.Host host HostManager<TService>, HostAgent<TService>, HostUtils
OutWit.InterProcess.Agent agent AgentApplication entry-point base with lifecycle handling

How a launch works

The whole cycle runs in five steps:

  1. The host prepares startup parameters: an IPC transport with a unique per-agent address, the host's process id, and shutdown settings. AgentStartupParameters carries Address, ParentProcessId, Timeout, and ShutdownOnParentProcessExited.
  2. HostUtils.RunAgent(...) starts the agent executable, passing the serialized parameters as command-line arguments.
  3. The agent parses the parameters, starts a WitRPC server on the given address, and begins monitoring the parent process.
  4. The host builds a WitClient for the same address, connects, and obtains the typed service proxy.
  5. From here the connection is ordinary WitRPC: the host calls methods, the agent raises events.

Two safety nets guard the lifecycle. If the host exits (including a crash), the agent notices its parent is gone and terminates itself, so no orphaned processes accumulate. And an agent that receives no connection within its Timeout shuts down on its own.

The host side

HostManager<TService> encapsulates the launch cycle. It is constructed with client options (transport family, serializer, security), the path to the agent executable, and a process timeout; each CreateClient call launches one agent and returns it initialized:

csharp
var manager = new HostManager<IProcessingService>(
    options,                            // WitClientBuilderOptions: transport, serializer, security
    "ProcessingAgent.exe",              // agent executable
    TimeSpan.FromSeconds(30));          // process timeout

IAgent<IProcessingService> agent = await manager.CreateClient(TimeSpan.FromSeconds(10));

IProcessingService service = agent.Service!;
service.ProgressChanged += p => Console.WriteLine($"Progress: {p}%");
await service.StartProcessingAsync(filePath);

The IAgent<TService> handle exposes the running agent: Service is the typed proxy, IsInitialized reports readiness, Initialized and Disposed events track the lifecycle, Stop() disconnects gracefully, and Shutdown() terminates the agent process. The manager tracks its agents by id (GetAgent(Guid), ShutdownAgent(Guid)), so a host can run a pool of identical workers and address each individually.

Calling CreateClient several times launches several independent agent processes, each with its own address and connection. That is the whole parallelism story: one line per extra worker.

The agent side

An agent is a small executable with three responsibilities: parse the startup parameters, host the service, and let the lifecycle machinery watch the parent. The entry point deserializes the command line and hands control to the application:

csharp
[STAThread]
static void Main(string[] args)
{
    var parameters = args.DeserializeCommandLine<AgentStartupParameters>();
    // start the WitRPC server on parameters.Address and run the agent application
}

The server inside the agent is a regular WitServerBuilder setup, with the transport address taken from the parameters instead of hard-coded:

csharp
var server = WitServerBuilder.Build(options =>
{
    options.WithService(new ProcessingService());
    options.WithNamedPipe(parameters.Address);   // address supplied by the host
    options.WithJson();
    options.WithEncryption();
});
server.StartWaitingForConnection();

AgentApplication, the provided entry-point base, wires the rest: it monitors the parent process id and shuts the agent down when the host disappears, and it manages the no-connection timeout (ResetTimeout() extends it while work is in progress).

The WPF dependency

AgentApplication inherits from System.Windows.Application and uses a WPF dispatcher timer, so an agent project built on it must set <UseWPF>true</UseWPF> and runs on Windows only. For a cross-platform or console agent, keep the same structure but supply your own entry point: parse AgentStartupParameters from the command line, start the server, monitor ParentProcessId yourself, and use System.Threading.Timer for the timeout. The host side does not care which variant the agent uses.

Events across the process boundary

Nothing changes. The service interface declares events, the agent's implementation raises them, and handlers subscribed on the host-side proxy run as the work progresses; a background worker reporting progress to a UI is the canonical use. The usual UI rule applies: handlers arrive on background threads, so marshal updates to the UI thread through the dispatcher.

Practices that pay off

Choose the transport for the data volume. Named pipes are the default for command-and-control traffic; memory-mapped files win when the host and one agent exchange large payloads. Both stay on the machine. Transports & Serialization → has the details.

Treat agent crashes as expected events. Subscribe to the agent's Disposed event and decide there: relaunch, degrade, or surface the error. The model's value is that this decision is yours to make while the host keeps running.

Keep agents single-purpose. One agent, one job. Pools of identical single-purpose agents are easy to reason about and scale by count; multi-role agents reintroduce the coupling the model exists to remove.

Extend the timeout during long work. An agent busy with a legitimate long task should call ResetTimeout() periodically, so the no-connection watchdog does not mistake work for abandonment.

A complete working pair (WPF host managing several agents) lives in the repository under Examples/InterProcess.