Skip to main content
A @public handler is open to anyone, and the actor pays for the work it starts, so an open actor needs some protection. This page builds a help desk that has it: it decides who may call each handler, limits what one caller can start, can be paused, and keeps its work small. First, install the CLI.

1. Create the project

Output

2. Write the actor

Replace actors/desk/main.py with:

Who may call each handler

A method with no decorator is for internal use, so name it with a leading underscore, such as _forget. A call that isn’t allowed raises PermissionDeniedError and changes nothing. One thing changes after an await. The code there runs when the answer arrives, and self.sender is then the actor itself, so read the caller before the first await, as ask does with who.

Limit what one caller can start

Each job a handler starts is paid from the actor’s balance, so one caller could spend it all. Two decorators cap that. @limit(per_caller=20, every=HOUR, pending=2) lets each caller call ask at most 20 times an hour, with at most 2 questions waiting for an answer at once, and a call past either limit raises LimitReached. @single_flight(key="thread") allows one open question per thread, and a second one raises Busy until the first finishes. The SDK also caps how many questions can wait at once and always leaves room for the owner and callers you name, so a flood can’t shut you out (limits). runner.cancel(cid) ends a wait.

Pause it

Code can’t change after deploy and no CLI command stops an actor, so an actor that may need stopping has to be built for it from the start. It inherits Pausable, and gives people an @exit handler to take out what is theirs. Pausable adds two handlers only its owner can call: pause and unpause. While the actor is paused, only the owner’s calls and handlers marked @exit can change anything, and scheduled runs are skipped. To end a schedule altogether, use every.stop. @exit grants no permission itself, so pair it with @public or @callable_by.

Bound the work

A call that does too much work fails, so keep each one small. Start by capping what you store: ask refuses a new thread past MAX_THREADS, so the dict, and every loop over it, stays small. Loops themselves can be bounded too. @bounded_loop(max_iterations=N) with BoundedRange raises LoopBoundExceeded past N turns. A handler can’t await in a loop, and it has at most 8 awaits (Call models, the web and tools).

3. Test it

Replace tests/test_desk.py with:
Output
mock_host.set_context(timestamp_ms=...) moves the clock, here one hour on, so the hourly limit starts over.

Ownership and code changes

The owner is the account that deployed the actor, and cowboy actor transfer-owner hands it to another account. The code can’t change after deploy, so to ship a change, deploy a new actor (Deploy and operate).

Next