e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
---
|
|
layout: default
|
|
---
|
|
|
|
# Agents and Workflows
|
|
|
|
Go Micro's AI primitives map directly onto the taxonomy in Anthropic's [Building Effective Agents](https://www.anthropic.com/engineering/building-effective-agents). That post draws one distinction that matters:
|
|
|
|
- **Workflows** — "LLMs and tools orchestrated through **predefined code paths**." Deterministic.
|
|
- **Agents** — "LLMs **dynamically direct their own processes** and tool usage." Model-driven.
|
|
|
|
Go Micro has both, plus the harness they run inside — and expresses them as plain services and tools, with no graph DSL. That's deliberate: the same post advises finding "the simplest solution possible" and being "cautious with frameworks… they obscure the underlying mechanics."
|
|
|
|
## The building block: the augmented LLM
|
|
|
|
Anthropic's foundational unit is the *augmented LLM* — a model with tools, retrieval, and memory. In Go Micro:
|
|
|
|
| Augmented LLM | Go Micro |
|
|
|---|---|
|
|
| the model | `ai` package (7 providers, one interface) |
|
|
| tools | every service endpoint, discovered from the registry |
|
|
| memory | the `store` (file, Postgres, NATS KV) |
|
|
|
|
Every endpoint is automatically a tool, so the augmented LLM is the default, not something you assemble.
|
|
|
|
## Workflow ↔ `flow`
|
|
|
|
A [`Flow`](../ai-integration.html) is a workflow in Anthropic's exact sense: a **predefined path** — an event on a broker topic triggers a prompt with a fixed set of tools, deterministically. Use it when the task is well-defined and you want predictability.
|
|
|
|
```go
|
|
f := micro.NewFlow("onboard-user",
|
|
micro.FlowTrigger("events.user.created"),
|
|
micro.FlowPrompt("New user {{.Data}} — create a workspace and send a welcome email."),
|
|
micro.FlowProvider("anthropic"),
|
|
)
|
|
```
|
|
|
|
### Flow triggers, Agent reasons
|
|
|
|
A flow doesn't have to do the reasoning itself. Point it at an agent and it becomes a pure trigger — the event fires, the flow renders the prompt, and a registered agent handles it over RPC with its full capabilities (plan, delegate, memory, guardrails):
|
|
|
|
```go
|
|
f := micro.NewFlow("onboard-user",
|
|
micro.FlowTrigger("events.user.created"),
|
|
micro.FlowPrompt("New user {{.Data}} — get them set up."),
|
|
micro.FlowAgent("conductor"), // the conductor agent reasons; the flow only triggers
|
|
)
|
|
```
|
|
|
|
This is the clean seam between the two halves of the taxonomy: the *workflow* (deterministic, event-driven) hands off to the *agent* (dynamic). One engine, two front doors — an event (`flow`) or a conversation (`agent.Ask`).
|
|
|
|
### Ordered, durable steps
|
|
|
|
A flow can be a **task made of ordered steps** rather than a single turn — the predefined path made explicit. Each step is checkpointed before and after, so if the process dies mid-run the run **resumes at the step it stopped on**, without re-running the steps that already completed (and already had their side effects). This is durable execution, store-backed by default, with no separate workflow engine.
|
|
|
|
```go
|
|
f := micro.NewFlow("checkout",
|
|
micro.FlowTrigger("events.order.placed"),
|
|
micro.FlowRetry(2), // retry each step; per-step override available
|
|
micro.FlowSteps(
|
|
micro.FlowStep{Name: "reserve", Run: micro.FlowCall("inventory", "Inventory.Reserve")},
|
|
micro.FlowStep{Name: "charge", Run: micro.FlowCall("payment", "Payment.Charge")},
|
|
micro.FlowStep{Name: "welcome", Run: micro.FlowDispatch("comms")}, // hand a step to an agent
|
|
),
|
|
// Durable by default; point the default store at Postgres/NATS KV to
|
|
// survive a real restart, or plug in Temporal/Restate via Checkpoint.
|
|
)
|
|
```
|
|
|
|
A step's action is an RPC (`FlowCall`), an agent hand-off (`FlowDispatch`), one model turn (`FlowLLM`), or any function. `State` carries a typed payload (`Set`/`Scan`) plus a `Stage` marker — the resume point. Runs are retained for success and failure (audit) unless you set `FlowDeleteOnSuccess`. On restart, `f.Pending(ctx)` lists incomplete runs and `f.Resume(ctx, runID)` continues one. See [examples/flow-durable](https://github.com/micro/go-micro/tree/master/examples/flow-durable).
|
|
|
|
The pluggability is the usual go-micro shape: the built-in `Checkpoint` is store-backed (swap the store backend freely); implement the `Checkpoint` interface to delegate durability to an external engine. Most teams need neither — the default is durable.
|
|
|
|
## Agent ↔ `agent`
|
|
|
|
An [`Agent`](plan-delegate.html) is an agent in Anthropic's exact sense: it **directs itself** — plans, calls tools, evaluates results, and decides the next step over many turns, with memory across them. Use it when you want flexibility and model-driven decisions.
|
|
|
|
```go
|
|
a := micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentProvider("anthropic"),
|
|
)
|
|
a.Ask(ctx, "Plan the launch, create the tasks, and have comms notify the owner.")
|
|
```
|
|
|
|
### Long-running memory
|
|
|
|
Agents use store-backed conversation memory by default, scoped under the agent's
|
|
name. That makes short restarts boring: the next `Ask` reloads the retained
|
|
history from the same store backend you already use for services and flows.
|
|
Long-running agents can also keep model context bounded without losing useful
|
|
prior context. If you want retrieval without summaries, enable bounded active
|
|
context plus a durable archive of every turn:
|
|
|
|
```go
|
|
a := micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentProvider("anthropic"),
|
|
micro.AgentRetrievalMemory(40), // active messages kept in prompt context
|
|
micro.AgentMemoryRecallLimit(5), // archived turns recalled per Ask
|
|
)
|
|
```
|
|
|
|
`AgentRetrievalMemory(activeLimit)` switches the default memory to a store-backed
|
|
retriever. The active conversation is capped at `activeLimit`, every turn is
|
|
archived in the same scoped store used by the agent, and future asks inject
|
|
matching archived turns ahead of active context. The built-in ranking is
|
|
deterministic and credential-free for CI.
|
|
|
|
When you also want a rolling summary in active context, use compacting memory:
|
|
|
|
```go
|
|
a := micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentProvider("anthropic"),
|
|
micro.AgentCompactMemory(40, 12), // max active messages, recent messages kept verbatim
|
|
micro.AgentMemoryRecallLimit(5), // compacted turns recalled per Ask
|
|
)
|
|
```
|
|
|
|
`AgentCompactMemory(maxMessages, keepRecent)` switches the default memory to a
|
|
deterministic compactor. Once active history grows past `maxMessages`, older
|
|
turns move into the durable archive, a provider-neutral summary is injected into
|
|
active context, and the newest `keepRecent` messages stay verbatim. On future
|
|
asks, archived turns whose text matches the current request are recalled ahead of
|
|
the active context. Teams that need embeddings or a vector database can still
|
|
provide their own `AgentMemory` implementation.
|
|
|
|
This is harness memory, not prompt-layer orchestration: services remain the
|
|
capabilities, agents remain the dynamic decision makers, and flows remain the
|
|
durable predefined paths. Compaction only keeps a scheduled or looping agent from
|
|
turning every past turn into model context while still letting it remember facts
|
|
that matter to the current service → agent → workflow run.
|
|
|
|
Checkpointed agent runs and compacted memory share the same store-backed shape.
|
|
If a provider call fails after the prompt has been recorded, `agent.Resume` uses
|
|
the checkpointed run id and does not append that same user turn a second time;
|
|
completed tool results and recalled archived memory remain available for the
|
|
retry.
|
|
|
|
## The patterns — most are already here
|
|
|
|
Anthropic lists five workflow patterns. Go Micro implements the two richest ones natively, as services and tools, and the rest are ordinary compositions:
|
|
|
|
| Pattern | Go Micro |
|
|
|---|---|
|
|
| **Routing** — classify input, dispatch to a specialist | `micro chat`'s router — discovers agents, classifies intent, routes over RPC |
|
|
| **Orchestrator-workers** — a central LLM breaks down a task, delegates to workers, synthesizes | the `agent` with **`plan`** (break down) + **`delegate`** (hand to workers) + reply (synthesize) — see [Plan & Delegate](plan-delegate.html) |
|
|
| **Prompt chaining** — sequential steps | chain flows, or steps in an agent's plan |
|
|
| **Parallelization** — independent subtasks at once | Go concurrency + multiple services/agents; fan out with `delegate` |
|
|
| **Evaluator-optimizer** — one LLM generates, another critiques in a loop | two agents over RPC (generator + evaluator) |
|
|
|
|
The orchestrator-workers example is worth calling out: the conductor agent that plans, creates tasks, and delegates the notification to a `comms` agent **is** orchestrator-workers — built without a graph engine. See [examples/agent-plan-delegate](https://github.com/micro/go-micro/tree/master/examples/agent-plan-delegate).
|
|
|
|
## Choosing
|
|
|
|
Follow Anthropic's guidance:
|
|
|
|
- Start with the **augmented LLM** (a single service call through a model). Most tasks need nothing more.
|
|
- Reach for a **workflow** (`flow`) when the path is well-defined and you want predictability.
|
|
- Reach for an **agent** (`agent`) when the task needs flexibility and model-driven decisions — and accept the higher cost and the need for guardrails.
|
|
|
|
## Guardrails
|
|
|
|
Anthropic is emphatic that autonomous agents need stopping conditions, human checkpoints, and sandboxed testing. Go Micro's agent has two built-in guardrails, both as plain options:
|
|
|
|
**Stopping condition** — `MaxSteps` bounds the number of actions an agent may take per `Ask`. Once exceeded, further tool calls are refused and the model is told to stop and summarize.
|
|
|
|
```go
|
|
micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentMaxSteps(8), // at most 8 tool calls per request
|
|
)
|
|
```
|
|
|
|
**Human-in-the-loop** — `ApproveTool` gates each action before it runs. Return `false` to block it; the reason is shown to the model so it can adapt. The internal `plan` tool is never gated (it's bookkeeping, not an action).
|
|
|
|
```go
|
|
micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentApproveTool(func(tool string, input map[string]any) (bool, string) {
|
|
if strings.HasPrefix(tool, "billing_") {
|
|
return false, "billing actions require human sign-off"
|
|
}
|
|
return true, ""
|
|
}),
|
|
)
|
|
```
|
|
|
|
These are harness guardrails, not a separate policy engine — a counter and a callback on the path every tool call already takes. For anything that must be predictable, still prefer a **workflow**, and test agents against the [integration harness](https://github.com/micro/go-micro/tree/master/internal/harness/plan-delegate).
|
|
|
|
## Why no graph DSL
|
|
|
|
Anthropic: "be cautious with frameworks… understand the underlying code." Go Micro's answer is that there is no separate framework to understand — the harness is the service runtime. Workflows and agents are services, and tool use is RPC. `plan` and `delegate` are tools, not a graph DSL. The patterns above are code you can read, not a DSL you have to learn. That's the [direction we took going all in on AI](/blog/14).
|
|
|
|
## See also
|
|
|
|
- [Building Effective Agents](https://www.anthropic.com/engineering/building-effective-agents) — Anthropic
|
|
- [Plan & Delegate](plan-delegate.html) — the agent's built-in tools
|
|
- [Agent Integration Patterns](agent-patterns.html) — multi-agent architectures
|
|
- [AI Integration](../ai-integration.html) — agents, flows, and the model interface
|