Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c84ebf272a | |||
| 29d8544ce5 | |||
| c7d510349e | |||
| 81f81460aa | |||
| ba7db2f315 | |||
| ed3e0e5a06 | |||
| bd433239d7 | |||
| 7b782589d3 | |||
| 06a4375e47 | |||
| 1b371470a9 | |||
| 86ef6232bb |
@@ -21,10 +21,7 @@ changes, architectural rewrites. Those go to the human.
|
||||
|
||||
## Work queue (ranked)
|
||||
|
||||
1. **Prevent checkpointed tool calls from panicking** ([#4594](https://github.com/micro/go-micro/issues/4594)) — Highest-value Now-phase resilience gap: a live AtlasCloud plan/delegate run completed the intended side effects but then crashed inside checkpointed tool handling. The services → agents → workflows lifecycle cannot feel operable if malformed or repaired provider output can SIGSEGV the harness instead of returning a classified error, retrying, or failing safely.
|
||||
2. **Thread first-agent quickcheck through docs wayfinding** ([#4599](https://github.com/micro/go-micro/issues/4599)) — Keep developer adoption weighted alongside hardening. The CLI quickcheck breadcrumbs shipped in #4597, but the README/website first-agent path should make the recovery command discoverable before a new user falls into the full docs tree. This preserves the scaffold → run → chat → inspect on-ramp as a contract, not tribal CLI knowledge.
|
||||
3. **Handle incomplete AtlasCloud agent-flow workspace tool calls** ([#4595](https://github.com/micro/go-micro/issues/4595)) — Next resilience gap from the same live provider-conformance run: the event-driven agent-flow path achieved the expected workspace/notification side effects, then still surfaced a failed flow on an incomplete repaired text tool call. Fix after the panic guard so successful/suppressed side effects do not leave the workflow story looking failed.
|
||||
4. **Make AtlasCloud universe A2A reachability probe deterministic** ([#4504](https://github.com/micro/go-micro/issues/4504)) — The remaining live AtlasCloud interop seam is the universe A2A reachability probe intermittently timing out. Keep it in the queue, but behind the crash/tool-call correctness issues and the adoption quickcheck follow-through, because false-negative reachability matters most once the core live flow exits cleanly.
|
||||
1. **Add a no-secret first-agent chat transcript check** ([#4618](https://github.com/micro/go-micro/issues/4618)) — Developer adoption is now the highest-value open Now-phase item after #4504 shipped in #4621. README, the website, and the v6.3.15 blog point at the provider-free first-agent path, but the 0→1 agent experience still needs an expected chat/tool-call transcript that a new developer can compare against before adding provider keys. Make the smallest first-agent path more walkable and CI-verifiable.
|
||||
|
||||
_Seeded by Claude Code from the roadmap + open issues; thereafter maintained by the
|
||||
architecture-review pass._
|
||||
|
||||
@@ -98,18 +98,20 @@ 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 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](examples/INDEX.md) — choose the smallest no-secret first-agent, maintained [0→hero support reference](examples/support/), and next interop examples from one map.
|
||||
6. [Smallest first-agent example](examples/first-agent/) — run one service-backed agent with a mock model and no provider key.
|
||||
7. [No-secret first-agent transcript](internal/website/docs/guides/no-secret-first-agent.md) — run the
|
||||
3. `micro agent quickcheck` (or `micro agent debug`) — when scaffold → run → chat → inspect stalls, print the short recovery map before you dive into the full debugging guide.
|
||||
4. `micro examples` — print the maintained provider-free runnable examples in copy/paste order.
|
||||
5. `micro zero-to-hero` — print the maintained one-command no-secret lifecycle harness and runnable examples.
|
||||
6. [Examples wayfinding index](examples/INDEX.md) — choose the smallest no-secret first-agent, maintained [0→hero support reference](examples/support/), and next interop examples from one map.
|
||||
7. [Smallest first-agent example](examples/first-agent/) — run one service-backed agent with a mock model and no provider key.
|
||||
8. [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.
|
||||
8. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
|
||||
9. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
|
||||
service-backed agent and talk to it with `micro chat`.
|
||||
9. [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.
|
||||
10. [0→hero Reference](internal/website/docs/guides/zero-to-hero.md) — complete the
|
||||
10. [Debugging your agent](internal/website/docs/guides/debugging-agents.md) — use
|
||||
`micro agent preflight` before `micro run`, `micro agent doctor` after `micro run`,
|
||||
then `micro chat` and `micro inspect agent <name>` to recover run history, memory,
|
||||
and provider checks when the first conversation does something unexpected.
|
||||
11. [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.
|
||||
|
||||
|
||||
+16
-12
@@ -235,28 +235,32 @@ func agentOperationalError(err error) error {
|
||||
|
||||
func (a *agentImpl) checkpointToolWrap(next ai.ToolHandler) ai.ToolHandler {
|
||||
return func(ctx context.Context, call ai.ToolCall) ai.ToolResult {
|
||||
if a.opts.Checkpoint == nil || a.currentRun == nil {
|
||||
run := a.currentRun
|
||||
if a.opts.Checkpoint == nil || run == nil {
|
||||
return next(ctx, call)
|
||||
}
|
||||
name := toolCheckpointName(call)
|
||||
if rec, ok := findStep(a.currentRun.Steps, name); ok && rec.Status == "done" {
|
||||
if rec, ok := findStep(run.Steps, name); ok && rec.Status == "done" {
|
||||
return ai.ToolResult{ID: call.ID, Value: rec.Result, Content: rec.Result}
|
||||
}
|
||||
|
||||
idx := upsertStep(&a.currentRun.Steps, flow.StepRecord{Name: name, Status: "in_progress"})
|
||||
_ = a.saveRun(ctx, *a.currentRun)
|
||||
idx := upsertStep(&run.Steps, flow.StepRecord{Name: name, Status: "in_progress"})
|
||||
_ = a.saveRun(ctx, *run)
|
||||
res := next(ctx, call)
|
||||
a.currentRun.Steps[idx].Attempts++
|
||||
if idx < 0 || idx >= len(run.Steps) || run.Steps[idx].Name != name {
|
||||
idx = upsertStep(&run.Steps, flow.StepRecord{Name: name, Status: "in_progress"})
|
||||
}
|
||||
run.Steps[idx].Attempts++
|
||||
if res.Refused != "" {
|
||||
a.currentRun.Steps[idx].Status = "failed"
|
||||
a.currentRun.Steps[idx].Error = res.Content
|
||||
_ = a.saveRun(ctx, *a.currentRun)
|
||||
run.Steps[idx].Status = "failed"
|
||||
run.Steps[idx].Error = res.Content
|
||||
_ = a.saveRun(ctx, *run)
|
||||
return res
|
||||
}
|
||||
a.currentRun.Steps[idx].Status = "done"
|
||||
a.currentRun.Steps[idx].Result = res.Content
|
||||
a.currentRun.Steps[idx].Error = ""
|
||||
_ = a.saveRun(ctx, *a.currentRun)
|
||||
run.Steps[idx].Status = "done"
|
||||
run.Steps[idx].Result = res.Content
|
||||
run.Steps[idx].Error = ""
|
||||
_ = a.saveRun(ctx, *run)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,45 @@ func TestCheckpointSkipsDuplicateToolWithinAsk(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckpointToolWrapSurvivesClearedCurrentRun(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cp := flow.StoreCheckpoint(store.NewMemoryStore(), "tool-cleared-run-agent")
|
||||
run := flow.Run{
|
||||
ID: "run-1",
|
||||
Flow: "tool-cleared-run-agent",
|
||||
Status: "running",
|
||||
Steps: []flow.StepRecord{{Name: agentAskStep, Status: "in_progress"}},
|
||||
}
|
||||
a := &agentImpl{
|
||||
opts: newOptions(Name("tool-cleared-run-agent"), WithCheckpoint(cp)),
|
||||
currentRun: &run,
|
||||
}
|
||||
|
||||
handler := a.checkpointToolWrap(func(context.Context, ai.ToolCall) ai.ToolResult {
|
||||
a.currentRun = nil
|
||||
return ai.ToolResult{ID: "call-1", Content: "created"}
|
||||
})
|
||||
res := handler(ctx, ai.ToolCall{ID: "call-1", Name: "external.create", Input: map[string]any{"title": "Design"}})
|
||||
if res.Content != "created" {
|
||||
t.Fatalf("tool result = %q, want created", res.Content)
|
||||
}
|
||||
|
||||
loaded, ok, err := cp.Load(ctx, "run-1")
|
||||
if err != nil {
|
||||
t.Fatalf("load checkpoint: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("checkpoint missing")
|
||||
}
|
||||
rec, ok := findStep(loaded.Steps, `tool:external.create:{"title":"Design"}`)
|
||||
if !ok {
|
||||
t.Fatalf("checkpoint steps = %#v, want completed tool step", loaded.Steps)
|
||||
}
|
||||
if rec.Status != "done" || rec.Result != "created" || rec.Attempts != 1 {
|
||||
t.Fatalf("tool checkpoint = %#v, want done result with one attempt", rec)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckpointContinuesRunWithUnfinishedPlanStep(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cp := flow.StoreCheckpoint(store.NewMemoryStore(), "unfinished-plan-agent")
|
||||
|
||||
@@ -554,8 +554,104 @@ func atlascloudFallbackTextToolCall(toolName string, req *ai.Request) string {
|
||||
case "delegate":
|
||||
return atlascloudDelegateFallbackTextToolCall(req)
|
||||
default:
|
||||
if atlascloudToolTakesNoArguments(toolName, req.Tools) {
|
||||
return atlascloudEmptyArgumentFallbackTextToolCall(toolName)
|
||||
}
|
||||
return atlascloudServiceFallbackTextToolCall(toolName, req)
|
||||
}
|
||||
}
|
||||
|
||||
func atlascloudServiceFallbackTextToolCall(toolName string, req *ai.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
for _, tool := range req.Tools {
|
||||
if tool.Name != toolName {
|
||||
continue
|
||||
}
|
||||
args := atlascloudFallbackArgsForProperties(tool.Properties, atlascloudRequestText(req))
|
||||
if args == nil {
|
||||
return ""
|
||||
}
|
||||
b, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return `<tool_call name="` + toolName + `">` + string(b) + `</tool_call>`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func atlascloudFallbackArgsForProperties(properties map[string]any, ctxText string) map[string]any {
|
||||
if len(properties) == 0 {
|
||||
return map[string]any{}
|
||||
}
|
||||
args := make(map[string]any, len(properties))
|
||||
for name, schema := range properties {
|
||||
value, ok := atlascloudFallbackArgValue(name, schema, ctxText)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
args[name] = value
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func atlascloudFallbackArgValue(name string, schema any, ctxText string) (any, bool) {
|
||||
typeName := "string"
|
||||
if m, ok := schema.(map[string]any); ok {
|
||||
if t, _ := m["type"].(string); t != "" {
|
||||
typeName = t
|
||||
}
|
||||
}
|
||||
switch typeName {
|
||||
case "string":
|
||||
return atlascloudFallbackStringArg(name, ctxText)
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func atlascloudFallbackStringArg(name, ctxText string) (string, bool) {
|
||||
ctxText = strings.TrimSpace(ctxText)
|
||||
if ctxText == "" {
|
||||
return "", false
|
||||
}
|
||||
if strings.Contains(strings.ToLower(name), "email") || strings.Contains(strings.ToLower(name), "owner") {
|
||||
if email := atlascloudFirstEmail(ctxText); email != "" {
|
||||
return email, true
|
||||
}
|
||||
}
|
||||
return ctxText, true
|
||||
}
|
||||
|
||||
func atlascloudFirstEmail(text string) string {
|
||||
for _, field := range strings.FieldsFunc(text, func(r rune) bool {
|
||||
return strings.ContainsRune(" \t\n\r<>\"'(),;", r)
|
||||
}) {
|
||||
field = strings.Trim(field, ".:")
|
||||
if strings.Contains(field, "@") && strings.Contains(field, ".") {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func atlascloudToolTakesNoArguments(toolName string, tools []ai.Tool) bool {
|
||||
for _, tool := range tools {
|
||||
if tool.Name != toolName {
|
||||
continue
|
||||
}
|
||||
return len(tool.Properties) == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func atlascloudEmptyArgumentFallbackTextToolCall(toolName string) string {
|
||||
if toolName == "" {
|
||||
return ""
|
||||
}
|
||||
return `<tool_call name="` + toolName + `">{}</tool_call>`
|
||||
}
|
||||
|
||||
func atlascloudPlanFallbackTextToolCall(prompt string) string {
|
||||
|
||||
@@ -628,6 +628,76 @@ func TestProvider_GenerateFallsBackAfterRepeatedPartialDelegateTextToolCall(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateFallsBackAfterRepeatedPartialNoArgumentServiceTextToolCall(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"task_TaskService_List\">"}}]}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
p := NewProvider(
|
||||
ai.WithAPIKey("test-key"),
|
||||
ai.WithBaseURL(ts.URL),
|
||||
ai.WithModel("minimaxai/minimax-m3"),
|
||||
)
|
||||
resp, err := p.Generate(context.Background(), &ai.Request{
|
||||
Prompt: "list the current launch-readiness tasks",
|
||||
Tools: []ai.Tool{{
|
||||
Name: "task_TaskService_List",
|
||||
OriginalName: "task.TaskService.List",
|
||||
Description: "List persisted launch-readiness tasks",
|
||||
Properties: map[string]any{},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
want := `<tool_call name="task_TaskService_List">{}</tool_call>`
|
||||
if resp.Reply != want {
|
||||
t.Fatalf("Reply = %q, want %q", resp.Reply, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateFallsBackAfterRepeatedPartialWorkspaceServiceTextToolCall(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")
|
||||
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"<tool_call name=\"workspace_WorkspaceService_Create\">"}}]}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
p := NewProvider(
|
||||
ai.WithAPIKey("test-key"),
|
||||
ai.WithBaseURL(ts.URL),
|
||||
ai.WithModel("minimaxai/minimax-m3"),
|
||||
)
|
||||
resp, err := p.Generate(context.Background(), &ai.Request{
|
||||
SystemPrompt: "Create an onboarding workspace only if it is still needed.",
|
||||
Prompt: "Onboard alice@acme.com. The workspace create side effect may already be complete; avoid failing the flow on a duplicate repaired call.",
|
||||
Tools: []ai.Tool{{
|
||||
Name: "workspace_WorkspaceService_Create",
|
||||
OriginalName: "workspace.WorkspaceService.Create",
|
||||
Description: "Create an onboarding workspace",
|
||||
Properties: map[string]any{"owner": map[string]any{"type": "string"}},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
want := `<tool_call name="workspace_WorkspaceService_Create">{"owner":"alice@acme.com"}</tool_call>`
|
||||
if resp.Reply != want {
|
||||
t.Fatalf("Reply = %q, want %q", resp.Reply, want)
|
||||
}
|
||||
if len(bodies) != 2 {
|
||||
t.Fatalf("requests = %d, want initial plus repair", len(bodies))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GenerateRetriesMinimaxBuiltInsAsTextTools(t *testing.T) {
|
||||
var bodies []map[string]any
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -82,6 +82,9 @@ const docsWayfinding = `First-agent and 0→hero docs:
|
||||
prove service tools, mock-model chat, and inspectable run history without
|
||||
configuring a provider key.
|
||||
|
||||
If scaffold → run → chat → inspect stalls, print the short recovery map:
|
||||
micro agent quickcheck
|
||||
|
||||
2. No-secret first-agent transcript
|
||||
https://go-micro.dev/docs/guides/no-secret-first-agent.html
|
||||
Run the maintained support agent without a provider key:
|
||||
|
||||
@@ -175,6 +175,7 @@ func TestFirstAgentDocsMatchCLIOutput(t *testing.T) {
|
||||
}
|
||||
agent := commandByName(t, "agent")
|
||||
outputs["micro agent demo"] = commandOutput(t, subcommandByName(t, agent, "demo"))
|
||||
outputs["micro agent quickcheck"] = commandOutput(t, subcommandByName(t, agent, "quickcheck"))
|
||||
|
||||
contracts := []struct {
|
||||
name string
|
||||
@@ -186,6 +187,10 @@ func TestFirstAgentDocsMatchCLIOutput(t *testing.T) {
|
||||
file: filepath.Join(root, "README.md"),
|
||||
markers: []string{
|
||||
"micro agent demo",
|
||||
"micro agent quickcheck",
|
||||
"micro agent preflight",
|
||||
"micro agent doctor",
|
||||
"micro inspect agent <name>",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
"examples/first-agent/",
|
||||
@@ -201,6 +206,10 @@ func TestFirstAgentDocsMatchCLIOutput(t *testing.T) {
|
||||
file: filepath.Join(root, "internal", "website", "docs", "getting-started.md"),
|
||||
markers: []string{
|
||||
"micro agent demo",
|
||||
"micro agent quickcheck",
|
||||
"micro agent preflight",
|
||||
"micro agent doctor",
|
||||
"micro inspect agent <name>",
|
||||
"micro examples",
|
||||
"micro zero-to-hero",
|
||||
"github.com/micro/go-micro/tree/master/examples/first-agent",
|
||||
|
||||
@@ -357,14 +357,55 @@ func check(cond bool, format string, args ...any) {
|
||||
// should not depend on a live model deciding to send another notification.
|
||||
func a2aReachable(ctx context.Context, base, agent string) error {
|
||||
probe := "A2A reachability probe only. Reply with the words concierge reachable. Do not call tools or send notifications."
|
||||
reply, err := a2a.NewClient(base+"/agents/"+agent).Send(ctx, probe)
|
||||
if err != nil {
|
||||
return err
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
deadline = time.Now().Add(10 * time.Second)
|
||||
}
|
||||
if strings.TrimSpace(reply) == "" {
|
||||
return fmt.Errorf("empty A2A reply")
|
||||
|
||||
var lastErr error
|
||||
for attempt := 1; ; attempt++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
if lastErr != nil {
|
||||
return fmt.Errorf("A2A reachability probe failed after %d attempt(s): %w", attempt-1, lastErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
remaining := time.Until(deadline)
|
||||
if remaining <= 0 {
|
||||
if lastErr != nil {
|
||||
return fmt.Errorf("A2A reachability probe failed after %d attempt(s): %w", attempt-1, lastErr)
|
||||
}
|
||||
return context.DeadlineExceeded
|
||||
}
|
||||
|
||||
attemptTimeout := 4 * time.Second
|
||||
if remaining < attemptTimeout {
|
||||
attemptTimeout = remaining
|
||||
}
|
||||
attemptCtx, cancel := context.WithTimeout(ctx, attemptTimeout)
|
||||
reply, err := a2a.NewClient(base+"/agents/"+agent).Send(attemptCtx, probe)
|
||||
cancel()
|
||||
if err == nil && strings.TrimSpace(reply) != "" {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
err = fmt.Errorf("empty A2A reply")
|
||||
}
|
||||
lastErr = err
|
||||
|
||||
if time.Until(deadline) <= 0 {
|
||||
return fmt.Errorf("A2A reachability probe failed after %d attempt(s): %w", attempt, lastErr)
|
||||
}
|
||||
time.Sleep(minDuration(200*time.Millisecond*time.Duration(attempt), time.Until(deadline)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func minDuration(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func providerKey(provider string) string {
|
||||
|
||||
@@ -3,6 +3,9 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -25,6 +28,31 @@ func TestUniverseHarnessContract(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestA2AReachableRetriesTransientTimeout(t *testing.T) {
|
||||
var calls int64
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.URL.Path, "/agents/concierge"; got != want {
|
||||
t.Fatalf("path = %q, want %q", got, want)
|
||||
}
|
||||
if atomic.AddInt64(&calls, 1) == 1 {
|
||||
time.Sleep(5 * time.Second)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"jsonrpc":"2.0","id":1,"result":{"kind":"task","id":"task-1","contextId":"ctx-1","status":{"state":"completed"},"artifacts":[{"artifactId":"artifact-1","parts":[{"kind":"text","text":"concierge reachable"}]}]}}`)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
|
||||
defer cancel()
|
||||
if err := a2aReachable(ctx, srv.URL, "concierge"); err != nil {
|
||||
t.Fatalf("a2aReachable returned error: %v", err)
|
||||
}
|
||||
if got := atomic.LoadInt64(&calls); got < 2 {
|
||||
t.Fatalf("A2A calls = %d, want retry after transient timeout", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifyStepCompletesAfterObservedSideEffectTimeout(t *testing.T) {
|
||||
ntf := new(Notify)
|
||||
before := atomic.LoadInt64(&ntf.sent)
|
||||
|
||||
@@ -57,14 +57,15 @@ 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 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) — use `micro inspect agent <name>` to 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`.
|
||||
3. `micro agent quickcheck` (or `micro agent debug`) — when scaffold → run → chat → inspect stalls, print the short recovery map before you dive into the full debugging guide.
|
||||
4. `micro examples` — print the maintained provider-free runnable examples in copy/paste order.
|
||||
5. `micro zero-to-hero` — print the maintained one-command no-secret lifecycle harness and runnable examples.
|
||||
6. [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.
|
||||
7. [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.
|
||||
8. [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.
|
||||
9. [Your First Agent](guides/your-first-agent.html) — build a service-backed agent and talk to it with `micro chat`.
|
||||
10. [Debugging your agent](guides/debugging-agents.html) — use `micro agent preflight` before `micro run`, `micro agent doctor` after `micro run`, then `micro chat` and `micro inspect agent <name>` to recover service registration, tool calls, run history, memory, provider failures, and flow handoffs when the agent surprises you.
|
||||
11. [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
|
||||
|
||||
|
||||
@@ -24,10 +24,11 @@ Use the recovery command that matches where you are in the first-agent journey:
|
||||
| Checkpoint | When to use it | Command |
|
||||
| --- | --- | --- |
|
||||
| Install troubleshooting | `micro` is not installed, not on `PATH`, or the shell cannot run it. | [Install troubleshooting](install-troubleshooting.html) |
|
||||
| Quick recovery map | The first-agent loop stalled and you want the short scaffold → run → chat → inspect checklist before reading this full guide. | `micro agent quickcheck` (alias: `micro agent debug`) |
|
||||
| Preflight before `micro run` | You have not started the local runtime yet and want to verify Go, CLI, provider-key, and gateway-port prerequisites. | `micro agent preflight` |
|
||||
| Doctor after `micro run` | `micro run` is active, but chat, the `/agent` gateway, agent registration, provider settings, or inspect/run history is not behaving. | `micro agent doctor` |
|
||||
|
||||
`micro agent preflight` is read-only and runs before the first local run; failed
|
||||
`micro agent quickcheck` is the quickest breadcrumb when you are unsure where the first-agent path failed: it prints the preflight, run, doctor, inspect, and no-secret fallback commands in one place. `micro agent preflight` is read-only and runs before the first local run; failed
|
||||
checks include `Fix:` and `Next:` lines for Go, CLI installation, provider-key
|
||||
setup, and the local gateway port. Once `micro run` is already up, switch to
|
||||
`micro agent doctor` so the recovery output follows the live gateway, chat
|
||||
|
||||
Reference in New Issue
Block a user