Core concepts

The pieces

libpolycall — the reusable native library (lib/libpolycall.a / .so / .dll). It implements configuration parsing, the runtime lifecycle, the wire protocol, and the public C API. It has no main() of its own.

polycall / polycall.exe — the operator program. A thin dispatcher (cli/main.c) over a fixed command registry (cli/cli_registry.c); it translates parsed command-line options into calls against libpolycall and renders the result as human text or JSON. It contains no business logic of its own — see the CLI reference.

The runtime — created and started by polycall runtime start (or an embedding application calling polycall_runtime_create/_start). It parses the Polycallfile, binds runtime.listen, accepts connections, authenticates them, and routes INVOKE requests to whichever registered provider declared that service.method. It does not implement any service's logic itself.

A provider — an application process (or, via the C provider API, a library user in the same style) that connects to the runtime, registers one or more methods, and answers INVOKE requests for them. The Python add(a, b) example and the C math_provider.c example are both providers.

A binding — the language-specific glue that speaks the wire protocol (or, for C, calls the public API directly) so an application in that language can act as a caller or a provider. examples/python-math/provider.py is a binding for Python, written out in full rather than packaged; there is currently no installable per-language package (see Language integration).

A caller — whoever issues the request: the polycall call invoke CLI command, or an embedding application using polycall_call_submit.

Polycallfile — the single, explicit source of runtime topology and policy: what's listening where, which services and methods exist and with what typed signatures, who is authenticated, and what they're authorized to do. See the Polycallfile reference.

Registration and method signature

Declaring [method.math.add] in the Polycallfile with params = ["i64", "i64"] and result = "i64" only makes that method invocable in principle. Nothing answers a call for it until a provider actually connects and sends a matching REGISTER for math.add, and the runtime's dispatcher accepts that registration (checking, among other things, that the provider's declared signature matches the configured one). Until then, call invoke reports unavailable — this is a real routing state, not a bug in the demo.

Typed result, not "whatever JSON produces"

A method's params and result are one of a fixed set of wire types (i64, bool, string, ...; see the Polycallfile reference). Arguments are checked against the declared types before a provider ever sees them, and results are checked on the way back. This is why sending a string where i64 is declared produces a structured INVALID_ARGUMENT error rather than a provider crash, and why a 64-bit integer round-trips exactly instead of being silently reinterpreted as a JavaScript-range float.

Why the provider does not parse Polycallfile

provider.py needs the runtime's listen address and the shared credential, both of which live in the Polycallfile. Rather than implementing a second parser for that grammar, it runs

polycall.exe config show --json <Polycallfile>

as a subprocess and reads libpolycall's own validated JSON output. This matches CFG-004: schema 1's grammar is deliberately restricted specifically so that independent adapters do not each grow their own, subtly different parser. A provider written in any language should do the same — shell out to polycall config show --json, not re-implement the grammar.

Local calls, cross-process calls, and HTTP are not interchangeable

  • A direct C ABI call (polycall_call_submit from an application linked against libpolycall) happens in-process. There is no network hop and no wire serialization; arguments are polycall_value_t handles.
  • A cross-process protocol call (what call invoke and every example provider use) goes over TCP, framed and encoded per LPV1-06: a length-prefixed JSON envelope carrying tagged values. This is what makes a Python provider reachable from a separately-running CLI process.
  • A plain HTTP request to some other port is neither of the above. If an application also happens to expose an HTTP endpoint (the old trial build's book-service demo did — see the migration guide), a successful curl/http.client request to it proves nothing about whether LibPolycall routed anything. Nothing in the current implementation bridges HTTP requests into the runtime's dispatcher.

Security terms, used precisely

Authentication answers "is this connection who it claims to be" — the runtime checks a shared credential (local-auto, generated and stored via the OS credential facility, or token, from a named environment variable) during HELLO/AUTH. Authorization answers "is this authenticated principal allowed to do this" — checked per-request against [grant.*] entries (actions = ["invoke", "register"], constrained to specific service/methods). These are two different checks; a connection can authenticate successfully and still have a specific invocation or registration denied. See Security and telemetry for what's actually implemented versus proposed.