The package registry is still being built. The docs are live, everything else is a preview.

Edge Python

Sign In

Sign in to publish packages and pin them by sha256.


By continuing, you accept our Terms and Conditions.

@
Palette

Actors

An actor pool runs many edge-python programs as cooperative tasks multiplexed over a few threads, not one OS thread per program. Each actor is its own interpreter with its own heap, they share nothing and talk only by message. It serves two shapes of work. You orchestrate your own programs as a pipeline of cooperating groups, or you run untrusted code from clients, each in its own sandbox. Both run from an actor.yml.

edge actor actor.yml
bash

The pool boots the groups, runs to quiescence, and exits, or stays up as a server when a listen: address is set.

Groups

A group is one program run as a pool of interchangeable actors. It gets its program one of three ways.

groups:
  actor:
    code: |            # an inline program
      msg = receive()
      print(msg)
  parser:
    run: app           # a main.py or a whole project directory
  runner:
    eval: true         # compile each message as its own program
yaml

code: is an inline body. run: points at a script or a project directory, and a directory runs its main.py and resolves that project’s edge.json and nested imports. eval: true is untrusted mode, covered below.

Actors and load

replicas: is a ceiling, not a count. Actors are born on demand up to it and an idle actor costs about 31 KB, so a group declares a large ceiling and pays only for the actors actually running. A message is handed to an idle actor first, then to a fresh one under the ceiling, then to the least-loaded live actor once the group is saturated.

groups:
  actor:
    run: app
    replicas: 100000   # ceiling, actors spawn as work arrives
yaml

Messages

An actor loops over receive() and forwards with send(group, body), both builtins that need no import. send takes two strings, returns None, and an actor of that group picks the message up with its own receive(). Sends are fire-and-forward, an actor never blocks waiting on another, which keeps a pool free of circular deadlock.

msg = receive()
send("transform", msg + "-done")   # hand it to the transform group
python

A group’s seed: list delivers messages before the pool starts, the entry point that kicks a run off. Groups resolve their imports through the edge.json beside actor.yml, or the one --manifest names. Outside a pool nothing drains the message, so under edge run or in a browser send() raises RuntimeError: send() needs an actor scheduler, missing in this runtime.

Group fields

FieldMeaning
codeInline program body
runScript path or project directory to run
evalUntrusted mode, compile each message as its own program
replicasActor ceiling, actors spawn on demand up to it
retryTimes a crashing message is retried before it is dropped
seedMessages delivered before the pool starts
outWhere print goes, stdout, null, or file://path
limitsPer-actor heap, ops, calls, and preempt overrides

The server

A listen: address turns the pool into a live server. It stays up instead of ending at quiescence, and its ingress accepts messages over TCP.

runtime:
  listen: tcp://127.0.0.1:7777
  durable: tmp/actor/log
  control: tcp://127.0.0.1:9090
  schedulers: auto     # one per core, or a fixed number
  max_actors: 1000000   # ceiling across every group
yaml

A client connects and sends one <group> <body> line per message over tcp, or posts the body to /pub/<group> on the control address when http fits better. Either way the body reaches an actor of that group through receive().

$ curl -X POST localhost:9090/pub/actor -d 'hello'
{"ok":true}   # 202, fire and forget like the tcp line
bash

A durable: path logs every message and replays what was unprocessed on restart, so a crash loses nothing. A control: address serves live counts at /stats, and the response itself proves the pool is alive. It also takes published messages at /pub/<group> and answers eval runs at /eval/<group>, covered in untrusted code below.

$ curl localhost:9090/stats
{"actors":4,"active":1,"idle":3,"pending":0,"crashes":0,"dead":0}
bash
FieldMeaning
actorsLive actors across every group
activeActors running a message right now
idleActors parked on receive() with an empty mailbox
pendingMessages queued and not yet delivered
crashesActors retired after an uncaught error
deadMessages dropped after exhausting their retries

Failure

An actor that raises is retired with its traceback. retry: re-delivers the message it was processing to another actor up to that many times, then drops it to the dead count so one poison message cannot take a group down. A group’s actors share a few wasm instances, so a fault in the engine itself retires every actor of the instance it hit the same way, and their queued messages move on to other actors.

groups:
  actor:
    run: app
    retry: 2           # three attempts, then the message is dropped dead
yaml

Untrusted code

An eval group runs code it does not trust. Each incoming message is compiled and run in a fresh wasm instance with its own linear memory, capped by the group’s heap limit inside the interpreter and by a 256 MiB reservation outside it, and cut off after ten seconds of wall-clock time by a deadline the host enforces from outside the instance, so a runaway loop or a long native operation ends even where the interpreter cannot yield. Nothing survives between messages, the instance is dropped when the run ends. A bundle that carries its own edge.json resolves through it, any other message through the pool’s manifest. Either way .wasm plugins are refused, remote modules load only from https://cdn.edgepython.com/, a JavaScript module’s runtime has no network and shares the run’s deadline, and send() has no scheduler to reach, so untrusted code cannot send to other groups, reach the network, or load modules from disk. network imports there, and every fetch fails with NetworkError when attempting to fetch resource.

A code or run group runs code you trust. It keeps state between messages, can send, and can reach network, with the metered limits as the only cap, so keep third-party code out of it and reach for eval instead.

For a project, edge build packs it into a .edge, and a client sends it base64-encoded behind an EDGEPKG: marker. The actor validates it and serves its files to the compiler from memory, so an untrusted bundle never touches the disk.

groups:
  runners:
    eval: true         # every message is untrusted, no send, no shared state
yaml

A client that wants the result posts the snippet or bundle to /eval/<group> on the control address, and the reply carries what the run printed.

$ curl -X POST localhost:9090/eval/runners -d 'print(2 + 3)'
{"ok":true,"stdout":"5\n"}
bash

A run that raises answers {"ok":false,"error":...} with its traceback, and the caller waits thirty seconds at most before a 504. The TCP ingress stays fire and forget, only these posts get a reply.

A three-stage pipeline

A seed flows through three groups, each stage sending to the next.

groups:
  ingest:
    seed: ["raw"]
    code: |
      send("transform", receive() + "-ingested")
  transform:
    code: |
      send("sink", receive() + "-transformed")
  sink:
    code: |
      print("sink:", receive())
yaml
$ edge actor actor.yml
sink: raw-ingested-transformed
text

See also

  • CLI for edge actor and edge build.
  • Async for the cooperative scheduler each actor runs on.
  • Snapshots for freezing and resuming a single run.