Skip to content

The model

A KlorPy program is a choreography: a global description of roles, of where values live, and of how they move. KlorPy compiles it into one endpoint program per role, with the communication already woven in.

Roles

A role is a symbolic participant. Roles are singletons created by the Role constructor; A, B, C are predefined. A role names both a player and a vantage point: expressions written at role A run only on A.

Locations

Every choreographic value has a static location — the set of roles that hold it. KlorPy tracks locations in three shapes:

Location Meaning Example
_UNIVERSAL an agreement value available to every role literals, unannotated parameters
frozenset an agreement value at those roles {A}A's local value
tuple a distributed tuple; element i has its own location (A, B) — element 0 at A, element 1 at B

This is a light-weight analogue of Klor's location type system, and it is what makes the whole thing sound:

  • narrow/move shrink a location; copy/lift widen it.
  • A move(x, A, B) requires x to be located at A — rejected otherwise.
  • A value can only be used where it is located, so the generated per-role programs never read a value they don't have.

Projection

@choreography parses the function source (inspect + ast) and runs three passes at definition time:

  1. Analyze — infer every variable's location (klorpy.projection.Analyzer).
  2. Type-check — branch/loop/return consistency of locations (klorpy.check).
  3. Project — emit each role's program as a list of async closures (klorpy.projection.Projector). Communication operators become explicit send/receive steps; each role keeps only the slice of code that concerns it.

The result is a Choreography object holding one endpoint per role, plus the metadata (roles, params, param_locs, retvar, retloc) the runtime needs.

Execution

The runtime runs the endpoints concurrently over a Transport:

simulate_chor  all roles, one process, in-memory queues
play_role      one role, user-supplied transport config
run_role       one role, async — reuse with long-lived transports (e.g. TCP)

Because every send/receive is determined by the choreography (not by decoding plausible program order out of per-role code), the projections are synchronized by construction: matching sends and receives, no deadlock, no divergence. Distributed tuples are the extreme example — each element already lives at its role, so pack/unpack, tuple parameters, and tuple returns need no communication at all.

Choice (knowledge of choice)

A branch is only reachable by roles that know the choice:

  • a guard located at every role → each role decides locally, no broadcast (x % 2 == 0 with an agreement x);
  • a guard located at a subset → one role chooses and broadcasts the decision to the others.

The same principle scales to match/case and loops — see Choice and Loops.