Generalize the manifest loader to io/fs so services can be baked into a static binary via embed.FS or read from disk via os.DirFS — LoadFS is the one path; LoadDir is os.DirFS sugar. This is what lets hanzoai/cloud mount embedded polyglot services into the single unified binary.
goa
Pure-Go polyglot embedding. Run Go, Python, TypeScript/JavaScript, and
Rust/WASM services in one process — as goroutines, no cgo, no subprocesses, no
GIL — so a fleet of services collapses into a single static binary
(CGO_ENABLED=0) that scales horizontally by replication.
import "github.com/hanzoai/goa"
Why
hanzoai/cloud is one Go binary: subsystems register and mount onto a zip (gofiber) app. Most of our backends are not Go — Langflow is Python, the migrated explorer services are Rust, assorted tools are TypeScript. The choice was a swarm of sidecar containers, or embed the runtimes. goa embeds them, in pure Go, preserving the static build.
Model
One contract, every language: JSON in, JSON out.
type Module interface {
Invoke(ctx context.Context, fn string, payload []byte) ([]byte, error)
Funcs() []string
Close() error
}
type Engine interface {
Lang() string
Aliases() []string
Load(ctx context.Context, src []byte, opt LoadOptions) (Module, error)
}
Four engines register themselves, all pure-Go:
| Lang | Engine | Notes |
|---|---|---|
| Go | native | map[string]HandlerFunc, zero overhead |
| JavaScript / TS | goja + esbuild | TS transpiled to ES2020 CommonJS |
| Python | gpython | no cgo, no GIL → true goroutine parallelism |
| Rust / WASM | wazero | wasm32 + WASI, trivial string ABI |
Engines that run single-threaded code (goja, gpython, a wasm instance) are made concurrent by a Pool: N independent interpreters, one per in-flight request, so every HTTP route is still just a goroutine.
Use
Embed source directly
m, _ := goa.Load(ctx, "python", []byte(`
import json
def greet(p):
return json.dumps({"hello": json.loads(p)["name"]})
`), goa.LoadOptions{Name: "greeter"})
out, _ := m.Invoke(ctx, "greet", []byte(`{"name":"ada"}`))
// {"hello":"ada"}
Mount Go natively (zero overhead)
m := goa.NewNativeModule(map[string]goa.HandlerFunc{
"greet": func(ctx context.Context, p []byte) ([]byte, error) { return p, nil },
})
Drop-in services: a manifest + a source file
hello-py.manifest.json
{
"name": "hello-py",
"lang": "python",
"source": "hello.py",
"pool": 8,
"prefix": "/v1/hello-py",
"routes": [{ "method": "POST", "path": "/greet", "func": "greet" }]
}
services, _ := goa.LoadDir(ctx, "services") // every *.manifest.json
mux := http.NewServeMux()
for _, s := range services {
s.Mount(mux)
}
In hanzoai/cloud the same Service mounts onto the gofiber app via
zip.AdaptNetHTTP(svc.Handler()) — goa stays host-agnostic.
Python stdlib
gpython is "batteries not included." goa ships Go-backed implementations of
the modules our backends actually use, registered under their real names — so
the Python keeps import json and runs at Go speed (the modules that are
C-accelerated in CPython are Go-native here). json ships today; re,
hashlib, base64, datetime, uuid extend the same pyToGo/goToPy
pattern in stdlib_gpython.go.
Two tiers
goa is Tier 1: in-process logic, pure-Go, for handlers and transforms. Anything that needs a real OS process — a long-running server, a GPU workload, heavy native Python (NumPy/Torch), an unported C extension — is Tier 2: run it out-of-process and front it with a native proxy module. Tier 1 is the default; reach for Tier 2 only when the workload genuinely needs an OS boundary.
Rust / WASM ABI
A guest module exports:
alloc(len: i32) -> i32 // buffer pointer for the payload
dealloc(ptr: i32, len: i32) // optional
<fn>(ptr: i32, len: i32) -> i64 // returns (resultPtr << 32 | resultLen)
The host writes JSON at alloc()'s pointer, calls <fn>, reads JSON back. A
#[goa::handler] proc-macro generates this boilerplate on the Rust side.
License
Apache-2.0