Vihren is a Go toolkit for building typed Temporal workflows with less
boilerplate. You write ordinary Go activities and workflows, mark them with
//vihren:activity and //vihren:workflow, and vihren-gen generates the
Temporal registration, workflow-safe activity proxies, and typed clients you'd
otherwise hand-write.
If you're building Temporal workflows in Go, Vihren gives you:
- Code Generation — typed activity/workflow plumbing without the hand-written boilerplate.
- Large Payload Storage — a claim-check pattern that keeps large data out of Temporal's workflow history.
- Embedded Temporal — an in-process server so examples and local development need no external infrastructure.
Requires Go 1.26 or newer.
go get vihren.dev/vihren
go install vihren.dev/vihren/cmd/vihren-gen@latestWrite the activity and workflow:
type GreetingInput struct{ Name string }
type GreetingOutput struct{ Message string }
//vihren:activity
func (a *GreetingActivities) ComposeGreeting(ctx context.Context, in GreetingInput) (GreetingOutput, error) {
return GreetingOutput{Message: fmt.Sprintf("%s, %s", a.Prefix, in.Name)}, nil
}
//vihren:workflow
func HelloWorkflow(ctx workflow.Context, in GreetingInput) (GreetingOutput, error) {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: time.Second})
return Activity.ComposeGreeting(ctx, in)
}Generate the plumbing:
go generate ./...vihren-gen writes Register (worker registration), the type-checked
Activity proxy used above, and a typed NewClient for starting workflows and
awaiting typed results — none of it hand-maintained.
Run it with zero infrastructure:
go run ./examples/codegenhello/cmd/codegenhello-embedded
# Hello, AdaSee examples/codegenhello for the full walkthrough.
Large workflow data — agentic LLM context, documents, ML models — can blow past Temporal's workflow history size limits. Vihren v0.3 provides two paths through one content-addressed backend:
- When workflow code needs to read the value, Temporal
ExternalStoragetransparently keeps the encoded payload out of history and materializes it for the workflow. - When workflow code only routes an activity result to another activity,
blob.Value[T]carries an inline value or a typed storage handle without fetching the large bytes into the workflow.
embeddedtemporal.WithLocalStorage wires both paths to the same local
filesystem/SQLite backend and DataConverter configuration.
//vihren:activity
func GenerateLargePrompt(ctx context.Context, input string) (blob.Value[string], error) {
veryLargeString := "..." // e.g., fetched from a database
return blob.Offload(ctx, veryLargeString)
}
//vihren:activity
func CallLLM(ctx context.Context, prompt blob.Value[string]) (string, error) {
actualPrompt, err := prompt.Resolve(ctx)
if err != nil {
return "", err
}
return runInference(actualPrompt), nil
}The workflow only ever passes prompt around; it never touches the
underlying bytes. If it later needs to inspect a stored value,
blob.GetValue[T] schedules an activity to materialize it deterministically.
Start with the storage design article,
then use the blob package documentation
and conversation-storage example for API and
runnable setup. Vihren v0.3 ships a local backend; cloud backends, garbage
collection, and ownership transfer remain future work. See the
v0.3.0 release for
the complete compatibility and limitation notes.
embeddedtemporal runs a Temporal server inside your own process — no
Docker, no daemon, no temporal server start-dev. It's built for examples,
demos, and single-binary local development, not production deployments.
server, err := embeddedtemporal.Start()
if err != nil {
return err
}
defer server.Close()See docs/embedded-temporal.md.
Vihren is licensed under the Apache License, Version 2.0. See the NOTICE file for attribution.