Apache 2.0

The Microservice Substrate for
Durable Agentic Workflows.

Build at the speed of a prompt. Really.

Microbus is the open source framework that teaches your agent to code production-grade agentic workflows. Prompt your way from a fully functional, debuggable system on localhost to a secure, globally distributed deployment that runs your critical workloads at scale.

Go from a prompt to a fully implemented and tested workflow in minutes
>Build for me a research workflow that fetches an article, summarizes it, critiques the summary, and translates the critique into French and Spanish in parallel.
Workflow graph: fetch-article, summarize, critique, then translate-french and translate-spanish in parallel.
The gap in the agentic workflow stack

Your agentic workflow stack is fraying at the seams.

Running agentic workflows in production today is an operational nightmare. Around the orchestrator, you stitch on the rest of the stack: auth, vector store, state, tracing. Silent failures get noticed by customers while your dashboards stay green. RCA reads like a mystery novel: clues strewn across multiple systems, smoking gun nowhere to be seen.

Workflow engines fall short

  • Silent failuresFailures drift instead of crash. Async traces fragment. You find out in production.
  • Lock-in by designProprietary state stores, self-host gated behind paid SKUs, observability metered per trace.
  • Inconsistent persistencePersistence splits across queue and state. After a worker crash, you replay a step or drop one. Fault tolerance is your problem.
  • Scaling overheadScaling means new clusters, sharding plans, replication tuning. Operational overhead grows with traffic.
  • Operational patchworkHTTP, auth, multi-tenancy, vector store, LLM clients: every layer is yours to integrate and operate.

Microbus brings it all together seamlessly

  • Instrumented end to endEvery call carries a trace span. Errors carry stacks across process and network boundaries.
  • Free open sourceApache 2.0, end to end. No managed-only features, no per-trace billing, no vendor consoles.
  • Fault tolerantWorkflows survive worker crashes and network partitions. Fault tolerance is woven into the fabric at every level.
  • Horizontally scalableScale linearly. Add a Foreman replica, introduce a new shard, done. No cluster ops, no surprises.
  • Holistically integratedWrap any service as a microservice. Auth, tracing, retries, and mocking come automatically.
Production-grade

Security, scale & observability. On by default.

Agentic workflows act on real authority, touch real data, and can run for minutes or hours at a time. They deserve the same operational backbone as any other production system. Microbus brings it on day one.

Security

Tighter than mTLS

Every request carries a broker-issued token. True-to-code ACLs enforce who can publish to whom. Port-based isolation walls off trust-root endpoints.

The actor's identity flows transparently through every workflow step and every LLM tool call, so when the agent acts on a user's behalf you know exactly whose authority it carried.

Scale

Horizontal, everywhere

Every microservice scales independently — including Foreman, the workflow orchestrator — so flows never bottleneck through a single coordinator.

Messages flow on NATS, a CNCF battle-tested technology that runs Fortune 500 production systems. It supplies queue-based load balancing and locality-aware routing across the entire fabric.

Observability

Every step of the way

OpenTelemetry traces follow each request through every microservice hop and every workflow step. Structured logs and metrics are wired in at the framework layer.

Plug in the collector you already run, Jaeger, Tempo, Datadog, and the audit trail is there.

Built for coding agents

From prompt to production workflow. In one session.

Microbus is engineered so a coding agent can read, author, and ship a workflow without ever leaving the context window. The conventions live in skills. The structure lives in the framework. The agent just fills in the logic.

Code-first

Workflows are real code

A workflow is a graph of typed functions. No DAG editor, no JSON spec, no proprietary state store. The agent reads it, debugs it, and deploys it exactly like any other backend code.

Agent-native

A recipe for every feature

Every feature type has a skill that walks the agent through the exact steps. MARKER comments locate every line of a feature across files. Manifests describe each microservice in one short YAML. The working set fits in any context window.

Tools, automatically

Every endpoint is an LLM tool

Microbus publishes every functional endpoint and workflow as an OpenAPI operation. Pass a list of URLs to the LLM client and they become callable tools - typed, described from godoc, no registration code required.

Code-first · What a workflow looks like

If you can read code, you can read your workflow.

A workflow is a graph of task endpoints. Each task is a normal function with typed inputs and typed outputs. Microbus carries shared state through the graph, fans out and fans back in, and reduces overlapping fields deterministically.

  • Tasks are addressable on the bus, like any other endpoint
  • State is JSON. Reducers are conventional and predictable
  • Foreman orchestrates the graph; you author the steps
  • Every step is persisted; a crashed flow resumes right where it left off
// ResearchArticle defines the workflow graph that fetches an article, summarizes it,
// critiques the summary, and translates the critique into French and Spanish in parallel.
func (svc *Service) ResearchArticle(ctx context.Context) (graph *workflow.Graph, err error) {
    graph = workflow.NewGraph("ResearchArticle")
    graph.SetEndpoint("FetchArticle", researchflowapi.FetchArticle.URL())
    graph.SetEndpoint("Summarize", researchflowapi.Summarize.URL())
    graph.SetEndpoint("Critique", researchflowapi.Critique.URL())
    graph.SetEndpoint("TranslateFrench", researchflowapi.TranslateFrench.URL())
    graph.SetEndpoint("TranslateSpanish", researchflowapi.TranslateSpanish.URL())
    graph.SetEndpoint("Finalize", researchflowapi.Finalize.URL())
    graph.SetFanIn("Finalize")
    graph.AddTransitionChain("FetchArticle", "Summarize", "Critique")
    graph.AddTransitionChain("Critique", "TranslateFrench", "Finalize")
    graph.AddTransitionChain("Critique", "TranslateSpanish", "Finalize")
    graph.AddTransitionChain("Finalize", workflow.END)
    return graph, nil
}

// FetchArticle retrieves the article body for the given URL via the HTTP egress proxy.
func (svc *Service) FetchArticle(ctx context.Context, flow *workflow.Flow, articleURL string) (articleText string, err error) {
    if articleURL == "" {
        return "", errors.New("articleURL is empty", http.StatusBadRequest)
    }
    resp, err := httpegressapi.NewClient(svc).Get(ctx, articleURL)
    if err != nil {
        return "", errors.Trace(err)
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return "", errors.New("fetch failed", "url", articleURL, "status", resp.StatusCode, resp.StatusCode)
    }
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return "", errors.Trace(err)
    }
    return string(body), nil
}

// Summarize produces a concise summary of the article text via the LLM service.
func (svc *Service) Summarize(ctx context.Context, flow *workflow.Flow, articleText string) (summary string, err error) {
    if articleText == "" {
        return "", errors.New("articleText is empty", http.StatusBadRequest)
    }
    summary, yield, err := svc.chat(ctx, flow,
        "You are a precise summarizer. Reply with a single short paragraph that captures the main idea of the article. Do not preface your answer.",
        "Summarize the following article:\n\n"+articleText,
    )
    if yield {
        return "", nil // parked; re-enters when the subflow ends
    }
    return summary, err
}

// Critique evaluates the summary for accuracy, clarity, and completeness via the LLM service.
func (svc *Service) Critique(ctx context.Context, flow *workflow.Flow, summary string) (critique string, err error) {
    if summary == "" {
        return "", errors.New("summary is empty", http.StatusBadRequest)
    }
    critique, yield, err := svc.chat(ctx, flow,
        "You are an editor. Reply with a short, candid critique of the summary, focused on clarity, completeness, and tone. Do not preface your answer.",
        "Critique the following summary:\n\n"+summary,
    )
    if yield {
        return "", nil // parked; re-enters when the subflow ends
    }
    return critique, err
}

// TranslateFrench returns a French translation of the critique via the LLM service.
func (svc *Service) TranslateFrench(ctx context.Context, flow *workflow.Flow, critique string) (translationFr string, err error) {
    if critique == "" {
        return "", errors.New("critique is empty", http.StatusBadRequest)
    }
    translationFr, yield, err := svc.chat(ctx, flow,
        "You translate English into French. Reply with the French translation only, no commentary.",
        critique,
    )
    if yield {
        return "", nil // parked; re-enters when the subflow ends
    }
    return translationFr, err
}

// TranslateSpanish returns a Spanish translation of the critique via the LLM service.
func (svc *Service) TranslateSpanish(ctx context.Context, flow *workflow.Flow, critique string) (translationEs string, err error) {
    if critique == "" {
        return "", errors.New("critique is empty", http.StatusBadRequest)
    }
    translationEs, yield, err := svc.chat(ctx, flow,
        "You translate English into Spanish. Reply with the Spanish translation only, no commentary.",
        critique,
    )
    if yield {
        return "", nil // parked; re-enters when the subflow ends
    }
    return translationEs, err
}

// Finalize is the fan-in nexus for the parallel translation branches. It reads both
// translations and reports which languages produced output. Having a single converging
// node lets the workflow's lineage validator close the fan-out frame opened at Critique.
func (svc *Service) Finalize(ctx context.Context, flow *workflow.Flow, translationFr string, translationEs string) (languages []string, err error) {
    languages = []string{}
    if translationFr != "" {
        languages = append(languages, "fr")
    }
    if translationEs != "" {
        languages = append(languages, "es")
    }
    return languages, nil
}

// chat runs a durable system+user exchange against the configured LLM provider
// as a child subgraph and returns the assistant's reply.
func (svc *Service) chat(ctx context.Context, flow *workflow.Flow, systemPrompt string, userPrompt string) (reply string, yield bool, err error) {
    items := []llmapi.Item{
        llmapi.NewMessage("system", systemPrompt).AsItem(),
        llmapi.NewMessage("user", userPrompt).AsItem(),
    }
    itemsOut, _, yield, err := llmapi.NewSubgraph(flow).ChatLoop(ctx, svc.Provider(), svc.Model(), items, nil, nil)
    if err != nil {
        return "", false, errors.Trace(err)
    }
    if yield {
        return "", true, nil // parked; re-enters when ChatLoop ends
    }
    return llmapi.LastAssistantMessage(itemsOut), false, nil
}
Built for agents · Tools, automatically

Every endpoint is an LLM tool. Naturally.

Workflow engines make you hand-write tool wrappers. Microbus already publishes every functional endpoint, web handler, and workflow as an OpenAPI operation. Pass a list of URLs to the LLM client and they become callable tools. Multiply your microservices by your endpoints. That is your toolbox.

  • No tool registration, no schema duplication, no glue code
  • Auth and required claims flow through, per call
  • Workflows are tools too: a parent flow can invoke another as a subgraph
// Spell out the opening message.
items := []llmapi.Item{
    llmapi.NewMessage("user", "Plan a trip to Lisbon next week.").AsItem(),
}

// Hand the LLM the URLs it may invoke —
// every endpoint is a tool, no glue code.
toolURLs := []string{
    calculatorapi.Arithmetic.URL(),
    weatherapi.Forecast.URL(),
    crmapi.LookupCustomer.URL(),
    researchflowapi.ResearchArticle.URL(), // a workflow, too
}

// Run the tool-calling loop as a durable subgraph.
itemsOut, _, yield, err := llmapi.NewSubgraph(flow).ChatLoop(ctx,
    llmapi.ProviderAny,
    llmapi.ModelFast,
    items,
    toolURLs,
    nil,
)
if yield {
    return // parked; re-enters when ChatLoop ends
}
reply := llmapi.LastAssistantMessage(itemsOut)
Built for agents · The feedback loop

Describe the workflow. Your agent builds it.

The agent doesn't just write code — it runs the tests, reads the errors, and fixes them in the same loop. Microbus is engineered so that loop closes in seconds, on a working set small enough for the agent to hold in its head.

  • Live integration tests run on every change — the agent verifies its own code
  • Generated client stubs let the agent call other services without reading their internals
  • Codegen keeps manifest.yaml and code in lockstep, so edits can't drift
You
Build a research workflow that fetches an article, summarizes it, critiques the summary, and translates the critique into French and Spanish in parallel.
Your coding agent
  • Scaffolded microservice research
  • Added task FetchArticle
  • Added task Summarize
  • Added task Critique
  • Added task TranslateFrench
  • Added task TranslateSpanish
  • Composed workflow Research with parallel translations
  • Updated manifest.yaml
  • Tests passing
Production-grade · Security in depth

The ironclad security that enterprises demand.

Agentic systems call out, fan in, and act on user data. That has to be auditable and controllable. Microbus carries security at every layer of the fabric, not bolted on after the graph runs.

Actor JWT

Per-endpoint claims

Every call carries a verified JWT identifying the actor. A boolean expression over the JWT's claims is enforced by the framework before your code runs.

Interservice ACLs

Wire-level access control

Publish and subscribe rights are derived from source code at deploy time. A microservice can only talk to the microservices it has been credentialed for.

Port firewall

Trust roots, isolated

Sensitive operations bind to dedicated ports (888 for control, 666 for trust roots) that the ingress proxy refuses inbound. Compromise blast radius is bounded.

Tracing

Auditable end to end

Every call across the bus is wrapped in a trace span. Every error carries a stack across microservice boundaries. Forensics are a query, not a project.

Perimeter

One door in

NATS runs on a private port with no external exposure. The HTTP ingress is the only door in. Microservices don't open inbound ports — they reach each other through the bus, not through listening sockets.

Two-token

Bearers never enter the bus

External bearer tokens stop at the ingress. The gate exchanges them for short-lived access tokens — operator-tunable to seconds or minutes — so a stolen internal token is useless before it could travel.

Production-grade · The whole lifecycle

One framework.
Build, test, deploy, operate.

The same framework that runs your workflow on a laptop runs it across regions. Nothing to swap, no parallel ops stack to maintain.

B

Build

Run an entire workflow, with every microservice it touches, on your laptop in one process.

T

Test

Spin up actual downstream microservices alongside the one under test. Live integration tests, no mocks required.

D

Deploy

Ship as a single binary, a modular monolith, or individual containers across regions. No code changes between modes.

O

Operate

Distributed tracing, metrics, structured logs, and error capture are built in. Plug into the dashboards you already run.

The right fabric makes all the difference.

Microbus is open source and free under Apache 2.0. Clone the repo, run the examples, and have a workflow on a real backend in an afternoon.