cerebellum: make the LLM seam async; add OpenHermesClient skeleton

A real model call is network/process IO, so the LLM seam is now a behaviour
with a response callback (ResponseFn), not a synchronous function. StubLLM
answers immediately; OpenHermesClient + OpenHermesConfig (OpenAI-compatible
url/model) are the real plug, swappable for the stub. CerebellumHarness chains
its passes through the async callback. Compiles on ponyc 0.64; demo green.
Next: wire the actual chat/completions call behind OpenHermesClient.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hmgREHNsxYCuim33yUF2c
This commit is contained in:
Claude 2026-06-19 22:10:34 +00:00
parent ca0260692e
commit a2a3a2d336
No known key found for this signature in database

View File

@ -4,19 +4,51 @@
// the synthesis back through the bus (Brain-bound, so it crosses D1). This is // the synthesis back through the bus (Brain-bound, so it crosses D1). This is
// the C1 integration spine ("plumbing + sequencing, not an organ"). // the C1 integration spine ("plumbing + sequencing, not an organ").
// //
// Plug-and-play: the model is the `LLM` seam below. StubLLM stands in now; drop // Plug-and-play: the model is the `LLM` seam. A real model call is network /
// a real OpenHermes client (HTTP / subprocess) in its place and nothing else // process IO, so the seam is ASYNCHRONOUS -- `infer` is a behaviour that hands
// on the bus changes. // its result to a callback. StubLLM answers immediately; OpenHermesClient is the
// real plug. Swap which one you register; nothing else on the bus changes.
// --- the model seam ------------------------------------------------------- // --- the model seam (async) ----------------------------------------------
interface val LLM type ResponseFn is {(String)} val
"""One inference call. Implement this to plug a model into the cerebellum.""" """Callback the model invokes with its output."""
fun infer(prompt: String): String
class val StubLLM is LLM interface tag LLM
"""Stand-in until a real OpenHermes client is wired.""" be infer(prompt: String, respond: ResponseFn)
fun infer(prompt: String): String =>
"[stub-openhermes] " + prompt // --- stub -----------------------------------------------------------------
actor StubLLM is LLM
new create() => None
be infer(prompt: String, respond: ResponseFn) =>
respond("[stub-openhermes] " + prompt)
// --- the OpenHermes plug --------------------------------------------------
class val OpenHermesConfig
"""Where to reach an OpenAI-compatible OpenHermes server."""
let url: String // base, e.g. http://localhost:8080/v1
let model: String
new val create(
url': String = "http://localhost:8080/v1",
model': String = "openhermes")
=>
url = url'
model = model'
actor OpenHermesClient is LLM
let _cfg: OpenHermesConfig
new create(cfg: OpenHermesConfig) =>
_cfg = cfg
be infer(prompt: String, respond: ResponseFn) =>
// TODO(loop next): POST an OpenAI-compatible chat/completions request to
// `_cfg.url`/chat/completions via the process seam (curl) or a Pony TCP
// client, parse choices[0].message.content, then call respond() with it.
// Until that IO is wired and testable in this env, surface intent rather
// than perform an untested network call.
respond("[openhermes " + _cfg.model + " @ " + _cfg.url + "] " + prompt)
// --- the harness ---------------------------------------------------------- // --- the harness ----------------------------------------------------------
actor CerebellumHarness is OrganReceiver actor CerebellumHarness is OrganReceiver
@ -29,18 +61,19 @@ actor CerebellumHarness is OrganReceiver
_out = out' _out = out'
_bus = bus _bus = bus
_llm = llm _llm = llm
// C1 L3: pass count is the responsiveness knob (energy-gated later), the // C1 L3: pass count is the responsiveness knob (energy-gated later); the
// 4+4 ordering itself is static. Clamp to >=1 so a turn always runs once. // 4+4 ordering is static. Clamp to >=1 so a turn always runs once.
_passes = if passes' < 1 then 1 else passes' end _passes = if passes' < 1 then 1 else passes' end
be receive(envl: Envelope) => be receive(envl: Envelope) =>
// A turn arrives off the bus. Sequence the passes through the model, then // A turn arrives off the bus: sequence the passes, then emit.
// emit the synthesis back onto the bus toward the Brain. _pass(envl.payload, 1)
var ctx: String = envl.payload
var pass: USize = 1 be _pass(ctx: String, n: USize) =>
while pass <= _passes do if n > _passes then
ctx = _llm.infer(ctx)
pass = pass + 1
end
_out.print("[cerebellum] " + _passes.string() + " passes -> " + ctx) _out.print("[cerebellum] " + _passes.string() + " passes -> " + ctx)
_bus.route(Envelope(Cerebellum, Brain, OrganSecretion, ctx)) _bus.route(Envelope(Cerebellum, Brain, OrganSecretion, ctx))
else
let self: CerebellumHarness tag = this
_llm.infer(ctx, {(out: String)(self, n) => self._pass(out, n + 1) } val)
end