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
169 lines
8.0 KiB
Markdown
169 lines
8.0 KiB
Markdown
---
|
|
layout: blog
|
|
title: "Agents That Plan and Delegate"
|
|
permalink: /blog/17
|
|
description: "An agent shouldn't just react tool by tool. It should form intent — plan what it's doing — and direct intent — delegate what it shouldn't do itself. Go Micro now gives every agent both, as plain tools."
|
|
---
|
|
|
|
# Agents That Plan and Delegate
|
|
|
|
*June 7, 2026 • By the Go Micro Team*
|
|
|
|
When we [introduced `micro.NewAgent()`](/blog/16), an agent was already a service with an LLM inside: scoped tools, persistent memory, and a `micro chat` router that dispatches across agents. And in [Agents for Services](/blog/15) we made the case that intelligence should be distributed — agents coordinate "not through code… through understanding."
|
|
|
|
This post is the next beat. An agent that only reacts, one tool call at a time, isn't really understanding anything — it's improvising. Two things turn reaction into intent: the agent should **plan** what it's doing before it does it, and **delegate** what it shouldn't be doing itself. Go Micro now gives every agent both.
|
|
|
|
True to the rest of the framework, they aren't a new layer. There's no harness, no workflow engine, no agent graph. `plan` and `delegate` are two ordinary tools — the LLM calls them exactly like it calls a service endpoint — added automatically to every agent. (If you've followed what everyone from Claude Code to LangChain calls "deep agents," this is the same idea, built the go-micro way: as tools, not as a framework.)
|
|
|
|
## The smallest version
|
|
|
|
An agent doesn't need any services to plan. Here's a complete program:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"go-micro.dev/v5"
|
|
)
|
|
|
|
func main() {
|
|
a := micro.NewAgent("assistant",
|
|
micro.AgentProvider("anthropic"),
|
|
micro.AgentAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
|
|
)
|
|
|
|
resp, err := a.Ask(context.Background(),
|
|
"Plan how to launch a product, then carry out what you can.")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println(resp.Reply)
|
|
}
|
|
```
|
|
|
|
Save it in a fresh module and run:
|
|
|
|
```bash
|
|
mkdir my-agent && cd my-agent
|
|
go mod init my-agent
|
|
go get go-micro.dev/v5
|
|
# save the code above as main.go
|
|
export ANTHROPIC_API_KEY=sk-ant-...
|
|
go run main.go
|
|
```
|
|
|
|
The agent records a plan with the `plan` tool, then works through it. That's the whole setup.
|
|
|
|
## plan: stating intent
|
|
|
|
`plan` is exactly what it sounds like: before multi-step work, the model writes down an ordered list of steps, and updates it as it goes.
|
|
|
|
```json
|
|
{
|
|
"steps": [
|
|
{"task": "draft the announcement", "status": "in_progress"},
|
|
{"task": "schedule the email", "status": "pending"},
|
|
{"task": "publish the blog post", "status": "pending"}
|
|
]
|
|
}
|
|
```
|
|
|
|
This builds directly on the memory we [already shipped](/blog/16): the plan is saved to the same [store](/docs/store) every service uses — file-backed by default, Postgres or NATS KV in production — under `agent/{name}/plan`, and folded back into the system prompt on the next turn. The agent stays oriented across a long task and picks up where it left off after a restart.
|
|
|
|
You get it for free. To make an agent reliably plan, just say so in its prompt:
|
|
|
|
```go
|
|
micro.AgentPrompt("For multi-step requests, call the plan tool first to record your steps, then carry them out.")
|
|
```
|
|
|
|
## delegate: directing intent
|
|
|
|
The harder move is knowing what *not* to do yourself.
|
|
|
|
A single agent managing ten services is a different kind of monolith — it knows a little about everything and a lot about nothing. We argued in [Agents for Services](/blog/15) that the fix is the same one microservices made for code: distribute it. Give each domain its own agent, and let them hand work to each other over RPC.
|
|
|
|
That hand-off already existed — an agent is a service, so any agent can call any other agent's `Agent.Chat` endpoint. `delegate` simply lets the agent reach for it *as part of its own reasoning*, instead of you wiring the routing. The model calls `delegate` with a subtask, and Go Micro resolves it **delegate-first**:
|
|
|
|
1. **If the target names a registered agent** that owns the relevant services, the subtask goes to it over RPC. The domain expert handles its own services.
|
|
2. **Otherwise** a focused, short-lived **sub-agent** is created for just that subtask, with a fresh, isolated context, and torn down when it's done.
|
|
|
|
```json
|
|
{
|
|
"task": "Notify owner@acme.com that the launch plan is ready",
|
|
"to": "comms"
|
|
}
|
|
```
|
|
|
|
One design decision worth calling out: we didn't add a "spawn" or a "fork" primitive. **A sub-agent is just an agent** — created with `New`, talked to with `Ask`, the same two calls you already use. There's no new concept to learn, because there's no new concept: it's the existing RPC model, surfaced as a tool. Ephemeral sub-agents load and persist no history and get no tools of their own — so they can't plan or re-delegate, which keeps delegation from recursing.
|
|
|
|
## Putting it together
|
|
|
|
Two services (`task`, `notify`) and two agents. The `conductor` owns `task`; `comms` owns `notify`. Ask the conductor to create some tasks and notify someone, and watch intent split across the system:
|
|
|
|
```go
|
|
comms := micro.NewAgent("comms",
|
|
micro.AgentServices("notify"),
|
|
micro.AgentPrompt("You handle outbound notifications."),
|
|
micro.AgentProvider("anthropic"),
|
|
micro.AgentAPIKey(key),
|
|
)
|
|
go comms.Run()
|
|
|
|
conductor := micro.NewAgent("conductor",
|
|
micro.AgentServices("task"),
|
|
micro.AgentPrompt(
|
|
"For multi-step requests, call the plan tool first. "+
|
|
"For notifications, delegate to the \"comms\" agent (to: \"comms\")."),
|
|
micro.AgentProvider("anthropic"),
|
|
micro.AgentAPIKey(key),
|
|
)
|
|
|
|
resp, _ := conductor.Ask(ctx,
|
|
"Create three launch tasks: Design, Build, and Ship. "+
|
|
"Then make sure owner@acme.com is notified that the launch plan is ready.")
|
|
```
|
|
|
|
A typical run:
|
|
|
|
```
|
|
→ plan({"steps":[{"task":"create Design task","status":"pending"}, ...]})
|
|
→ task_TaskService_Add({"title":"Design"})
|
|
→ task_TaskService_Add({"title":"Build"})
|
|
→ task_TaskService_Add({"title":"Ship"})
|
|
→ delegate({"task":"Notify owner@acme.com that the launch plan is ready","to":"comms"})
|
|
📨 notify: to=owner@acme.com message="The launch plan is ready"
|
|
```
|
|
|
|
The conductor never learned how to send a notification. It learned *who does*. `comms` handled it with its own service, in its own context, over RPC — exactly the distributed-intelligence picture from [blog 15](/blog/15), now driven by the agent itself rather than a router.
|
|
|
|
The full runnable code is in [examples/agent-plan-delegate](https://github.com/micro/go-micro/tree/master/examples/agent-plan-delegate). Set any provider key (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …) and `go run main.go`.
|
|
|
|
## Why it's only two tools
|
|
|
|
It would have been easy to ship a planning engine, a sub-agent scheduler, a delegation graph. We didn't, on purpose. Every one of those is a new abstraction to learn and maintain, and Go Micro's bet has been consistent since we [went all in on AI](/blog/14): services are the only abstraction, the LLM calls them as tools, and an agent's own capabilities are no exception.
|
|
|
|
`plan` and `delegate` are two small tools added to mechanisms that already existed — the store, and agent-to-agent RPC. That's the entire feature. It's also why there's nothing to configure: if you've written a `micro.NewAgent`, you already have them.
|
|
|
|
## Getting started
|
|
|
|
The fastest way to see it end to end is the runnable example in the repo — it has the module set up and both agents wired:
|
|
|
|
```bash
|
|
git clone https://github.com/micro/go-micro
|
|
cd go-micro/examples/agent-plan-delegate
|
|
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GEMINI_API_KEY, ...
|
|
go run main.go
|
|
```
|
|
|
|
To start from scratch in your own project, use the smallest-agent snippet above (`go mod init` + `go get go-micro.dev/v5`).
|
|
|
|
Read the [Plan & Delegate guide](/docs/guides/plan-delegate) for the full reference, or the [agent patterns guide](/docs/guides/agent-patterns) for where this fits among the other ways to build with agents.
|
|
|
|
---
|
|
|
|
*Go Micro is open source. Star us on [GitHub](https://github.com/micro/go-micro), join the [Discord](https://discord.gg/G8Gk5j3uXr), or read the [docs](https://go-micro.dev/docs).*
|