Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dffa6bc550 |
@@ -21,8 +21,7 @@ changes, architectural rewrites. Those go to the human.
|
||||
|
||||
## Work queue (ranked)
|
||||
|
||||
1. **Make plan-delegate harness complete delegated notification steps** ([#4138](https://github.com/micro/go-micro/issues/4138)) — the duplicate-notification fix and first-agent tutorial harness have both shipped, but the live atlascloud plan-delegate matrix still exposed a higher-severity Now-phase contract break: a delegated notification can succeed or be reused while the persisted plan step remains incomplete. Fixing that keeps the provider conformance evaluator trustworthy without relaxing the services → agents → workflows lifecycle gate.
|
||||
2. **Verify no-secret agent debugging walkthrough** ([#4142](https://github.com/micro/go-micro/issues/4142)) — keep adoption pressure balanced with hardening by extending the maintained 0→1 path past scaffold/run/chat into inspect and debugging. A provider-free smoke check for the documented first-agent debug sequence will catch command drift at the exact seam where new developers need confidence after the first conversation behaves unexpectedly.
|
||||
1. **Add a one-command 0→hero demo entrypoint** ([#4097](https://github.com/micro/go-micro/issues/4097)) — the first-agent, install, debugging, and architecture wayfinding have landed, but the adoption path still asks newcomers to translate docs into several commands before they see the whole services → agents → workflows lifecycle. Add a discoverable `micro` entrypoint that either runs the maintained provider-free 0→hero flow or prints exact commands for it, link it from the first-agent docs, and guard the CLI/docs boundary with focused tests.
|
||||
|
||||
_Seeded by Claude Code from the roadmap + open issues; thereafter maintained by the
|
||||
architecture-review pass._
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
harness-live:
|
||||
name: Provider harnesses (live LLM conformance)
|
||||
runs-on: ubuntu-latest
|
||||
# Only on the hourly schedule or a manual run — never automatically on
|
||||
# Only on the daily schedule or a manual run — never automatically on
|
||||
# every push/PR, so changes don't quietly burn API credits. Trigger it
|
||||
# by hand (Actions → Harness → Run workflow) when changing the agent,
|
||||
# flow, or AI internals and you want a real-model check.
|
||||
|
||||
@@ -16,40 +16,6 @@ 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
|
||||
|
||||
@@ -91,17 +91,16 @@ 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. [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
|
||||
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
|
||||
maintained support agent with a mock model and see services → agents → workflows succeed without a key.
|
||||
7. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
|
||||
6. [Your First Agent](internal/website/docs/guides/your-first-agent.md) — build a
|
||||
service-backed agent and talk to it with `micro chat`.
|
||||
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
|
||||
7. [Debugging your agent](internal/website/docs/guides/debugging-agents.md) — use
|
||||
`micro agent inspect`, run history, memory, and provider checks when the first
|
||||
conversation does something unexpected.
|
||||
9. [0→hero Reference](internal/website/docs/guides/zero-to-hero.md) — complete the
|
||||
8. [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.
|
||||
|
||||
|
||||
+2
-23
@@ -400,7 +400,7 @@ func preserveCompletedPlanSteps(stored string, input map[string]any) map[string]
|
||||
continue
|
||||
}
|
||||
task, _ := step["task"].(string)
|
||||
if completed[planTaskCompletionKey(task)] && isUnfinishedPlanStatus(step["status"]) {
|
||||
if completed[normalizePlanTask(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 = planTaskCompletionKey(task); task != "" {
|
||||
if task = normalizePlanTask(task); task != "" {
|
||||
completed[task] = true
|
||||
}
|
||||
}
|
||||
@@ -434,27 +434,6 @@ 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"
|
||||
|
||||
@@ -79,27 +79,6 @@ 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)
|
||||
|
||||
+2
-3
@@ -66,11 +66,10 @@ provider-free agent path:
|
||||
|
||||
```
|
||||
micro agent demo
|
||||
micro examples
|
||||
```
|
||||
|
||||
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.
|
||||
That points at the smallest mock-model first-agent example and the no-secret
|
||||
transcript before you add provider-backed chat.
|
||||
|
||||
### Output
|
||||
|
||||
|
||||
+2
-40
@@ -46,33 +46,6 @@ 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
|
||||
@@ -98,8 +71,8 @@ const docsWayfinding = `First-agent and 0→hero docs:
|
||||
https://go-micro.dev/docs/guides/debugging-agents.html
|
||||
Inspect agent runs and memory with:
|
||||
micro agent doctor
|
||||
micro inspect agent <name>
|
||||
micro agent history <name>
|
||||
micro inspect agent
|
||||
micro runs <agent>
|
||||
|
||||
5. 0→hero Reference
|
||||
https://go-micro.dev/docs/guides/zero-to-hero.html
|
||||
@@ -177,17 +150,6 @@ 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",
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestPrintNextStepsSurfacesFirstAgentPath(t *testing.T) {
|
||||
"micro agent preflight",
|
||||
"go run .",
|
||||
"micro chat",
|
||||
"micro inspect agent <name>",
|
||||
"micro inspect agent",
|
||||
"micro agent demo",
|
||||
"micro docs",
|
||||
"your-first-agent.html",
|
||||
@@ -84,7 +84,7 @@ func TestPrintNextStepsNoMCPSkipsMCPHints(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
printNextSteps(&out, "worker", true)
|
||||
|
||||
for _, want := range []string{"micro agent preflight", "micro chat", "micro inspect agent <name>", "micro agent demo", "micro docs"} {
|
||||
for _, want := range []string{"micro agent preflight", "micro chat", "micro inspect agent", "micro agent demo", "micro docs"} {
|
||||
if !strings.Contains(out.String(), want) {
|
||||
t.Fatalf("--no-mcp next steps missing %q:\n%s", want, out.String())
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ func printNextSteps(w io.Writer, dir string, noMCP bool) {
|
||||
fmt.Fprintln(w, " micro agent preflight")
|
||||
fmt.Fprintln(w, " go run .")
|
||||
fmt.Fprintln(w, " micro chat")
|
||||
fmt.Fprintln(w, " micro inspect agent <name>")
|
||||
fmt.Fprintln(w, " micro inspect agent")
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, " First-agent path:")
|
||||
fmt.Fprintln(w, " micro agent demo")
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestFirstAgentWalkthroughCLIBoundaries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, want := range []string{"new", "run", "chat", "inspect", "agent", "docs", "examples"} {
|
||||
for _, want := range []string{"new", "run", "chat", "inspect", "agent", "docs"} {
|
||||
if !commands[want] {
|
||||
t.Fatalf("first-agent walkthrough missing %q command", want)
|
||||
}
|
||||
@@ -68,42 +68,12 @@ func TestFirstAgentWalkthroughCLIBoundaries(t *testing.T) {
|
||||
"micro run",
|
||||
"micro chat",
|
||||
"micro agent doctor # after micro run: chat/gateway/inspect recovery",
|
||||
"micro inspect agent <name>",
|
||||
"micro agent history <name>",
|
||||
"micro inspect agent",
|
||||
} {
|
||||
if !strings.Contains(out.String(), want) {
|
||||
t.Fatalf("micro docs output missing %q:\n%s", want, out.String())
|
||||
}
|
||||
}
|
||||
if strings.Contains(out.String(), "micro runs") {
|
||||
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") {
|
||||
|
||||
@@ -43,7 +43,7 @@ contract.
|
||||
|
||||
## Scheduled CI
|
||||
|
||||
The hourly/manual `Harness (E2E)` workflow runs the same matrix with
|
||||
The daily/manual `Harness (E2E)` workflow runs the same matrix with
|
||||
`GO_MICRO_AGENT_CONFORMANCE_LIVE=1` and the provider secrets exported. Providers
|
||||
whose keys are absent still skip cleanly, while any configured provider must pass
|
||||
the shared tool-calling scenario. This keeps scheduled conformance key-gated: PR
|
||||
|
||||
@@ -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 := notifyDedupKey(req.To, req.Message)
|
||||
key := strings.ToLower(strings.TrimSpace(req.To)) + "\x00" + strings.ToLower(strings.TrimSpace(req.Message))
|
||||
s.attempts++
|
||||
if !s.bySend[key] {
|
||||
s.bySend[key] = true
|
||||
@@ -184,25 +184,6 @@ 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
|
||||
@@ -218,10 +199,6 @@ type mockModel struct {
|
||||
// still keeping the regression deterministic and keyless.
|
||||
unknownDelegateOnce bool
|
||||
emittedUnknownDelegate bool
|
||||
|
||||
// duplicateNotify makes the comms mock replay the same notification call.
|
||||
// The notify service should collapse that replay to one durable side effect.
|
||||
duplicateNotify bool
|
||||
}
|
||||
|
||||
func newMock(opts ...ai.Option) ai.Model {
|
||||
@@ -236,12 +213,6 @@ func newMockUnknownDelegate(opts ...ai.Option) ai.Model {
|
||||
return m
|
||||
}
|
||||
|
||||
func newMockDuplicateNotify(opts ...ai.Option) ai.Model {
|
||||
m := &mockModel{duplicateNotify: true}
|
||||
_ = m.Init(opts...)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *mockModel) Init(opts ...ai.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
@@ -283,14 +254,10 @@ func (m *mockModel) Generate(ctx context.Context, req *ai.Request, _ ...ai.Gener
|
||||
// comms agent: owns notify, has Send but not Add.
|
||||
case hasSend && !hasAdd:
|
||||
send := findTool(req.Tools, "Send")
|
||||
input := map[string]any{
|
||||
m.call("comms", send, map[string]any{
|
||||
"to": "owner@acme.com",
|
||||
"message": "The launch plan is ready",
|
||||
}
|
||||
m.call("comms", send, input)
|
||||
if m.duplicateNotify {
|
||||
m.call("comms", send, input)
|
||||
}
|
||||
})
|
||||
return &ai.Response{Answer: "Notified owner@acme.com."}, nil
|
||||
|
||||
// conductor: has the task Add tool — plan, create tasks, delegate.
|
||||
@@ -355,8 +322,6 @@ func runPlanDelegate(provider string) error {
|
||||
ai.Register("mock", newMock)
|
||||
case "mock-unknown-delegate":
|
||||
ai.Register("mock-unknown-delegate", newMockUnknownDelegate)
|
||||
case "mock-duplicate-notify":
|
||||
ai.Register("mock-duplicate-notify", newMockDuplicateNotify)
|
||||
default:
|
||||
apiKey = providerKey(provider)
|
||||
if apiKey == "" {
|
||||
@@ -542,8 +507,8 @@ func waitForPlanDelegateExecution(done <-chan error, taskSvc *TaskService, notif
|
||||
}
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
if notifySvc.count() == 1 {
|
||||
continue
|
||||
if dup := notifySvc.duplicateAttempts(); dup > 0 {
|
||||
return fmt.Errorf("duplicate notify attempts: got %d duplicate replay(s), want 0", dup)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,6 +520,9 @@ func waitForNotifySideEffect(notifySvc *NotifyService, timeout time.Duration) (b
|
||||
if notifySvc.count() == 1 {
|
||||
return true, nil
|
||||
}
|
||||
if dup := notifySvc.duplicateAttempts(); dup > 0 {
|
||||
return false, fmt.Errorf("duplicate notify attempts: got %d duplicate replay(s), want 0", dup)
|
||||
}
|
||||
if !time.Now().Before(deadline) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -572,7 +540,7 @@ func isClientTimeout(err error) bool {
|
||||
}
|
||||
|
||||
func main() {
|
||||
provider := flag.String("provider", "mock", "LLM provider: mock (default), mock-unknown-delegate, mock-duplicate-notify, anthropic, openai, gemini, groq, mistral, together, atlascloud")
|
||||
provider := flag.String("provider", "mock", "LLM provider: mock (default), mock-unknown-delegate, anthropic, openai, gemini, groq, mistral, together, atlascloud")
|
||||
flag.Parse()
|
||||
|
||||
if err := runPlanDelegate(*provider); err != nil {
|
||||
|
||||
@@ -231,15 +231,6 @@ func TestPlanDelegateRetriesAfterUnknownDelegateTool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanDelegateIdempotentDuplicateNotifyReplay(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("0→hero harness boots an end-to-end system; skipped with -short")
|
||||
}
|
||||
if err := runPlanDelegate("mock-duplicate-notify"); err != nil {
|
||||
t.Fatalf("0→hero harness with duplicate notify replay: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskServiceAddIsIdempotentForLaunchTitles(t *testing.T) {
|
||||
svc := new(TaskService)
|
||||
for _, title := range []string{"Design", "design task", "Build", "Build launch task", "Ship", "ship readiness"} {
|
||||
@@ -256,7 +247,7 @@ func TestTaskServiceAddIsIdempotentForLaunchTitles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanDelegateExecutionAcceptsDuplicateNotifyReplay(t *testing.T) {
|
||||
func TestPlanDelegateExecutionReportsDuplicateNotifyBeforeTimeout(t *testing.T) {
|
||||
notifySvc := new(NotifyService)
|
||||
for i := 0; i < 2; i++ {
|
||||
var rsp SendResponse
|
||||
@@ -265,16 +256,20 @@ func TestPlanDelegateExecutionAcceptsDuplicateNotifyReplay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
done <- nil
|
||||
if err := waitForPlanDelegateExecution(done, new(TaskService), notifySvc); err != nil {
|
||||
t.Fatalf("waitForPlanDelegateExecution returned %v, want duplicate replay accepted", err)
|
||||
}
|
||||
if got := notifySvc.count(); got != 1 {
|
||||
t.Fatalf("notify count = %d, want 1 after duplicate replay", got)
|
||||
}
|
||||
if got := notifySvc.duplicateAttempts(); got != 1 {
|
||||
t.Fatalf("duplicate attempts = %d, want 1 recorded replay", got)
|
||||
done := make(chan error)
|
||||
errCh := make(chan error, 1)
|
||||
go func() { errCh <- waitForPlanDelegateExecution(done, new(TaskService), notifySvc) }()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err == nil {
|
||||
t.Fatal("waitForPlanDelegateExecution returned nil, want duplicate notify error")
|
||||
}
|
||||
if got := err.Error(); !strings.Contains(got, "duplicate notify attempts") {
|
||||
t.Fatalf("error = %q, want duplicate notify attempts", got)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("waitForPlanDelegateExecution did not report duplicate notify before timeout")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,14 +417,9 @@ func TestPlanDelegateExecutionClassifiesPartialClientTimeout(t *testing.T) {
|
||||
|
||||
func TestNotifyServiceSendIsIdempotentForDuplicateDelivery(t *testing.T) {
|
||||
svc := new(NotifyService)
|
||||
messages := []string{
|
||||
"The launch plan is ready",
|
||||
"The launch plan is ready.",
|
||||
"Launch readiness: the plan is ready!",
|
||||
}
|
||||
for i, message := range messages {
|
||||
for i := 0; i < 3; i++ {
|
||||
var rsp SendResponse
|
||||
if err := svc.Send(context.Background(), &SendRequest{To: "owner@acme.com", Message: message}, &rsp); err != nil {
|
||||
if err := svc.Send(context.Background(), &SendRequest{To: "owner@acme.com", Message: "The launch plan is ready"}, &rsp); err != nil {
|
||||
t.Fatalf("Send attempt %d: %v", i+1, err)
|
||||
}
|
||||
if !rsp.Sent {
|
||||
@@ -439,7 +429,4 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ go run ./internal/harness/provider-conformance \
|
||||
## Scheduled CI behavior
|
||||
|
||||
The `Harness (E2E)` workflow runs on pushes and pull requests with deterministic
|
||||
mock LLMs, including `provider-conformance -providers mock`. On the hourly
|
||||
mock LLMs, including `provider-conformance -providers mock`. On the daily
|
||||
schedule and manual dispatch it also runs the live provider conformance job. A
|
||||
manual dispatch can narrow `providers` or `harnesses`, and can set
|
||||
`require_configured=true` to fail fast when an expected repository secret is
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHarnessWorkflowSchedulesLiveProviderMatrix(t *testing.T) {
|
||||
path := filepath.Join(repoRoot(), ".github", "workflows", "harness.yml")
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read harness workflow: %v", err)
|
||||
}
|
||||
workflow := string(b)
|
||||
|
||||
checks := []string{
|
||||
`name: Harness (E2E)`,
|
||||
`schedule:`,
|
||||
`cron: "17 * * * *"`,
|
||||
`workflow_dispatch:`,
|
||||
`harness-live:`,
|
||||
`if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'`,
|
||||
`ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}`,
|
||||
`OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}`,
|
||||
`GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}`,
|
||||
`GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}`,
|
||||
`MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}`,
|
||||
`MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}`,
|
||||
`TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}`,
|
||||
`ATLASCLOUD_API_KEY: ${{ secrets.ATLASCLOUD_API_KEY }}`,
|
||||
`-summary-json provider-conformance-summary.json`,
|
||||
`-summary-markdown provider-conformance-summary.md`,
|
||||
`-capabilities-markdown provider-capabilities.md`,
|
||||
`actions/upload-artifact@v4`,
|
||||
}
|
||||
for _, want := range checks {
|
||||
if !strings.Contains(workflow, want) {
|
||||
t.Fatalf("harness workflow missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package zerotoheroci
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -78,76 +77,6 @@ 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"))
|
||||
|
||||
@@ -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|TestYourFirstAgentTutorialSmoke' -count=1
|
||||
go test ./internal/harness/zero-to-hero-ci -run 'TestNoSecretFirstAgentTranscript|TestZeroToHeroReferenceDocs' -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
|
||||
|
||||
@@ -57,13 +57,12 @@ 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. [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`.
|
||||
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`.
|
||||
|
||||
## Write a Service
|
||||
|
||||
|
||||
@@ -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`, 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:
|
||||
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:
|
||||
|
||||
```sh
|
||||
micro agent preflight
|
||||
@@ -177,14 +177,7 @@ 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. 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
|
||||
result. If the model refuses to call tools, tighten the prompt so it explicitly
|
||||
uses the `task` service before answering.
|
||||
|
||||
## 4. Know what just happened
|
||||
|
||||
@@ -19,10 +19,10 @@ cloud credentials?"
|
||||
| --- | --- | --- |
|
||||
| Scaffold | `micro new` generates a runnable service with and without MCP support. | `go test ./cmd/micro/cli/new -run TestZeroToOne -count=1` |
|
||||
| First-agent wayfinding | README and the website getting-started docs keep the no-secret → first-agent → debugging → 0→hero links present and in order. | `go test ./internal/harness/zero-to-hero-ci -run TestFirstAgentWayfindingDocs -count=1` |
|
||||
| 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` |
|
||||
| First agent | `micro new`, `micro agent preflight`, `micro run`, `micro chat`, and `micro inspect agent` 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`, `micro inspect flow`, and `micro flow runs` remain discoverable for run history. | `go test ./cmd/micro -run TestZeroToHeroCLIBoundaries -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` |
|
||||
|
||||
@@ -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, `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.
|
||||
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.
|
||||
|
||||
Otherwise continue to read the docs for more information about the framework.
|
||||
|
||||
@@ -25,7 +25,6 @@ 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
|
||||
|
||||
@@ -43,13 +43,12 @@ 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 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.
|
||||
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.
|
||||
|
||||
After that first-agent path, branch out to:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user