prelude
v0.1.0 · compiled with katari 0.1.0
prelude
17 declarationsadd#
primitive agentprimitive agent add[T extends number](left: T, right: T) -> TAdd two numbers; preserves integer when both operands are integers.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
subtract#
primitive agentprimitive agent subtract[T extends number](left: T, right: T) -> TSubtract two numbers; preserves integer when both operands are integers.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
multiply#
primitive agentprimitive agent multiply[T extends number](left: T, right: T) -> TMultiply two numbers; preserves integer when both operands are integers.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
modulo#
primitive agentprimitive agent modulo[T extends number](left: T, right: T) -> TFloor-modulo two numbers; preserves integer when both operands are integers. A zero divisor panics (guard with a comparison first).
Generics
Parameters
The dividend.
The divisor.
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
divide#
primitive agentprimitive agent divide(left: number, right: number) -> numberDivide two numbers; always yields a number. A zero divisor panics (guard with a comparison first) — division never yields Infinity / NaN.
Parameters
The dividend.
The divisor.
Returns
Wire view (JSON Schema)
negate#
primitive agentprimitive agent negate[T extends number](value: T) -> TNegate a number; preserves integer.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
equal#
primitive agentprimitive agent equal[T](left: T, right: T) -> booleanWhether two values are equal.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
not_equal#
primitive agentprimitive agent not_equal[T](left: T, right: T) -> booleanWhether two values are not equal.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
less_than#
primitive agentprimitive agent less_than[T extends number](left: T, right: T) -> booleanWhether the left number is less than the right.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
less_or_equal#
primitive agentprimitive agent less_or_equal[T extends number](left: T, right: T) -> booleanWhether the left number is less than or equal to the right.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
greater_than#
primitive agentprimitive agent greater_than[T extends number](left: T, right: T) -> booleanWhether the left number is greater than the right.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
greater_or_equal#
primitive agentprimitive agent greater_or_equal[T extends number](left: T, right: T) -> booleanWhether the left number is greater than or equal to the right.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
and#
primitive agentprimitive agent and(left: boolean, right: boolean) -> booleanLogical conjunction.
Parameters
Returns
Wire view (JSON Schema)
or#
primitive agentprimitive agent or(left: boolean, right: boolean) -> booleanLogical disjunction.
Parameters
Returns
Wire view (JSON Schema)
not#
primitive agentprimitive agent not(value: boolean) -> booleanLogical negation.
Parameters
Returns
Wire view (JSON Schema)
concat#
primitive agentprimitive agent concat(left: string, right: string) -> stringConcatenate two strings.
Parameters
Returns
Wire view (JSON Schema)
throw#
requestrequest throw[T](error: T) -> neverSignal a typed error the caller may recover from. The payload is a domain-specific error data (e.g. json.parse_error); a handler at that payload type catches it — use handler { request throw(error: my_error) -> never { break fallback } } — and an unhandled throw fails the run with the payload. -> never means a throw cannot be resumed: a handler recovers by breaking out of the handle (or rethrows).
Generics
Parameters
The error payload; a handler naming this payload type catches it.
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.array
11 declarationsget#
primitive agentprimitive agent get[T](target: array[T], index: integer) -> T | nullThe element at index (0-based), or null when the index is out of bounds.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
length#
primitive agentprimitive agent length(target: array[unknown]) -> integerThe number of elements.
Parameters
Returns
Wire view (JSON Schema)
append#
primitive agentprimitive agent append[T](target: array[T], value: T) -> array[T]A copy of the array with value appended.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
concat#
primitive agentprimitive agent concat[T](left: array[T], right: array[T]) -> array[T]The left array followed by the right.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
slice#
primitive agentprimitive agent slice[T](target: array[T], start: integer, end: integer) -> array[T]The elements from start (inclusive) to end (exclusive), 0-based, clamped to the bounds.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
contains#
primitive agentprimitive agent contains[T](target: array[T], value: T) -> booleanWhether value occurs among the elements (structural equality, like ==).
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
index_of#
primitive agentprimitive agent index_of[T](target: array[T], value: T) -> integer | nullThe index of the first element equal to value (structural equality), or null when none is.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
flatten#
primitive agentprimitive agent flatten[T](target: array[array[T]]) -> array[T]The arrays' elements concatenated in order — one level of nesting removed. With for's mapping this is also the filter idiom: map each element to [x] or [], then flatten.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
reverse#
primitive agentprimitive agent reverse[T](target: array[T]) -> array[T]The elements in reverse order.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
range#
primitive agentprimitive agent range(start: integer, end: integer) -> array[integer]The integers from start (inclusive) to end (exclusive), in order — for's counted-loop source. Empty when end <= start.
Parameters
Returns
Wire view (JSON Schema)
empty#
primitive agentprimitive agent empty() -> array[never]The empty array.
Returns
Wire view (JSON Schema)
prelude.env
3 declarationsmissing_secret#
datadata missing_secret(key: string, message: string)No secret env entry is set under key. Thrown by get_secret — catch it to fall back in-program (use handler { request throw(error: env.missing_secret) -> never { ... } }).
Parameters
The key that had no secret entry.
Wire view (JSON Schema)
get_secret#
primitive agentprimitive agent get_secret(key: string) -> string of private with prelude.throw[missing_secret]Read a secret env entry by key, returning its value as a private string (the value is tainted secret and cannot flow to a user-facing boundary). A missing key throws missing_secret (carrying the key), so a program can fall back for optional config; uncaught, the throw fails the run.
Parameters
The secret entry's key in the project's env store.
Returns
Effects
Wire view (JSON Schema)
get_all#
primitive agentprimitive agent get_all() -> record[string]Read every non-secret env entry as a record of bare (public) strings, keyed by the env key.
Returns
Wire view (JSON Schema)
prelude.files
7 declarationsgone#
datadata gone(message: string)The file a handle names is gone — it was freed, or the id never named a blob in this project (a model hallucinating a $katari_ref). One error covers both: a slim handle cannot tell which, and a program that catches this reacts the same way (re-fetch, skip). Thrown by every reader below; catch it to recover.
Parameters
Wire view (JSON Schema)
malformed_base64#
datadata malformed_base64(message: string)The content passed to from_base64 is not valid base64 (bad alphabet, length, or padding). Thrown by from_base64 — catch it to fall back when a model or upstream API hands back a corrupt base64 payload.
Parameters
Wire view (JSON Schema)
read_base64#
primitive agentprimitive agent read_base64(value: file) -> string with prelude.throw[gone]The file's bytes, base64-encoded — the inline shape multimodal model APIs take. The whole content materialises as one string, so mind the size (a large file makes a large request body). Throws gone if the handle no longer names a live blob (freed, or a made-up id).
Parameters
Returns
Effects
Wire view (JSON Schema)
content_type#
primitive agentprimitive agent content_type(value: file) -> string with prelude.throw[gone]The file's recorded MIME type (e.g. "image/png"), read from the project's file catalog; "" when none was recorded. Throws gone if the handle no longer names a live blob.
Parameters
Returns
Effects
Wire view (JSON Schema)
size#
primitive agentprimitive agent size(value: file) -> integer with prelude.throw[gone]The file's size in bytes, read from the project's file catalog (no content download). Throws gone if the handle no longer names a live blob.
Parameters
Returns
Effects
Wire view (JSON Schema)
from_base64#
primitive agentprimitive agent from_base64(content: string, content_type: string) -> file with prelude.throw[malformed_base64]Make a file from a base64 string — the engine's blob producer, for lifting a model's / API's embedded base64 (a gemini inline_data image) OFF the value plane and INTO a real blob handle, so the heavy bytes travel runtime-side like any other file. The new file is owned by the current run and reclaimed with it (or explicitly, via free). Two calls with the SAME bytes are DIFFERENT files (a file is a resource, not a literal). Throws malformed_base64 if content is not valid base64.
Parameters
The file's bytes, base64-encoded.
The MIME type to record (e.g. "image/png"); "" to record none.
Returns
Effects
Wire view (JSON Schema)
free#
primitive agentprimitive agent free(value: file) -> nullRelease a file the current run owns, freeing its bytes now rather than at run teardown — for a large, short-lived intermediate (a decoded image already handed to a model). Idempotent and silent: freeing an already-gone file, or one the run does NOT own (a user-uploaded file, whose lifetime is the file API's), is a no-op — so a retried block that frees the same handle twice behaves identically each attempt. A later read of a freed handle throws gone.
Parameters
Returns
Wire view (JSON Schema)
prelude.http
13 declarationsfetch_error#
datadata fetch_error(message: string)An HTTP request never completed: DNS failure, refused connection, timeout, or a mid-flight runtime restart. Thrown by fetch. (A response that did arrive is never an error — branch on its status.)
Parameters
Wire view (JSON Schema)
text#
datadata text(content: string of private)A text body: the content string, sent verbatim (Content-Type is the caller's to set). The content may be a secret (an OAuth refresh_token in a form-encoded body).
Parameters
Wire view (JSON Schema)
binary#
datadata binary(content: file)A raw-bytes body: the file content's bytes, sent as-is — the shape an upload API (an S3 PUT) takes. The Content-Type defaults to the file's recorded content type; set a Content-Type header to override it.
Parameters
Wire view (JSON Schema)
multipart#
datadata multipart(parts: array[multipart_part])A multipart/form-data body (RFC 7578): the parts, each a named form field — text or file. The transport generates the boundary and sets the Content-Type, so do not set a Content-Type header for it.
Parameters
Wire view (JSON Schema)
json#
datadata json(value: unknown of private)A JSON body from a value value tree (records / arrays / scalars / files, mixed): serialised to a JSON document where every file leaf — and only a file leaf — becomes the base64 of its bytes, in place. This is the REST convention (a GitHub content upload, an AI provider's inline image), NOT an HTTP standard. To transmit a file's HANDLE instead of its bytes (almost never what an API wants), stringify it yourself first (e.g. embed json.stringify(value = a_file) as a string). Everything but a file goes on the wire as the tree you built. The tree may carry secrets (its type is private-capable), revealed only to the destination.
Parameters
Wire view (JSON Schema)
body#
typetype body = text | binary | multipart | jsonDefinition
multipart_text#
datadata multipart_text(name: string, content: string of private)A text form field: the field named name with the text content (which may be a secret).
Parameters
Wire view (JSON Schema)
multipart_file#
datadata multipart_file(name: string, filename: string, content: file)A file form field: the field named name carrying the file content, advertised with filename. Its per-part Content-Type is the file's recorded content type.
Parameters
Wire view (JSON Schema)
multipart_part#
typetype multipart_part = multipart_text | multipart_fileDefinition
fetch#
external agentreactor: httpexternal agent fetch(url: string, method: string, headers: record[string of private], body: body) -> {status: integer, headers: record[string], body: string} with prelude.throw[fetch_error] from "http"Perform an HTTP request to url with method (e.g. "GET" / "POST"), the given headers and body. The body is one of http.text / http.binary / http.multipart / http.json (a file in any of them is read from the blob store at the send boundary, never materialised earlier). Each header value AND a private body surface (a text's content, a json's tree) may be a secret, submitted to url's server, while url and method stay public. Returns the response status code, its headers (names lowercased; repeated headers joined with ", ") and its body text. A request that never completes throws fetch_error (catchable with use handler { request throw(error: http.fetch_error) -> never { ... } }).
Parameters
The absolute URL to request; public by type (a URL leaks into logs and proxies, so a secret here is a type error).
The HTTP method, e.g. "GET" / "POST" (case-insensitive). For GET / HEAD the body is not sent.
Request headers by name; each value may be a secret, revealed only to url's server.
The request body: http.text / http.binary / http.multipart / http.json; dropped for GET / HEAD.
Returns
Effects
Wire view (JSON Schema)
fetch_file#
external agentreactor: httpexternal agent fetch_file(url: string, method: string, headers: record[string of private], body: body) -> {status: integer, headers: record[string], file: file} with prelude.throw[fetch_error] from "http"Perform an HTTP request to url with method, the given headers and body — EXACTLY fetch's request — but capture the RESPONSE body as a downloaded file rather than as text. The bytes land in the blob store and the reply carries only the slim file HANDLE; the raw bytes (and their base64) never touch the value plane, the durable call record, or the trace. This is the receive-side twin of a binary REQUEST body, and it exists for the same reason: to keep a payload's bytes OFF the value plane. A plain fetch of a megabyte image would read it into the response body STRING, dragging that text (and its base64, once re-encoded) through every value, event, and trace it flows into — whereas fetch_file streams it straight to a blob and hands back a handle. Reach for it to download a file by URL: an image, a PDF, a CSV export, a release artifact. It is NOT for a base64 field buried inside a JSON reply (a data-URI in an API's JSON document) — fetch_file only ever captures the WHOLE response body; decode an embedded base64 string with files.from_base64 after an ordinary fetch. The downloaded file's recorded content type is the response's Content-Type (falling back to application/octet-stream when the server sent none). Returns the response status code (branch on it exactly like fetch — a non-2xx reply's body is still captured to a file, so a caller inspects status before trusting the download), its headers (names lowercased; repeated headers joined with ", "), and the downloaded file. A request that never completes throws fetch_error, exactly like fetch.
Parameters
The absolute URL to request; public by type (a URL leaks into logs and proxies, so a secret here is a type error).
The HTTP method, e.g. "GET" / "POST" (case-insensitive). For GET / HEAD the body is not sent.
Request headers by name; each value may be a secret, revealed only to url's server.
The request body: http.text / http.binary / http.multipart / http.json; dropped for GET / HEAD.
Returns
Effects
Wire view (JSON Schema)
status_error#
datadata status_error(status: integer, body: string)A JSON API responded with a non-2xx status. Thrown by post_json (the raw fetch never treats an arrived response as an error — this wrapper takes the opinionated JSON-API stance).
Parameters
The response's non-2xx status code.
The response body text.
Wire view (JSON Schema)
post_json#
agentagent post_json(url: string, body: unknown of private, headers: record[string of private]) -> unknown with io | prelude.throw[status_error | fetch_error | parse_error]POST body — a JSON value tree — to url as application/json with the given headers, and return the reply PARSED into its document value. This is the canonical one-call shape of a JSON API integration: build the body as an ordinary record / array / scalar tree, post it, and read the reply as a value — no manual json.stringify / json.parse around the call. body AND each header value may be a secret (an auth token — an "Authorization" of "Bearer <key>" for OpenAI, an "x-goog-api-key" for Gemini — or a refresh_token in the body of an OAuth exchange): both are submitted only to url's server, which stays public. A file placed anywhere in body rides as base64 (the http.json slot contract). The Content-Type defaults to application/json unless a header overrides it. A non-2xx status throws status_error (its body text attached); a transport failure throws fetch_error; a 2xx body that is not JSON throws json.parse_error (a JSON API that broke its promise) — an empty 2xx body is null. To send already-serialized text, or to keep the raw reply text, call fetch directly.
Parameters
The absolute URL to POST to; public by type.
The JSON body as a value tree (records / arrays / scalars / files, mixed); may carry secrets.
Extra headers by name (the caller wins over the application/json default on a shared name); values may be secrets.
Returns
Effects
Wire view (JSON Schema)
prelude.json
11 declarationsparse_error#
datadata parse_error(message: string)A JSON document failed to parse: the text is not JSON. Thrown by parse / parse_as.
Parameters
Wire view (JSON Schema)
validation_error#
datadata validation_error(message: string)A value does not conform to the requested type's schema. Thrown by validate / parse_as; message names the offending path.
Parameters
Wire view (JSON Schema)
parse#
primitive agentprimitive agent parse(text: string) -> unknown with prelude.throw[parse_error]Parse JSON text into the value it denotes — a record / array / string / integer / number / boolean / null, or a file where the text carries a $katari_ref handle. Traverse a document with shape filters (case record(r)) and the readers below, or pin its shape with parse_as[T]. A number without a fractional part parses as integer. Malformed text throws parse_error.
Parameters
The text of one complete JSON document.
Returns
Effects
Wire view (JSON Schema)
validate#
primitive agentprimitive agent validate[T](value: unknown) -> T with prelude.throw[validation_error]Check value against T's schema and return it unchanged, or throw validation_error naming the offending path. A pure check — it never rewrites the value. Instantiate T explicitly (it appears only in the result, so it cannot be inferred).
Generics
Parameters
The value to check against T.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
parse_as#
agentagent parse_as[T](text: string) -> T with prelude.throw[parse_error | validation_error]Parse JSON text directly as a T — parse then validate[T], the typed text boundary. Instantiate T explicitly (it appears only in the result, so it cannot be inferred). Malformed text throws parse_error; a value that does not conform to T's schema throws validation_error naming the offending path.
Generics
Parameters
The text of one complete JSON document.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
stringify#
primitive agentprimitive agent stringify[T](value: T) -> stringRender ANY value as its (compact) JSON text — TOTAL and CANONICAL (one value, one text). A document value (record / array / string / integer / number / boolean / null) round-trips verbatim, so stringify(parse(s)) is s up to whitespace; a file becomes its $katari_ref handle object, a data value nests its fields under $katari_value ({ $katari_constructor: name, $katari_value: { ...fields } }), and an agent / closure / tool its handle object. stringify is the READ CHANNEL for showing an AI a handle / data value (an error render, a handle embed, a tool result rendered back to a model). A secret operand taints the whole result (the monotonic information-flow rule).
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
entries#
agentagent entries(target: unknown) -> record[unknown]The record target holds (its entries), or the empty record if target is not a record.
Parameters
Returns
Wire view (JSON Schema)
items#
agentagent items(target: unknown) -> array[unknown]The array target holds (its elements), or the empty array if target is not an array.
Parameters
Returns
Wire view (JSON Schema)
field#
agentagent field(target: unknown, key: string) -> unknownThe child at object key key (null if target is not a record or has no such key).
Parameters
Returns
Wire view (JSON Schema)
element#
agentagent element(target: unknown, index: integer) -> unknownThe element at array index index (null if target is not an array or index is out of range).
Parameters
Returns
Wire view (JSON Schema)
text#
agentagent text(target: unknown) -> stringThe string target holds (the empty string if it is not a string).
Parameters
Returns
Wire view (JSON Schema)
prelude.math
6 declarationsabs#
primitive agentprimitive agent abs[T extends number](value: T) -> TThe absolute value; preserves integer.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
min#
primitive agentprimitive agent min[T extends number](left: T, right: T) -> TThe smaller of the two; preserves integer when both operands are integers.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
max#
primitive agentprimitive agent max[T extends number](left: T, right: T) -> TThe larger of the two; preserves integer when both operands are integers.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
floor#
primitive agentprimitive agent floor(value: number) -> integerThe largest integer not above the value. A non-finite value cannot occur (division never yields one).
Parameters
Returns
Wire view (JSON Schema)
ceil#
primitive agentprimitive agent ceil(value: number) -> integerThe smallest integer not below the value.
Parameters
Returns
Wire view (JSON Schema)
round#
primitive agentprimitive agent round(value: number) -> integerThe nearest integer (half away from zero, the grade-school rule — not banker's rounding).
Parameters
Returns
Wire view (JSON Schema)
prelude.mcp
11 declarationsserver_error#
datadata server_error(message: string)The MCP server rejected a listing or a tool call, or the transport failed (the server-reported or transport message). Also raised as the scope backstop: a tool called after its provide returned reports its closed scope this way. Catch it to control retry — a retried call reconnects.
Parameters
Wire view (JSON Schema)
auth_error#
datadata auth_error(message: string)The server rejected the headers key material. Retrying with the same material does not help: a human must fix the key the program supplies. Never raised on the oauth path — an unusable oauth credential pauses the run on an authorization escalation instead of failing it. Distinct from server_error so a program can catch the two differently.
Parameters
Wire view (JSON Schema)
headers#
datadata headers(values: record[string of private])Header-based access: values ride on every request to the server (each value may be a secret, such as a bearer key). Anonymous access is headers(values = record.empty()).
Parameters
The headers sent on every request, by name; a value may be a secret.
Wire view (JSON Schema)
oauth#
datadata oauth(name: string)A named OAuth credential, stored server-side. The runtime injects and refreshes its tokens itself; the name is not a secret. While the name has no usable credential, any use pauses the run on a prelude.oauth.authorize escalation — answering it (the runtime-hosted browser flow) establishes the credential and resumes the run.
Parameters
The stored credential's registered name; not a secret.
Wire view (JSON Schema)
auth#
typetype auth = headers | oauthDefinition
scope#
effecteffect scopeThe built-in scope capability for the dynamic provide path: a nullary phantom marker, carried in a tool's effect row, never performable, never handleable. provide mints it for its continuation's row and discharges it from its own, so a tool tagged scope cannot outlive the provide that introduced it. A generated katari mcp pull binding declares its own module-local marker instead; distinct nullary markers never merge and never widen, so there is no covariance to reason about.
tool#
typetype tool[effect Scope] = agent never -> unknown with io | Scope | prelude.throw[server_error | auth_error]Generics
Definition
toolbox#
typetype toolbox[effect Scope] = record[tool[Scope]]Generics
Definition
provide#
external agentreactor: mcpexternal agent provide[effect Scope, R, effect E](url: string, auth: auth, continuation: agent(value: toolbox[Scope]) -> R with E | Scope) -> R with E from "mcp"Plug an MCP server's tools into a program for the extent of continuation, as a scoped provider — the use mcp.provide(url = ..., auth = ...) form. The runtime lists the server (a missing or dead oauth credential pauses on an authorization escalation and retries once answered; rejected headers material throws auth_error; a rejected listing throws server_error), hands continuation a toolbox[Scope] of one runtime-minted agent per tool (each carrying the server signature — for reflection.get_metadata and call-site validation — and the descriptor it was listed with), and settles with the continuation's result. Scope rides the continuation's row (that is what a tool call raises) and is DISCHARGED from provide's own row, so a tool cannot escape the block: the scope closes when continuation returns (or the run is cancelled), and a stray later call is rejected as a typed server_error. Scope is the marker the tools are gated by — the built-in mcp.scope for the dynamic path, or a generated binding's module-local marker; pass it explicitly as the first [...] argument (mcp.provide[mcp.scope](...)), and the toolbox binder's annotation must agree. Discharging Scope is provide's alone (only it opens the connection), so a live tool context cannot be forged. url is a plain routing value: the runtime connects and caches under the {url, auth} descriptor. DECLARE ONE MARKER PER LOGICAL CONNECTION. When you call provide directly you choose Scope yourself: share ONE marker across two connections and their scopes MERGE at the type level (the two servers' tools become mutually substitutable in a row that carries the marker) — the same widening the old dynamic-URL toolbox[string] had, no worse. It is only a TYPE distinction: routing and the live-provide backstop are always enforced by each tool value's own {url, auth} descriptor, so a call never reaches the wrong server — at worst a tool whose provide has closed fails with a typed server_error. A katari mcp pull binding needs no such discipline: its module namespace mints a distinct marker (github.connection) per connection automatically, so pulled servers never share a scope.
Generics
Parameters
The MCP server's endpoint URL (streamable HTTP, with SSE fallback).
How the runtime authenticates with the server: headers(...) or oauth(...).
The scope body (bound by use): receives the toolbox, and the tools are callable exactly while it runs.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
call#
external agentreactor: mcpexternal agent call[effect Scope, T](url: string, auth: auth, tool: string, arguments: unknown) -> T with Scope | prelude.throw[server_error | auth_error | json.validation_error] from "mcp"Call one tool on an MCP server directly — the static counterpart of provide, with no listing and no minted agent value. The seam the binding modules katari mcp pull generates are built on: tool is the server-declared tool name, arguments its arguments object as a plain value tree (records / arrays / scalars, and a file becomes base64 in place — the same slot contract as http.json). The result is read as a value and checked against T inside the runtime: a $katari_ref handle object becomes a REAL file value, records / arrays / scalars land as themselves, a plain-text reply reads only into a string-shaped T, and a mismatch throws validation_error. Instantiate T with unknown to keep the reply as a raw document value (the codegen's choice when no outputSchema maps); a $katari_ref in it is still a real file — the wire-to-value conversion is unconditional, never schema-directed. T appears only in the result, so it is never inferable — instantiate it explicitly; the Scope marker is a phantom the row never infers, so a caller writes both to [...], mcp.call[Scope, T](url = ..., ...) — a generated binding passes its own module-local marker. Nothing validates arguments before the server does (unlike a minted tool) — a rejected call throws server_error. Scope-gated the same way as a minted tool (Scope on its row): a generated binding calls this only inside a provide scope over the same marker and descriptor, and the runtime rejects a call with no live scope for that descriptor as a typed server_error.
Generics
Parameters
The MCP server's endpoint URL (the same descriptor the surrounding provide scope was opened with).
How the runtime authenticates with the server: headers(...) or oauth(...).
The server-declared tool name.
The tool's arguments object, as a plain value tree; only the server validates it.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
serve#
external agentreactor: mcpexternal agent serve[R, effect E](tools: toolbox[scope], subscriber: agent(url: string) -> R with E) -> R with E from "mcp"Publish tools as a live MCP server for the extent of subscriber. The runtime mints a fresh, unguessable capability URL and serves the record's agents as MCP tools there — each record key is the published tool name, each agent's declared signature the tool's advertised schema. The URL is the key: possession grants access, nobody without it can call in. subscriber receives the URL, hands it to whoever should connect (an AI session, a teammate's MCP client), and stays alive while calls should be served; when it returns the URL deactivates and its result becomes serve's result. Cancelling the run cancels subscriber and deactivates the URL the same way. The subscriber's effects flow to the caller's handlers unchanged; the served agents are self-contained (their effects are handled, or are at most io and this module's throws), so any agent fits once its own effects are handled. A served tool that throws or panics with no handler of its own cancels the whole endpoint (its failure proxies up and fails the run unless caught above subscriber) — wrap a tool's body in a handler for per-request resilience.
Generics
Parameters
The agents to publish, keyed by the tool name each is published under.
The endpoint's owner: receives the capability URL, and the server serves exactly while it runs.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.oauth
2 declarationsserver_error#
datadata server_error(message: string)The OAuth token could not be resolved for a TRANSIENT reason — a network error, or the token endpoint failing (5xx) while refreshing. Thrown by token. Catch it to control retry — a retried call re-resolves. (A credential that needs a human to (re)authorize is never a throw: the run pauses on an authorization escalation instead.)
Parameters
Wire view (JSON Schema)
token#
external agentreactor: oauthexternal agent token(name: string) -> string of private with io | prelude.throw[server_error] from "oauth"Resolve a usable OAuth bearer token for the stored credential name, as a private string (the token is tainted secret and may flow only to a submission sink — an http Authorization header — never to a user-facing boundary). The runtime holds the token material and refreshes it on demand, so no token ever enters the program. When name has no usable token (never authorized, or refresh-dead), the run PAUSES on a prelude.oauth.authorize escalation and resumes once the credential is established (the runtime-hosted browser flow, or a proactive login) — it does not fail. A transient resolution failure throws server_error (catchable with use handler { request throw(error: oauth.server_error) -> never { ... } }); uncaught, it fails the run.
Parameters
The registered name of a stored credential (e.g. "github"); the name itself is not a secret.
Returns
Effects
Wire view (JSON Schema)
prelude.record
10 declarationsget#
primitive agentprimitive agent get[T](target: record[T], key: string) -> T | nullRead the value under key, or null when the key is absent.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
set#
primitive agentprimitive agent set[T](target: record[T], key: string, value: T) -> record[T]A copy of the record with value under key (replacing an existing entry).
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
remove#
primitive agentprimitive agent remove[T](target: record[T], key: string) -> record[T]A copy of the record without key (unchanged when the key is absent).
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
keys#
primitive agentprimitive agent keys(target: record[unknown]) -> array[string]Every key, in sorted order.
Parameters
Returns
Wire view (JSON Schema)
has#
primitive agentprimitive agent has(target: record[unknown], key: string) -> booleanWhether key is present.
Parameters
Returns
Wire view (JSON Schema)
size#
primitive agentprimitive agent size(target: record[unknown]) -> integerThe number of entries.
Parameters
Returns
Wire view (JSON Schema)
entries#
primitive agentprimitive agent entries[T](target: record[T]) -> array[[string, T]]Every entry as a [key, value] pair, in sorted key order — the record's for-iterable view.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
values#
agentagent values[T](target: record[T]) -> array[T]Every value, in sorted key order — the value-side counterpart of keys.
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
merge#
primitive agentprimitive agent merge[T](left: record[T], right: record[T]) -> record[T]The two records' entries in one record; on a shared key the right value wins (the override direction of layered configuration, e.g. default headers extended per call).
Generics
Parameters
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
empty#
primitive agentprimitive agent empty() -> record[never]The empty record.
Returns
Wire view (JSON Schema)
prelude.reflection
4 declarationsagent_metadata#
datadata agent_metadata(name: string, description: string, input: unknown, output: unknown, requests: unknown)The public metadata of one callable: its (qualified) name, its @"..." description, and the JSON Schemas of its input, output and requests — each schema a plain document value, ready to embed in an AI tool list. A local (closure) callable has an empty name.
Parameters
The qualified name; empty for a local closure.
The @"..." documentation text; empty when undocumented.
The input record's JSON Schema, as a document value.
The output's JSON Schema, as a document value.
The requests' schemas: one { name, input, output } object per request the callable may perform.
Wire view (JSON Schema)
get_metadata#
primitive agentprimitive agent get_metadata(value: agent never -> unknown with all) -> agent_metadataDerive agent_metadata from a callable value (a top-level agent, a data constructor, a request, an external, a local closure, or a runtime-minted tool such as an MCP server's). A generic callable instantiated with [T] yields schemas specialised to the instantiation; a runtime-minted tool yields the name / description / input schema its provider declared.
Parameters
The callable value to inspect.
Returns
Wire view (JSON Schema)
call_error#
datadata call_error(message: string)A dynamic dispatch failed before the target ran: the target is not a callable, or args does not conform to its input schema (message names the offending path). Thrown by call_agent.
Parameters
The non-callable kind, or the offending path of the schema mismatch.
Wire view (JSON Schema)
call_agent#
primitive agentprimitive agent call_agent[R, effect E](target: agent never -> R with E, args: unknown) -> R with E | prelude.throw[call_error]Invoke a callable VALUE with a runtime-built argument record — dynamic dispatch for a target an AI picked out of a tool list. args is validated against the target's input schema at the delegation boundary; a mismatch throws call_error at this call site (catch it to let the model retry). The target's effects flow through unchanged.
Generics
Parameters
The callable value to dispatch (e.g. the tool an AI picked).
The target's whole argument record, keyed by its parameter names.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.region
8 declarationsscope#
effecteffect scopeThe built-in nursery scope: a nullary phantom marker, carried in a fiber's effect row and every operation's row, never performable, never handleable. provide mints it for its continuation and discharges it from its own row, so a fiber cannot outlive its nursery. Declare your own marker per nursery when nesting, so two regions' scopes never merge.
fiber#
typetype fiber[effect Scope, T] = agent never -> T with ScopeGenerics
Definition
nursery#
typetype nursery[effect Scope, effect E] = agent(gate: agent never -> unknown with Scope, bound: agent never -> unknown with E) -> {gate: agent never -> unknown with Scope, bound: agent never -> unknown with E}Generics
Definition
provide#
external agentreactor: regionexternal agent provide[effect Scope, effect E, R, effect Eouter](continuation: agent(value: nursery[Scope, E]) -> R with Eouter | Scope) -> R with Eouter from "region"Open a nursery for the extent of continuation, as a scoped provider — the use region.provide[region.scope, E](...) form. It hands continuation a nursery[Scope, E] handle whose fibers may raise at most the effects E, and settles with the continuation's result. Scope rides the continuation's row (that is what a fork / join / cancel / watch raises) and is DISCHARGED from provide's own row, so no fiber outlives the block: the nursery closes when continuation returns (its still-running fibers are cancelled). Pin Scope and the ceiling E as the first two type arguments (region.provide[region.scope, my_effects]); R and the residual outer row are inferred from the continuation. Discharging Scope is provide's alone (only it opens the nursery), so a live join context cannot be forged. NEST UNDER DISTINCT MARKERS: share one marker across two nurseries and their scopes MERGE (fibers become mutually joinable), so declare a module-local effect per nursery when nesting.
Generics
Parameters
The nursery body (bound by use): receives the handle, and its fibers run exactly while it does.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
fork#
external agentreactor: regionexternal agent fork[effect Scope, effect E, A, T](nursery: nursery[Scope, E], task: agent(input: A) -> T with E, argument: A) -> fiber[Scope, T] with Scope from "region"Spawn task (applied to argument) as a fiber in the nursery nursery, returning immediately with a fiber[Scope, T] handle. task's effect must fit under the nursery's ceiling E (a child that raises less is fine; one that raises more is a type error), so every fiber is bounded by E — which is exactly what makes watch a total obligation. The fiber's escalations do not surface here; they well up at watch. Scope-gated (with Scope): callable only inside the provide that opened this nursery.
Generics
Parameters
The nursery handle from the enclosing provide.
The child agent to run; its effect must fit under the nursery's ceiling E.
The argument applied to task.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
join#
external agentreactor: regionexternal agent join[effect Scope, effect E, T](nursery: nursery[Scope, E], handle: fiber[Scope, T]) -> T with Scope from "region"Await handle and return the value it settled with. Scope is pinned by nursery, and handle's own scope must match it, so a fiber from a DIFFERENT nursery (a different scope marker) is a type error here — a fiber is joinable only in the region that spawned it. Scope-gated (with Scope): callable only inside the opening provide.
Generics
Parameters
The nursery handle from the enclosing provide.
A fiber spawned in THIS nursery (its scope marker must match).
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
watch#
external agentreactor: regionexternal agent watch[effect Scope, effect E](nursery: nursery[Scope, E]) -> never with E | Scope from "region"The nursery's WHITE HOLE: re-emit the fibers' escalations into the enclosing program as the ceiling effect E. It returns never — it only ever raises, never yields — so install a handler (or any row that covers E) around it to service the fibers' requests. Because every fiber's effect fits under E and watch re-emits the FULL E, covering E here covers every request any fiber can raise. Scope-gated (with Scope): callable only inside the opening provide.
Generics
Parameters
The nursery handle from the enclosing provide.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
cancel#
external agentreactor: regionexternal agent cancel[effect Scope, effect E, T](nursery: nursery[Scope, E], handle: fiber[Scope, T]) -> null with Scope from "region"Tear handle down: cancel the fiber and settle. Scope is pinned by nursery, and handle's own scope must match it, so a fiber from a different nursery is a type error. Idempotent on an already-settled fiber. Scope-gated (with Scope): callable only inside the opening provide.
Generics
Parameters
The nursery handle from the enclosing provide.
A fiber spawned in THIS nursery (its scope marker must match).
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.replay
9 declarationsinterrupted#
requestrequest interrupted[F](failure: F) -> neverThe replay signal — the ONE request a replay provider catches, and the ONLY performable request this module exports. A converter performs it to ask that the enclosing block be re-run, carrying the failure it chose to represent as failure (of the converter's own type F). -> never: performing it transfers control to the provider (which applies its delay policy and re-runs) and does not return. This is the whole seam between policy and mechanism — a provider reasons only about interrupted, never about throw or panic.
Generics
Parameters
The converter-chosen value standing for the underlying failure (re-raised as throw[F] when a bounded provider exhausts).
Returns
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
ran#
datadata ran[R](value: R)One run of the continuation that completed with a value value (it did not signal a replay).
Generics
Parameters
The completed run's result.
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
signalled#
datadata signalled[F](failure: F)One run of the continuation that performed interrupted, carrying the failure it signalled.
Generics
Parameters
The failure the run's interrupted carried.
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
run_once#
agentagent run_once[R, F, effect E](continuation: agent(value: null) -> R with {...E, interrupted[F]}) -> ran[R] | signalled[F] with ERun the continuation once and reify which happened: ran with its value, or signalled with the failure it performed interrupted on. This is the single boundary where interrupted is caught and turned into a value the loop can match on in ordinary control flow (the mechanism-side analog of the old attempt, but catching only the replay signal — never throw / panic, which the converter already handled). interrupted[F] sits in the continuation's OVERWRITE row because this handler DISCHARGES it (the discord- provider shape); F is inferred from the continuation and preserved into signalled.
Generics
Parameters
The rest of the block, run once with interrupted served.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
immediate#
agentagent immediate[R, F, effect E](continuation: agent(value: null) -> R with {...E, interrupted[F]}) -> R with EReplay the rest of the block on every interrupted, unbounded and with NO artificial delay: it re-runs at once. The retry cadence is therefore set entirely by whatever the converter does BEFORE it signals — e.g. a converter that performs a human-in-the-loop escalation and parks before signalling gives the attended / re-auth loop, human-paced with no polling. Never re-raises and never returns on its own; a converter that rethrows instead of signalling is how a failure leaves this loop. Written use replay.immediate().
Generics
Parameters
The rest of the block (bound by use).
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
forever#
agentagent forever[R, F, effect E](initial_delay_milliseconds: number, factor: number, max_delay_milliseconds: number, continuation: agent(value: null) -> R with {...E, interrupted[F]}) -> R with E | ioReplay the rest of the block on every interrupted, unbounded, with exponential backoff capped at max_delay_milliseconds. For daemons that must stay up across transient failures (e.g. keeping a time.watch alive): it never re-raises and never returns on its own. The backoff delay lives in the loop's own delay var and grows by factor per replay up to the cap (math.min), so the durable state stays flat across any number of replays. Written use replay.forever(initial_delay_milliseconds = ..., factor = ..., max_delay_milliseconds = ...).
Generics
Parameters
The delay before the first re-run, in milliseconds.
The per-replay delay multiplier (e.g. 2 doubles the delay each replay).
The backoff ceiling, in milliseconds; the delay never grows past it.
The rest of the block (bound by use).
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
replay_after#
datadata replay_after()A bounded-retry decision: keep going after sleeping the current delay.
Wire view (JSON Schema)
give_up#
datadata give_up()The exhausted arm of a bounded-retry decision: no attempts remain, so the provider re-raises the failure.
Wire view (JSON Schema)
exponential#
agentagent exponential[R, F, effect E](initial_delay_milliseconds: number, factor: number, max_attempts: number, continuation: agent(value: null) -> R with {...E, interrupted[F]}) -> R with E | io | prelude.throw[F]Replay the rest of the block on interrupted with exponential backoff, up to max_attempts times. On failure number n (1-based) with n < max_attempts: durably sleep initial_delay_milliseconds * factor^(n-1), then re-run. When the budget is spent (the max_attempts-th interrupted): re-raise that failure as a TYPED throw[F] — F is the converter's chosen failure representation, so exhaustion keeps the error typed end to end and never needs to re-panic. The attempt count and delay live in the loop's own vars (one owner). The exhausted-vs-continue decision is genuine sum dispatch: the ONE comparison of the count to the budget mints a replay_after | give_up, and the signalled arm matches it once — no code re-tests the counter. Written use replay.exponential(initial_delay_milliseconds = ..., factor = ..., max_attempts = ...).
Generics
Parameters
The delay before the first re-run, in milliseconds.
The per-replay delay multiplier (e.g. 2 doubles the delay each replay).
The total run budget, not a retry count: the max_attempts-th interrupted re-raises instead of re-running.
The rest of the block (bound by use).
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.string
15 declarationslength#
primitive agentprimitive agent length(value: string) -> integerThe number of Unicode code points.
Parameters
Returns
Wire view (JSON Schema)
split#
primitive agentprimitive agent split(value: string, separator: string) -> array[string]The substrings between occurrences of separator. An empty separator splits into single code points.
Parameters
Returns
Wire view (JSON Schema)
join#
primitive agentprimitive agent join(parts: array[string], separator: string) -> stringThe parts joined with separator between them.
Parameters
Returns
Wire view (JSON Schema)
slice#
primitive agentprimitive agent slice(value: string, start: integer, end: integer) -> stringThe code points from start (inclusive) to end (exclusive), 0-based, clamped to the bounds.
Parameters
Returns
Wire view (JSON Schema)
contains#
primitive agentprimitive agent contains(value: string, search: string) -> booleanWhether search occurs in the string.
Parameters
Returns
Wire view (JSON Schema)
starts_with#
primitive agentprimitive agent starts_with(value: string, search: string) -> booleanWhether the string starts with search.
Parameters
Returns
Wire view (JSON Schema)
ends_with#
primitive agentprimitive agent ends_with(value: string, search: string) -> booleanWhether the string ends with search.
Parameters
Returns
Wire view (JSON Schema)
index_of#
primitive agentprimitive agent index_of(value: string, search: string) -> integer | nullThe code-point index of the first occurrence of search, or null when it does not occur.
Parameters
Returns
Wire view (JSON Schema)
replace#
primitive agentprimitive agent replace(value: string, search: string, replacement: string) -> stringThe string with every occurrence of search replaced by replacement (literal text, not a pattern). An empty search returns the value unchanged.
Parameters
Returns
Wire view (JSON Schema)
trim#
primitive agentprimitive agent trim(value: string) -> stringThe string with leading and trailing whitespace removed.
Parameters
Returns
Wire view (JSON Schema)
to_upper#
primitive agentprimitive agent to_upper(value: string) -> stringThe string uppercased (Unicode default casing, no locale).
Parameters
Returns
Wire view (JSON Schema)
to_lower#
primitive agentprimitive agent to_lower(value: string) -> stringThe string lowercased (Unicode default casing, no locale).
Parameters
Returns
Wire view (JSON Schema)
to_string#
primitive agentprimitive agent to_string(value: null | boolean | number | string) -> stringRender a scalar as its string form (an integer prints without a decimal point). For a composite value use json.stringify(value = ...) instead.
Parameters
Returns
Wire view (JSON Schema)
to_integer#
primitive agentprimitive agent to_integer(value: string) -> integer | nullRead the string as a base-10 integer (an optional sign and digits, nothing else), or null when it is not one — including a magnitude the number model cannot hold exactly. Compose with trim for padded input; for a diagnosed parse use json.parse_as[integer] instead.
Parameters
Returns
Wire view (JSON Schema)
to_number#
primitive agentprimitive agent to_number(value: string) -> number | nullRead the string as a number (any JSON-style numeric form), or null when it is not one. Compose with trim for padded input; for a diagnosed parse use json.parse_as[number] instead.
Parameters
Returns
Wire view (JSON Schema)
prelude.time
7 declarationsnow#
external agentreactor: timeexternal agent now() -> number from "time"The current wall-clock time, as epoch milliseconds (whole milliseconds since 1970-01-01T00:00:00Z). The instant becomes durable together with the work that first observes it, so once any downstream step has seen the value it never changes under replay or recovery. (A restart in the narrow window before that commit re-reads the clock and resolves a fresh instant — sound, because nothing durable saw the first.)
Returns
Wire view (JSON Schema)
sleep#
external agentreactor: timeexternal agent sleep(milliseconds: number) -> null from "time"Sleep for milliseconds before resolving with null. The wake deadline (now + milliseconds) is persisted: a runtime restart re-arms the timer, and a deadline that already passed while the runtime was down resolves immediately on recovery. A milliseconds that is zero or negative resolves at once.
Parameters
The delay, in milliseconds; zero or negative resolves at once.
Returns
Wire view (JSON Schema)
sleep_until#
external agentreactor: timeexternal agent sleep_until(time: number) -> null from "time"Sleep until the absolute epoch-millisecond instant time, then resolve with null. The same durable deadline as sleep, but pinned to an absolute wall-clock instant rather than a relative delay — an instant already in the past resolves immediately.
Parameters
The absolute wake instant, in epoch milliseconds; a past instant resolves at once.
Returns
Wire view (JSON Schema)
interval#
datadata interval(milliseconds: number)A fixed interval: an occurrence every milliseconds after the watch starts (the first occurrence is one interval in, not at the start). milliseconds must be positive.
Parameters
The gap between occurrences, in milliseconds; a non-positive gap panics the watch.
Wire view (JSON Schema)
cron#
datadata cron(expression: string, timezone: string)A cron schedule: the occurrences of the standard cron expression (the 5-field form, or the 6-field form whose leading field is seconds) read in the IANA timezone (e.g. "Asia/Tokyo", "UTC"). The timezone is required and explicit — there is no ambient default, because "every day at 09:00" means a different instant in each zone and the runtime must not guess which one.
Parameters
5 fields, or 6 with a leading seconds field; a malformed expression panics the watch.
An IANA zone name; an unknown zone panics the watch.
Wire view (JSON Schema)
schedule#
typetype schedule = interval | cronDefinition
watch#
external agentreactor: timeexternal agent watch[effect E](schedule: schedule, deliver_to: agent(time: number) -> null with E) -> never with E | io from "time"Call deliver_to once per schedule occurrence, forever, passing the occurrence's scheduled epoch millisecond as time. watch never resolves on its own (-> never); it runs until the run is cancelled, at which point the in-flight delivery is cancelled and the watch tears down. deliver_to's effects E flow to the caller's handlers unchanged.
Durability. The next occurrence is persisted; a restart re-arms it. If one or more occurrences were missed while the runtime was down, watch fires EXACTLY ONCE immediately on recovery (the earliest missed occurrence's scheduled time — it does not backfill every missed one), then continues on schedule. Deliveries are serialized: the next occurrence is not armed until the current delivery settles, so a deliver_to slower than the interval simply rate-limits the ticks rather than queueing them.
No built-in retry. A deliver_to that throws or panics is NOT retried — the failure propagates and kills the watch, exactly as an uncaught failure in any callee does. Resilience is composed at the call site (a replay provider plus a converter around deliver_to), never baked into watch.
Generics
Parameters
When occurrences fire: an interval(...) or a cron(...) value.
Called once per occurrence; its time is the occurrence's scheduled epoch millisecond, not the delivery instant.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.
prelude.webhook
1 declarationsinbound#
external agentreactor: webhookexternal agent inbound[R, effect E](callback: agent never -> unknown with E, subscriber: agent(url: string) -> R with E) -> R with E from "webhook"Serve a fresh, dynamically generated public webhook URL for the extent of subscriber. The runtime converts every POST to the URL into a call of callback (JSON body in, JSON result out, validated against callback's input schema). subscriber receives the URL, registers it wherever deliveries should come from, and stays alive while they flow; when it returns, the URL is deactivated and its result becomes inbound's result. Both callables' effects flow to the caller's handlers unchanged.
Generics
Parameters
Called once per POST. A body failing its input schema is rejected 400 before it runs; its unhandled failure cancels the endpoint.
The URL's owner: receives the minted public URL, and the endpoint serves exactly while it runs.
Returns
Effects
Wire view — the wire shape is fixed at the call site, where the generic parameters are instantiated.