Compare commits

..

17 Commits

Author SHA1 Message Date
Codex 65b86a9274 docs: refresh planner priorities for 4168
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-06 18:51:03 +00:00
Asim Aslam dbce523437 agent: cover durable checkpoint resume smoke (#4167)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 19:29:11 +01:00
Asim Aslam 98125cd770 Stabilize AtlasCloud follow-up tool fallback (#4165)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 18:37:42 +01:00
Asim Aslam 3454a08079 docs: refresh planner priorities for 4160 (#4161)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 18:10:32 +01:00
Asim Aslam decfa7c63e Fix AtlasCloud tool schema normalization (#4159)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 16:58:24 +01:00
Asim Aslam b6980e27a9 docs: refresh planner priorities for 4154 (#4155)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 16:17:30 +01:00
Asim Aslam d92d63943c Add no-secret agent debugging smoke (#4153)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 15:08:34 +01:00
Asim Aslam ec1d46526c docs: refresh planner priorities for 4147 (#4149)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 14:28:46 +01:00
Asim Aslam 40308cf779 Preserve delegated notification plan completion (#4146)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 13:03:39 +01:00
Asim Aslam e198810390 docs: refresh planner priorities for 4141 (#4143)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 12:44:39 +01:00
Asim Aslam 3ec50d1a7d Add first-agent tutorial smoke harness (#4140)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 11:06:12 +01:00
Asim Aslam 327f99cd19 docs: refresh coherence changelog (#4135)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 09:40:01 +01:00
Asim Aslam 498aac59f4 Stabilize duplicate plan-delegate notify replays (#4133)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 09:12:58 +01:00
Asim Aslam 6833b3c73c docs: refresh planner priorities for 4127 (#4131)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 08:46:21 +01:00
Asim Aslam ec27ce2e25 Fix first-agent quickstart numbering (#4125)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 07:18:03 +01:00
Asim Aslam 45cf24162b Add first-agent examples CLI wayfinding (#4124)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 07:17:16 +01:00
Asim Aslam 2778472096 docs: refresh planner priorities for 4121 (#4122)
Co-authored-by: Codex <codex@openai.com>
2026-07-06 06:39:18 +01:00
24 changed files with 613 additions and 55 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ changes, architectural rewrites. Those go to the human.
## Work queue (ranked)
1. **Add CLI examples wayfinding for first-agent paths** ([#4115](https://github.com/micro/go-micro/issues/4115)) — keep developer adoption first while the on-ramp is the explicit gap: make the maintained no-secret first-agent, debugging, and 0→hero examples discoverable from the CLI itself. Align CLI output, README, and website guide references so the scaffold → run → chat → inspect path stays copy/pasteable after install.
2. **Stabilize plan-delegate harness against duplicate delegated notifications** ([#4118](https://github.com/micro/go-micro/issues/4118)) — the scheduled provider-conformance matrix is now in place, and its first live signal exposed an atlascloud/minimax duplicate-notify regression. Preserve the semantic “exactly one launch-readiness notification” contract with focused regression coverage so the live matrix remains a useful evaluator rather than a noisy gate.
1. **Make AtlasCloud delegated notification side effects exact-once** ([#4163](https://github.com/micro/go-micro/issues/4163)) — the prior AtlasCloud schema/fallback fixes closed #4157, but the latest live provider-conformance signal shows a remaining Now-phase reliability seam: the `plan-delegate` harness can produce duplicate delegated `notify` side effects before the exact-once guard fails. Fixing this first keeps the same services → agents → workflows story portable across providers without changing public APIs.
2. **Add `micro loop` quickstart wayfinding to README and website docs** ([#4169](https://github.com/micro/go-micro/issues/4169)) — the blog now says the autonomous loop is shipped and reusable, but the lived developer on-ramp still depends on finding the launch post. Surface `micro loop init` / `micro loop verify`, token setup, branch protection, and CI-as-gate expectations in durable docs so the harness-building-itself story is adoptable, not just announced.
_Seeded by Claude Code from the roadmap + open issues; thereafter maintained by the
architecture-review pass._
+34
View File
@@ -16,6 +16,40 @@ next version when it ships.
## [Unreleased]
### Added
- **First-agent examples CLI wayfinding** — `micro examples` now prints the maintained provider-free first-agent examples in copy/paste order. (`cmd/micro/`)
- **0→hero CLI entrypoint** — `micro zero-to-hero` now points developers at the maintained no-secret services → agents → workflows harness and runnable examples. (`cmd/micro/`)
### Fixed
- **Plan/delegate notify replays** — duplicate and replayed plan-delegate notifications are now idempotent, so resumed runs do not duplicate completed notifications. (`agent/`, `internal/harness/`)
- **Provider conformance scheduling** — provider conformance workflow dispatches now guard their scheduling path more reliably. (`.github/workflows/`)
### Documentation
- **First-agent quickstart numbering** — the first-agent on-ramp numbering is consistent across the README and website docs. (`README.md`, `internal/website/docs/`)
- **First-agent inspect command** — docs now use the maintained `micro inspect agent <name>` form. (`README.md`, `internal/website/docs/`)
---
## [6.3.16] - July 2026
### Added
- **No-secret agent demo CLI** — the CLI now surfaces `micro agent demo`, making the provider-free first-agent path discoverable from the installed binary. (`cmd/micro/`)
- **First-agent recovery doctor** — first-agent recovery checks now help diagnose install, scaffold, and provider setup issues before the live agent run. (`cmd/micro/`, `internal/website/docs/guides/`)
### Changed
- **Architecture lifecycle docs** — the architecture guide now leads with the services → agents → workflows lifecycle and the first-agent on-ramp. (`internal/website/docs/architecture.md`)
- **First-agent on-ramp** — README and website docs now lead new users through install troubleshooting, no-secret demos, the smallest first-agent example, debugging, and the 0→hero reference path in the same order. (`README.md`, `internal/website/docs/`)
### Fixed
- **Config close idempotency** — config close paths now tolerate repeated closes safely. (`config/`)
- **OpenTelemetry child span events** — agent traces now preserve child span events more reliably. (`agent/`)
### Documentation
- **Security reporting** — security docs now route vulnerability reports through GitHub Security Advisories. (`SECURITY.md`, `internal/website/docs/`)
- **Install troubleshooting** — the first-agent on-ramp now includes clearer install and PATH recovery guidance. (`internal/website/docs/guides/install-troubleshooting.md`)
---
## [6.3.15] - July 2026
### Added
+7 -6
View File
@@ -91,16 +91,17 @@ walkable agent path in this order:
1. [Install troubleshooting](internal/website/docs/guides/install-troubleshooting.md) — verify the binary installer or `go install`, `PATH`, `micro --version`, and the no-secret smoke path before agent work.
2. `micro agent demo` — print the provider-free first-agent demo command and next docs steps from the installed CLI.
3. `micro zero-to-hero` — print the maintained one-command no-secret lifecycle harness and runnable examples.
4. [Smallest first-agent example](examples/first-agent/) — run one service-backed agent with a mock model and no provider key.
5. [No-secret first-agent transcript](internal/website/docs/guides/no-secret-first-agent.md) — run the
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. [Smallest first-agent example](examples/first-agent/) — run one service-backed agent with a mock model and no provider key.
6. [No-secret first-agent transcript](internal/website/docs/guides/no-secret-first-agent.md) — run the
maintained support agent with a mock model and see services → agents → workflows succeed without a key.
6. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
7. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
service-backed agent and talk to it with `micro chat`.
7. [Debugging your agent](internal/website/docs/guides/debugging-agents.md) — use
8. [Debugging your agent](internal/website/docs/guides/debugging-agents.md) — use
`micro inspect agent <name>`, run history, memory, and provider checks when the first
conversation does something unexpected.
8. [0→hero Reference](internal/website/docs/guides/zero-to-hero.md) — complete the
9. [0→hero Reference](internal/website/docs/guides/zero-to-hero.md) — complete the
services → agents → workflows loop with scaffold, run, chat, inspect, flow
history, and deploy dry-run commands that match the maintained harness.
+23 -2
View File
@@ -400,7 +400,7 @@ func preserveCompletedPlanSteps(stored string, input map[string]any) map[string]
continue
}
task, _ := step["task"].(string)
if completed[normalizePlanTask(task)] && isUnfinishedPlanStatus(step["status"]) {
if completed[planTaskCompletionKey(task)] && isUnfinishedPlanStatus(step["status"]) {
step["status"] = "done"
}
}
@@ -423,7 +423,7 @@ func completedPlanTasks(plan map[string]any) map[string]bool {
continue
}
task, _ := step["task"].(string)
if task = normalizePlanTask(task); task != "" {
if task = planTaskCompletionKey(task); task != "" {
completed[task] = true
}
}
@@ -434,6 +434,27 @@ func normalizePlanTask(task string) string {
return strings.Join(strings.Fields(strings.ToLower(task)), " ")
}
func planTaskCompletionKey(task string) string {
normalized := normalizePlanTask(task)
if normalized == "" {
return ""
}
if isLaunchReadinessDelegationPlanTask(normalized) {
return "launch-readiness-notification"
}
return normalized
}
func isLaunchReadinessDelegationPlanTask(task string) bool {
task = normalizePlanTask(task)
if !strings.Contains(task, "notify") && !strings.Contains(task, "notification") {
return false
}
hasLaunchReadiness := strings.Contains(task, "launch") || strings.Contains(task, "readiness") || strings.Contains(task, "ready")
hasOwnerComms := strings.Contains(task, "owner") && strings.Contains(task, "comms")
return hasLaunchReadiness || hasOwnerComms
}
func isUnfinishedPlanStatus(status any) bool {
s, _ := status.(string)
return s == "" || s == "pending" || s == "in_progress"
+21
View File
@@ -79,6 +79,27 @@ func TestHandlePlanPreservesCompletedSteps(t *testing.T) {
}
}
func TestHandlePlanPreservesCompletedLaunchReadinessNotification(t *testing.T) {
mem := store.NewMemoryStore()
a := New(Name("planner"), WithStore(mem)).(*agentImpl)
a.handlePlan(ai.ToolCall{Name: toolPlan, Input: map[string]any{
"steps": []any{
map[string]any{"task": "notify owner via comms", "status": "done"},
},
}})
a.handlePlan(ai.ToolCall{Name: toolPlan, Input: map[string]any{
"steps": []any{
map[string]any{"task": "Delegate launch readiness notification for owner@acme.com to comms agent", "status": "in_progress"},
},
}})
if unfinished := a.unfinishedPlanSteps(); len(unfinished) != 0 {
t.Fatalf("unfinished plan steps = %v, want launch readiness notification preserved as done", unfinished)
}
}
func TestPlanShowsInPrompt(t *testing.T) {
mem := store.NewMemoryStore()
a := New(Name("planner"), Prompt("base prompt"), WithStore(mem)).(*agentImpl)
+5 -1
View File
@@ -50,9 +50,13 @@ func (a *agentImpl) saveRun(ctx context.Context, run flow.Run) error {
return fmt.Errorf("agent %s checkpoint save: %w", a.opts.Name, err)
}
if info, ok := ai.RunInfoFrom(ctx); ok {
stage := run.State.Stage
if stage == "" && len(run.Steps) > 0 {
stage = run.Steps[0].Name
}
a.recordTimelineEvent(ctx, RunEvent{
Time: time.Now(), RunID: info.RunID, ParentID: info.ParentID, Agent: info.Agent,
Kind: "checkpoint", Name: run.State.Stage, Status: run.Status,
Kind: "checkpoint", Name: stage, Status: run.Status,
})
}
return nil
+44 -2
View File
@@ -287,7 +287,8 @@ func TestCheckpointContinuesRunThroughSeveralSingleStepTurns(t *testing.T) {
func TestResumeFailedCheckpointAfterFreshAgentRestart(t *testing.T) {
ctx := context.Background()
cp := flow.StoreCheckpoint(store.NewMemoryStore(), "restart-resume-agent")
st := store.NewMemoryStore()
cp := flow.StoreCheckpoint(st, "restart-resume-agent")
toolRuns := 0
modelCalls := 0
failFirst := true
@@ -308,7 +309,7 @@ func TestResumeFailedCheckpointAfterFreshAgentRestart(t *testing.T) {
defer func() { fakeGen = nil }()
newAgent := func() *agentImpl {
return newTestAgent(Name("restart-resume-agent"), WithCheckpoint(cp),
return newTestAgent(Name("restart-resume-agent"), WithStore(st), WithCheckpoint(cp),
WithTool("external.provision", "provision service once", nil, func(context.Context, map[string]any) (string, error) {
toolRuns++
return "provisioned", nil
@@ -330,6 +331,19 @@ func TestResumeFailedCheckpointAfterFreshAgentRestart(t *testing.T) {
if len(runs) != 1 {
t.Fatalf("Pending before restart returned %d runs, want 1", len(runs))
}
summaries, err := ListRunSummaries(st, "restart-resume-agent")
if err != nil {
t.Fatalf("ListRunSummaries before restart: %v", err)
}
if len(summaries) != 1 {
t.Fatalf("run summaries before restart = %d, want 1", len(summaries))
}
if summaries[0].RunID != runs[0].ID || summaries[0].Status != "error" || summaries[0].Checkpoint != "failed" || summaries[0].Stage != agentAskStep {
t.Fatalf("summary before restart = %#v, want failed ask checkpoint for %s", summaries[0], runs[0].ID)
}
if summaries[0].Events < 4 || summaries[0].LastError == "" {
t.Fatalf("summary before restart lacks debug history/error: %#v", summaries[0])
}
restarted := newAgent()
resp, err := Resume(ctx, restarted, runs[0].ID)
@@ -352,6 +366,34 @@ func TestResumeFailedCheckpointAfterFreshAgentRestart(t *testing.T) {
if loaded.Status != "done" || loaded.ParentID != runs[0].ParentID {
t.Fatalf("loaded run status/parent = %s/%s, want done/%s", loaded.Status, loaded.ParentID, runs[0].ParentID)
}
summaries, err = ListRunSummaries(st, "restart-resume-agent")
if err != nil {
t.Fatalf("ListRunSummaries after restart: %v", err)
}
if len(summaries) != 1 {
t.Fatalf("run summaries after restart = %d, want 1", len(summaries))
}
if summaries[0].RunID != runs[0].ID || summaries[0].Status != "done" || summaries[0].Checkpoint != "done" || summaries[0].Stage != agentAskStep {
t.Fatalf("summary after restart = %#v, want done ask checkpoint for %s", summaries[0], runs[0].ID)
}
if summaries[0].Events < 7 {
t.Fatalf("summary after restart recorded %d events, want durable failure/resume/done history", summaries[0].Events)
}
events, err := LoadRunEvents(st, "restart-resume-agent", runs[0].ID)
if err != nil {
t.Fatalf("LoadRunEvents after restart: %v", err)
}
seen := map[string]bool{"run": false, "tool": false, "checkpoint": false, "error": false, "resume": false, "done": false}
for _, e := range events {
if _, ok := seen[e.Kind]; ok {
seen[e.Kind] = true
}
}
for kind, ok := range seen {
if !ok {
t.Fatalf("events after restart missing %s: %#v", kind, events)
}
}
}
func TestResumeFailedCheckpointDoesNotDuplicateCompactedMemory(t *testing.T) {
+1 -1
View File
@@ -624,7 +624,7 @@ func runStatus(events []RunEvent) string {
if e.Error != "" || e.Kind == "error" {
status = runErrorStatus(e.ErrorKind)
}
if e.Kind == "done" && status == "running" {
if e.Kind == "done" {
status = "done"
}
}
+65 -15
View File
@@ -93,20 +93,7 @@ func (p *Provider) Options() ai.Options { return p.opts }
func (p *Provider) String() string { return "atlascloud" }
func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.GenerateOption) (*ai.Response, error) {
var tools []map[string]any
for _, t := range req.Tools {
tools = append(tools, map[string]any{
"type": "function",
"function": map[string]any{
"name": t.Name,
"description": t.Description,
"parameters": map[string]any{
"type": "object",
"properties": t.Properties,
},
},
})
}
tools := atlascloudTools(req.Tools)
messages := []map[string]any{
{"role": "system", "content": req.SystemPrompt},
@@ -191,7 +178,17 @@ func (p *Provider) Generate(ctx context.Context, req *ai.Request, opts ...ai.Gen
resp.ToolCalls = allToolCalls
}
if followUpResp.Reply != "" {
resp.Answer = 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
}
} else if len(toolResults) > 0 {
resp.Answer = strings.Join(toolResults, "\n")
}
@@ -379,6 +376,59 @@ func (p *Provider) callAPI(ctx context.Context, phase string, req map[string]any
return response, rawMessage, nil
}
func atlascloudTools(input []ai.Tool) []map[string]any {
tools := make([]map[string]any, 0, len(input))
for _, t := range input {
tools = append(tools, map[string]any{
"type": "function",
"function": map[string]any{
"name": t.Name,
"description": t.Description,
"parameters": map[string]any{
"type": "object",
"properties": normalizeAtlasCloudSchema(t.Properties),
},
},
})
}
return tools
}
func normalizeAtlasCloudSchema(schema map[string]any) map[string]any {
if schema == nil {
return nil
}
out := make(map[string]any, len(schema))
for k, v := range schema {
out[k] = normalizeAtlasCloudSchemaValue(v)
}
return out
}
func normalizeAtlasCloudSchemaValue(v any) any {
switch val := v.(type) {
case map[string]any:
out := make(map[string]any, len(val)+1)
for k, nested := range val {
out[k] = normalizeAtlasCloudSchemaValue(nested)
}
if typ, _ := out["type"].(string); typ == "array" {
if _, ok := out["items"]; !ok {
out["items"] = map[string]any{}
}
}
return out
case []any:
out := make([]any, len(val))
for i, nested := range val {
out[i] = normalizeAtlasCloudSchemaValue(nested)
}
return out
default:
return v
}
}
func normalizeAtlasCloudToolCalls(toolCalls []atlasToolCall) []map[string]any {
out := make([]map[string]any, 0, len(toolCalls))
for _, tc := range toolCalls {
+100
View File
@@ -289,6 +289,58 @@ func TestProvider_GenerateMinimaxToolRequests(t *testing.T) {
}
}
func TestProvider_GenerateNormalizesBuiltInToolSchemas(t *testing.T) {
var body map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Fatalf("decode request: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"ok"}}]}`))
}))
defer ts.Close()
planProperties := map[string]any{
"steps": map[string]any{
"type": "array",
"description": "ordered plan steps",
},
}
p := NewProvider(
ai.WithAPIKey("test-key"),
ai.WithBaseURL(ts.URL),
ai.WithModel("minimaxai/minimax-m3"),
)
_, err := p.Generate(context.Background(), &ai.Request{
Prompt: "plan and delegate",
Tools: []ai.Tool{
{Name: "task_TaskService_Add", Description: "add task", Properties: map[string]any{"title": map[string]any{"type": "string"}}},
{Name: "plan", Description: "record a plan", Properties: planProperties},
{Name: "request_input", Description: "request input", Properties: map[string]any{"prompt": 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)
}
tools := body["tools"].([]any)
if len(tools) != 4 {
t.Fatalf("tools = %d, want custom tool plus built-ins", len(tools))
}
planTool := tools[1].(map[string]any)
fn := planTool["function"].(map[string]any)
params := fn["parameters"].(map[string]any)
props := params["properties"].(map[string]any)
steps := props["steps"].(map[string]any)
if _, ok := steps["items"].(map[string]any); !ok {
t.Fatalf("plan steps schema = %#v, want array items for AtlasCloud/minimax", steps)
}
if _, mutated := planProperties["steps"].(map[string]any)["items"]; mutated {
t.Fatalf("Generate mutated caller tool schema: %#v", planProperties)
}
}
func TestProvider_GenerateExecutesFollowUpToolCall(t *testing.T) {
var bodies []map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -354,6 +406,54 @@ func TestProvider_GenerateExecutesFollowUpToolCall(t *testing.T) {
}
}
func TestProvider_GeneratePreservesFollowUpTextToolCallInReply(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-1","function":{"name":"conformance_echo","arguments":"{\"value\":\"agent-conformance\"}"}}]}}]}`))
case 2:
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"delegate\">{\"task\":\"summarize the conformance marker\",\"to\":\"blocked-reviewer\"}</tool_call>"}}]}`))
default:
t.Fatalf("unexpected API call %d", len(bodies))
}
}))
defer ts.Close()
p := NewProvider(
ai.WithAPIKey("test-key"),
ai.WithBaseURL(ts.URL),
ai.WithToolHandler(func(ctx context.Context, call ai.ToolCall) ai.ToolResult {
if call.Name != "conformance_echo" {
t.Fatalf("unexpected structured tool call %+v", call)
}
return ai.ToolResult{ID: call.ID, Content: `{"marker":"agent-conformance-ok"}`}
}),
)
resp, err := p.Generate(context.Background(), &ai.Request{
Prompt: "run conformance",
Tools: []ai.Tool{
{Name: "conformance_echo", Description: "echo conformance marker", Properties: map[string]any{"value": 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)
}
if !strings.Contains(resp.Reply, `<tool_call name="delegate">`) {
t.Fatalf("Reply = %q, want tagged delegate follow-up for agent text fallback", resp.Reply)
}
if resp.Answer != "" {
t.Fatalf("Answer = %q, want follow-up text preserved only as Reply", resp.Answer)
}
}
func TestProvider_GenerateToolCallHTTPErrorIncludesRequestContext(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, `{"code":400,"msg":"bad request"}`, http.StatusBadRequest)
+3 -2
View File
@@ -66,10 +66,11 @@ provider-free agent path:
```
micro agent demo
micro examples
```
That points at the smallest mock-model first-agent example and the no-secret
transcript before you add provider-backed chat.
Those commands point at the smallest mock-model first-agent example, the no-secret
transcript, and the support app before you add provider-backed chat.
### Output
+38
View File
@@ -46,6 +46,33 @@ Full local contract:
Guide: https://go-micro.dev/docs/guides/zero-to-hero.html`
const examplesWayfinding = `First-agent examples (no provider key required)
Run these from a go-micro repository checkout in this order:
1. Smallest service-backed agent
go run ./examples/first-agent
Proves an agent can call a service tool with the deterministic mock model.
2. No-secret support-agent transcript
go test ./internal/harness/zero-to-hero-ci -run TestNoSecretFirstAgentTranscript -count=1
Exercises service tools, mock-model chat, and inspectable run history.
3. Full services → agents → workflows reference app
go run ./examples/support
Shows the support desk service, agent, workflow, and approval gate together.
Then continue the same path with the installed CLI:
micro agent demo
micro docs
micro zero-to-hero
Guides:
https://go-micro.dev/docs/guides/no-secret-first-agent.html
https://go-micro.dev/docs/guides/your-first-agent.html
https://go-micro.dev/docs/guides/debugging-agents.html
https://go-micro.dev/docs/guides/zero-to-hero.html`
const docsWayfinding = `First-agent and 0→hero docs:
1. Start with the no-secret CLI demo
@@ -150,6 +177,17 @@ func init() {
return nil
},
},
{
Name: "examples",
Usage: "Show provider-free first-agent example paths",
Description: `Print the maintained no-secret examples for the services → agents →
workflows on-ramp: first-agent, transcript, support app, and matching guides.`,
Action: func(ctx *cli.Context) error {
fmt.Fprintln(ctx.App.Writer, examplesWayfinding)
return nil
},
},
{
Name: "zero-to-hero",
Usage: "Show the no-secret 0→hero lifecycle demo command",
+27 -1
View File
@@ -22,7 +22,7 @@ func TestFirstAgentWalkthroughCLIBoundaries(t *testing.T) {
}
}
for _, want := range []string{"new", "run", "chat", "inspect", "agent", "docs"} {
for _, want := range []string{"new", "run", "chat", "inspect", "agent", "docs", "examples"} {
if !commands[want] {
t.Fatalf("first-agent walkthrough missing %q command", want)
}
@@ -79,6 +79,32 @@ func TestFirstAgentWalkthroughCLIBoundaries(t *testing.T) {
t.Fatalf("micro docs output should use the first-agent inspect command, not the legacy runs shortcut:\n%s", out.String())
}
examples := commandByName(t, "examples")
if !strings.Contains(examples.Usage, "first-agent") {
t.Fatalf("micro examples should advertise the first-agent examples path; usage was %q", examples.Usage)
}
out.Reset()
if err := examples.Action(cli.NewContext(app, nil, nil)); err != nil {
t.Fatalf("micro examples failed: %v", err)
}
for _, want := range []string{
"First-agent examples",
"go run ./examples/first-agent",
"go test ./internal/harness/zero-to-hero-ci -run TestNoSecretFirstAgentTranscript -count=1",
"go run ./examples/support",
"micro agent demo",
"micro docs",
"micro zero-to-hero",
"no-secret-first-agent.html",
"your-first-agent.html",
"debugging-agents.html",
"zero-to-hero.html",
} {
if !strings.Contains(out.String(), want) {
t.Fatalf("micro examples output missing %q:\n%s", want, out.String())
}
}
agent := commandByName(t, "agent")
if !strings.Contains(agent.Usage, "micro agent demo") {
t.Fatalf("micro agent help should advertise the no-secret demo; usage was %q", agent.Usage)
+20 -1
View File
@@ -157,7 +157,7 @@ func (s *NotifyService) Send(ctx context.Context, req *SendRequest, rsp *SendRes
if s.bySend == nil {
s.bySend = map[string]bool{}
}
key := strings.ToLower(strings.TrimSpace(req.To)) + "\x00" + strings.ToLower(strings.TrimSpace(req.Message))
key := notifyDedupKey(req.To, req.Message)
s.attempts++
if !s.bySend[key] {
s.bySend[key] = true
@@ -184,6 +184,25 @@ func (s *NotifyService) duplicateAttempts() int {
return s.duplicates
}
func notifyDedupKey(to, message string) string {
recipient := strings.ToLower(strings.TrimSpace(to))
body := normalizeNotifyText(message)
if recipient == "owner@acme.com" && isLaunchReadinessNotify(body) {
body = "launch-readiness"
}
return recipient + "\x00" + body
}
func normalizeNotifyText(message string) string {
return strings.Join(strings.Fields(strings.ToLower(strings.TrimSpace(message))), " ")
}
func isLaunchReadinessNotify(message string) bool {
return strings.Contains(message, "launch") &&
strings.Contains(message, "plan") &&
(strings.Contains(message, "ready") || strings.Contains(message, "readiness"))
}
// ---------------------------------------------------------------------------
// mock LLM provider — the ONLY fake. It "reasons" by simple heuristics
// over the tools it's offered and the system prompt it's given, calling
+10 -2
View File
@@ -422,9 +422,14 @@ func TestPlanDelegateExecutionClassifiesPartialClientTimeout(t *testing.T) {
func TestNotifyServiceSendIsIdempotentForDuplicateDelivery(t *testing.T) {
svc := new(NotifyService)
for i := 0; i < 3; i++ {
messages := []string{
"The launch plan is ready",
"The launch plan is ready.",
"Launch readiness: the plan is ready!",
}
for i, message := range messages {
var rsp SendResponse
if err := svc.Send(context.Background(), &SendRequest{To: "owner@acme.com", Message: "The launch plan is ready"}, &rsp); err != nil {
if err := svc.Send(context.Background(), &SendRequest{To: "owner@acme.com", Message: message}, &rsp); err != nil {
t.Fatalf("Send attempt %d: %v", i+1, err)
}
if !rsp.Sent {
@@ -434,4 +439,7 @@ func TestNotifyServiceSendIsIdempotentForDuplicateDelivery(t *testing.T) {
if got := svc.count(); got != 1 {
t.Fatalf("notify count = %d, want 1 after duplicate delivery replays", got)
}
if got := svc.duplicateAttempts(); got != len(messages)-1 {
t.Fatalf("duplicate notify attempts = %d, want %d", got, len(messages)-1)
}
}
+5 -3
View File
@@ -11,9 +11,11 @@ scripted so CI can run it on every push without external services or model keys.
documented first-agent walkthrough path.
2. **Run**`micro run` remains available as the local development entry point.
3. **Chat**`micro chat` remains available as the interactive agent entry point.
4. **Inspect**`micro inspect agent <name>` and `micro inspect flow <name>`
remain available as the local run-history inspection step, with `micro flow
runs` preserving durable workflow history inspection.
4. **Inspect/debugging**`micro inspect agent <name>`, `micro agent history <name>`,
and `micro inspect flow <name>` remain available as the local run-history
inspection step. The no-secret debugging smoke seeds durable agent run history
and memory, then runs the documented inspect/history commands without provider
credentials; `micro flow runs` preserves durable workflow history inspection.
5. **Deploy**`micro deploy --dry-run <target>` remains available as the
deployment-boundary checkpoint. The dry run resolves configured deploy targets
and services and prints the remote build/copy/systemd/health plan without
@@ -1,11 +1,17 @@
package zerotoheroci
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
goagent "go-micro.dev/v6/agent"
"go-micro.dev/v6/store"
)
func TestZeroToHeroReferenceDocs(t *testing.T) {
@@ -77,6 +83,76 @@ func TestGuidesNavigationLeadsWithDoing(t *testing.T) {
}
}
func TestYourFirstAgentTutorialSmoke(t *testing.T) {
root := filepath.Clean(filepath.Join("..", "..", ".."))
absRoot, err := filepath.Abs(root)
if err != nil {
t.Fatalf("resolve repository root: %v", err)
}
guide := readFile(t, filepath.Join(root, "internal", "website", "docs", "guides", "your-first-agent.md"))
for _, want := range []string{
"go test ./internal/harness/zero-to-hero-ci -run TestYourFirstAgentTutorialSmoke -count=1",
"micro agent preflight",
"mkdir first-agent",
"go mod init example.com/first-agent",
"go get go-micro.dev/v6@v6",
"micro run",
"micro call task TaskService.Create",
"micro call task TaskService.List",
"micro chat assistant",
"micro inspect agent assistant",
} {
if !strings.Contains(guide, want) {
t.Fatalf("Your First Agent guide missing copy/paste boundary %q", want)
}
}
mainGo := extractFirstAgentMain(t, guide)
workspace := t.TempDir()
writeFile(t, filepath.Join(workspace, "go.mod"), "module example.com/first-agent\n\ngo 1.24\n\nrequire go-micro.dev/v6 v6.0.0\n\nreplace go-micro.dev/v6 => "+absRoot+"\n")
writeFile(t, filepath.Join(workspace, "main.go"), mainGo)
runInWorkspace(t, workspace, "go", "mod", "tidy")
runInWorkspace(t, workspace, "go", "test", "./...")
}
func extractFirstAgentMain(t *testing.T, guide string) string {
t.Helper()
start := strings.Index(guide, "Add `main.go`:")
if start == -1 {
t.Fatal("Your First Agent guide is missing the main.go section")
}
rest := guide[start:]
open := strings.Index(rest, "```go")
if open == -1 {
t.Fatal("Your First Agent guide is missing a Go code fence for main.go")
}
rest = rest[open+len("```go"):]
close := strings.Index(rest, "```")
if close == -1 {
t.Fatal("Your First Agent guide main.go code fence is not closed")
}
return strings.TrimSpace(rest[:close]) + "\n"
}
func writeFile(t *testing.T, name, contents string) {
t.Helper()
if err := os.WriteFile(name, []byte(contents), 0o644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
func runInWorkspace(t *testing.T, workspace, name string, args ...string) {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = workspace
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Your First Agent tutorial command %q does not pass from a clean workspace: %v\n%s", strings.Join(append([]string{name}, args...), " "), err, out)
}
}
func TestArchitectureDocsAlignWithAgentHarnessLifecycle(t *testing.T) {
root := filepath.Clean(filepath.Join("..", "..", ".."))
doc := readFile(t, filepath.Join(root, "internal", "website", "docs", "architecture.md"))
@@ -498,6 +574,110 @@ func TestNoSecretFirstAgentTranscript(t *testing.T) {
}
}
func TestNoSecretFirstAgentDebuggingSmoke(t *testing.T) {
root := filepath.Clean(filepath.Join("..", "..", ".."))
home := t.TempDir()
storeDir := filepath.Join(home, "micro", "store")
st := store.NewFileStore(store.DirOption(storeDir))
seedNoSecretAgentDebuggingState(t, st)
if err := st.Close(); err != nil {
t.Fatalf("close seeded store: %v", err)
}
micro := buildMicroBinary(t, root)
for _, tc := range []struct {
name string
args []string
want []string
}{
{
name: "demo advertises provider-free debug path",
args: []string{"agent", "demo"},
want: []string{"No-secret first-agent demo", "provider-free", "run history", "micro inspect agent <name>"},
},
{
name: "inspect shows seeded run history",
args: []string{"inspect", "agent", "assistant", "--limit", "1"},
want: []string{`Agent "assistant" runs`, "run-debug-smoke", "status=done", "events=3", "last=done", "trace=trace-debug-"},
},
{
name: "inspect filters documented statuses",
args: []string{"inspect", "agent", "--status", "done", "--json", "assistant"},
want: []string{"run-debug-smoke", `"status": "done"`, `"trace_id": "trace-debug-smoke"`},
},
{
name: "agent history shows memory and run index",
args: []string{"agent", "history", "assistant"},
want: []string{"user:", "Triage ticket-1", "assistant:", "ticket-1 is ready", "Runs:", "run-debug-smoke", "status=done"},
},
} {
t.Run(tc.name, func(t *testing.T) {
out := runMicroCLIWithHome(t, micro, home, tc.args...)
for _, want := range tc.want {
if !strings.Contains(out, want) {
t.Fatalf("micro %s output missing %q:\n%s", strings.Join(tc.args, " "), want, out)
}
}
})
}
}
func seedNoSecretAgentDebuggingState(t *testing.T, st store.Store) {
t.Helper()
scoped := store.Scope(st, "agent", "assistant")
runID := "run-debug-smoke"
events := []goagent.RunEvent{
{Time: time.Unix(1700000000, 0), RunID: runID, Agent: "assistant", TraceID: "trace-debug-smoke", Kind: "run", Name: "ask"},
{Time: time.Unix(1700000001, 0), RunID: runID, Agent: "assistant", TraceID: "trace-debug-smoke", Kind: "model", Provider: "mock", Model: "first-agent-mock"},
{Time: time.Unix(1700000002, 0), RunID: runID, Agent: "assistant", TraceID: "trace-debug-smoke", Kind: "done", Name: "answer"},
}
for _, event := range events {
b, err := json.Marshal(event)
if err != nil {
t.Fatal(err)
}
key := "runs/" + event.RunID + "/" + event.Time.Format("20060102150405.000000000") + "-" + event.Kind
if err := scoped.Write(&store.Record{Key: key, Value: b}); err != nil {
t.Fatalf("seed run event: %v", err)
}
}
mem := goagent.NewMemory(scoped, "history", 10)
mem.Add("user", "Triage ticket-1 for Alice")
mem.Add("assistant", "ticket-1 is ready for Alice without provider secrets")
}
func buildMicroBinary(t *testing.T, root string) string {
t.Helper()
bin := filepath.Join(t.TempDir(), "micro")
cmd := exec.Command("go", "build", "-o", bin, "./cmd/micro")
cmd.Dir = root
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("build micro CLI failed: %v\n%s", err, out)
}
return bin
}
func runMicroCLIWithHome(t *testing.T, micro, home string, args ...string) string {
t.Helper()
cmd := exec.Command(micro, args...)
cmd.Env = append(os.Environ(),
"HOME="+home,
"MICRO_AI_API_KEY=",
"OPENAI_API_KEY=",
"ANTHROPIC_API_KEY=",
"GEMINI_API_KEY=",
)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("micro %s failed: %v\n%s", strings.Join(args, " "), err, out)
}
return string(out)
}
func TestFirstAgentWayfindingTargetsExist(t *testing.T) {
root := filepath.Clean(filepath.Join("..", "..", ".."))
for _, target := range []string{
+1 -1
View File
@@ -8,7 +8,7 @@ cd "$ROOT"
# without secrets or long-running daemons.
go test ./cmd/micro -run 'TestFirstAgentWalkthroughCLIBoundaries|TestZeroToHeroCLIBoundaries' -count=1
go test ./cmd/micro/cli/deploy -run TestDeployDryRun -count=1
go test ./internal/harness/zero-to-hero-ci -run 'TestNoSecretFirstAgentTranscript|TestZeroToHeroReferenceDocs' -count=1
go test ./internal/harness/zero-to-hero-ci -run 'TestNoSecretFirstAgentTranscript|TestNoSecretFirstAgentDebuggingSmoke|TestZeroToHeroReferenceDocs|TestYourFirstAgentTutorialSmoke' -count=1
# Deterministic no-secret reference scenarios. These use the real Go Micro
# runtime and mock only the LLM provider. The support example is the maintained
+7 -6
View File
@@ -57,12 +57,13 @@ After this quick start, follow the agent path in order:
1. [Install troubleshooting](guides/install-troubleshooting.html) — verify the CLI install before agent work.
2. `micro agent demo` — print the provider-free first-agent demo command and next docs steps from the installed CLI.
3. `micro zero-to-hero` — print the maintained one-command no-secret lifecycle harness and runnable examples.
4. [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.
5. [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.
6. [Your First Agent](guides/your-first-agent.html) — build a service-backed agent and talk to it with `micro chat`.
7. [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.
8. [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`.
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. [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
@@ -100,6 +100,7 @@ CI keeps those CLI boundaries present with:
```sh
go test ./cmd/micro -run TestFirstAgentWalkthroughCLIBoundaries -count=1
go test ./internal/harness/zero-to-hero-ci -run TestNoSecretFirstAgentDebuggingSmoke -count=1
```
## Debug transcript checkpoint
@@ -47,7 +47,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
Plain service calls work without a model key; the key is only needed when the
agent reasons over tools.
Run the read-only first-agent preflight before starting the walkthrough. The same CLI boundary is covered by CI with `go test ./cmd/micro -run TestFirstAgentWalkthroughCLIBoundaries -count=1`, so the documented scaffold → run → chat → inspect path stays visible in the local harness:
Run the read-only first-agent preflight before starting the walkthrough. The same CLI boundary is covered by CI with `go test ./cmd/micro -run TestFirstAgentWalkthroughCLIBoundaries -count=1`, and the copy/paste tutorial code is built from a clean temporary workspace with `go test ./internal/harness/zero-to-hero-ci -run TestYourFirstAgentTutorialSmoke -count=1`, so the documented scaffold → run → chat → inspect path stays visible in the local harness:
```sh
micro agent preflight
@@ -177,7 +177,14 @@ Create a task called "Review the first-agent walkthrough", then show me all task
```
A healthy run shows the agent calling the task service and then summarizing the
result. If the model refuses to call tools, tighten the prompt so it explicitly
result. Inspect the recorded run when you want to see the tool calls, memory,
and timing behind the answer:
```sh
micro inspect agent assistant
```
If the model refuses to call tools, tighten the prompt so it explicitly
uses the `task` service before answering.
## 4. Know what just happened
+1 -1
View File
@@ -22,7 +22,7 @@ cloud credentials?"
| First agent | `micro new`, `micro agent preflight`, `micro run`, `micro chat`, and `micro inspect agent <name>` stay available for the documented first-agent walkthrough. | `go test ./cmd/micro -run TestFirstAgentWalkthroughCLIBoundaries -count=1` |
| Run | `micro run` remains the local development entry point. | `go test ./cmd/micro -run TestZeroToHeroCLIBoundaries -count=1` |
| Chat | `micro chat` remains the interactive agent entry point. | `go test ./cmd/micro -run TestZeroToHeroCLIBoundaries -count=1` |
| Inspect | `micro inspect agent <name>`, `micro inspect flow <flow>`, and `micro flow runs <flow>` remain discoverable for run history. | `go test ./cmd/micro -run TestZeroToHeroCLIBoundaries -count=1` |
| Inspect | `micro inspect agent <name>`, `micro agent history <name>`, `micro inspect flow <flow>`, and `micro flow runs <flow>` remain discoverable for run history; the no-secret debugging smoke seeds durable agent history and runs the documented inspect/history commands without provider keys. | `go test ./internal/harness/zero-to-hero-ci -run TestNoSecretFirstAgentDebuggingSmoke -count=1` |
| Deploy | `micro deploy --dry-run` resolves deploy targets without touching remote infrastructure. | `go test ./cmd/micro/cli/deploy -run TestDeployDryRun -count=1` |
| Smallest first agent | `examples/first-agent` runs one service-backed agent with a deterministic mock model and no provider key. | `go test ./examples/first-agent -run TestRunFirstAgent -count=1` |
| Runtime reference app | `examples/support` runs typed services, an agent using those services as tools, an event-driven flow handoff, and an approval gate with only the model mocked. | `go test ./examples/support -run 'TestRunSupportMockSmoke|TestZeroToHeroReadmeDocumentsLifecycle' -count=1` |
+2 -1
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, [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.
@@ -25,6 +25,7 @@ Otherwise continue to read the docs for more information about the framework.
- [Getting Started](getting-started.html)
- [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
- [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
+7 -6
View File
@@ -43,12 +43,13 @@ You now have the service half of the services → agents → workflows lifecycle
1. **[Install troubleshooting](guides/install-troubleshooting.html)** - verify the binary installer or `go install`, `PATH`, `micro --version`, and the no-secret smoke path.
2. `micro agent demo` - print the provider-free first-agent demo command and the next docs steps from the installed CLI.
3. `micro zero-to-hero` - print the maintained one-command no-secret lifecycle harness and runnable examples.
4. **[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.
5. **[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.
6. **[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.
7. **[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.
8. **[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.
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. **[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: