Endowments

An endowment is a name a program requests, which the shop may deny. That is the whole definition. It is about the request, not about how powerful the thing is: $posix_file is an endowment because a shop may refuse it, and $cores — a number saying how many processors the machine has — is an endowment for exactly the same reason.

Endowments are written with a $ prefix. They look like variables in source but they are not. You cannot declare a $ name; the compiler treats every free $name it sees as a claim, records it in the compiled unit, and the shop decides. See Shop for that gate.

A denial is a build outcome. A program whose claim is refused does not compile, so there is no such thing as an actor running without an endowment it claimed.

Endowments and globals

send, log, parallel, and the other root names are globals, not endowments, and the difference is deniability. A shop will not refuse to build a program because it sends a message. It may refuse to build one that asks for the filesystem.

They travel the same way and are installed the same way — everything in the next section applies to both. Only the gate differs: an endowment claim is a question the shop answers, a global claim is a statement of what the unit needs.

How a granted name reaches an actor

An endowment is a def and a constant — from the code’s side it is simply a name bound to a value that never changes for the life of the actor. The value can be anything: a number, a text, a record of functions, an actor token.

The runner that starts an actor sees exactly three forms, and no fourth:

A value handed to it. The realization carries the value itself and the runner binds it. $cores is a number the shop resolved at build time; $self is an actor token the runtime fills in for this particular actor. Both are constants to the runner, which is the level that matters — it does not ask where a value came from, only which name to bind it to. Do not read this as “a constant the shop knows”: the shop cannot know an actor’s own token.

A unit to run. $delay, $clock, $couple, $contact and log are ordinary Pit modules — the actor never uses them, it receives them. The realization names the unit, the unit runs during startup like any other, and what it returns is the endowment.

A compiled-in provider to load. $posix_file, $metal and every other raw endowment written in C is a key the runner looks up against the binary it is running in. The row names the key; the static extension table answers it.

So the runner is handed an array and walks it: for each row, bind this value, run this unit and bind its result, or load this provider. That is the whole of what it knows, which is what lets it stay dumb — and it is the same three kinds an ordinary def import takes, because an endowment is a result bound to a $name rather than a different mechanism. See The Executable as an Array and Booting.

So the runtime holds no recipes. There is no built-in constructor for $delay and no table of known names anywhere in the start path: the realization says which name gets which value, and startup installs exactly that.

Layering over a raw primitive

A module that implements an endowment usually needs something only C can provide — a timer hook, a socket, a frame callback. It claims that raw thing itself, so the shape is always two layers:

$delay        portable Pit module
  └─ claims   a raw timer primitive from the runtime

std::file      portable Pit module
  └─ claims   $posix_file / $iocp / $playdate_file, per target

The language endowments and the platform endowments follow the same rule, which is why there is no special case for either. The raw layer is named for what it actually is — $darwin_file, $io_uring, $memfs, $playdate_file, $bsd_socket — and you rarely name one directly, because a portable module does it for you and the claim underneath changes per target. See Packages for how those arms are selected.

A missing provider is the shop’s error, not the runner’s. The shop sees every endowment and module an executable wants, collects them, and assembles the package the runner walks — so “this build has no provider for $iocp” is caught while that package is being built, where it can be reported against the claim that asked for it. The binary rather than a catalog is authoritative about which powers exist, and the shop is what reads it.

If something does fail while the runner is walking the package, the actor sends a message to the shop and dies. No unwinding, no partial start, no recovery path in the runner — which is the point of it being dumb. That path should stay unexercised; a build that reaches it has a shop bug.

Provider resolution during a build

The build has one dependency mechanism and one additional provider walk:

  1. The shop resolves the program and every ordinary use() through package identities in its links, installed package state, and dependency aliases. It compiles that module closure. A neighboring directory is not a package merely because it has package.json.
  2. Each compiled unit reports the global and $ names it claims.
  3. For a claim that needs a provider, the shop walks, in recipe order, the package identities in endowments and endowments_whole. It asks those already-loaded package roots for a matching top-level .cm, .c, or .cpp module.
  4. The matching file enters through the ordinary module compiler. If it is native and its symbol is already in the runtime’s static extension table, the shop uses that copy. If it is not present and the build permits dynamic native loading, the normal native builder compiles and loads its dylib. A source-less provider already supplied by the binary is the other valid answer.
  5. The module’s result is published under the claimed name, and policy decides whether this executable may receive it.

There is no repository package enumeration between these steps and no manifest list of provider files. The recipe supplies provider package identities; the package root supplies ordinary modules by position.

What is not claimed is not there

Because a value arrives only when the realization names it, and the realization names it only because some unit claimed it, an actor carries nothing it did not ask for. An actor that never sends a message has no send; one that never waits has no $delay.

This needs no analysis pass and no stripping step. The claim a unit records at compile time is already the liveness answer: no claim, no row, no unit, nothing instantiated.

Declaring one

A name a provider package offers is a top-level module. parallel.cm provides parallel; delay.cm can answer $delay; vm.c can answer $vm. A package may hold both .c and .cm — the extension decides whether the ordinary module is compiled by the Pit compiler or the native builder.

Whether the claim carries the $ is what makes it refusable, not the filename. A package the shop draws names from answers both kinds, because both arrive the same way — the section above is the load-bearing one: everything in “How a granted name reaches an actor” applies to globals and endowments alike, and only the gate differs. So parallel and $delay can sit side by side in one directory, and reading that directory tells you which names the package offers without telling you which of them a shop will refuse.

apple/
  package.json          aliases, modules, compilation — nothing about the files below
  darwin_file.c         $darwin_file
  darwin_tls.c          $darwin_tls
  nsurl_session.c       $nsurl_session

darwin/
  package.json
  appkit_window.c       $appkit_window
  coreaudio.c           $coreaudio
  fsevents.c            $fsevents

metal/
  package.json
  metal.c               $metal

lang-endowments/
  package.json
  delay.cm              $delay (and ordinary module lang-endowments::delay)
  start.cm              $start
  parallel.cm           parallel        — a global: offered here, never refused
  race.cm               race

The recipe does not enumerate these modules. When a compiled unit claims a name, the shop walks the provider package roots currently loaded by that recipe and probes their top-level modules for the matching stem. Provider names are flat: a file in a subdirectory provides nothing through this walk.

A provider may need help that is not itself a name anyone claims — the requestor library behind parallel, race and sequence is one module, not five. It imports that help with ordinary use() resolution. Provider packages are ordinary packages too: code may import std_endowments::vm, and the same vm.c may answer $vm during the claim walk. Being on an endowment list adds a role; it does not create a different kind of package or module.

One additional rule attaches to native providers:

  • A .c endowment needs its package on the shop’s c_native list too. Offering C and being permitted to compile it are separate decisions, and a package makes only the first of them. Two things a filename cannot say, and the C file says both of them itself:
PIT_USE_LANE(main)                     // must run on the platform main thread
PIT_USE_LEASE(watchkit.interface)      // and holds this exclusive OS slot

lane is where the provider must run, and it is structural: holding $appkit_window pins its actor to the main thread before the actor boots. lease names a genuinely exclusive OS slot. Both expand to nothing and are read by the same scanner that already reads PIT_USE_PROBE and PIT_USE_BOOT, and the build renders them into its output as data.

They are macros for the same reason the probe is: this shim has to be pinned to a thread is a fact about the C, so it belongs in the C rather than in a table beside it that someone has to remember to update. Most files declare neither. A machine that runs everything on one host callback declares a lane in every file, which reads as a fact about that machine.

Because a target’s endowments are the files that exist in its package, selection needs no arms. $iocp exists on Windows because iocp.c is in the Windows package and nowhere else. Raw endowments are named for what they are rather than for a role they fill, so there is nothing to disambiguate — portability is assembled above them, in pit, by modules like std::file.

Presence means “it compiles here”

A package holds the endowments that compile on the platforms it is for, and that is the whole of what its boundary means. The posix provider package is whatever compiles against posix; Windows does not name it because it does not compile there, and a build using an msys2-flavoured toolchain legitimately could.

So the package boundary is the compilation boundary, and nothing narrower:

  • $posix_spawn is present on iOS. It compiles, it is a thin wrapper, and it returns -1 exactly as the operating system does. Telling the truth about what a call does on a given OS is the runtime’s job — the OS gives an answer and the provider passes it through.
  • There is no per-file subsetting, and no catalog row saying that this platform takes these eleven files out of that package. A capability policy written as a list of files is a policy nobody can read off the tree, and it is the shape this design removes.
  • There are no capability packages — one package per missing call would multiply without end and still not say anything the OS does not already answer.

Preference is a separate question with an existing answer. Wanting $darwin_file rather than $posix_file on iOS is a module arm, or simply the programmer’s own choice: an iOS-specific program that claims $posix_file directly is fine, and nothing in the shop second-guesses it.

Which packages a particular target draws from is the recipe’s business, so the endowment lists are per-target settings in a recipe rather than one shop-wide set. See Recipes.

A product may carry fewer than its target offers

The recipe says what a target can provide. A product built from that target may carry less: a feature it does not need is stripped, which drops the provider module along with the C behind it, so the endowment has no shim and nothing in the binary answers it. $hook is the one that works this way today.

That absence is never silent. A claim of a stripped endowment is refused where the claim is resolved:

cannot realize a claim of '$hook': this program was built without the 'hook'
feature ($hook's call and return hook — the debugger's site half), so no
provider answers $hook. Rebuild the product with the feature, or drop the claim.

This is the same rule as “what is not claimed is not there” read from the other end: what is not built is not there either, and the shop says which build decision made it so. See Features are per product, not per target.

A third family sits beside the posix and platform ones: c_endowments, one endowment package per C standard header — stdio.h, and the rest — so a program can claim the C library surface it wants by name.

Which packages may provide them

A package containing a possible provider module is making an offer. A recipe deciding to load that package as a provider root is what makes the offer real:

endowments:       ["lang-endowments", "posix", "darwin"]
endowments_whole: []
c_native:         ["lang-endowments", "posix", "darwin"]
fallback:         ["pitlib", "std"]
bin:              ["shop_tools", "cake"]

c_native is the gate on compiling C: when populated it allows only the packages it names; its current empty spelling allows every package. To know what a build can grant, read the package roots endowments and endowments_whole name. See Shop for what the others answer.

This is what lets endowments be written by anyone while remaining the shop’s to give. A driver for a new machine is an ordinary package — a folder with some C in it — published like any other. A shop that wants it names it. Adding hardware support is adding a package, not editing the build system, and the shop that loads it is the only thing that can hand the result to a program.

Two permitted packages offering the same name is a build error reporting both, for the same reason two imports resolving one module to different bytes is: an ambiguity about identity is never silently settled by preferring one.

Selecting a target’s endowments is therefore choosing packages, not choosing files. A Windows build names a Windows provider package; macOS names its own beside a POSIX one, because it genuinely has both. This is the only place a target decides which provider roots exist, and it decides by naming package identities the shop resolves through its links, installed store, or other package state.

Nothing unclaimed is built

A provider enters a binary because something in the closure claimed it. Nothing collects the platform’s full set and trims afterwards — an endowment no program asked for is never compiled, never linked, and never named in a catalog.

That is what makes a shipped artifact small by construction rather than by pruning. A cart whose actors never start children carries no $start, and the code behind it is not present to be found. The same claim that decides what an actor receives decides what the build contains.

Unless the package is taken whole

That rule assumes the shop can still compile. On a development machine it can: an actor asks for $iocp, the shop builds the provider and hands it over, so what can be granted is simply what can be built.

A shipped runtime with no compiler cannot do that. A browser playground that runs code its author never saw has to carry every endowment that code might ask for, and no claim exists yet to pull any of them in. So beside endowments sits a second list, endowments_whole:

endowments:       ["lang-endowments", "web"]            on demand — the default
endowments_whole: ["web", "debug-endowments"]           every top-level file, claimed or not

On demand is the default and is what a shipped game wants. Whole links every top-level file in the package whether or not anything claimed it, which is what a playground, a development runtime, or a generic platform build wants. It is a companion list to endowments, holding only package names like every other list the shop keeps.

This is also how a build provides $hook and $pgo when nothing claims them. They live in a provider package like anything else; a development recipe takes that package whole. There is no such thing as an optional claim — a claim stays a hard requirement, and a component needing $inspect fails honestly on a runtime built without it rather than degrading.

Whole-linking changes what exists, not who decides. Two different questions are being answered, and by two different actors:

AsksAnswered by
existencedoes this endowment exist in this build at all?the shop, mechanically
permissionmay this executable have it?the policy actor, which you write

Taking a package whole only moves the first answer. A whole-linked playground says yes to existence for everything in the package, and its policy actor — written to be permissive — says yes to permission too. A shipped game links what it needs and ships a policy actor that denies most things to a mod it starts. Same two gates, different answers.

That split is why policy is a small program rather than a setting. The shop’s half is mechanical and identical everywhere; the interesting half is a few lines you write for the build you are shipping, and a different runtime can ship a different one. See Capabilities.

The honest caveat is narrower than it first looks: a build that takes packages whole should not be described as small-by-construction, because its artifact carries providers nothing claimed. It is still fully gated — just at the policy actor rather than by absence.

Which layer branches: the package, or the module

Two mechanisms exist for target variation, and they belong at different layers:

Below, the package is the boundary. Raw endowments for different platforms share no code — $iocp and $darwin_file have nothing in common but a purpose — so per-platform packages are the honest shape, and an arm selecting between them would only restate which package a file is in.

Above, the module arm is the boundary. A portable module like std::file or sdl::window exists precisely to present one interface over those raw surfaces, and most of it is shared. Splitting it into a package per platform would scatter a single interface across several directories and duplicate the shared part in each. So it stays one package whose manifest selects an implementation per target, and its manifest is the one place you read to see every implementation.

The rule in one line: a package boundary where the platforms share nothing, a module arm where they share an interface.

Where the values come from

Knowing which layer supplies an endowment tells you how portable your code is.

The language. An actor has to be able to identify itself, receive messages, start children, and stop: $self, $overling, $receiver, $start, $stop, $couple, $clock, $delay, $time_limit, $unneeded. Code using only these runs on any target that runs ƿit at all.

$clock is the one worth reading closely, because its shape is the reason it can be portable at all. Getting the time is not uniform enough to be one C surface — on some machines it is an asynchronous request — so $clock is asynchronous everywhere: you give it a callback and it comes back with the time. That single shape is what a per-platform time endowment sits underneath, and $clock claims it like any other layered module. $clock is the door, and a shop closes the other time-related surfaces to ordinary actors so that it is the only one; the raw reading stays available to the time module that is built on it.

The shop. A shop supplies references to its own services and facts — $clerk, $logger, $args, $shop_path. The path is the constant case: the shop knows them while it builds the realization, and they travel as data. A different shop may offer a different set, and code using one is tied to shops that provide it.

$clerk is the substantial one. The clerk mounts and knows what a package is: you ask it for <package>::<path> and it tries hard to answer, which may mean reading its own mounts, or starting a git actor to pull a repository, or an http fetcher to pull an archive — and eventually saying it could not. It also answers what it has mounted, what files a package holds, and what aliases a package declares, and it holds the shop’s lists. $start goes to the clerk too, because it is the thing that walks the resolution chain. See Booting and Assets.

Three unrelated authorities that one actor happens to answer are three endowments, each separately deniable: naming and finding is $clerk, listing actors is $inspect, and stopping an actor or the runtime is $runtime. Which actor answers is not what decides how a power is granted.

Introduction. Reaching an actor on another runtime is a pair of endowments backed by the shop’s single courier:

EndowmentGives you
$contact$contact(callback, {public_key, address, port, ...}) — after an authenticated introduction, hands back a stamped actor target you can send to
$portal$portal(callback, port) — asks the courier to own a listener; delivers a contact record that is itself a valid reply target

One courier per shop owns the runtime keypair, may own several listeners, authenticates peers, caches sessions, and routes later remote sends by destination actor id. An exported token identifies an actor together with its home runtime; an outbound-only target (a browser, say) invents no address and is routed back over the live authenticated session. The runtime-side view is in Actors and Memory.

The target. The facilities the running binary was built with. Most are platform surfaces — a file provider, a socket layer, a window, an audio device — reached through the layering above. The four debugging endowments come from here too: $vm for memory accounting, $hook for the call and return hook, $pgo for execution counters, and $inspect for read-only views of a running actor. Each is a provider a build links in, and they divide along what they cost the machine while present, so a build carries the ones it wants to pay for. See Debugging.

Attenuating one

The shop grants a whole endowment or withholds it. Narrowing one — turning “the filesystem” into “this one log file” — is something your program does for itself, by wrapping the endowment it holds and handing on the wrapper rather than the original.

That is ordinary code, not a runtime feature: a function that closes over a file endowment and exposes only an append operation is the attenuated capability. Whoever receives it can do only what it offers.

Narrowing happens between actors. An actor is one trust domain and one heap, so a value held by one unit inside an actor is a construction detail rather than a privilege — every other unit in that actor could reach it. The boundary that means something is the one actor isolation already enforces, so an actor holding a powerful endowment narrows it by granting a derived capability to another actor. Nothing in the start path treats a name as private.

Why they are not ambient

An endowment has to be given. There is no way to reach for one that was not granted, which is what makes a closure’s authority readable: what an executable can do is the set of claims it declares, and the shop sees that set before anything is built.

This is also what makes untrusted code tractable. A module that was never handed a socket cannot open one, whatever it does internally — so the interesting question about a dependency is not what it computes but what it claims.

How one is implemented

The C behind a raw endowment is a provider, and how it delivers work — on which thread, as a one-shot answer or an event stream, holding a scarce slot or not — is Providers and Lanes.