Skip to main content
inspiration21 min read

From Code Distribution to Capability Distribution: A Network-Native Python Library Abstraction

Silan Hu
From Code Distribution to Capability Distribution: A Network-Native Python Library Abstraction cover
Body

Abstract

Traditional Python libraries are built on one default premise: that implementations of code, dependencies, models, and data can be copied to the caller’s machine and executed in a local process. This premise is being broken by an increasing number of workloads. A large model may reside on a single GPU; enterprise data may not leave the intranet; cameras, robots, and browsers only exist on specific devices; algorithm implementations may be constrained by licenses or trade secrets; services may come online and migrate behind NAT.

Existing approaches usually pick one of two extremes: either clone the implementation and bear the burden of the full runtime environment, or turn functions into HTTP/RPC services and lose the Python library experience. The former copies implementations that shouldn’t be copied; the latter exposes network complexity that shouldn’t be exposed.

I want to propose an abstraction for a Network-Native Python Library: the user installs not a function implementation, but a set of function contracts that are signed and versioned by the publisher; the Python side gets ordinary modules, functions, type hints, and docstrings, while the actual execution still happens where the function, model, data, or device resides.

EasyRemote provides Python authoring, packaging, and function facade; EasyNet provides Realm, identity, directory, routing, signed Invocation, and Receipt. The end user can write, without provider source code, models, or run-time environments, for example:

javascript
from easyremote.silan.lotus import sem_filter

result = sem_filter(
    documents,
    predicate="Preserve discussions about memory safety",
)

This is not turning RPC into a function, but separating the installable Python interface from the migratable remote execution into two distinct layers.


1. Problem Background

Python package managers distribute code. Users run:

javascript
pip install package

Afterwards, the package’s implementations, dependencies, and resources enter the local environment and are executed by the local Python interpreter.

This model fits:

  • pure Python algorithms;
  • small dependencies;
  • reproducible data;
  • broadly available CPU/GPU environments;
  • functions that do not depend on particular device state.

But more and more real workloads do not meet these conditions.

An AI function might depend on tens of gigabytes of model weights and a specific CUDA environment; an enterprise analytics function must be near a private database; a camera function can only run on a device with camera permissions; a robotic action must run in a local secure controller; a long-running browser agent carries login state, cookies, sessions, and local files.

In these scenarios, the truly valuable asset is not a piece of copyable source code, but:

An ability provided by a subject, in a governed execution environment, that is callable.

The problem thus shifts. We no longer ask merely, “How to publish Python code?” but ask:

Can we make an ability that cannot be copied, cannot be migrated, and cannot even be viewed, still installable, importable, composable, and versioned like a normal Python library?


2. Concrete Use Case: Semantic Operator Library on GPU

Suppose a research team owns a GPU workstation. The workstation runs a tuned semantic model and exposes three functions:

javascript
def sem_filter(
    documents: list[Document],
    predicate: str,
) -> list[Document]:
    ...

def sem_map(
    documents: list[Document],
    instruction: str,
) -> list[Document]:
    ...

def sem_join(
    left: list[Document],
    right: list[Document],
    condition: str,
) -> list[tuple[Document, Document]]:
    ...

Other team members only have ordinary laptops. They want to use these operators in notebooks, pipelines, or agents, but without dealing with:

  • CUDA and driver versions;
  • model download and warm-start;
  • GPU scheduling;
  • network ports;
  • NAT and reverse proxies;
  • API tokens;
  • HTTP DTOs;
  • provider source code;
  • changing service addresses.

The ideal experience is:

javascript
pip install easyremote

easyremote add @silan/lotus@^1.4 \
  --realm easynet.run

Then use directly:

javascript
from easyremote.silan.lotus import sem_filter, sem_map

filtered = sem_filter(
    documents,
    predicate="Discuss memory safety",
)

for item in sem_map.stream(
    filtered,
    instruction="extract core conclusions per item",
):
    print(item)

The caller does not clone the provider repo, nor obtain model weights. Import reads a locally generated Python facade; only when calling the function is there a network Invocation.

Similar needs exist for other workloads:

  • Hospitals expose only cohort_summary(), not raw patient records leaving hospital devices;
  • Camera devices expose only a limited frame stream, not a camera handle or device shell;
  • Enterprises expose only invoice_normalize(), not database credentials;
  • Robots expose only bounded move() commands, not arbitrary control instructions;
  • Browser nodes expose search_orders(), while login session remains on the owner machine.

The common structure of these workloads is:

javascript
- Implementation and resources cannot migrate
- Invocation interfaces must be reusable
- Execution must have identity, permissions, end-state, and auditability

3. Existing Bad Cases

3.1 Clone Repository

The most straightforward approach is for the consumer to clone the provider project:

javascript
git clone ...
pip install -r requirements.txt
python run.py

The problem is not installation friction; it is the erroneous assumption that the implementation can migrate. For GPU workloads, the consumer may not have a GPU; for enterprise workloads, databases and credentials cannot be copied; for device workloads, cameras, robots, and login sessions do not exist on the consumer machine; for commercial algorithms, source and weights should not be distributed.

Cloning also makes interface dependencies into implementation dependencies. The caller only wants to use sem\_filter(), but is forced to take over models, environments, resources, and deployment lifecycles.

3.2 HTTP API

Another approach is to turn each function into an HTTP endpoint:

javascript
requests.post(
    "https://gpu-node.example/api/filter",
    headers={"Authorization": "..."},
    json={
        "documents": documents,
        "predicate": "...",
    },
)

HTTP solves remote communication but does not form a Python library abstraction. Each call still requires understanding:

  • endpoint;
  • authentication;
  • timeout;
  • request/response schema;
  • error codes;
  • retry;
  • streaming protocol;
  • API version;
  • service discovery.

As the number of functions grows, the system accumulates many hand-written clients, DTOs, and endpoint conventions. The developer no longer perceives sem\_filter() but a transport contract for a service.

3.3 Generic RPC

Systems like gRPC, Ray, Pyro reduce remote call costs, but still miss several dimensions needed by an open-world package manager:

  • Who published this function?
  • Which trust domain does it belong to?
  • If there are two identically named functions, which owner should be chosen?
  • Which interface version is installed by the caller?
  • If a route changes, is the contract still consistent?
  • How does the team reproduce the exact same dependency set?
  • Does remote execution yield verifiable end-state?

RPC answers how to call, but not automatically answer what to call, who to trust, or which version to install.

3.4 Accessing Network at Import Time

A seemingly elegant approach is to implement dynamic import hooks:

javascript
from network.someone.lotus import sem_filter

When Python imports a module, the system searches the network in real time and constructs the function.

This approach makes imports unpredictable:

  • network unavailability can cause import to fail;
  • the same code at different times can yield different APIs;
  • IDEs and type checkers can’t know module contents;
  • new identically named functions can change resolution results;
  • a provider can influence local import via network metadata;
  • a normal Python import can implicitly carry remote trust decisions.

Therefore the network can change, but the Python import surface must come from a local, versioned, reproducible snapshot.

3.5 Discovering by Bare Function Name Only

If the caller only writes:

javascript
client.call("sem_filter", ...)

When multiple sem\_filter exist in the network, the system can randomly select, choose by order, or rely on hidden strategies.

This looks convenient in demos, but is dangerous in an open network. Identically named functions from different publishers, different Realms, and different versions may have completely different permissions, costs, and semantics.

Discovery can be fuzzy; installation and execution must be precise.


4. High Requirements

Network-Native Python Library is not achievable with a decorator alone on RPC. It must meet at least the following:

R1. Python-Native Interface

Consumers must obtain ordinary Python modules and functions:

javascript
from easyremote.silan.lotus import sem_filter

And support:

  • inspect.signature();
  • IDE completion;
  • mypy/pyright;
  • default parameters;
  • docstrings;
  • sync, async, and stream;
  • local parameter binding and basic validation.

R2. Import Must Be Local

import cannot access the network. Network packages must first be parsed and materialized into local .py/.pyi facades.

Network failures can cause a function call to fail, but cannot cause an already installed module to disappear.

R3. No Implementation Distribution

Installing a network package must not download or execute provider source code. It can install:

  • signatures;
  • schemas;
  • documentation;
  • descriptor references;
  • type stubs;
  • deterministic proxy code.

The system should not support provider-supplied install scripts.

R4. Global Naming Without Global Monopolies

A package identity must include:

javascript
Realm + Publisher + Package + Version

Example:

javascript
easynet.run :: @silan/lotus @ 1.4.2

Realm denotes identity, trust, and routing domain; Publisher denotes who is responsible for the package; Package is the installable interface set; Version describes the public library surface.

npm uses @scope/package to solve naming conflicts across users and organizations and allows mapping between scope and registry. Scope reference: npm Scope. Network-Native Package distributes interfaces, not implementations.

R5. Reproducible Resolution

Projects must have a lockfile fixing:

  • Realm identity;
  • Publisher URA;
  • package version;
  • manifest hash;
  • exported Ability URA;
  • descriptor reference;
  • schema hash;
  • call mode.

Team members on different machines performing restore should obtain the same Python symbols and the same function contracts.

R6. Stable Contract, Dynamic Placement

Function contracts and execution addresses must be decoupled.

descriptor\_ref fixes the interface the caller relies on; Directory resolves the current execution route dynamically. Providers can move machines, restart processes, or migrate from GPU-A to GPU-B without changing the consumer’s import.

Conversely, if a function signature undergoes an incompatible change, the system must not call it just because a function with the same name is still found.

R7. Explicit Trust and Realm Crossing

Search can cross Realms, but installation must not cross Realms implicitly.

Callers must explicitly declare:

javascript
easyremote add @silan/lotus@^1.4 \
  --realm easynet.run

The same @silan/lotus in another Realm is a different package. The Publisher handle must resolve to a canonical principal identity rather than a mutable string.

R8. Complete and Auditable Invocation

A remote call cannot degrade to:

javascript
function name + args

It must at least determine:

  • caller;
  • callee;
  • descriptor;
  • subject;
  • nonce;
  • causal context;
  • arguments.

Invocations must have a unique terminal state: success, failure, or cancellation. Streams must have bounded buffering and a single terminal closure. Retries and replays must be identifiable, and results auditable via Receipt.

R9. Honest Remote Semantics

A network-native function may have local function syntax but cannot hide the network reality.

It must expose understandable exceptions:

  • unavailable;
  • deadline exceeded;
  • permission denied;
  • schema mismatch;
  • resource exhausted;
  • cancelled.

Network latency does not disappear simply because Python syntax is used. Large objects cannot be masqueraded as memory references.

R10. Data-Aware Invocation

Small JSON data can be passed directly; large DataFrames, videos, and datasets should use Resource references or streaming:

javascript
dataset = resource(
    "easynet:///r/health.example/resource/hospital/datasets/cohort"
)

summary = cohort_summary(dataset, query="...")

Capabilities should reside near data and devices rather than copy everything to the caller’s machine by default.


5. Our Solution

I propose dividing the system into two independent but composable planes.

5.1 Capability Plane

The Capability Plane manages the actual facts of execution:

  • who is the caller;
  • who owns the capability;
  • canonical identity of the Ability;
  • Descriptor;
  • current execution location;
  • admission;
  • routing;
  • invocation lifecycle;
  • Receipt.

EasyNet is responsible for this layer. Realm, Directory, Runtime, and Hub answer:

Is this signed invocation allowed to execute in the current network state, and where should it execute?

5.2 Package Plane

The Package Plane manages developer dependencies:

  • package name;
  • publisher scope;
  • semantic version;
  • exports;
  • dependencies;
  • local alias;
  • lockfile;
  • Python facade generation.

EasyRemote handles this layer. It answers:

Which remote capabilities does a Python project depend on, and how should these capabilities appear as a stable Python API?

The Package Plane does not execute functions, nor decide network routing by itself. It simply compiles governance-approved AbilityDescriptors into a Python library surface.


6. Library Manifest

A Network Package is essentially an interface manifest signed by the publisher:

javascript
{
  "realm": "easynet.run",
  "publisher": "@silan",
  "publisher_ura": "easynet:///r/easynet.run/user/silan",
  "name": "lotus",
  "version": "1.4.2",
  "exports": {
    "sem_filter": {
      "ability_ura": "easynet:///r/easynet.run/ability/...",
      "descriptor_ref": "...",
      "schema_hash": "sha256:...",
      "call_mode": "rpc"
    },
    "sem_map": {
      "ability_ura": "easynet:///r/easynet.run/ability/...",
      "descriptor_ref": "...",
      "schema_hash": "sha256:...",
      "call_mode": "stream"
    }
  }
}

Manifest is not an implementation bundle. It merely declares:

javascript
This publisher
has published this version of the library in this Realm
these Python symbols correspond to these governed Ability contracts

Manifest can exist as a network Resource owned by the publisher, without introducing a new protocol identity kind.


7. End-to-End Workflow

7.1 Provider

Providers write ordinary Python functions:

javascript
from easyremote import ComputeNode

node = ComputeNode(namespace="lotus")

@node.register
def sem_filter(
    documents: list[dict],
    predicate: str,
) -> list[dict]:
    ...

@node.register
def sem_map(
    documents: list[dict],
    instruction: str,
):
    yield from ...

node.serve()

EasyRemote generates a schema from function signatures and registers implementations to a local Runtime. Implementations, models, GPUs, and data remain on the provider machine.

Then publish the interface package:

javascript
easyremote publish @silan/lotus@1.4.2 \
  --realm easynet.run

The publish operation reads an existing AbilityDescriptor, generates and signs the Library Manifest; it does not upload source code.

7.2 Consumer Installation

Consumer runs:

javascript
easyremote add @silan/lotus@^1.4 \
  --realm easynet.run

Package resolution:

  1. Resolve Realm;
  2. Resolve @silan to canonical publisher identity;
  3. Obtain manifest that satisfies the version range;
  4. Verify signatures and manifest integrity;
  5. Obtain each AbilityDescriptor;
  6. Verify schema and descriptor reference;
  7. Write to lockfile;
  8. Generate local Python module;
  9. Atomically switch to the new snapshot.

If any step fails, the old version remains available, with no half-installed state.

7.3 Consumer Import

After installation:

javascript
from easyremote.silan.lotus import sem_filter

import only loads the locally generated facade and does not access the network.

If the user wants a short name:

javascript
easyremote add @silan/lotus@^1.4 \
  --realm easynet.run \
  --as lotus

then:

javascript
from easyremote.lotus import sem_filter

Here, lotus is a local alias, not a global package identity.

7.4 Invocation

Calling:

javascript
result = sem_filter(documents, predicate="...")

Execution path:

javascript
Python call
   ↓
local typed RemoteFunction
   ↓
descriptor-bound Invocation
   ↓
local EasyNet Runtime
   ↓
Directory route resolution
   ↓
remote execution Runtime
   ↓
provider-resident Python implementation
   ↓
result / stream / terminal Receipt

The caller sees a normal Python function; internally the system preserves full identity, permissions, routing, and lifecycle.


8. Version Model

A Network Package carries at least three distinct version concepts.

Layer

Meaning

Package version

The public surface of the entire Python library

Descriptor version

The contract of a single Ability invocation

Implementation binding

The actual execution implementation and location

They must not be conflated.

Adding a compatible function typically only requires a package minor version; removing a function or changing parameters requires a major version; fixing a remote algorithm while preserving input/output contracts can update only the implementation binding; migrating GPUs does not require the consumer to upgrade the package.

This means providers can evolve deployments while consumers rely on stable interfaces.


9. Why This Is Not Just RPC

RPC centers on the abstraction of:

javascript
Call remote method

Network-Native Python Library centers on:

javascript
Install a Python dependency governed by a defined principal,
resolved in a defined Realm,
having a defined version and function contracts,
but with a location that can dynamically change

It combines the classic library system aspects of:

  • import;
  • typing;
  • version;
  • dependency;
  • lockfile;
  • namespace;
    with capability network aspects of:
  • identity;
  • trust;
  • routing;
  • admission;
  • execution;
  • Receipt;

so that they form a closed loop.

The user experiences it as “usable like a Python library,” but the result is not syntactic sugar; it is package resolution, contract binding, runtime routing, and execution accountability all at once.


10. Evaluation Plan

This system should not be validated with a simple hello world. At least three workload categories are needed.

Workload A: GPU Semantic Operators

Validation:

  • consumer has no model or provider repo;
  • IDE can complete function signatures correctly;
  • provider can swap GPU hosts without changing business code;
  • incompatible descriptor versions fail before invocation;
  • streaming remains incremental.

Workload B: Private Data Analytics

Validation:

  • raw data never leaves owner environment;
  • consumer only receives restricted aggregates;
  • subject and caller permissions are strictly enforced;
  • Receipt can prove who executed which capability on which data.

Workload C: Edge Camera or Robot

Validation:

  • consumer does not obtain device shell or raw drivers;
  • binary streams are bounded;
  • cancellation yields a single terminal state;
  • device disconnects, recovers, and route updates do not alter the package interface.

Baseline comparisons should include:

  • clone and local deployment;
  • REST/OpenAPI client;
  • generic RPC;
  • dynamic service discovery.

Evaluation metrics should cover not only latency but also:

  • how many implementations a consumer must manage;
  • whether model, data, or credentials are copied;
  • when interface mismatches are discovered;
  • whether identically named packages may be parsed incorrectly;
  • whether route migrations require code changes;
  • whether failures yield a definite end-state;
  • whether the same lockfile yields identical APIs;
  • whether streams and queues remain bounded.

11. Limitations

This abstraction cannot eliminate network itself.

Remote functions may still fail due to network, permissions, resources, and provider lifecycle; large Python objects cannot be transferred between processes without cost; functions relying on local object identity, closures, file handles, and thread state cannot directly become network functions.

Mapping JSON Schema to Python types has expressive limits. Complex custom types require explicit portable schemas and cannot rely on downloading provider class definitions.

Multiple provider choices cannot rely on random identical-name matches. If a package supports multiple implementations, load balancing must be a governance-based route policy, not hidden behavior of the Python facade.

Finally, package availability and execution availability are different states. An installed package can be imported, but if there is no executable provider, calls must clearly return unavailable rather than silently degrade to another incompatible function.


12. Conclusion

The value of a traditional Python library is that developers can reuse it without understanding an implementation. But traditional package managers achieve reuse by copying implementations.

Network-Native Python Library preserves the same developer experience while changing the physical means of reuse:

javascript
Traditional Python Package:
Distribute the implementation → Execute locally

Network-Native Python Package:
Distribute the interface → Execute remotely under governance

So my current framing for EasyRemote is that it is not merely a simpler RPC decorator, nor magic that makes the internet look like a local function.

It is a Python capability package system: developers install stable, typed, versioned interfaces; implementations, models, data, and devices stay with the owners; Realms decide trust and boundary; Runtime decides execution location; Invocation and Receipt decide how a call is authorized, completed, and auditable.

What’s truly worth discussing is not whether from easyremote... import ... is elegant enough, but whether the following four conditions can be simultaneously satisfied behind that line in real workloads:

  • No copying of implementations, yet reuse remains;
  • No fixed addresses, yet resolvable naming remains;
  • No exposure of network details, yet failure modes stay truthful;
  • No sacrifice of Python experience, yet identity, permissions, versioning, and auditing are preserved.

If these four can be demonstrably proven in reproducible workloads, then “the network itself becoming the execution backbone of a Python library” will not just be a syntactic dream but a valid systemic abstraction.

0 likes
Silan Hu2 readsShare:

No comments yet