Overview
This tutorial walks through the full development loop for a Cowboy Actor: scaffold a project, write a counter actor with thecowboy_sdk Python library, unit-test it off-chain, deploy it to a local devnet, and call it from the CLI.
The Quickstart deploys a pre-built starter actor; this tutorial is about writing your own. If you haven’t booted a devnet yet, do steps 1–2 of the Quickstart first — this page assumes a validator is reachable at http://localhost:4000 and the cowboy CLI is on your PATH.
What you’ll learn:
- The shape of an SDK actor:
@actor, handlers,self.storage - Handler modes (
@purevs@deferred) and permissions (@public) - How to unit-test an actor before deploying it
- The deploy → execute → inspect → iterate loop
1. Scaffold a project
.cowboy/ directory with a funded devnet wallet and a config pointing at http://localhost:4000 (see cowboy init). Create a directory for your actor:
2. Write the actor
Createactors/counter/main.py:
What each piece does
| Piece | Purpose |
|---|---|
@actor | Class decorator that injects self.storage (persistent key/value state) and self.address, and wires handler dispatch |
self.storage["count"] | Dict-like persistent state; values are CBOR-encoded automatically and metered in Cells |
@public | Permission decorator — handlers are deny-by-default; @public lets anyone call this handler |
@pure | Declares that the handler issues no asynchronous side effects (no send(), job submissions, or timers); state reads/writes and synchronous call() are allowed. Handlers that need async operations use @deferred instead |
runtime.emit_event(name, payload) | Emits an event (string, bytes, or dict payload) visible in actor logs |
codec.encode(...) | Canonical CBOR encoding — the wire format for everything crossing a trust boundary |
| Module-level wrappers | Required for on-chain dispatch: the PVM invokes top-level functions by handler name |
The
init handler runs once at deploy time (the deploy flow invokes the handler named init by default). It is a normal handler, not Python’s __init__.Determinism rules you’ll hit first
Actor code must be deterministic — every validator replays it and must get identical results. The SDK ships replacements for the usual suspects:import timeis forbidden → useruntime.get_block_height()/runtime.get_timestamp_ms()(block-derived time)import randomis forbidden → useruntime.randomness(domain)(protocol randomness)pickleis forbidden → usecowboy_sdk.codec(CBOR)set()iteration order is not deterministic — prefercowboy_sdk.ordered_setwhen you iterate over a set
3. Test it off-chain
The SDK includesSimulatedChain, an in-memory harness that stubs the PVM host so handlers run as plain Python. Create actors/counter/test_counter.py:
cowboy_sdk package on your PYTHONPATH — node/pvm/Lib as a whole is the PVM’s Python standard library and will shadow CPython’s stdlib if you add the entire directory:
4. Deploy
cowboy actor address). Save it:
5. Call it
counter.incremented events in the logs.
6. Iterate
Editmain.py, re-run the tests, and redeploy. Deploying with a new salt (e.g. --salt 0x02) gives you a fresh actor at a new address; to upgrade the code at an existing address, see cowboy upgrade-actor.
A natural next step: schedule the counter to increment itself. Actors can register block-height timers — see the worked example in examples/21-pure-timer-scheduler/ and the Scheduler overview.
Next steps
Testing Actors Locally
SimulatedChain, CallMock, and determinism checks in depth
Submitting Jobs to Runners
Call LLMs, HTTP APIs, and MCP tools from your actor
Working with Tokens
Create and move CIP-20 fungible tokens from actors and the CLI
SDK Overview
The full cowboy_sdk surface: models, continuations, guards

