LLM Brief
LLM Brief
Section titled “LLM Brief”This page is for AI agents and LLMs. If you are building on Wippy or generating code for a Wippy project, read this first.
What Wippy Is
Section titled “What Wippy Is”Wippy is a single-binary application runtime built on the actor model. It runs Lua code in isolated processes with message passing — no shared memory, no locks. Three compute models exist: functions (stateless, request-scoped), processes (long-lived actors with state), and workflows (durable actors backed by Temporal that survive crashes). The system is designed so that agents can generate code, register it, and improve applications without redeployment.
Mental Model
Section titled “Mental Model”Everything in Wippy is a registry entry. Entries have an ID (namespace:name), a kind (which determines behavior), metadata, and data. YAML files are one way to declare entries, but the registry is the runtime source of truth and entries can be created, updated, or deleted while the system is running.
Kinds determine what an entry does:
function.lua— stateless callable functionprocess.lua— long-running actorworkflow.lua— durable workflow (Temporal)http.service— HTTP serverhttp.router— route group with middlewarehttp.endpoint— HTTP handlerdb.sql.postgres/mysql/sqlite— database connectionstore.memory/store.sql— key-value storequeue.queue— message queueprocess.host— process execution hostprocess.service— supervised processcontract.definition/contract.binding— typed service interfacesregistry.entry— configuration data
Project Structure
Section titled “Project Structure”myapp/├── .wippy.yaml # Runtime configuration├── wippy.lock # Source directories└── src/ ├── _index.yaml # Entry definitions (namespace: app) ├── api/ │ ├── _index.yaml # namespace: app.api │ └── handler.lua └── workers/ ├── _index.yaml # namespace: app.workers └── task.luaEntry definitions live in _index.yaml files:
version: "1.0"namespace: app.api
entries: - name: get_user kind: function.lua source: file://handler.lua method: get_user modules: [sql, json]
- name: get_user.endpoint kind: http.endpoint meta: router: app:api_router method: GET path: /users/{id} func: app.api:get_userWriting Functions
Section titled “Writing Functions”Functions are stateless. They receive arguments, do work, return results. They inherit the caller’s context and cancel if the caller cancels.
local sql = require("sql")local json = require("json")local http = require("http")
local function get_user(id) local db, err = sql.get("app:main_db") if err then return nil, err end
local rows, err = db:query("SELECT * FROM users WHERE id = $1", id) if err then return nil, err end if #rows == 0 then return nil, errors.new({kind = errors.NOT_FOUND, message = "user not found"}) end
return rows[1]end
return get_userFor HTTP handlers, use the http module:
local http = require("http")local json = require("json")
local function handler() local req = http.request() local res = http.response()
local id = req:param("id") local user, err = funcs.call("app.api:get_user", id) if err then res:set_status(404) res:write_json({error = err:message()}) return end
res:write_json(user)end
return handlerWriting Processes
Section titled “Writing Processes”Processes are actors. They have their own PID, receive messages via inbox, and maintain state across messages. They yield on blocking I/O, allowing thousands to run concurrently.
local function worker(initial_config) local inbox = process.inbox() local events = process.events()
while true do local r = channel.select { inbox:case_receive(), events:case_receive() }
if r.channel == events then local ev = r.value if ev.type == process.event.CANCEL then break end elseif r.channel == inbox then local msg = r.value local topic = msg:topic() local data = msg:payload():data() handle_message(topic, data) end endend
return workerSpawn processes from other code:
local pid = process.spawn("app.workers:task", "app:process_host", config)process.send(pid, "work", {item_id = 123})Writing Workflows
Section titled “Writing Workflows”Workflows are durable — they survive crashes and restarts. Code looks like normal Lua. The runtime automatically records function call results, sleeps, and random values so replay is deterministic.
local function order_flow(order) local inventory = funcs.call("app:reserve_inventory", order.items) if not inventory then return nil, errors.new("out of stock") end
local payment = funcs.call("app:charge_payment", order.total) if not payment then funcs.call("app:release_inventory", inventory.id) return nil, errors.new("payment failed") end
-- Wait for approval signal (can block for days) local msg = process.inbox():receive() if not msg:payload():data().approved then funcs.call("app:refund_payment", payment.id) funcs.call("app:release_inventory", inventory.id) return nil, errors.new("rejected") end
return funcs.call("app:fulfill_order", order.id)end
return order_flowKey APIs
Section titled “Key APIs”Calling Functions
Section titled “Calling Functions”local funcs = require("funcs")
-- Synchronouslocal result, err = funcs.call("namespace:function_name", arg1, arg2)
-- Asynchronous (returns Future)local future = funcs.async("namespace:function_name", arg1)local result, err = future:result()
-- With contextlocal exec = funcs.new():with_context({user_id = "123"})exec:call("namespace:function_name")Process Communication
Section titled “Process Communication”-- Send message (fire-and-forget)process.send(pid, "topic", data)
-- Receive messageslocal inbox = process.inbox()local msg, ok = inbox:receive()local topic = msg:topic()local data = msg:payload():data()
-- Monitor another process (receive EXIT on death)process.monitor(pid)
-- Link processes (bidirectional failure notification)process.spawn_linked("namespace:name", "host")Channels
Section titled “Channels”Go-style channels for coroutine communication:
local ch = channel.new(10) -- bufferedch:send(value)local val, ok = ch:receive()
-- Select on multiple channelslocal r = channel.select { ch1:case_receive(), ch2:case_receive(), timeout:case_receive()}Error Handling
Section titled “Error Handling”Functions return result, error pairs. Errors are typed objects:
local result, err = some_operation()if err then if errors.is(err, errors.NOT_FOUND) then -- handle not found end return nil, errors.wrap(err, "context message")endError kinds: UNKNOWN, INVALID, NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, TIMEOUT, CANCELED, UNAVAILABLE, INTERNAL, CONFLICT, RATE_LIMITED.
Data Access
Section titled “Data Access”-- SQLlocal sql = require("sql")local db = sql.get("app:main_db")local rows, err = db:query("SELECT * FROM users WHERE active = $1", true)db:execute("INSERT INTO users (name) VALUES ($1)", name)
-- Key-value storelocal store = require("store")local cache = store.get("app:cache")cache:set("key", value, 3600) -- TTL in secondslocal val = cache:get("key")
-- Queuelocal queue = require("queue")queue.publish("app:tasks", {task = "process", id = 123})
-- Filesystemlocal fs = require("fs")local vol = fs.get("app:storage")local data = vol:readfile("path/to/file.txt")vol:writefile("output.txt", content)HTTP Client
Section titled “HTTP Client”local http_client = require("http_client")
local resp, err = http_client.get("https://api.example.com/data", { headers = {Authorization = "Bearer token"}, timeout = "10s"})local body = resp.bodySecurity
Section titled “Security”local security = require("security")
local actor = security.actor() -- who is callinglocal scope = security.scope() -- what permissions applylocal allowed = security.can("read", "resource:users")
-- Token managementlocal ts = security.token_store("app:tokens")local token = ts:create(actor, scope, {expiration = "24h"})local validated_actor, validated_scope = ts:validate(token)local time = require("time")
time.sleep("5s")local now = time.now()local timeout = time.after("30s") -- channel that fires oncelocal ticker = time.ticker("10s") -- repeating channelRegistry
Section titled “Registry”local registry = require("registry")
local entry = registry.get("app.api:get_user")local tests = registry.find({["meta.type"] = "test"})
-- Create entries at runtimelocal snap = registry.snapshot()local changes = snap:changes()changes:create({id = "app:new_func", kind = "function.lua", data = {...}})changes:apply()Events
Section titled “Events”local events = require("events")
-- Publishevents.send("orders", "order.created", "/orders/123", {order_id = "123"})
-- Subscribe (wildcards supported)local sub = events.subscribe("orders.*")local ch = sub:channel()local evt = ch:receive()Module Access Control
Section titled “Module Access Control”Each entry declares which modules it can require(). Modules not listed are simply unavailable — there is no os.execute, io.open, debug.*, or package.* unless you explicitly grant them. The runtime does not scan or validate source code; it controls access at the module level. If a module is not in the list, it does not exist for that entry.
modules: [sql, json, http, time, funcs, store]This is also how workflow determinism works — workflow entries only receive deterministic modules. The runtime intercepts time.now(), uuid.v4(), and other non-deterministic calls at the module level, recording results for replay.
Framework Modules
Section titled “Framework Modules”Wippy has framework modules installed via dependencies:
- wippy/llm — LLM integration (OpenAI, Anthropic, Google).
llm.generate(), structured output, embeddings, streaming. - wippy/agent — Agent framework with tool use, delegation, traits, memory. Agents defined as registry entries.
- wippy/test — BDD testing.
describe/itblocks, assertions, mocking. - wippy/dataflow — DAG-based workflow orchestration. Function, agent, cycle, parallel nodes.
- wippy/relay — WebSocket relay with central hub, per-user hubs, plugin routing.
- wippy/views — Page and component system with template rendering.
- wippy/facade — Frontend iframe facade with authentication bridging.
Conventions
Section titled “Conventions”- Entry IDs use
namespace:nameformat - Names use dots for semantic separation, underscores for words:
get_user.endpoint - Functions return
result, error— always check the error - Processes communicate via message passing, never shared state
- Use
channel.selectto multiplex multiple event sources - Supervision trees handle failures — design for “let it crash”
- Context (trace IDs, user info, security) propagates automatically through function calls
- Workflows must not use non-deterministic operations directly — the runtime handles this for
funcs.call,time.sleep,uuid.v4,time.now
Documentation
Section titled “Documentation”Full documentation is available at wippy.ai/docs. LLM-friendly endpoints:
- Browse structure:
https://wippy.ai/llm/toc - Search:
https://wippy.ai/llm/search?q=query - Fetch page:
https://wippy.ai/llm/path/en/<path> - Batch fetch:
https://wippy.ai/llm/context?paths=path1,path2