Compare commits

..

1 Commits

Author SHA1 Message Date
Codex 749a3ee382 docs: refresh planner priorities for 4248
Harness (E2E) / Harnesses (mock LLM) (push) Waiting to run
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Run Tests / Unit Tests (push) Waiting to run
Run Tests / Etcd Integration Tests (push) Waiting to run
2026-07-07 13:56:07 +00:00
12 changed files with 80 additions and 334 deletions
+3 -1
View File
@@ -21,7 +21,9 @@ changes, architectural rewrites. Those go to the human.
## Work queue (ranked)
1. **Make AtlasCloud guarded delegation pass reliably** ([#4244](https://github.com/micro/go-micro/issues/4244)) — Now-phase cross-provider conformance is now the highest open loop item after the website first-agent examples wayfinding shipped in #4266 and #4241 was closed; keep it in queue until the live agent harness consistently observes the guarded delegate within the retry budget.
1. **Prevent duplicate delegated notifications in plan-delegate harness** ([#4245](https://github.com/micro/go-micro/issues/4245)) — Now-phase safety is the current red edge after the AtlasCloud 400 fallback shipped: the services → agents → workflows loop must not repeat delegated side effects while recovering or continuing a plan. Make the plan-delegate path idempotent and CI-verifiable before adding more depth.
2. **Make AtlasCloud guarded delegation pass reliably** ([#4244](https://github.com/micro/go-micro/issues/4244)) — keep cross-provider conformance close behind the side-effect fix: AtlasCloud now gets past the Minimax request-shape 400s, but the live agent harness still misses the required guarded delegate within the retry budget.
3. **Link examples wayfinding from website getting-started path** ([#4241](https://github.com/micro/go-micro/issues/4241)) — keep adoption weighted with hardening after the examples index shipped: the repo README and CLI now point at the first-agent/0→hero map, but go-micro.dev getting-started and quickstart pages should expose the same path with a CI-guarded docs smoke check.
_Seeded by Claude Code from the roadmap + open issues; thereafter maintained by the
architecture-review pass._
-6
View File
@@ -99,12 +99,6 @@ type agentImpl struct {
// holding mu. Tool execution updates it so resumed runs can reuse
// completed tool results without replaying side effects.
currentRun *flow.Run
// delegateCalls collapses concurrent equivalent delegate tool calls so a
// provider replay cannot fan out duplicate delegated side effects before the
// durable delegate-result cache is written.
delegateMu sync.Mutex
delegateCalls map[string]*delegateCall
}
// New creates a new Agent.
+1 -49
View File
@@ -27,11 +27,6 @@ const (
toolHumanInput = "request_input"
)
type delegateCall struct {
done chan struct{}
res ai.ToolResult
}
// builtinTools returns the tool definitions exposed to the model in
// addition to the agent's scoped service tools.
func builtinTools() []ai.Tool {
@@ -590,7 +585,7 @@ func (a *agentImpl) handleHumanInput(call ai.ToolCall) ai.ToolResult {
// if 'to' names a registered agent, it is called via RPC. Otherwise an
// ephemeral sub-agent is created with a fresh, isolated context, asked
// the subtask, and its reply returned.
func (a *agentImpl) handleDelegate(ctx context.Context, call ai.ToolCall) (res ai.ToolResult) {
func (a *agentImpl) handleDelegate(ctx context.Context, call ai.ToolCall) ai.ToolResult {
input := call.Input
task, _ := input["task"].(string)
if task == "" {
@@ -601,12 +596,6 @@ func (a *agentImpl) handleDelegate(ctx context.Context, call ai.ToolCall) (res a
return cached
}
key := delegateResultKey(to, task)
if cached, ok := a.joinDelegateCall(ctx, call.ID, key); ok {
return cached
}
defer func() { a.finishDelegateCall(key, res) }()
// An external agent on another framework, addressed by A2A URL.
if strings.HasPrefix(to, "http://") || strings.HasPrefix(to, "https://") {
reply, err := a2a.NewClient(to).Send(ctx, task)
@@ -658,38 +647,6 @@ func (a *agentImpl) handleDelegate(ctx context.Context, call ai.ToolCall) (res a
return a.storeDelegateResult(call.ID, to, task, map[string]any{"reply": resp.Reply})
}
func (a *agentImpl) joinDelegateCall(ctx context.Context, id, key string) (ai.ToolResult, bool) {
a.delegateMu.Lock()
if a.delegateCalls == nil {
a.delegateCalls = map[string]*delegateCall{}
}
if inFlight := a.delegateCalls[key]; inFlight != nil {
a.delegateMu.Unlock()
select {
case <-ctx.Done():
return errResult(id, ctx.Err().Error()), true
case <-inFlight.done:
return withToolResultID(inFlight.res, id), true
}
}
a.delegateCalls[key] = &delegateCall{done: make(chan struct{})}
a.delegateMu.Unlock()
return ai.ToolResult{}, false
}
func (a *agentImpl) finishDelegateCall(key string, res ai.ToolResult) {
a.delegateMu.Lock()
inFlight := a.delegateCalls[key]
if inFlight == nil {
a.delegateMu.Unlock()
return
}
inFlight.res = res
delete(a.delegateCalls, key)
close(inFlight.done)
a.delegateMu.Unlock()
}
func (a *agentImpl) cachedDelegateResult(id, to, task string) (ai.ToolResult, bool) {
recs, err := a.stateStore().Read(delegateResultKey(to, task))
if err != nil || len(recs) == 0 {
@@ -709,11 +666,6 @@ func (a *agentImpl) storeDelegateResult(id, to, task string, out map[string]any)
return ai.ToolResult{ID: id, Value: out, Content: string(b)}
}
func withToolResultID(res ai.ToolResult, id string) ai.ToolResult {
res.ID = id
return res
}
func delegateResultKey(to, task string) string {
fp := normalizeDelegateTarget(to) + "\x00" + normalizeDelegateTask(task)
sum := sha256.Sum256([]byte(fp))
-40
View File
@@ -3,9 +3,7 @@ package agent
import (
"context"
"encoding/json"
"sync"
"testing"
"time"
"go-micro.dev/v6/ai"
"go-micro.dev/v6/registry"
@@ -209,44 +207,6 @@ func TestDelegateResultCacheReusesLaunchReadinessParaphrases(t *testing.T) {
}
}
func TestDelegateInFlightReplaysShareFirstResult(t *testing.T) {
a := New(Name("planner"), WithStore(store.NewMemoryStore())).(*agentImpl)
key := delegateResultKey("comms", "Notify owner@acme.com that the launch plan is ready")
if _, joined := a.joinDelegateCall(context.Background(), "delegate-1", key); joined {
t.Fatal("first delegate call unexpectedly joined an existing in-flight call")
}
var wg sync.WaitGroup
wg.Add(1)
results := make(chan ai.ToolResult, 1)
go func() {
defer wg.Done()
res, joined := a.joinDelegateCall(context.Background(), "delegate-2", key)
if !joined {
t.Error("replayed delegate call did not join the in-flight call")
return
}
results <- res
}()
select {
case res := <-results:
t.Fatalf("replayed delegate returned before first call finished: %+v", res)
case <-time.After(25 * time.Millisecond):
}
first := ai.ToolResult{ID: "delegate-1", Content: `{"reply":"Notified owner@acme.com."}`}
a.finishDelegateCall(key, first)
wg.Wait()
replayed := <-results
if replayed.ID != "delegate-2" {
t.Fatalf("replayed result ID = %q, want delegate-2", replayed.ID)
}
if replayed.Content != first.Content {
t.Fatalf("replayed content = %q, want %q", replayed.Content, first.Content)
}
}
func TestIsAgent(t *testing.T) {
reg := registry.NewMemoryRegistry()
+9 -37
View File
@@ -19,8 +19,7 @@ type textToolCall struct {
Name string `json:"name"`
Tool string `json:"tool"`
Input map[string]any `json:"input"`
Arguments any `json:"arguments"`
Function *textToolCall `json:"function"`
Arguments map[string]any `json:"arguments"`
}
// executeTextToolCalls is a compatibility fallback for providers that return a
@@ -177,7 +176,14 @@ func collectTextToolCalls(v any, allowed map[string]string) []ai.ToolCall {
return collectTextToolCalls(nested, allowed)
}
call := mapToTextToolCall(x)
name, input := textToolCallNameAndInput(call)
name := call.Name
if name == "" {
name = call.Tool
}
input := call.Input
if input == nil {
input = call.Arguments
}
if name == "" || allowed[name] == "" || input == nil {
return nil
}
@@ -191,40 +197,6 @@ func collectTextToolCalls(v any, allowed map[string]string) []ai.ToolCall {
}
}
func textToolCallNameAndInput(call textToolCall) (string, map[string]any) {
name := call.Name
if name == "" {
name = call.Tool
}
input := call.Input
if input == nil {
input = textToolArguments(call.Arguments)
}
if call.Function != nil {
fnName, fnInput := textToolCallNameAndInput(*call.Function)
if name == "" {
name = fnName
}
if input == nil {
input = fnInput
}
}
return name, input
}
func textToolArguments(raw any) map[string]any {
switch args := raw.(type) {
case map[string]any:
return args
case string:
var input map[string]any
if err := json.Unmarshal([]byte(args), &input); err == nil {
return input
}
}
return nil
}
func decodeTaggedTextToolCalls(text string, allowed map[string]string) []ai.ToolCall {
var out []ai.ToolCall
for _, match := range singleTaggedToolCall.FindAllStringSubmatch(text, -1) {
-19
View File
@@ -56,22 +56,3 @@ func TestParseTextToolCallsCreateAliasForAddTool(t *testing.T) {
t.Fatalf("title = %v, want Design", got)
}
}
func TestParseTextToolCallsOpenAICompatibleFunctionArgumentsString(t *testing.T) {
tools := []ai.Tool{{Name: "delegate"}}
reply := `<tool_call>{"id":"call-2","type":"function","function":{"name":"delegate","arguments":"{\"task\":\"summarize the conformance marker\",\"to\":\"blocked-reviewer\"}"}}</tool_call>`
calls := parseTextToolCalls(reply, tools)
if len(calls) != 1 {
t.Fatalf("parseTextToolCalls returned %d calls, want 1: %+v", len(calls), calls)
}
if calls[0].Name != "delegate" {
t.Fatalf("call name = %q, want delegate", calls[0].Name)
}
if got := calls[0].Input["task"]; got != "summarize the conformance marker" {
t.Fatalf("task = %v, want summarize the conformance marker", got)
}
if got := calls[0].Input["to"]; got != "blocked-reviewer" {
t.Fatalf("to = %v, want blocked-reviewer", got)
}
}
+56 -57
View File
@@ -138,77 +138,76 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
}
if p.opts.ToolHandler != nil {
var allToolCalls []ai.ToolCall
allToolCalls := append([]ai.ToolCall(nil), resp.ToolCalls...)
var toolResults []string
pendingToolCalls := append([]ai.ToolCall(nil), resp.ToolCalls...)
followUpMessages := append(messages, map[string]any{
"role": "assistant",
"content": rawMessage["content"],
"tool_calls": rawMessage["tool_calls"],
})
for attempt := 0; len(pendingToolCalls) > 0 && attempt < 4; attempt++ {
for _, tc := range pendingToolCalls {
result := p.opts.ToolHandler(ctx, tc)
for _, tc := range resp.ToolCalls {
content := p.opts.ToolHandler(ctx, tc).Content
if content != "" {
toolResults = append(toolResults, content)
}
followUpMessages = append(followUpMessages, map[string]any{
"role": "tool",
"tool_call_id": tc.ID,
"content": content,
})
}
followUpReq := map[string]any{
"model": p.opts.Model,
"messages": followUpMessages,
}
if len(tools) > 0 {
// Keep the tool schema available during the follow-up turn. Minimax
// models behind Atlas Cloud sometimes call one required tool, inspect
// that result, and then issue a second tool call (for example a guarded
// delegate conformance check) instead of completing immediately.
followUpReq["tools"] = tools
}
followUpResp, _, err := p.callAPI(ctx, "tool-follow-up", followUpReq)
if err != nil {
if atlascloudShouldRetryWithoutTools(err, followUpReq) {
delete(followUpReq, "tools")
followUpResp, _, err = p.callAPI(ctx, "tool-follow-up-no-tools", followUpReq)
}
if err != nil {
return nil, err
}
}
if len(followUpResp.ToolCalls) > 0 {
for i := range followUpResp.ToolCalls {
result := p.opts.ToolHandler(ctx, followUpResp.ToolCalls[i])
if result.Refused != "" {
tc.Error = result.Refused
followUpResp.ToolCalls[i].Error = result.Refused
}
if result.Content != "" {
tc.Result = result.Content
followUpResp.ToolCalls[i].Result = result.Content
toolResults = append(toolResults, result.Content)
}
allToolCalls = append(allToolCalls, tc)
resp.ToolCalls = allToolCalls
followUpMessages = append(followUpMessages, map[string]any{
"role": "tool",
"tool_call_id": tc.ID,
"content": result.Content,
})
}
followUpReq := map[string]any{
"model": p.opts.Model,
"messages": followUpMessages,
allToolCalls = append(allToolCalls, followUpResp.ToolCalls...)
resp.ToolCalls = allToolCalls
}
if followUpResp.Reply != "" {
if strings.Contains(followUpResp.Reply, "<tool_call") || strings.Contains(followUpResp.Reply, "function=") {
// Preserve follow-up assistant content as Reply, not Answer, when
// it may contain a text-encoded tool call. The agent harness
// inspects Reply for text fallback calls after Generate returns,
// which covers AtlasCloud/minimax turns that emit a second
// required call (for example guarded delegate) as markup instead
// of native tool_calls.
resp.Reply = followUpResp.Reply
} else {
resp.Answer = followUpResp.Reply
}
if len(tools) > 0 {
// Keep the tool schema available during follow-up turns. Minimax
// models behind Atlas Cloud sometimes complete a multi-tool task
// one call at a time (plan, then service tools, then delegate).
followUpReq["tools"] = tools
}
followUpResp, followUpRawMessage, err := p.callAPI(ctx, "tool-follow-up", followUpReq)
if err != nil {
if atlascloudShouldRetryWithoutTools(err, followUpReq) {
delete(followUpReq, "tools")
followUpResp, followUpRawMessage, err = p.callAPI(ctx, "tool-follow-up-no-tools", followUpReq)
}
if err != nil {
return nil, err
}
}
if len(followUpResp.ToolCalls) == 0 {
if followUpResp.Reply != "" {
if strings.Contains(followUpResp.Reply, "<tool_call") || strings.Contains(followUpResp.Reply, "function=") {
// Preserve follow-up assistant content as Reply, not Answer, when
// it may contain a text-encoded tool call. The agent harness
// inspects Reply for text fallback calls after Generate returns.
resp.Reply = followUpResp.Reply
} else {
resp.Answer = followUpResp.Reply
}
} else if len(toolResults) > 0 {
resp.Answer = strings.Join(toolResults, "\n")
}
break
}
followUpMessages = append(followUpMessages, map[string]any{
"role": "assistant",
"content": followUpRawMessage["content"],
"tool_calls": followUpRawMessage["tool_calls"],
})
pendingToolCalls = followUpResp.ToolCalls
} else if len(toolResults) > 0 {
resp.Answer = strings.Join(toolResults, "\n")
}
}
-66
View File
@@ -355,8 +355,6 @@ func TestProvider_GenerateExecutesFollowUpToolCall(t *testing.T) {
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"","tool_calls":[{"id":"call-1","function":{"name":"conformance_echo","arguments":"{\"value\":\"agent-conformance\"}"}}]}}]}`))
case 2:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"","tool_calls":[{"id":"call-2","function":{"name":"delegate","arguments":"{\"task\":\"summarize the conformance marker\",\"to\":\"blocked-reviewer\"}"}}]}}]}`))
case 3:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"blocked by policy"}}]}`))
default:
t.Fatalf("unexpected API call %d", len(bodies))
}
@@ -408,70 +406,6 @@ func TestProvider_GenerateExecutesFollowUpToolCall(t *testing.T) {
}
}
func TestProvider_GenerateExecutesMultiStepFollowUpToolCalls(t *testing.T) {
var bodies []map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatalf("decode request: %v", err)
}
bodies = append(bodies, body)
w.Header().Set("Content-Type", "application/json")
switch len(bodies) {
case 1:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"","tool_calls":[{"id":"call-plan","function":{"name":"plan","arguments":"{\"steps\":[{\"task\":\"create tasks\"},{\"task\":\"notify owner\"}]}"}}]}}]}`))
case 2:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"","tool_calls":[{"id":"call-add","function":{"name":"task_TaskService_Add","arguments":"{\"title\":\"Design\"}"}}]}}]}`))
case 3:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"","tool_calls":[{"id":"call-delegate","function":{"name":"delegate","arguments":"{\"task\":\"notify owner@acme.com\",\"to\":\"comms\"}"}}]}}]}`))
case 4:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"done"}}]}`))
default:
t.Fatalf("unexpected API call %d", len(bodies))
}
}))
defer ts.Close()
var calls []string
p := NewProvider(
ai.WithAPIKey("test-key"),
ai.WithBaseURL(ts.URL),
ai.WithToolHandler(func(ctx context.Context, call ai.ToolCall) ai.ToolResult {
calls = append(calls, call.Name)
return ai.ToolResult{ID: call.ID, Content: `{"ok":true}`}
}),
)
resp, err := p.Generate(context.Background(), &ai.Request{
Prompt: "plan, create tasks, and delegate notification",
Tools: []ai.Tool{
{Name: "plan", Description: "record a plan", Properties: map[string]any{"steps": map[string]any{"type": "array"}}},
{Name: "task_TaskService_Add", Description: "add task", Properties: map[string]any{"title": map[string]any{"type": "string"}}},
{Name: "delegate", Description: "delegate work", Properties: map[string]any{"task": map[string]any{"type": "string"}, "to": map[string]any{"type": "string"}}},
},
})
if err != nil {
t.Fatalf("Generate returned error: %v", err)
}
wantCalls := []string{"plan", "task_TaskService_Add", "delegate"}
if strings.Join(calls, ",") != strings.Join(wantCalls, ",") {
t.Fatalf("tool calls = %v, want %v", calls, wantCalls)
}
if len(resp.ToolCalls) != 3 {
t.Fatalf("ToolCalls = %+v, want all multi-step calls", resp.ToolCalls)
}
if resp.Answer != "done" {
t.Fatalf("Answer = %q, want final follow-up reply", resp.Answer)
}
if len(bodies) != 4 {
t.Fatalf("requests = %d, want initial plus three follow-ups", len(bodies))
}
for i := 1; i < 4; i++ {
if _, ok := bodies[i]["tools"].([]any); !ok {
t.Fatalf("follow-up request %d did not include tools: %#v", i+1, bodies[i])
}
}
}
func TestProvider_GeneratePreservesFollowUpTextToolCallInReply(t *testing.T) {
var bodies []map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -248,40 +248,6 @@ func TestFirstAgentWayfindingDocs(t *testing.T) {
"guides/install-troubleshooting.html",
"micro agent demo",
"micro zero-to-hero",
"https://github.com/micro/go-micro/blob/master/examples/INDEX.md",
"https://github.com/micro/go-micro/tree/master/examples/support",
"https://github.com/micro/go-micro/tree/master/examples/first-agent",
"guides/no-secret-first-agent.html",
"guides/your-first-agent.html",
"guides/debugging-agents.html",
"guides/zero-to-hero.html",
},
},
{
name: "website quickstart next steps",
file: filepath.Join(root, "internal", "website", "docs", "quickstart.md"),
heading: "## Next Steps",
links: []string{
"guides/install-troubleshooting.html",
"micro agent demo",
"micro zero-to-hero",
"https://github.com/micro/go-micro/blob/master/examples/INDEX.md",
"https://github.com/micro/go-micro/tree/master/examples/support",
"https://github.com/micro/go-micro/tree/master/examples/first-agent",
"guides/no-secret-first-agent.html",
"guides/your-first-agent.html",
"guides/debugging-agents.html",
"guides/zero-to-hero.html",
},
},
{
name: "website docs index learn more",
file: filepath.Join(root, "internal", "website", "docs", "index.md"),
heading: "## Learn More",
links: []string{
"getting-started.html",
"https://github.com/micro/go-micro/blob/master/examples/INDEX.md",
"https://github.com/micro/go-micro/tree/master/examples/support",
"guides/no-secret-first-agent.html",
"guides/your-first-agent.html",
"guides/debugging-agents.html",
@@ -341,16 +307,6 @@ func TestFirstAgentWayfindingLinkTargetsResolve(t *testing.T) {
file: filepath.Join(root, "internal", "website", "docs", "getting-started.md"),
heading: "### First-agent on-ramp",
},
{
name: "website quickstart next steps",
file: filepath.Join(root, "internal", "website", "docs", "quickstart.md"),
heading: "## Next Steps",
},
{
name: "website docs index learn more",
file: filepath.Join(root, "internal", "website", "docs", "index.md"),
heading: "## Learn More",
},
}
for _, check := range checks {
+5 -6
View File
@@ -59,12 +59,11 @@ After this quick start, follow the agent path in order:
2. `micro agent demo` — print the provider-free first-agent demo command and next docs steps from the installed CLI.
3. `micro examples` — print the maintained provider-free runnable examples in copy/paste order.
4. `micro zero-to-hero` — print the maintained one-command no-secret lifecycle harness and runnable examples.
5. [Examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) — choose the smallest no-secret first-agent, maintained [0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support), and next interop examples from one map.
6. [Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent) — run one service-backed agent with a mock model and no provider key.
7. [No-secret first-agent transcript](guides/no-secret-first-agent.html) — run a useful support agent with a mock model before setting up a provider key.
8. [Your First Agent](guides/your-first-agent.html) — build a service-backed agent and talk to it with `micro chat`.
9. [Debugging your agent](guides/debugging-agents.html) — inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent surprises you.
10. [0→hero reference path](guides/zero-to-hero.html) — prove the full scaffold → run → chat → inspect → deploy dry-run lifecycle with commands exercised by `make harness`.
5. [Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent) — run one service-backed agent with a mock model and no provider key.
6. [No-secret first-agent transcript](guides/no-secret-first-agent.html) — run a useful support agent with a mock model before setting up a provider key.
7. [Your First Agent](guides/your-first-agent.html) — build a service-backed agent and talk to it with `micro chat`.
8. [Debugging your agent](guides/debugging-agents.html) — inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent surprises you.
9. [0→hero reference path](guides/zero-to-hero.html) — prove the full scaffold → run → chat → inspect → deploy dry-run lifecycle with commands exercised by `make harness`.
## Write a Service
+1 -3
View File
@@ -16,7 +16,7 @@ It's built on a pluggable architecture of Go interfaces: service discovery, clie
## Learn More
Start with [Getting Started](getting-started.html) for install and the first local service. Then follow the first-agent on-ramp: `micro agent demo` for the installed no-secret CLI affordance, [examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) for the maintained examples map, [the 0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support) for the full no-secret lifecycle example, [No-secret first-agent transcript](guides/no-secret-first-agent.html) to run a mock-model support agent, [Your First Agent](guides/your-first-agent.html) to build and chat with a service-backed agent, [Debugging your agent](guides/debugging-agents.html) to inspect runs and memory, and the [0→hero reference path](guides/zero-to-hero.html) to walk the full scaffold → run → chat → inspect → deploy dry-run lifecycle covered by CI.
Start with [Getting Started](getting-started.html) for install and the first local service. Then follow the first-agent on-ramp: `micro agent demo` for the installed no-secret CLI affordance, `micro examples` for copy/pasteable runnable examples, [No-secret first-agent transcript](guides/no-secret-first-agent.html) to run a mock-model support agent, [Your First Agent](guides/your-first-agent.html) to build and chat with a service-backed agent, [Debugging your agent](guides/debugging-agents.html) to inspect runs and memory, and the [0→hero reference path](guides/zero-to-hero.html) to walk the full scaffold → run → chat → inspect → deploy dry-run lifecycle covered by CI.
Otherwise continue to read the docs for more information about the framework.
@@ -26,8 +26,6 @@ Otherwise continue to read the docs for more information about the framework.
- [0→hero Reference](guides/zero-to-hero.html) - Walk scaffold → run → chat → inspect → deploy dry-run with CI-backed commands
- `micro agent demo` - Show the provider-free first-agent demo command and next docs steps
- `micro examples` - Show provider-free first-agent examples in copy/paste order
- [Examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md) - Choose the first-agent, support, and interop examples from one map
- [0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support) - Run the maintained no-secret services → agents → workflows example
- [No-secret first-agent transcript](guides/no-secret-first-agent.html) - Run the first useful agent path without a provider key
- [Your First Agent](guides/your-first-agent.html) - Build a service-backed agent end to end
- [MCP & AI Agents](mcp.html) - Turn services into AI-callable tools with the Model Context Protocol
+5 -6
View File
@@ -45,12 +45,11 @@ You now have the service half of the services → agents → workflows lifecycle
2. `micro agent demo` - print the provider-free first-agent demo command and the next docs steps from the installed CLI.
3. `micro examples` - print the maintained provider-free runnable examples in copy/paste order.
4. `micro zero-to-hero` - print the maintained one-command no-secret lifecycle harness and runnable examples.
5. **[Examples wayfinding index](https://github.com/micro/go-micro/blob/master/examples/INDEX.md)** - choose the smallest no-secret first-agent, maintained **[0→hero support reference](https://github.com/micro/go-micro/tree/master/examples/support)**, and next interop examples from one map.
6. **[Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent)** - run a mock-model, no-secret agent before adding provider keys.
7. **[No-secret first-agent transcript](guides/no-secret-first-agent.html)** - run a useful support agent with a mock model before setting up a provider key.
8. **[Your First Agent](guides/your-first-agent.html)** - turn this service into an agent-callable tool, chat with it, and learn the `micro agent preflight``micro run``micro chat` loop.
9. **[Debugging your agent](guides/debugging-agents.html)** - inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent does something surprising.
10. **[0→hero Reference](guides/zero-to-hero.html)** - walk the maintained scaffold → run → chat → inspect → deploy dry-run path that proves services, agents, and workflows together.
5. **[Smallest first-agent example](https://github.com/micro/go-micro/tree/master/examples/first-agent)** - run a mock-model, no-secret agent before adding provider keys.
6. **[No-secret first-agent transcript](guides/no-secret-first-agent.html)** - run a useful support agent with a mock model before setting up a provider key.
7. **[Your First Agent](guides/your-first-agent.html)** - turn this service into an agent-callable tool, chat with it, and learn the `micro agent preflight``micro run``micro chat` loop.
8. **[Debugging your agent](guides/debugging-agents.html)** - inspect service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent does something surprising.
9. **[0→hero Reference](guides/zero-to-hero.html)** - walk the maintained scaffold → run → chat → inspect → deploy dry-run path that proves services, agents, and workflows together.
After that first-agent path, branch out to: