Blog › ICP guides

Pony developer on retainer: reference capabilities, actor model, iso/val/ref/tag, sendable types, and Pony actor-model systems programming on monthly retainer

September 26, 2026 · ~15 min read

A Pony actor system was processing configuration objects that needed to cross actor boundaries for parallel work dispatch. The developer created a Config object with mutable fields and attempted to pass it as a message to a worker actor: worker.process(config). Pony’s reference capability system rejected this at compile time. In Pony, every reference carries a capability annotation that determines what operations are permitted and whether the reference can be sent across actor message boundaries. The Config object had a ref capability — mutable, readable and writable, but not sendable across actor boundaries because other ref aliases to the same object could exist in the sending actor, and allowing two actors to hold writable references to the same object would create a data race. The compiler error read: cannot send ref capability across actor boundary. Compilation errors: 2 per send call. The developer restructured the Config object creation inside a recover block to obtain an iso capability (isolated, uniquely owned, sendable): let config = recover iso Config.create(host, port) end. The iso capability guarantees that only one actor holds a reference to the object at any time — sending it transfers ownership, and the sending actor can no longer access the original reference. Compilation errors: 2/send → 0. The Pony developer on retainer diagnosed the capability mismatch and restructured the object construction path: the problem was not the send call but the construction approach that produced a non-sendable capability.

The work log entry read “fixed message passing, 7h.” It names the result and duration. It cannot explain why the ref capability caused compile-time rejection of the send — Pony’s reference capabilities are not just annotations but formal guarantees enforced by the type system: ref declares that the holder can read and write, but makes no uniqueness guarantee; other ref aliases to the same object can exist in the same actor; if ref could be sent to another actor, both actors could hold writable references to the same heap object simultaneously, violating memory safety without a runtime data race detector; Pony eliminates this class of bug at compile time by requiring all actor messages to be sendable (‘sendable’ means either uniquely owned via iso, deeply immutable via val, or identity-only via tag). It cannot explain the six-capability taxonomy that governs every object reference in Pony — iso (isolated, uniquely owned, sendable; only one reference to the object exists globally; sending consumes the iso and produces a new reference in the recipient); val (deeply immutable, shareable across actors; no write access from any reference; constructed via recover val ... end once all writes are complete); ref (mutable, non-sendable; the normal mutable reference in an actor’s local heap); box (read-only view, non-sendable; permits reading but not writing; produced by viewing an iso or ref without consuming ownership); tag (identity-only, sendable; permits neither read nor write; useful for registering callbacks or passing an actor reference for future message sends); trn (transitional; write access during construction, can be frozen to val when construction is complete). It cannot explain the recover block semantics that make capability upgrading possible — inside a recover block, references to outer-scope objects cannot be captured (this is the isolation invariant); any value constructed inside the block with ref capability can be ‘recovered’ to a stronger capability (iso or val) because the block guarantees no external aliases exist; the 7 hours of capability analysis, recover block placement, and ownership transfer design are invisible in the diff.

Pony reference capabilities: iso, val, ref, box, tag, trn, and the sendability rules that govern actor message passing

Pony’s capability system is the mechanism that makes data-race-free concurrency provable at compile time without runtime synchronization overhead or garbage collection pauses. Every var and let binding in Pony carries one of six capabilities appended as a type suffix: iso, val, ref, box, tag, trn. The capability determines the permissions on the reference: whether you can read through it, write through it, create additional aliases to the same object, and whether the reference can be transferred to another actor. The sendability rule is simple: a reference can cross an actor boundary if and only if its capability is iso, val, or tag. The iso and val capabilities provide the two fundamental safety guarantees that make concurrent sharing safe — iso by ensuring at most one actor holds any reference to the object (unique ownership, the actor model’s own safety mechanism), val by ensuring no actor can write to the shared object (deep immutability, the functional programming safety mechanism). tag is safe to send because it provides neither read nor write access — the recipient can only use the tag to send messages to the tagged actor.

The recover expression is the bridge between the mutable local world (ref) and the sendable world (iso or val). recover iso Config.create(host, port) end constructs a Config inside an isolation bubble: the block proves to the compiler that no outer-scope ref aliases to the constructed object escape the block, so the constructed object can be safely declared iso. The rule enforced inside a recover block: no references to objects with ref or box capability from outside the block may be accessed inside the block (this would create an alias through the isolation boundary). In practice, this means recover blocks must be self-contained — they can use primitives, val objects, and newly constructed objects, but cannot capture mutable outer-scope state. The consume keyword transfers iso ownership: actor.send(consume config) moves the iso reference from the sender to the message queue, leaving the sender with no valid reference to config; subsequent use of config after consume is a compile error. Pony was designed by Sylvan Clebsch and first released in 2014. Its actor model, capability type system, and garbage-collection-free design make it particularly suited to high-performance concurrent network services, runtime infrastructure, and embedded real-time systems. Its retainer work is primarily in systems requiring lock-free concurrency guarantees, high-throughput actor networks, and programs where data-race elimination at compile time is required. Its closest retainer neighbors are Rust developer retainers (shared ownership/borrowing reasoning) and Erlang developer retainers (shared actor model context), but Pony’s six-capability type system, recover block isolation semantics, and garbage-collection-free causal messaging model make the retainer work distinct.

Pony actor model: behaviors, Promise[T], capability viewpoint conversion, and actor-network design

Pony’s actor model treats actors as objects with an asynchronous behavior interface. An actor declaration in Pony is syntactically similar to a class declaration: it has fields, constructors (new), synchronous methods (fun), and asynchronous behaviors (be). The distinction: fun methods execute synchronously in the caller’s turn and return values; be behaviors enqueue a message in the actor’s mailbox and return immediately with no return value. This asynchronous-by-default model means that common patterns requiring return values — calling a behavior and getting a result — require explicit continuation design. The idiomatic Pony pattern is Promise[T]: a Promise[T] iso is created by the caller, consumed into the behavior message, and fulfilled or rejected by the callee when the result is ready. The then method on Promise[T] registers a continuation that fires when the promise resolves, enabling behavior chaining without blocking any actor.

Capability viewpoint conversion — the way that the capability of a field depends on both the field’s declared capability and the capability of the reference through which the field is accessed — is the most subtle aspect of Pony capability reasoning in retainer engagements. The rule: the effective capability of a field access is the ‘viewpoint adaptation’ of the outer capability and the field capability. For example, reading a ref-capability field through an iso-capability outer reference yields a box-capability result (you cannot hold a ref to a field of an iso without breaking the uniqueness guarantee). Reading a val-capability field through any outer capability yields val (immutability is preserved regardless of the outer view). Reading an iso-capability field through a ref outer reference yields a tag (the field’s isolation is preserved by giving read-only identity access). These viewpoint conversion rules are enforced by the type checker and are the most common source of unexpected capability errors in Pony programs beyond the initial sendability issue. The retainer work involves auditing field declarations, viewpoint adaptation chains, and recover block placement to produce the intended capability at each use site. Most diffs show only the corrected capability annotations; the reasoning about viewpoint conversion chains that produced the correct annotation is invisible.

How HourTab tracks Pony developer retainer hours

Pony retainer work carries the invisible-hours problem specific to capability type systems: the program may appear structurally correct — actors declared, behaviors defined, messages sent — until the capability mismatch causes a compile-time rejection. The ref-to-iso restructuring described above is the single most common correctness issue in Pony programs written by developers familiar with concurrent programming in Go or Erlang: they model message passing as “send a reference to the object” without recognizing that Pony’s type system enforces at compile time that no two actors hold writable references to the same object simultaneously. Diagnosing this requires understanding all six capabilities, the sendability rules, the recover block isolation semantics, and the consume ownership transfer mechanism. A retainer engagement typically involves capability audit (every actor message type verified for sendability), recover block audit (every recover verified for isolation correctness), and viewpoint conversion audit (every field access through an iso or trn reference verified for correct effective capability).

HourTab gives Pony developers a public retainer-hours URL they send to clients — typically systems teams building high-performance concurrent network services, runtime infrastructure requiring data-race-free guarantees, and embedded real-time systems where garbage-collection pauses are unacceptable. For Pony retainers, each work log entry should name the mechanism (capability: iso/val/ref/box/tag/trn annotation, sendability mismatch; recover: isolation block, consume transfer, alias analysis; actor: behavior design, Promise[T] continuation, viewpoint conversion; trn: construction-then-freeze, write-once pattern), the specific capability annotation, actor message type, and before/after compilation error count, and the capability design rationale. Pony retainers are often compared to Rust developer retainers for the shared ownership-reasoning context, but Pony’s six-capability taxonomy with sendability rules, recover block isolation semantics, behavior-based async interface, and garbage-collection-free causal messaging model make the retainer work distinct in concurrent system design, capability viewpoint conversion reasoning, and actor network architecture. HourTab’s work log makes the capability analysis, recover block placement, and ownership transfer design visible to clients who would otherwise see only the symptom — compile errors on message sends — and not understand why the fix required understanding that a Pony ref reference is not a flaw but the correct mutable capability for actor-local work, and why the difference between ref and iso is the difference between mutable-local and isolated-sendable, and why the recover block is the language mechanism that bridges those two worlds.

Track Pony developer retainer hours without the status emails

HourTab gives Pony developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your capability audit log — iso/val sendability diagnosis, recover block isolation analysis, viewpoint conversion reasoning — becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: Pony developer retainers

What does a Pony developer on retainer typically do?

A Pony developer on monthly retainer covers Pony reference capabilities (iso: isolated, uniquely owned, sendable across actor boundaries; val: deeply immutable, sendable, shareable; ref: mutable but not sendable; box: read-only view, not sendable; tag: identity-only reference, no read or write access, sendable; trn: write-once transitional capability for constructing immutable values), Pony actor model design (actor keyword declares concurrent entity; be behavior method invoked asynchronously by message send; no return values from behavior calls; Promise[T] for async result propagation; Main actor as program entry point), and Pony type system (union types with | syntax; intersection types with & syntax; type aliases; generics with type constraint syntax; nominal subtyping via interface and trait; structural subtyping via interface).

What Pony work is most commonly underlogged in a retainer?

Actor message sendability diagnosis (developer created object with ref capability — mutable, non-sendable; attempted to send it as actor message; compiler rejected with ‘cannot send ref capability across actor boundary’; restructured with iso capability via recover block; compilation errors: 2/send → 0; 6–10 hrs invisible); capability viewpoint conversion (recover block to upgrade capability from ref to iso or val; consume keyword for transferring iso ownership; alias analysis for determining when capability recovery is safe; 5–9 hrs invisible); behavior interface design (no return value from behavior call; Promise[T] callback pattern for async result; Fulfillment and Rejection continuations on Promise; chaining behaviors via continuation actors; 4–8 hrs invisible); trn transitional capability design (write access during construction phase; freeze to val after construction completes; prevents aliasing during mutable construction; 4–7 hrs invisible).

What are typical Pony developer retainer rates?

Entry-level Pony developers (1–2 years, basic capability annotations, actor syntax, behavior message-passing) bill at $65–$115/hr. Mid-level Pony actor model programmers (2–4 years, iso/val/ref/box/tag/trn capability reasoning, recover blocks, Promise[T] async patterns, generic type constraints) bill at $110–$185/hr. Senior Pony capabilities systems developers (4–8 years, complex capability viewpoint conversion, alias analysis for capability recovery, high-performance actor network design, production concurrent system architecture) bill at $155–$275/hr. Monthly retainer ranges: $2,500–$5,000/mo advisory (15–25 hrs), $7,000–$18,000/mo for full Pony concurrent systems engineering.

What should a Pony developer retainer agreement include?

A Pony developer retainer agreement should specify: capability scope (iso isolated sendable; val immutable sendable; ref mutable non-sendable; box read-only non-sendable; tag identity-only sendable; trn write-then-freeze transitional); actor model scope (actor declaration; be behavior async invocation; no return values from behaviors; Promise[T] for async results; Main actor entry point); capability recovery scope (recover block for upgrading capability; consume keyword for transferring iso ownership; alias analysis determining when recovery is safe; capability viewpoint conversion rules); type system scope (union and intersection types; generic constraints; interface and trait structural vs nominal subtyping); and hour logging format (advisory category: capability, actor design, promise pattern, recover block; specific capability annotation, actor boundary crossing, and before/after compilation error count).

How should Pony developer retainer hours be logged?

Log each Pony retainer session with: advisory category (capability: iso/val/ref/box/tag/trn annotation, sendability diagnosis; actor: behavior design, Promise[T] async result, Main actor; recover: upgrade from ref to iso, consume transfer, alias analysis; trn: construction-then-freeze pattern); the specific capability annotation, actor message type, and before/after compilation error count (object: Config; original capability: ref — mutable, non-sendable; send attempt: worker.process(config) where config: Config ref; compiler error: cannot send ref capability; fix: create config in recover block to obtain iso; compilation errors: 2/send → 0); and the before/after metric. Include whether fix required recover block, consume keyword, val construction, or redesign using tag for identity-only access.