mirror of
https://github.com/SHOGGOTH-SECTOR/sica-fondt.git
synced 2026-08-01 16:40:24 +00:00
PickLLM reads OPENHERMES_URL/OPENHERMES_MODEL from the environment and mounts OpenHermesClient when a URL is set, else StubLLM -- the bus wiring is identical either way. EnvLookup helper parses Env.vars. Verified both paths compile + run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015hmgREHNsxYCuim33yUF2c
111 lines
4.1 KiB
Pony
111 lines
4.1 KiB
Pony
// The cerebellum: the OpenHermes agent harness, mounted as an organ on the
|
|
// Ichor bus. It owns NO cognition -- it SEQUENCES a turn: receive input off the
|
|
// bus, drive the metacognitive passes through a swappable model adapter, emit
|
|
// the synthesis back through the bus (Brain-bound, so it crosses D1). This is
|
|
// the C1 integration spine ("plumbing + sequencing, not an organ").
|
|
//
|
|
// Plug-and-play: the model is the `LLM` seam. A real model call is network /
|
|
// process IO, so the seam is ASYNCHRONOUS -- `infer` is a behaviour that hands
|
|
// 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 (async) ----------------------------------------------
|
|
type ResponseFn is {(String)} val
|
|
"""Callback the model invokes with its output."""
|
|
|
|
interface tag LLM
|
|
be infer(prompt: String, respond: ResponseFn)
|
|
|
|
// --- 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)
|
|
|
|
// --- config plumbing ------------------------------------------------------
|
|
primitive EnvLookup
|
|
"""Find KEY in an Env.vars array ("KEY=value" entries); None if absent."""
|
|
fun apply(vars: Array[String] val, key: String): (String | None) =>
|
|
for v in vars.values() do
|
|
let parts: Array[String] val = v.split_by("=", 2)
|
|
try
|
|
if parts(0)? == key then
|
|
return parts(1)?
|
|
end
|
|
end
|
|
end
|
|
None
|
|
|
|
primitive PickLLM
|
|
"""Plug-and-play model selection: OPENHERMES_URL picks the real client."""
|
|
fun apply(out: OutStream, vars: Array[String] val): LLM =>
|
|
match EnvLookup(vars, "OPENHERMES_URL")
|
|
| let url: String =>
|
|
let model =
|
|
match EnvLookup(vars, "OPENHERMES_MODEL")
|
|
| let m: String => m
|
|
else "openhermes"
|
|
end
|
|
out.print("[cerebellum] model: OpenHermes " + model + " @ " + url)
|
|
OpenHermesClient(OpenHermesConfig(url, model))
|
|
else
|
|
out.print("[cerebellum] model: StubLLM (set OPENHERMES_URL for a real model)")
|
|
StubLLM
|
|
end
|
|
|
|
// --- the harness ----------------------------------------------------------
|
|
actor CerebellumHarness is OrganReceiver
|
|
let _out: OutStream
|
|
let _bus: Broker
|
|
let _llm: LLM
|
|
let _passes: USize
|
|
|
|
new create(out': OutStream, bus: Broker, llm: LLM, passes': USize = 4) =>
|
|
_out = out'
|
|
_bus = bus
|
|
_llm = llm
|
|
// C1 L3: pass count is the responsiveness knob (energy-gated later); the
|
|
// 4+4 ordering is static. Clamp to >=1 so a turn always runs once.
|
|
_passes = if passes' < 1 then 1 else passes' end
|
|
|
|
be receive(envl: Envelope) =>
|
|
// A turn arrives off the bus: sequence the passes, then emit.
|
|
_pass(envl.payload, 1)
|
|
|
|
be _pass(ctx: String, n: USize) =>
|
|
if n > _passes then
|
|
_out.print("[cerebellum] " + _passes.string() + " passes -> " + 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
|