* feat: add plan and delegate as built-in agent tools Give agents two self-capabilities, expressed as plain tools wired into the existing tool handler — no harness or graph, consistent with "services are the only abstraction": - plan: record/update an ordered plan, persisted to store-backed memory and surfaced in the system prompt on later turns (externalized planning). - delegate: hand a self-contained subtask to another agent. Delegate-first — if the target names a registered agent it is called via RPC; otherwise a focused ephemeral sub-agent is created with agent.New + Ask in a fresh, isolated context (loads/persists no history, no built-in tools, so it cannot re-delegate). Both are added automatically to any non-ephemeral agent, so existing micro.NewAgent services and micro chat routing get them for free. Tests are hermetic (memory store + memory registry). * feat: add agent-plan-delegate example and document plan/delegate - examples/agent-plan-delegate: coordinator that plans multi-step work, creates tasks with its own tools, and delegates notification to a separate registered comms agent over RPC. - integration tests driving the full Ask loop through a fake provider: plan tool exposure + persistence, ephemeral delegation with isolated context, delegate-first RPC routing to a registered agent. - docs: README (Building Agents + features + examples), AGENT_DESIGN (Built-in Capabilities), agent-patterns guide (Pattern 9), CLAUDE.md. * docs: blog post and guide for plan & delegate - blog/17: "Plan & Delegate: Deep Agents in Go" — what the feature is, how plan and delegate work, and a runnable getting-started path. - guides/plan-delegate: reference guide with the smallest-agent snippet, plan/delegate semantics, and the multi-agent example; linked in nav. - example: auto-detect provider/key from common env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...) so 'export KEY && go run main.go' just works. - onboarding: getting-started paths now include go mod init / go get and a clone-and-run path, so a reader can actually run it from a cold start. * refactor: reframe plan/delegate blog and clean up sub-agent construction - blog/17 retitled "Agents That Plan and Delegate" and reframed around intent (plan = state intent, delegate = direct it), positioned as the next beat after blog 15/16 and tied to the existing store + agent RPC rather than re-announcing them. "Deep agents" now a single in-passing nod, matching how blog 14 references LangChain. - agent: add unexported newEphemeral constructor for sub-agents instead of type-asserting the public Agent interface to set an internal field; matches the options-only construction idiom used elsewhere. * feat: expose plan & delegate in the micro chat fallback Add agent.Builtins(opts...) — returns the built-in tools plus a handler, so the plan/delegate capabilities can be wired into a tool loop that isn't a running Agent. micro chat's direct-service fallback now reuses it (single source of truth, no duplicated handler logic), so planning and delegation are available there too, not just for registered agents. Adds a test for the accessor; notes CLI availability in the guide. --------- Co-authored-by: Claude <noreply@anthropic.com>
6.7 KiB
Agent Interface Design
Principle
Service = capability. Agent = intelligence. An agent IS a service — it has a real RPC server, a proto-defined Agent.Chat endpoint, and registers in the registry like everything else.
micro.New("task") // creates a service
micro.NewAgent("task-mgr") // creates an agent (which is also a service)
Same package. Same level. Same communication (RPC). Different responsibilities.
Interface
type Agent interface {
Name() string
Init(...AgentOption)
Options() AgentOptions
Ask(ctx context.Context, message string) (*Response, error)
Run() error
Stop() error
String() string
}
Ask is the programmatic API. Send a message, get a response.
Run starts a real RPC server, registers the Agent.Chat endpoint in the registry, and blocks.
Proto Definition
service Agent {
rpc Chat(ChatRequest) returns (ChatResponse) {}
}
message ChatRequest {
string message = 1;
}
message ChatResponse {
string reply = 1;
string agent = 2;
repeated ToolCall tool_calls = 3;
}
The agent is callable by any go-micro client:
micro call task-mgr Agent.Chat '{"message": "What tasks are overdue?"}'
Options
type AgentOptions struct {
Name string
Services []string // which services this agent manages
Prompt string // system prompt — identity, domain knowledge, boundaries
Provider string // LLM provider (anthropic, openai, etc.)
Model string // LLM model (optional)
APIKey string
Registry registry.Registry // discover services and other agents
Client client.Client // call service endpoints and other agents
Store store.Store // agent memory (persists across restarts)
HistoryLimit int // max conversation turns to retain
}
Functional options:
agent := micro.NewAgent("task-mgr",
micro.AgentServices("task"),
micro.AgentPrompt("You manage tasks. You understand deadlines and priorities."),
micro.AgentProvider("anthropic"),
)
Scoped Tools
An agent only sees the endpoints of its assigned services (plus excludes its own endpoints so it doesn't call itself).
Memory
Agents persist conversation history in the store. Memory survives restarts.
agent/{name}/history — conversation history
Built-in Capabilities
Beyond its scoped service tools, every agent gets two built-in tools. They are not service endpoints — they are capabilities the agent has over itself and over other agents. They are plain tools wired into the agent's tool handler; there is no separate harness, loop engine, or graph. The LLM calls them exactly like any other tool.
plan
For multi-step work the agent records an ordered plan: a list of steps, each with a task and a status (pending, in_progress, done). The plan is persisted to the store and surfaced back in the system prompt on later turns, so the agent stays oriented.
agent/{name}/plan — current plan
delegate
The agent hands a self-contained subtask to another agent. Delegate-first resolution:
- If the target names a registered agent (a service advertising
type=agent), the subtask is sent to it via RPC (Agent.Chat). Intelligence stays distributed — the domain expert handles its own services. - Otherwise a focused ephemeral sub-agent is created with
New(...)+Ask(...), given a fresh, isolated context, asked the subtask, and torn down.
A sub-agent is just an agent — no new "spawn"/"fork" concept. Ephemeral sub-agents load and persist no history and have no built-in tools, so they cannot plan or re-delegate (which bounds recursion).
These capabilities are added automatically to any non-ephemeral agent, so existing NewAgent services and micro chat routing get them for free.
Registration
Agents register as real services via server.NewServer with metadata:
server.Metadata(map[string]string{
"type": "agent",
"services": "task,project",
})
The server has a real address, real transport, real endpoints. micro agent list discovers agents by checking server metadata for type=agent.
The Router (micro chat)
micro chat is a router. It discovers agents from the registry and dispatches to them via RPC.
- One agent → routes directly via
client.Call(agentName, "Agent.Chat", ...) - Multiple agents → LLM classifies intent, calls
route_to_agenttool - No agents → falls back to direct service access (current behaviour)
Agent-to-Agent Communication
Agents call each other via standard RPC. An agent is a service — it has an Agent.Chat endpoint. Any agent can call any other agent the same way it calls a service.
// From inside an agent's logic, call another agent:
client.Call("comms-mgr", "Agent.Chat", &ChatRequest{Message: "Notify Alice"})
No special protocol. No broker topics. Just RPC.
Usage Patterns
Single-service agent
agent := micro.NewAgent("task-mgr",
micro.AgentServices("task"),
micro.AgentPrompt("You manage tasks."),
micro.AgentProvider("anthropic"),
)
agent.Run()
Multi-service agent
agent := micro.NewAgent("project-mgr",
micro.AgentServices("task", "project", "milestone"),
micro.AgentPrompt("You manage the project system."),
micro.AgentProvider("anthropic"),
)
agent.Run()
Programmatic
agent := micro.NewAgent("support", ...)
agent.Init()
resp, _ := agent.Ask(ctx, "What tickets are open?")
Agent alongside service
func main() {
svc := micro.New("task")
svc.Handle(new(TaskHandler))
agent := micro.NewAgent("task-mgr",
micro.AgentServices("task"),
micro.AgentPrompt("You manage tasks."),
micro.AgentProvider("anthropic"),
)
go svc.Run()
agent.Run()
}
CLI
micro agent list # list registered agents
micro agent describe task-mgr # show agent details
micro chat # routes to agents automatically
micro call task-mgr Agent.Chat '{"message": "..."}' # direct RPC
Generation
micro run --prompt creates services AND an agent:
micro run --prompt "task management system"
Generated:
task/ ← service
project/ ← service
agent/ ← agent (manages task, project)
The agent reads MICRO_AI_PROVIDER and MICRO_AI_API_KEY from the environment.
What Doesn't Change
- Services are still services — same interface, same code, same deployment
- You can run services without agents
- You can call services directly via
micro call, the API, or MCP - The framework interfaces (registry, client, server, store) are unchanged
micro run,micro deploy,micro buildwork the same way