Skip to content

Minimal Example

This page shows the smallest practical Papyra program:

  • define an Actor
  • start an ActorSystem
  • spawn the actor
  • send a couple of messages
  • shut the system down cleanly

Warning

Papyra persistence records system facts (lifecycle events, audits, dead letters). It does not automatically persist or restore your actor's internal state. If you want state restoration (event sourcing, snapshots, etc.), you build that on top.

1) A tiny actor

Create an actor that keeps a counter in memory and returns values for request/reply calls.

from __future__ import annotations

from dataclasses import dataclass

from papyra.actor import Actor


@dataclass(slots=True)
class Increment:
    by: int = 1


class Counter(Actor):
    def __init__(self) -> None:
        self.value = 0

    async def on_start(self) -> None:
        # `self.context` becomes available only after the actor is spawned.
        # You can inspect system metadata from here.
        _ = self.context.system.system_id

    async def receive(self, message):
        if isinstance(message, Increment):
            self.value += message.by
            return self.value
        if message == "get":
            return self.value
        return None

2) Start a system, spawn the actor, send messages

from __future__ import annotations

import anyio

from papyra.system import ActorSystem

# from your_module import Counter, Increment


async def main() -> None:
    # The system can be used as an async context manager.
    # It will start automatically on enter and close on exit.
    async with ActorSystem() as system:
        ref = system.spawn(Counter, name="counter")

        # Fire-and-forget (no result expected)
        # Depending on your ActorRef API, this is typically `tell(...)`.
        await ref.tell(Increment())

        # Request/reply (returns the value from `receive`)
        # Depending on your ActorRef API, this is typically `ask(...)`.
        value = await ref.ask("get")
        print("counter =", value)


if __name__ == "__main__":
    anyio.run(main)

Notes

  • ActorSystem() defaults to an in-memory persistence backend (great for local dev/tests).
  • In receive(...), you can return a value for request/reply usage.
  • on_start() is the right place for initialization that needs self.context.

3) Adding persistence (optional)

To persist system facts to disk (events/audits/dead letters), pass a persistence backend.

from __future__ import annotations

import anyio

from papyra.persistence.json import JsonFilePersistence
from papyra.system import ActorSystem

# from your_module import Counter


async def main() -> None:
    persistence = JsonFilePersistence("./papyra.ndjson")

    async with ActorSystem(persistence=persistence) as system:
        system.spawn(Counter, name="counter")


if __name__ == "__main__":
    anyio.run(main)

That file will grow over time because it is append-only. To physically shrink it, use compaction:

papyra persistence compact --path ./papyra.ndjson

4) Where persistence is accessible at runtime

Actors can access the system's configured persistence backend via:

  • self.context.system.persistence
  • and via system operations exposed by the ActorSystem

If you need to write your own application-level state, keep it separate from the persistence of system facts.