{"id":32406961,"url":"https://github.com/goadesign/goa-ai","last_synced_at":"2026-05-26T05:04:44.206Z","repository":{"id":318074802,"uuid":"1034273709","full_name":"goadesign/goa-ai","owner":"goadesign","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-16T20:17:00.000Z","size":5006,"stargazers_count":6,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-16T22:43:57.152Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/goadesign.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-08-08T06:08:04.000Z","updated_at":"2026-05-16T20:17:04.000Z","dependencies_parsed_at":"2025-10-04T23:31:19.109Z","dependency_job_id":"6d8178d2-e963-41e1-814c-8e2a348c69d5","html_url":"https://github.com/goadesign/goa-ai","commit_stats":null,"previous_names":["goadesign/goa-ai"],"tags_count":122,"template":false,"template_full_name":null,"purl":"pkg:github/goadesign/goa-ai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goadesign%2Fgoa-ai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goadesign%2Fgoa-ai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goadesign%2Fgoa-ai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goadesign%2Fgoa-ai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/goadesign","download_url":"https://codeload.github.com/goadesign/goa-ai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/goadesign%2Fgoa-ai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33410346,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T18:09:33.147Z","status":"ssl_error","status_checked_at":"2026-05-23T18:09:31.380Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-25T12:57:48.413Z","updated_at":"2026-05-23T20:03:20.705Z","avatar_url":"https://github.com/goadesign.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://goa.design\"\u003e\n    \u003cimg alt=\"Goa-AI\" src=\"https://raw.githubusercontent.com/goadesign/goa-ai/main/docs/img/goa-ai-banner.png\" width=\"50%\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/goadesign/goa-ai/releases/latest\"\u003e\u003cimg alt=\"Release\" src=\"https://img.shields.io/github/v/release/goadesign/goa-ai?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://goa.design/docs/2-goa-ai/\"\u003e\u003cimg alt=\"Documentation\" src=\"https://img.shields.io/badge/docs-goa.design-blue.svg?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/goa.design/goa-ai\"\u003e\u003cimg alt=\"Go Doc\" src=\"https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/goadesign/goa-ai/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"GitHub Action: CI\" src=\"https://img.shields.io/github/actions/workflow/status/goadesign/goa-ai/ci.yml?branch=main\u0026style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://goreportcard.com/report/goa.design/goa-ai\"\u003e\u003cimg alt=\"Go Report Card\" src=\"https://goreportcard.com/badge/goa.design/goa-ai?style=for-the-badge\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"Software License\" src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg?style=for-the-badge\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eDesign-First Agentic Systems in Go\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eDeclare agents, tools, MCP servers, policies, and structured model output in Goa. Generate the plumbing. Run it durably.\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e |\n  \u003ca href=\"#what-you-can-build\"\u003eWhat You Can Build\u003c/a\u003e |\n  \u003ca href=\"#how-it-works\"\u003eHow It Works\u003c/a\u003e |\n  \u003ca href=\"#production\"\u003eProduction\u003c/a\u003e |\n  \u003ca href=\"#learn-more\"\u003eLearn More\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Why Goa-AI\n\nMost agent frameworks start with code and ask you to keep the contracts in your head: JSON schemas, tool names, retry behavior, model output formats, UI events, and workflow state. Goa-AI starts with the contract.\n\nYou describe the agent system in the same design-first style as Goa services. `goa gen` turns that design into typed Go packages: tool specs, JSON schemas, codecs, workflow registrations, clients, MCP adapters, registry clients, and structured completion helpers. The runtime then executes the generated contracts with policy enforcement, streaming, replayable run logs, and an engine you can swap from in-memory development to Temporal-backed production.\n\n| If you care about... | Goa-AI gives you... |\n| --- | --- |\n| Strong tool contracts | Goa types, validations, examples, generated JSON Schema, generated codecs |\n| Durable agent execution | A plan/execute/resume workflow loop with retries, budgets, cancellation, and Temporal support |\n| Existing service logic | `BindTo` and generated transforms that connect tools to Goa service methods |\n| Structured final answers | Service-owned `Completion(...)` contracts with unary and streaming helpers |\n| Multi-agent systems | First-class agent-as-tool composition with child runs and linked streams |\n| Human approval | Await/clarification flows plus design-time and runtime tool confirmation |\n| Real-time UI | Typed stream events for tool progress, assistant text, usage, awaits, workflow status, and child links |\n| External tools | MCP callers, generated MCP servers, external MCP schemas, and registry-backed discovery |\n| Production operations | Mongo-backed stores, Pulse streaming, OpenAI/Bedrock/Anthropic/gateway clients, telemetry hooks |\n\nGoa-AI is not a prompt wrapper. It is a contract and runtime layer for agentic Go services.\n\n---\n\n## Quick Start\n\nThis path gives you a generated, runnable agent and a typed direct-completion helper. The generated example uses the in-memory engine, so there are no external services required.\n\n### 1. Create a Module\n\n```bash\ngo install goa.design/goa/v3/cmd/goa@latest\n\nmkdir quickstart \u0026\u0026 cd quickstart\ngo mod init example.com/quickstart\ngo get goa.design/goa/v3@latest goa.design/goa-ai@latest\nmkdir design\n```\n\n### 2. Add `design/design.go`\n\n```go\npackage design\n\nimport (\n\t. \"goa.design/goa/v3/dsl\"\n\t. \"goa.design/goa-ai/dsl\"\n)\n\nvar _ = API(\"orchestrator\", func() {})\n\nvar AskPayload = Type(\"AskPayload\", func() {\n\tAttribute(\"question\", String, \"User question to answer\")\n\tExample(map[string]any{\"question\": \"What is the capital of Japan?\"})\n\tRequired(\"question\")\n})\n\nvar Answer = Type(\"Answer\", func() {\n\tAttribute(\"text\", String, \"Answer text\")\n\tExample(map[string]any{\"text\": \"Tokyo is the capital of Japan.\"})\n\tRequired(\"text\")\n})\n\nvar TaskDraft = Type(\"TaskDraft\", func() {\n\tAttribute(\"name\", String, \"Task name\")\n\tAttribute(\"goal\", String, \"Outcome-style goal\")\n\tRequired(\"name\", \"goal\")\n})\n\nvar _ = Service(\"orchestrator\", func() {\n\tCompletion(\"draft_task\", \"Produce a task draft directly\", func() {\n\t\tReturn(TaskDraft)\n\t})\n\n\tAgent(\"chat\", \"Friendly Q\u0026A assistant\", func() {\n\t\tUse(\"helpers\", func() {\n\t\t\tTool(\"answer\", \"Answer a simple question\", func() {\n\t\t\t\tArgs(AskPayload)\n\t\t\t\tReturn(Answer)\n\t\t\t})\n\t\t})\n\t\tRunPolicy(func() {\n\t\t\tDefaultCaps(MaxToolCalls(2), MaxConsecutiveFailedToolCalls(1))\n\t\t\tTimeBudget(\"15s\")\n\t\t})\n\t})\n})\n```\n\n### 3. Generate and Run\n\n```bash\ngoa gen example.com/quickstart/design\ngoa example example.com/quickstart/design\ngo run ./cmd/orchestrator\n```\n\nExpected shape:\n\n```text\nRunID: orchestrator-chat-...\nAssistant: Hello from example planner.\nCompletion draft_task: ...\nCompletion stream draft_task: ...\n```\n\nGeneration creates application-owned scaffolding under `internal/agents/` and generated contract code under `gen/`. Edit the planner and bootstrap files; do not edit `gen/`.\n\n### 4. Run an Agent from Application Code\n\nThe generated agent package exposes a typed client. Sessionful runs require an explicit session; one-shot runs do not.\n\n```go\nrt, cleanup, err := bootstrap.New(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer cleanup()\n\nif _, err := rt.CreateSession(ctx, \"session-1\"); err != nil {\n\tlog.Fatal(err)\n}\n\nclient := chat.NewClient(rt)\nout, err := client.Run(ctx, \"session-1\", []*model.Message{{\n\tRole:  model.ConversationRoleUser,\n\tParts: []model.Part{model.TextPart{Text: \"Hello\"}},\n}})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Println(out.RunID)\n\n// For request/response work that should not belong to a session:\nout, err = client.OneShotRun(ctx, []*model.Message{{\n\tRole:  model.ConversationRoleUser,\n\tParts: []model.Part{model.TextPart{Text: \"Summarize this file\"}},\n}})\n```\n\n### 5. Replace the Stub Planner\n\nPlanners decide what happens next: final response, tool calls, await human input, or terminal tool result. Tool executors decide how work is performed.\n\n```go\nfunc (p *Planner) PlanStart(ctx context.Context, in *planner.PlanInput) (*planner.PlanResult, error) {\n\tmc, ok := in.Agent.PlannerModelClient(\"default\")\n\tif !ok {\n\t\treturn nil, errors.New(\"model client default is not registered\")\n\t}\n\n\tsummary, err := mc.Stream(ctx, \u0026model.Request{\n\t\tMessages: in.Messages,\n\t\tTools:    in.Agent.AdvertisedToolDefinitions(),\n\t\tStream:   true,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(summary.ToolCalls) \u003e 0 {\n\t\treturn \u0026planner.PlanResult{ToolCalls: summary.ToolCalls}, nil\n\t}\n\treturn \u0026planner.PlanResult{\n\t\tFinalResponse: \u0026planner.FinalResponse{\n\t\t\tMessage: \u0026model.Message{\n\t\t\t\tRole:  model.ConversationRoleAssistant,\n\t\t\t\tParts: []model.Part{model.TextPart{Text: summary.Text}},\n\t\t\t},\n\t\t},\n\t\tStreamed: true,\n\t}, nil\n}\n```\n\nRegister model clients during bootstrap with `rt.RegisterModel(...)` or runtime factories such as `rt.NewOpenAIModelClient(...)` and `rt.NewBedrockModelClient(...)`.\n\n---\n\n## How It Works\n\n```text\ndesign/*.go\n  Agents, toolsets, completions, policies, MCP, registries\n      |\n      | goa gen\n      v\ngen/\n  Agent packages, tool specs, codecs, schemas, workflow registrations,\n  typed clients, completion helpers, MCP adapters, registry clients\n      |\n      | runtime.New(...)\n      v\nRuntime\n  Plan -\u003e execute tools -\u003e resume -\u003e finish\n  Policy, memory, streaming, run log, telemetry, engine integration\n      |\n      +-- in-memory engine for development\n      +-- Temporal engine for durable production workers\n```\n\nThe key separation is deliberate:\n\n- The **DSL** owns contracts: names, schemas, validations, examples, tags, policies, confirmation, MCP exposure, and registry sources.\n- **Generated code** owns repetitive infrastructure: JSON codecs, JSON Schema, route metadata, workflow/activity registrations, client helpers, completion helpers, and transforms.\n- The **runtime** owns execution: planner calls, tool admission, policy checks, tool activities, child workflows, awaits, streaming, memory, run logs, and telemetry.\n- **Your code** owns judgment and side effects: planners, service methods, tool executors, model choice, storage, deployment, UI, and product policy.\n\n---\n\n## What You Can Build\n\n### Typed Tools and Toolsets\n\nToolsets are callable capabilities. They can be inline, service-backed, MCP-backed, registry-backed, or implemented by another agent.\n\n```go\nvar Docs = Toolset(\"docs\", func() {\n\tDescription(\"Document retrieval tools\")\n\tTags(\"docs\", \"read\")\n\n\tTool(\"search\", \"Search indexed documents\", func() {\n\t\tArgs(func() {\n\t\t\tAttribute(\"query\", String, \"Search phrase\", func() {\n\t\t\t\tMinLength(1)\n\t\t\t\tMaxLength(500)\n\t\t\t})\n\t\t\tAttribute(\"limit\", Int, \"Maximum results\", func() {\n\t\t\t\tMinimum(1)\n\t\t\t\tMaximum(50)\n\t\t\t\tDefault(10)\n\t\t\t})\n\t\t\tRequired(\"query\")\n\t\t})\n\t\tReturn(ArrayOf(Document))\n\t\tBoundedResult()\n\t\tCallHintTemplate(\"Searching docs for {{ .Query }}\")\n\t})\n})\n```\n\n**What you get:**\n- JSON Schema for LLM function calling (auto-generated)\n- Validation at boundaries: invalid calls get structured retry hints, including\n  generated JSON type mismatch guidance, not crashes or schema-string parsing\n- Type-safe Go structs for payloads and results\n- Explicit control-plane contracts: `Bookkeeping()` keeps tools durable,\n  budget-exempt, and hidden from future planner turns\n\n### Bind Tools to Goa Services\n\nUse `BindTo` when the best tool implementation is already a service method. Use `Inject` for infrastructure fields that should not be model-visible.\n\n```go\nMethod(\"search_documents\", func() {\n\tPayload(func() {\n\t\tAttribute(\"query\", String, \"Search phrase\")\n\t\tAttribute(\"session_id\", String, \"Current session\")\n\t\tRequired(\"query\", \"session_id\")\n\t})\n\tResult(ArrayOf(Document))\n})\n\nAgent(\"chat\", \"Document assistant\", func() {\n\tUse(\"docs\", func() {\n\t\tTool(\"search\", \"Search documents\", func() {\n\t\t\tArgs(func() {\n\t\t\t\tAttribute(\"query\", String, \"Search phrase\")\n\t\t\t\tRequired(\"query\")\n\t\t\t})\n\t\t\tReturn(ArrayOf(Document))\n\t\t\tBindTo(\"search_documents\")\n\t\t\tInject(\"session_id\")\n\t\t})\n\t})\n})\n```\n\nThe generator emits typed transforms where shapes are compatible. Runtime metadata supplies supported injected fields such as `run_id`, `session_id`, `turn_id`, and `tool_call_id`.\n\n### Structured Direct Completions\n\nUse `Completion(...)` when the model should return a typed value directly instead of calling a tool.\n\n```go\nvar Draft = Type(\"Draft\", func() {\n\tAttribute(\"name\", String, \"Task name\")\n\tAttribute(\"goal\", String, \"Outcome-style goal\")\n\tRequired(\"name\", \"goal\")\n})\n\nvar _ = Service(\"tasks\", func() {\n\tCompletion(\"draft_from_transcript\", \"Produce a task draft directly\", func() {\n\t\tReturn(Draft)\n\t})\n})\n```\n\n`goa gen` emits `gen/\u003cservice\u003e/completions/` with schemas, codecs, `completion.Spec` values, `Complete\u003cName\u003e(...)`, `StreamComplete\u003cName\u003e(...)`, and `Decode\u003cName\u003eChunk(...)`. Completion names are part of the contract: 1-64 ASCII characters, letters/digits/`_`/`-`, starting with a letter or digit.\n\nUnary helpers request provider-enforced structured output and decode with generated codecs. Streaming helpers expose preview `completion_delta` chunks but decode only the final canonical `completion` chunk. Providers that cannot preserve the structured-output contract fail explicitly with `model.ErrStructuredOutputUnsupported`.\n\n### Agent-as-Tool Composition\n\nAgents can export toolsets that other agents use. The nested agent runs as a child workflow, not as flattened helper code.\n\n```go\nAgent(\"researcher\", \"Research specialist\", func() {\n\tExport(\"research\", func() {\n\t\tTool(\"deep_search\", \"Perform deep research\", func() {\n\t\t\tArgs(ResearchRequest)\n\t\t\tReturn(ResearchReport)\n\t\t})\n\t})\n})\n\nAgent(\"coordinator\", \"Delegates specialist work\", func() {\n\tUse(AgentToolset(\"orchestrator\", \"researcher\", \"research\"))\n})\n```\n\nParent runs receive a tool result with a child run link. Streams emit `child_run_linked` so UIs can render nested runs without losing identity, logs, or telemetry.\n\n### Runtime Policies, Tags, and Timing\n\nPolicies are runtime-enforced, not planner suggestions.\n\n```go\nAgent(\"operator\", \"Production operations agent\", func() {\n\tRunPolicy(func() {\n\t\tDefaultCaps(MaxToolCalls(20), MaxConsecutiveFailedToolCalls(3))\n\t\tTiming(func() {\n\t\t\tBudget(\"5m\")\n\t\t\tPlan(\"45s\")\n\t\t\tTools(\"90s\")\n\t\t})\n\t\tInterruptsAllowed(true)\n\t\tOnMissingFields(\"await_clarification\")\n\t\tHistory(func() {\n\t\t\tKeepRecentTurns(20)\n\t\t})\n\t\tCache(func() {\n\t\t\tAfterSystem()\n\t\t\tAfterTools()\n\t\t})\n\t})\n})\n```\n\nPer-run options can further restrict execution:\n\n```go\nout, err := client.Run(ctx, \"session-1\", messages,\n\truntime.WithRunTimeBudget(2*time.Minute),\n\truntime.WithRestrictToTool(\"docs.search\"),\n\truntime.WithTagPolicyClauses([]runtime.TagPolicyClause{\n\t\t{AllowedAny: []string{\"read\", \"safe\"}},\n\t\t{DeniedAny: []string{\"destructive\"}},\n\t}),\n)\n```\n\n### Human-in-the-Loop and Confirmation\n\nPlanners can pause for clarification or external tool results. Sensitive tools can require approval before execution.\n\n```go\nTool(\"change_setpoint\", \"Change a device setpoint\", func() {\n\tArgs(ChangeSetpointRequest)\n\tReturn(ChangeSetpointResult)\n\tConfirmation(func() {\n\t\tTitle(\"Confirm setpoint change\")\n\t\tPromptTemplate(\"Set {{ .DeviceID }} to {{ .Value }}?\")\n\t\tDeniedResultTemplate(`{\"status\":\"denied\"}`)\n\t})\n})\n```\n\nAt runtime, the workflow emits an await-confirmation event, waits for `ProvideConfirmation`, records a durable authorization event, and only then executes the tool. Denials produce schema-compliant tool results so planners and transcripts remain deterministic.\n\n### Bounded Results and Server Data\n\nLarge results need two views: a small model-facing view and rich server-side data for UIs or downstream systems.\n\n```go\nTool(\"get_time_series\", \"Get a bounded time-series view\", func() {\n\tArgs(TimeSeriesRequest)\n\tReturn(TimeSeriesSummary)\n\tBoundedResult(func() {\n\t\tCursor(\"cursor\")\n\t\tNextCursor(\"next_cursor\")\n\t})\n\tServerData(\"charts.points\", TimeSeriesPoints, func() {\n\t\tDescription(\"Chart points for observer-facing UI\")\n\t\tAudienceInternal()\n\t\tFromMethodResultField(\"ChartPoints\")\n\t})\n\tServerDataDefault(\"off\")\n})\n```\n\n`BoundedResult` makes truncation explicit through runtime-owned bounds metadata (`returned`, `truncated`, optional `total`, `next_cursor`, and `refinement_hint`). `ServerData` attaches rich data that is never sent to model providers.\n\n### Bookkeeping and Terminal Tools\n\nUse `Bookkeeping()` for control-plane side effects such as status updates, progress snapshots, or terminal commits.\n\n```go\nTool(\"set_step_status\", \"Update task step status\", func() {\n\tArgs(SetStepStatusRequest)\n\tReturn(TaskProgressSnapshot)\n\tBookkeeping()\n})\n\nTool(\"commit_report\", \"Commit final report\", func() {\n\tArgs(CommitReportRequest)\n\tReturn(CommitReportResult)\n\tBookkeeping()\n\tTerminalRun()\n})\n```\n\nBookkeeping tools do not consume the normal `MaxToolCalls` budget. Their events are still durable and streamed. Successful results stay hidden from future planner turns; retryable failures stay visible with their retry hints so the planner can repair the failed call.\n\nThe workflow runtime evaluates one admitted planner result as one step: it executes tool and await work, records durable and planner-facing outputs through one canonical path, then applies one transition policy to resume, finish, or finalize. A terminal payload may only accompany hidden, non-terminal bookkeeping side effects; budgeted tools, retryable bookkeeping failures, terminal tools, and awaits must be separate planner decisions.\n\n---\n\n## Runtime and Observability\n\nEvery run follows the same lifecycle:\n\n```text\nStart -\u003e PlanStart -\u003e execute admitted tools -\u003e PlanResume -\u003e ... -\u003e final response\n                     \\-\u003e await clarification / confirmation / external results\n                     \\-\u003e child workflow for agent-as-tool\n                     \\-\u003e terminal tool result\n```\n\nThe runtime emits typed hook and stream events for:\n\n- run start, phase changes, completion, cancellation, and failure\n- prompt rendering and prompt provenance\n- tool scheduled, updated, completed, failed, and authorized\n- assistant chunks, final messages, planner thoughts, thinking blocks, and token usage\n- awaits for clarification, external tools, and confirmation\n- child run links for agent-as-tool composition\n\nWire a stream sink for real-time UIs:\n\n```go\nrt := runtime.New(\n\truntime.WithStream(mySink),\n\truntime.WithMemoryStore(memoryStore),\n\truntime.WithRunEventStore(runLogStore),\n\truntime.WithLogger(logger),\n\truntime.WithMetrics(metrics),\n\truntime.WithTracer(tracer),\n)\n```\n\nFor model streaming inside planners, choose one style per planner call:\n\n- `PlannerContext.PlannerModelClient(id)` is recommended. It owns assistant/thinking/usage event emission and returns a `planner.StreamSummary`.\n- `PlannerContext.ModelClient(id)` gives you a raw `model.Client`. Pair it with `planner.ConsumeStream` or drain the stream yourself when you need lower-level control.\n\n---\n\n## MCP and Registries\n\n### Consume MCP Servers\n\nUse `FromMCP` for MCP servers declared in the same Goa design. Use `FromExternalMCP` when the server is external and the Goa design owns the local schema contract.\n\n```go\nvar LocalAssistantTools = Toolset(FromMCP(\"assistant\", \"assistant-mcp\"))\n\nvar RemoteSearch = Toolset(\"remote-search\", FromExternalMCP(\"remote\", \"search\"), func() {\n\tTool(\"web_search\", \"Search the web\", func() {\n\t\tArgs(func() {\n\t\t\tAttribute(\"query\", String, \"Search query\")\n\t\t\tRequired(\"query\")\n\t\t})\n\t\tReturn(func() {\n\t\t\tAttribute(\"results\", ArrayOf(String), \"Search results\")\n\t\t\tRequired(\"results\")\n\t\t})\n\t})\n})\n\nAgent(\"chat\", \"MCP-enabled assistant\", func() {\n\tUse(LocalAssistantTools)\n\tUse(RemoteSearch)\n})\n```\n\nRuntime MCP callers support stdio, HTTP, and SSE transports through `runtime/mcp`.\n\n### Expose Goa Services as MCP Servers\n\n```go\nService(\"calculator\", func() {\n\tMCP(\"calc\", \"1.0.0\", ProtocolVersion(\"2025-06-18\"))\n\n\tMethod(\"add\", func() {\n\t\tPayload(func() {\n\t\t\tAttribute(\"a\", Int, \"First number\")\n\t\t\tAttribute(\"b\", Int, \"Second number\")\n\t\t\tRequired(\"a\", \"b\")\n\t\t})\n\t\tResult(func() {\n\t\t\tAttribute(\"sum\", Int, \"Sum\")\n\t\t\tRequired(\"sum\")\n\t\t})\n\t\tTool(\"add\", \"Add two numbers\")\n\t})\n})\n```\n\nThe generated MCP adapter maps Goa methods to JSON-RPC tools, resources, prompts, notifications, subscriptions, and SSE where appropriate.\n\n### Discover Tools Through Registries\n\nFor independently deployed tool providers, declare a registry source and use registry-backed toolsets.\n\n```go\nvar CorpRegistry = Registry(\"corp\", func() {\n\tURL(\"https://registry.corp.internal\")\n\tSecurity(CorpAPIKey)\n\tSyncInterval(\"5m\")\n\tCacheTTL(\"1h\")\n})\n\nvar DataTools = Toolset(FromRegistry(CorpRegistry, \"data-tools\"), func() {\n\tVersion(\"1.2.3\")\n})\n\nAgent(\"analyst\", \"Data analysis agent\", func() {\n\tUse(DataTools)\n})\n```\n\nThere are three registry layers:\n\n- `Registry(...)` and `FromRegistry(...)` in the DSL declare dynamic catalog sources.\n- `gen/\u003cservice\u003e/registry/\u003cname\u003e/` contains generated agent-side registry clients and helpers.\n- `runtime/toolregistry` and `registry/` provide the Pulse wire protocol and clustered gateway for health-monitored cross-process invocation.\n\nGenerated `registry.go` files in agent packages are local runtime registration helpers; they are not the clustered registry service.\n\n---\n\n## Production\n\nStart simple with `runtime.New()`. Move to production by adding durable execution, persistent stores, model providers, stream delivery, policy, and telemetry.\nWhen a tracer is configured, goa-ai emits OpenTelemetry GenAI semantic-convention\nspans for planner-scoped model calls (`chat {model}`), tool calls\n(`execute_tool {tool}`), and agent-as-tool delegation (`invoke_agent {agent}`).\nThese spans carry conversation ID, agent identity, model request/response\nfields, token usage, finish reasons, and streaming time-to-first-chunk where\navailable. Prompt text, chat history, tool arguments, and tool results are not\nrecorded by default.\n\n```go\neng, err := temporal.NewWorker(temporal.Options{\n\tClientOptions: \u0026client.Options{\n\t\tHostPort:  \"temporal:7233\",\n\t\tNamespace: \"default\",\n\t},\n\tWorkerOptions: temporal.WorkerOptions{\n\t\tTaskQueue: \"orchestrator_chat_workflow\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer eng.Close()\n\nrt := runtime.New(\n\truntime.WithEngine(eng),\n\truntime.WithMemoryStore(memoryStore),\n\truntime.WithSessionStore(sessionStore),\n\truntime.WithRunEventStore(runLogStore),\n\truntime.WithPromptStore(promptStore),\n\truntime.WithStream(streamSink),\n\truntime.WithPolicy(policyEngine),\n\truntime.WithLogger(logger),\n\truntime.WithMetrics(metrics),\n\truntime.WithTracer(tracer),\n)\n\nmodelClient, err := rt.NewOpenAIModelClient(runtime.OpenAIConfig{\n\tAPIKey:       os.Getenv(\"OPENAI_API_KEY\"),\n\tDefaultModel: \"gpt-5-mini\",\n\tHighModel:    \"gpt-5\",\n\tSmallModel:   \"gpt-5-nano\",\n\tMaxTokens:    4096,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nif err := rt.RegisterModel(\"default\", modelClient); err != nil {\n\tlog.Fatal(err)\n}\n\nif err := chat.RegisterUsedToolsets(ctx, rt, chat.WithHelpersExecutor(helperExec)); err != nil {\n\tlog.Fatal(err)\n}\nif err := chat.RegisterChatAgent(ctx, rt, chat.ChatAgentConfig{Planner: chatPlanner}); err != nil {\n\tlog.Fatal(err)\n}\n\nsealCtx, cancel := context.WithTimeout(ctx, 90*time.Second)\ndefer cancel()\nif err := rt.Seal(sealCtx); err != nil {\n\tlog.Fatal(err)\n}\n```\n\nProduction checklist:\n\n- Keep all model-facing schemas in the DSL. Regenerate instead of hand-editing `gen/`.\n- Register models, toolsets, agents, stores, streams, policy, and telemetry before the first run.\n- Call `rt.Seal(ctx)` for worker processes before serving traffic; Temporal workers start at the seal boundary.\n- Use `CreateSession` before sessionful `Run`/`Start`, or use `OneShotRun`/`StartOneShot` for sessionless work.\n- Use persistent stores for transcripts, sessions, prompt overrides, and run logs when runs must survive process restarts.\n- Use stream events rather than polling for UI updates.\n- Put irreversible or operator-sensitive actions behind `Confirmation(...)`.\n- Use `BoundedResult()` and `ServerData(...)` for large data so models see bounded summaries while UIs retain full-fidelity data.\n\n---\n\n## Generated Layout\n\n| Path | What it contains |\n| --- | --- |\n| `gen/\u003cservice\u003e/agents/\u003cagent\u003e/` | Agent ID, route, typed client, workflow/activity names, registration helpers |\n| `gen/\u003cservice\u003e/agents/\u003cagent\u003e/specs/` | Aggregated agent tool catalog and `tool_schemas.json` |\n| `gen/\u003cservice\u003e/toolsets/\u003ctoolset\u003e/` | Tool payload/result/server-data types, codecs, specs, transforms, provider adapters |\n| `gen/\u003cservice\u003e/completions/` | Service-owned structured-output specs, codecs, unary and streaming helpers |\n| `gen/\u003cservice\u003e/registry/\u003cname\u003e/` | Generated registry client and discovery helpers |\n| `gen/mcp_\u003cservice\u003e/` | Generated MCP adapter code for services that declare `MCP(...)` |\n| `internal/agents/` | Application-owned scaffold from `goa example`: bootstrap, planner stubs, tool adapters |\n| `AGENTS_QUICKSTART.md` | Contextual generated wiring guide for the module |\n\n---\n\n## Feature Packages\n\n| Package | Purpose |\n| --- | --- |\n| `runtime/agent/runtime` | Runtime, clients, run options, policy overrides, stores, registration |\n| `runtime/agent/planner` | Planner interfaces, plan results, tool requests, streaming helpers |\n| `runtime/agent/model` | Provider-neutral model client, messages, tool definitions, streaming chunks |\n| `runtime/agent/engine/inmem` | In-memory development engine |\n| `runtime/agent/engine/temporal` | Temporal worker/client engine |\n| `runtime/mcp` | MCP callers for stdio, HTTP, and SSE |\n| `runtime/toolregistry` | Registry wire protocol, executor, provider support, schema validation |\n| `features/model/openai` | OpenAI Responses API adapter |\n| `features/model/bedrock` | AWS Bedrock adapter, including visible Claude thinking support |\n| `features/model/anthropic` | Direct Anthropic adapter |\n| `features/model/gateway` | Remote model gateway client |\n| `features/model/middleware` | Rate limiting, logging, metrics middleware |\n| `features/memory/mongo` | Mongo-backed transcript memory store |\n| `features/session/mongo` | Mongo-backed session store |\n| `features/runlog/mongo` | Mongo-backed append-only run event store |\n| `features/prompt/mongo` | Mongo-backed prompt override store |\n| `features/stream/pulse` | Pulse/Redis stream sink and subscribers |\n| `features/policy/basic` | Basic policy engine for tool filtering and caps |\n| `registry` | Clustered registry service for cross-process tool discovery and invocation |\n\n---\n\n## Common Questions\n\n### What should go in the DSL versus application code?\n\nPut stable contracts in the DSL: agent names, tool schemas, validations, completion schemas, policy defaults, tags, confirmation requirements, bounded-result contracts, MCP exposure, and registry sources. Put runtime choices in application code: planner implementation, model provider, stores, streams, telemetry, deployment, per-run overrides, and service logic.\n\n### Do I have to use Temporal?\n\nNo. `runtime.New()` uses the in-memory engine by default and is ideal for local development and tests. Use the Temporal engine when runs must survive worker restarts, support asynchronous coordination, or scale across worker processes.\n\n### How do agents use tools?\n\nPlanners receive `AdvertisedToolDefinitions()` and return `planner.ToolRequest` values. The runtime validates payloads with generated codecs, executes the matching toolset, records the result, and calls `PlanResume` with canonical tool outputs.\n\n### How do I make a long-running UI?\n\nConfigure a stream sink or Pulse runtime streams. Subscribe by session/run, render typed events, and treat `run_stream_end` or terminal `workflow` events as completion markers. Child agents are linked with `child_run_linked` events instead of flattening nested streams.\n\n### How do I avoid huge tool results in prompts?\n\nDeclare `BoundedResult()` and make the service return a bounded semantic result plus `planner.ToolResult.Bounds`. Attach full-fidelity data with `ServerData(...)` when observers need charts, tables, maps, evidence, or downstream attachments.\n\n### How do I expose existing services to external agents?\n\nUse `MCP(...)` on a Goa service and mark methods with `Tool(...)`, `Resource(...)`, prompts, notifications, or subscriptions. Goa-AI generates MCP adapter code while Goa still owns service and transport generation.\n\n---\n\n## Best Practices\n\n- Design first: contracts belong in `design/*.go`; generated code is the artifact, not the source of truth.\n- Add descriptions, examples, and validations. Better schemas make better tool calls and better retry hints.\n- Use generated codecs and clients. Do not hand-encode tool payloads or structured completion results.\n- Keep planners focused on decisions. Service methods and tool executors perform side effects.\n- Use `PlannerModelClient` for streaming unless you need raw stream control.\n- Use tags and policy clauses to narrow tool availability before model prompting and again before execution.\n- Prefer agent-as-tool for specialist delegation when you want isolated runs, linked observability, and durable child workflows.\n- Use confirmations for sensitive tools and bounded/server-data contracts for large or UI-rich results.\n- Regenerate after every DSL change: `goa gen`, then `goa example` when you want scaffold updates.\n\n---\n\n## Requirements\n\n- Go 1.25+ for this repository\n- Goa v3 CLI: `go install goa.design/goa/v3/cmd/goa@latest`\n- Optional for production: Temporal, MongoDB, Redis/Pulse\n\n---\n\n## Learn More\n\n| Resource | Use it for |\n| --- | --- |\n| [`quickstart/README.md`](quickstart/README.md) | Copy-paste runnable project setup |\n| [`docs/overview.md`](docs/overview.md) | Architecture and mental model |\n| [`docs/dsl.md`](docs/dsl.md) | Complete DSL reference and patterns |\n| [`docs/runtime.md`](docs/runtime.md) | Runtime API, planners, engines, stores, streaming, policies |\n| [`DESIGN.md`](DESIGN.md) | Generator design and repository architecture |\n| [Goa-AI docs](https://goa.design/docs/2-goa-ai/) | Published guides |\n| [Go package docs](https://pkg.go.dev/goa.design/goa-ai) | API reference |\n\n---\n\n## Contributing\n\nIssues and PRs are welcome. Include a Goa design, a failing test, or a clear reproduction when reporting behavior. See [`AGENTS.md`](AGENTS.md) for repository guidelines.\n\n## License\n\nMIT License (C) Raphael Simon and the [Goa community](https://goa.design).\n\n\u003cp align=\"center\"\u003e\n  \u003ci\u003eBuild agent systems with contracts you can read, code you can trust, and runtime behavior you can operate.\u003c/i\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoadesign%2Fgoa-ai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoadesign%2Fgoa-ai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoadesign%2Fgoa-ai/lists"}