Reactor Apps & Smart Panels
Turn logic-cell rules into live applications: JSON APIs, shareable web forms, and an encrypted per-app records store — no server code, no deployment.
Reactor builds directly on Logic Cells: a rule you can query is a rule you can serve.
The idea
You have a rule in a namespace:
quote(Country, Product, Price) :- base_price(Product, P), vat(Country, V), Price is P * (1 + V).
reactor.expose turns it into:
-
a JSON API at
/apps/<app>/<slug>.api— callable by anything that speaks HTTP; -
a Smart Panel at
/servers/<app>/<slug>.sp— a branded, shareable web form for the same rule.
Requests bind the rule’s inputs, the engine solves, outputs return as JSON (or rendered results in the panel). Every execution runs through the same sandboxed query path as logic.query — an exposed rule can do exactly what a queried rule can do, nothing more.
Exposing a rule
{ "rule": "quote", "namespace": "pricing" }
That’s the common case — the contract is auto-derived from the rule head: the last argument becomes the output, the rest become inputs. To control it explicitly, pass a mode string:
{ "rule": "quote", "namespace": "pricing", "modes": "+country:atom +product:string -price:float" }
+name:type binds from the request; -name:type is collected and returned. Types: atom, string, integer, float, bool, any. The resolved contract comes back in the response along with both URLs.
Verbs and safety
-
verb: "get"— cacheable, prefetchable; for pure queries. -
verb: "post"— for anything else.
Rules whose body calls call_tool, assertz, or retract are forced to POST regardless of what you request — side-effecting endpoints must not be GET-cacheable. This is detected from the rule body, not trusted from the caller.
Auth
auth: "none" (public), "bearer" (token in the Authorization header; a token is minted and returned at expose time), or "password" (for panels — a shared secret gate on the form).
Battery dependencies
If the rule depends on battery predicates, expose hydrates the declared batteries into the namespace automatically — the endpoint works on first call.
The records store
Reactor apps get an encrypted records store for state that doesn’t belong in the fact space — submissions, orders, profiles:
| Tool | Does |
|---|---|
store.put |
Upsert a record by key (payload encrypted at rest) |
store.get |
Fetch one record (decrypted) by key |
store.query |
List/filter by plaintext metadata containment (e.g. {country: "be"}), newest first |
store.delete |
Remove a record by key |
Records are scoped to the calling server, an optional site, and a named collection — one app can keep orders and subscribers separate, and one server can back multiple sites. Payloads are encrypted; the metadata you choose to attach is plaintext and is what store.query filters on — put only what you’re willing to index there.
A common pattern: the exposed rule handles the decision (validate, price, classify) and a follow-up call_tool to store.put inside the rule persists the outcome — the whole app is one rule plus the store.
Smart Panels
Smart Panels are the human-facing surface: a branded form generated from the endpoint’s contract, served at the .sp URL, submitting to the rule and streaming results back. Panels are built and customized in the Panel Studio (field labels, ordering, branding, result presentation) and shared by URL — visitors don’t need a DataGrout account (subject to the endpoint’s auth setting).
Panels render the same contract the API serves: one exposure, two surfaces. Anything a panel can submit, the API accepts, and vice versa.
Availability
Reactor and Smart Panels are feature-gated (per-account, or enabled site-wide by the platform). Having Reactor implies Smart Panels — a reactor app is never left API-only. If reactor.expose reports the feature is unavailable, ask your admin.
Quick reference
| Setting | Where | Values |
|---|---|---|
| Rule + namespace |
reactor.expose |
any stored rule |
| Contract |
modes |
+in:type / -out:type, auto-derived if omitted |
| Verb |
verb |
get / post (side-effecting rules forced to post) |
| Auth |
auth |
none / bearer / password |
| API URL |
response url |
/apps/<app>/<slug>.api |
| Panel URL |
response panel_url |
/servers/<app>/<slug>.sp |
| Store scoping |
store.* |
server + optional site + collection |
| Store filtering |
store.query |
plaintext metadata containment |