Architecture

Calling a remote object looks exactly like calling a local one.

From your interface definition, Canopy generates a matched proxy and stub. The proxy stands in for the real object on the caller's side; the stub dispatches to the real implementation on the other side. Everything between them — serialisation, transport, lifetime, error propagation — is generated, not hand-written.

How a call crosses the boundary

Your code holds an rpc::shared_ptr<i_foo> and calls its methods like any local object. The generated proxy serialises the arguments and hands them to whichever transport is configured. On the far side, the generated stub deserialises them, dispatches to your real implementation, and the results return along the same path. Your service code and your calling code never mention the transport or the wire format.

CLIENT ZONE SERVER ZONE Your calling code holds rpc::shared_ptr<i_foo> Generated proxy serialises args, marshals the call Transport any link Generated stub deserialises, dispatches Your implementation the real method runs here 1 2 · serialise + send 3 4 · result returns

The proxy/stub pair is generated at two levels: a generic object layer carries the RPC machinery, and a typed interface layer (i_foo_proxy / i_foo_stub) handles method dispatch and parameter serialisation in whichever format the connection uses — YAS binary, JSON, or Protocol Buffers.

Zones, services, and routing

Every object lives in a zone — an execution context with its own identity and object namespace. A zone is whatever boundary you need it to be: a process, a machine, a plugin or dynamic library, a child subprocess, or a secure enclave. Each zone has a service that acts as its authority — registering objects, issuing identities, managing transport connections, and tracking reference counts.

Adjacent zones are joined by a transport. When two zones are not directly connected, calls route through intermediary zones via passthroughs — automatic multi-hop routing that keeps the whole path alive while traffic flows through it. The result: an object at any depth in any zone can call an object at any depth in any other zone, without the caller knowing how many hops lie between them.

ZoneAn execution-context boundary — process, machine, plugin, subprocess, or enclave. Objects in a zone are addressable from outside it.
ServiceThe per-zone authority for object registration, identity, transport connections, and reference counting.
TransportA direct connection between two adjacent zones — local, IPC, TCP, TLS, WebSocket, or SGX enclave.
PassthroughMulti-hop routing through intermediary zones when two zones are not adjacent. A zone can exist purely to route.

A zone stays alive exactly as long as something references it — objects in it, proxies out of it, or passthroughs through it. When the last reference is released the zone dies and its objects are reclaimed. See Pointers for the distributed-lifetime model.

Remotable objects

Any C++ class that inherits from rpc::base<T, i_interface> is remotable. Methods return an integer error code and pass results back through [out] parameters — a convention that serialises cleanly across supported transports and keeps the blocking and coroutine builds source-identical.

C++ virtual interface

class i_animal {
  virtual std::string
    make_sound() = 0;
};

Canopy IDL equivalent

interface i_animal {
  int make_sound(
    [out] std::string& s);
};

From that one definition the generator emits the proxy, the stub, and the serialisation code for every format you request — see IDL.