Files
go-micro/internal/docs/AGENT_DESIGN.md
T
Asim AslamandClaude cb33decd97 Add built-in plan and delegate tools for agents with examples (#2949)
* 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>
2026-06-07 10:55:34 +01:00

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:

  1. 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.
  2. 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_agent tool
  • 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 build work the same way