Skip to main content

Overview

The cowboy_sdk is a Python library that provides the canonical developer experience for writing Cowboy actors. It ships with the PVM and lives at node/pvm/Lib/cowboy_sdk/ in the monorepo. On deployed actors, sys.path already includes it — you can import cowboy_sdk directly.
The SDK is specified by CIP-6. APIs may still evolve; see the CIP for the authoritative surface.

What the SDK Gives You

FeatureModulePurpose
@actor decoratorcowboy_sdk.actorInjects self.storage, self.address, and wires CBOR auto-serialization
runner.continuation FSMcowboy_sdk.runnerPause an actor mid-handler while a runner job is in flight; resume on callback
CowboyModelcowboy_sdk.modelsTyped, CBOR-serializable structured data
capture()cowboy_sdk.continuationExplicitly declare variables preserved across await
codeccowboy_sdk.codecCBOR encode/decode with deterministic ordering
runtimecowboy_sdk.runtimeAccess to block context, gas, events, logging
SoftFloatcowboy_sdk.typesDeterministic floating-point type
mock_hostcowboy_sdk.mock_hostStub the PVM host for unit tests off-chain

Two Ways to Write an Actor

Low-level (host API): import pvm_host and manage bytes serialization yourself. Used by actors/feed-subscriber/main.py.
import pvm_host

def increment(ctx, payload):
    val = pvm_host.get_state(b"counter") or b"\x00" * 8
    n = int.from_bytes(val, "big") + 1
    pvm_host.set_state(b"counter", n.to_bytes(8, "big"))
    return n
Recommended — CIP-6 SDK:
from cowboy_sdk import actor, runner, capture, CowboyModel

class ChatEntry(CowboyModel):
    id: int
    sender: str
    message: str

@actor
class Counter:
    def __init__(self):
        self.storage["count"] = 0

    def increment(self):
        self.storage["count"] += 1
        return self.storage["count"]
The @actor decorator auto-CBOR-encodes values written to self.storage, injects self.address, and registers handlers with the PVM. CowboyModel fields are schema-checked and serialize deterministically.

Continuation FSM for Off-Chain Jobs

Calling a runner (LLM/HTTP/MCP) is asynchronous — the result arrives in a later block as a callback transaction. The SDK compiles @runner.continuation methods into a synchronous state machine under the hood so you can write code that looks sequential:
from cowboy_sdk import actor, runner, capture

@actor
class Chat:
    @runner.continuation
    async def chat(self, msg: str):
        capture(msg)  # preserve across the await
        result = await runner.llm(prompt=msg, model="claude-sonnet-4")
        self.storage[f"reply:{msg}"] = result.text
        return result.text
The compiler splits the method at each await, persists captured variables, and resumes on the callback. See CIP-6 Appendix A for the full rule set (await point limits, non-reversible send, etc.).

Unit Testing

Use cowboy_sdk.mock_host to run actors under pytest without a live chain:
from cowboy_sdk.mock_host import MockHost

def test_increment():
    host = MockHost()
    with host.install():
        from my_actor import Counter
        c = Counter()
        assert c.increment() == 1
        assert c.increment() == 2

Where to Find More

  • Source: node/pvm/Lib/cowboy_sdk/ — read the code directly
  • Spec: CIP-6 — normative API
  • Examples: node/examples/llm_chat/, node/examples/ring-demo/, node/examples/token/ — end-to-end actors
  • Actor anatomy: Minimal Actor
  • Transaction format: tx-format
  • Best practices: best-practices