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:
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.
Python package managers distribute code. Users run:
pip install packageAfterwards, 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?
Suppose a research team owns a GPU workstation. The workstation runs a tuned semantic model and exposes three functions:
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:
pip install easyremote
easyremote add @silan/lotus@^1.4 \
--realm easynet.runThen use directly:
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:
- Implementation and resources cannot migrate
- Invocation interfaces must be reusable
- Execution must have identity, permissions, end-state, and auditabilityThe most straightforward approach is for the consumer to clone the provider project:
git clone ...
pip install -r requirements.txt
python run.pyThe 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.
Another approach is to turn each function into an HTTP endpoint:
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.
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.
A seemingly elegant approach is to implement dynamic import hooks:
from network.someone.lotus import sem_filterWhen 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.
If the caller only writes:
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.
Network-Native Python Library is not achievable with a decorator alone on RPC. It must meet at least the following:
Consumers must obtain ordinary Python modules and functions:
from easyremote.silan.lotus import sem_filterAnd support:
- inspect.signature();
- IDE completion;
- mypy/pyright;
- default parameters;
- docstrings;
- sync, async, and stream;
- local parameter binding and basic validation.
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.
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.
A package identity must include:
Realm + Publisher + Package + VersionExample:
easynet.run :: @silan/lotus @ 1.4.2Realm 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.
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.
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.
Search can cross Realms, but installation must not cross Realms implicitly.
Callers must explicitly declare:
easyremote add @silan/lotus@^1.4 \
--realm easynet.runThe 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.
A remote call cannot degrade to:
function name + argsIt 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.
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.
Small JSON data can be passed directly; large DataFrames, videos, and datasets should use Resource references or streaming:
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.
I propose dividing the system into two independent but composable planes.
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?
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.
A Network Package is essentially an interface manifest signed by the publisher:
{
"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:
This publisher
has published this version of the library in this Realm
these Python symbols correspond to these governed Ability contractsManifest can exist as a network Resource owned by the publisher, without introducing a new protocol identity kind.
Providers write ordinary Python functions:
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:
easyremote publish @silan/lotus@1.4.2 \
--realm easynet.runThe publish operation reads an existing AbilityDescriptor, generates and signs the Library Manifest; it does not upload source code.
Consumer runs:
easyremote add @silan/lotus@^1.4 \
--realm easynet.runPackage resolution:
- Resolve Realm;
- Resolve @silan to canonical publisher identity;
- Obtain manifest that satisfies the version range;
- Verify signatures and manifest integrity;
- Obtain each AbilityDescriptor;
- Verify schema and descriptor reference;
- Write to lockfile;
- Generate local Python module;
- Atomically switch to the new snapshot.
If any step fails, the old version remains available, with no half-installed state.
After installation:
from easyremote.silan.lotus import sem_filterimport only loads the locally generated facade and does not access the network.
If the user wants a short name:
easyremote add @silan/lotus@^1.4 \
--realm easynet.run \
--as lotusthen:
from easyremote.lotus import sem_filterHere, lotus is a local alias, not a global package identity.
Calling:
result = sem_filter(documents, predicate="...")Execution path:
Python call
↓
local typed RemoteFunction
↓
descriptor-bound Invocation
↓
local EasyNet Runtime
↓
Directory route resolution
↓
remote execution Runtime
↓
provider-resident Python implementation
↓
result / stream / terminal ReceiptThe caller sees a normal Python function; internally the system preserves full identity, permissions, routing, and lifecycle.
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.
RPC centers on the abstraction of:
Call remote methodNetwork-Native Python Library centers on:
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 changeIt 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.
This system should not be validated with a simple hello world. At least three workload categories are needed.
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.
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.
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.
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.
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:
Traditional Python Package:
Distribute the implementation → Execute locally
Network-Native Python Package:
Distribute the interface → Execute remotely under governanceSo 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.

