LLM-Kurzinfo
Diese Seite ist fuer KI-Agenten und LLMs gedacht. Wenn du auf Wippy aufbaust oder Code fuer ein Wippy-Projekt generierst, lies dies zuerst.
Was Wippy ist
Wippy ist eine Single-Binary-Anwendungs-Runtime, die auf dem Aktor-Modell basiert. Sie fuehrt Lua-Code in isolierten Prozessen mit Nachrichtenuebermittlung aus — kein gemeinsamer Speicher, keine Locks. Es gibt drei Rechenmodelle: Funktionen (zustandslos, Request-gebunden), Prozesse (langlebige Aktoren mit Zustand) und Workflows (dauerhafte Aktoren, die von Temporal gestuetzt werden und Abstuerze ueberstehen). Das System ist so konzipiert, dass Agenten Code generieren, registrieren und Anwendungen ohne erneute Bereitstellung verbessern koennen.
Mentales Modell
Alles in Wippy ist ein Registry-Eintrag. Eintraege haben eine ID (namespace:name), eine Art (die das Verhalten bestimmt), Metadaten und Daten. YAML-Dateien sind eine Moeglichkeit, Eintraege zu deklarieren, aber die Registry ist zur Laufzeit die Quelle der Wahrheit, und Eintraege koennen waehrend des Systembetriebs erstellt, aktualisiert oder geloescht werden.
Arten bestimmen, was ein Eintrag tut:
function.lua— zustandslose aufrufbare Funktionprocess.lua— langlaufender Aktorworkflow.lua— dauerhafter Workflow (Temporal)http.service— HTTP-Serverhttp.router— Routengruppe mit Middlewarehttp.endpoint— HTTP-Handlerdb.sql.postgres/mysql/sqlite— Datenbankverbindungstore.memory/store.sql— Key-Value-Speicherqueue.queue— Nachrichtenwarteschlangeprocess.host— Prozessausfuehrungs-Hostprocess.service— ueberwachter Prozesscontract.definition/contract.binding— typisierte Dienstschnittstellenregistry.entry— Konfigurationsdaten
Projektstruktur
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.lua
Eintragsdefinitionen befinden sich in _index.yaml-Dateien:
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_user
Funktionen schreiben
Funktionen sind zustandslos. Sie erhalten Argumente, fuehren Arbeit aus und geben Ergebnisse zurueck. Sie erben den Kontext des Aufrufers und werden abgebrochen, wenn der Aufrufer abbricht.
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(errors.NOT_FOUND, "user not found") end
return rows[1]
end
return get_user
Verwende fuer HTTP-Handler das http-Modul:
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 handler
Prozesse schreiben
Prozesse sind Aktoren. Sie haben ihre eigene PID, empfangen Nachrichten ueber einen Posteingang und behalten ihren Zustand ueber Nachrichten hinweg. Sie yield'en bei blockierendem I/O, wodurch Tausende gleichzeitig laufen koennen.
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
end
end
return worker
Prozesse aus anderem Code spawnen:
local pid = process.spawn("app.workers:task", "app:process_host", config)
process.send(pid, "work", {item_id = 123})
Workflows schreiben
Workflows sind dauerhaft — sie ueberstehen Abstuerze und Neustarts. Der Code sieht wie normales Lua aus. Die Runtime zeichnet automatisch Ergebnisse von Funktionsaufrufen, Sleeps und Zufallswerte auf, damit der Replay deterministisch ist.
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_flow
Wichtige APIs
Funktionen aufrufen
local funcs = require("funcs")
-- Synchronous
local 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 context
local exec = funcs.new():with_context({user_id = "123"})
exec:call("namespace:function_name")
Prozesskommunikation
-- Send message (fire-and-forget)
process.send(pid, "topic", data)
-- Receive messages
local 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")
Kanaele
Kanaele im Go-Stil fuer die Koroutinen-Kommunikation:
local ch = channel.new(10) -- buffered
ch:send(value)
local val, ok = ch:receive()
-- Select on multiple channels
local r = channel.select {
ch1:case_receive(),
ch2:case_receive(),
timeout:case_receive()
}
Fehlerbehandlung
Funktionen geben result, error-Paare zurueck. Fehler sind typisierte Objekte:
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")
end
Fehlerarten: UNKNOWN, INVALID, NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, TIMEOUT, CANCELED, UNAVAILABLE, INTERNAL, CONFLICT, RATE_LIMITED.
Datenzugriff
-- SQL
local 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 store
local store = require("store")
local cache = store.get("app:cache")
cache:set("key", value, 3600) -- TTL in seconds
local val = cache:get("key")
-- Queue
local queue = require("queue")
queue.publish("app:tasks", {task = "process", id = 123})
-- Filesystem
local fs = require("fs")
local vol = fs.get("app:storage")
local data = vol:readfile("path/to/file.txt")
vol:writefile("output.txt", content)
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.body
Sicherheit
local security = require("security")
local actor = security.actor() -- who is calling
local scope = security.scope() -- what permissions apply
local allowed = security.can("read", "resource:users")
-- Token management
local ts = security.token_store("app:tokens")
local token = ts:create(actor, scope, {expiration = "24h"})
local validated_actor, validated_scope = ts:validate(token)
Zeit
local time = require("time")
time.sleep("5s")
local now = time.now()
local timeout = time.after("30s") -- channel that fires once
local ticker = time.ticker("10s") -- repeating channel
Registry
local registry = require("registry")
local entry = registry.get("app.api:get_user")
local tests = registry.find({["meta.type"] = "test"})
-- Create entries at runtime
local snap = registry.snapshot()
local changes = snap:changes()
changes:create({id = "app:new_func", kind = "function.lua", data = {...}})
changes:apply()
Events
local events = require("events")
-- Publish
events.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()
Zugriffskontrolle auf Module
Jeder Eintrag deklariert, welche Module er require() darf. Nicht aufgefuehrte Module sind schlicht nicht verfuegbar — es gibt kein os.execute, io.open, debug.* oder package.*, sofern du sie nicht ausdruecklich gewaehrst. Die Runtime scannt oder validiert keinen Quellcode; sie steuert den Zugriff auf Modulebene. Wenn ein Modul nicht in der Liste steht, existiert es fuer diesen Eintrag nicht.
modules: [sql, json, http, time, funcs, store]
So funktioniert auch der Determinismus von Workflows — Workflow-Eintraege erhalten nur deterministische Module. Die Runtime faengt time.now(), uuid.v4() und andere nicht-deterministische Aufrufe auf Modulebene ab und zeichnet die Ergebnisse fuer den Replay auf.
Framework-Module
Wippy verfuegt ueber Framework-Module, die als Abhaengigkeiten installiert werden:
- wippy/llm — LLM-Integration (OpenAI, Anthropic, Google).
llm.generate(), strukturierte Ausgabe, Embeddings, Streaming. - wippy/agent — Agenten-Framework mit Tool-Nutzung, Delegation, Traits, Speicher. Agenten werden als Registry-Eintraege definiert.
- wippy/test — BDD-Tests.
describe/it-Bloecke, Assertions, Mocking. - wippy/dataflow — DAG-basierte Workflow-Orchestrierung. Function-, Agent-, Cycle-, Parallel-Nodes.
- wippy/relay — WebSocket-Relay mit zentralem Hub, Per-User-Hubs, Plugin-Routing.
- wippy/views — Seiten- und Komponentensystem mit Template-Rendering.
- wippy/facade — Frontend-Iframe-Fassade mit Authentifizierungs-Bridging.
Konventionen
- Eintrags-IDs verwenden das Format
namespace:name - Namen verwenden Punkte zur semantischen Trennung, Unterstriche fuer Woerter:
get_user.endpoint - Funktionen geben
result, errorzurueck — pruefe den Fehler immer - Prozesse kommunizieren ueber Nachrichtenuebermittlung, niemals ueber gemeinsamen Zustand
- Verwende
channel.select, um mehrere Ereignisquellen zu multiplexen - Supervisions-Baeume behandeln Fehler — design fuer "let it crash"
- Kontext (Trace-IDs, Benutzerinfo, Sicherheit) propagiert automatisch durch Funktionsaufrufe
- Workflows duerfen nicht-deterministische Operationen nicht direkt verwenden — die Runtime uebernimmt dies fuer
funcs.call,time.sleep,uuid.v4,time.now
Dokumentation
Die vollstaendige Dokumentation ist unter wippy.ai/docs verfuegbar. LLM-freundliche Endpunkte:
- Struktur durchsuchen:
https://wippy.ai/llm/toc - Suche:
https://wippy.ai/llm/search?q=query - Seite abrufen:
https://wippy.ai/llm/path/en/<path> - Batch-Abruf:
https://wippy.ai/llm/context?paths=path1,path2