DSLリファレンス

Goa-AI の DSL 関数(エージェント、ツールセット、ポリシー、MCP 連携)を網羅した完全リファレンス。

このドキュメントは Goa-AI の DSL 関数の完全なリファレンスを提供します。ランタイム ガイドと合わせて読むことで、デザインがランタイムの挙動にどのように変換されるかを理解できます。

Goa-AI は、型付きの直接アシスタント出力のために、サービス所有の Completion(...) コントラクトもサポートします。goa gen を実行すると、 これらのコントラクトは gen/<service>/completions 配下のコードを生成します。

completion 名はコントラクトの一部であり、1-64 文字の ASCII、 英字・数字・_- のみ、先頭は英字または数字でなければなりません。 生成コードには unary Complete<Name>(...) と streaming StreamComplete<Name>(...) / Decode<Name>Chunk(...) が含まれます。 completion_delta はプレビュー専用で、正規の値は最後の 1 つの completion chunk だけです。 生成されたスキーマは正規のサービス契約のままであり、モデル アダプターは制約付きデコードのために provider 固有のサブセットへ 正規化できますが、宣言された契約を表現できない provider は 明示的に拒否しなければなりません。

DSL クイックリファレンス

関数コンテキスト説明
エージェント関数
AgentServiceLLM ベースのエージェントを定義する
CompletionServiceサービス所有の型付き直接アシスタント出力コントラクトを宣言する
UseAgentツールセットの利用(消費)を宣言する
ExportAgent, Service他エージェント向けにツールセットを公開する
AgentToolsetUse 引数他エージェントが公開したツールセットを参照する
UseAgentToolsetAgentAgentToolset + Use のエイリアス
PassthroughTool(Export 内)決定論的にサービスメソッドへフォワードする
DisableAgentDocsAPIAGENTS_QUICKSTART.md の生成を無効にする
ツールセット関数
Toolsetトップレベルプロバイダ所有のツールセットを宣言する
FromMCPToolset 引数MCP バックエンドのツールセットとして構成する
FromRegistryToolset 引数レジストリ由来のツールセットとして構成する
DescriptionToolsetツールセットの説明を設定する
ツール関数
ToolToolset, Method呼び出し可能なツールを定義する
ArgsTool入力パラメータのスキーマを定義する
ReturnTool出力結果のスキーマを定義する
ServerDataToolツール結果に付随するサーバーデータ(モデルには送らない)のスキーマを定義する
FromMethodResultFieldServerDatabound service method result field から server-data を投影する
AudienceTimelineServerDataserver-data を timeline/UI eligible としてマークする (default)
AudienceInternalServerDataserver-data を internal composition attachment としてマークする
AudienceEvidenceServerDataserver-data を provenance または audit evidence としてマークする
BoundedResultTool結果を「境界付きビュー」としてマークし、bounds フィールドの標準形を適用する(任意で cursor 設定のサブ DSL を指定可能)
CursorBoundedResultページング用 cursor を格納する payload フィールド名を指定する(任意)
NextCursorBoundedResult次ページ cursor を格納する result フィールド名を指定する(任意)
IdempotentToolrun transcript 内で idempotent な tool としてマークし、同一呼び出しの de-duplication を可能にする
TagsTool, Toolsetメタデータラベルを付与する
BindToToolツールをサービスメソッドにバインドする
InjectToolランタイム注入フィールドを指定する
CallHintTemplateTool呼び出し表示用テンプレート(ヒント)
ResultHintTemplateTool結果表示用テンプレート(ヒント)
ResultReminderToolツール結果後の静的なシステムリマインダ
ConfirmationTool実行前に明示的な帯域外確認を要求する
TerminalRunToolツールを run の終端としてマーク: ツール成功直後にランが終了し、後続プランナーターンは行われない
BookkeepingToolツールを bookkeeping としてマーク: 呼び出しは run レベルの MaxToolCalls バジェットを消費せず、既定では将来のプランナーターンから隠される
PlannerVisibleTool非終端 bookkeeping 結果を次のプランナーターンに可視化したままにする
ポリシー関数
RunPolicyAgent実行制約を設定する
DefaultCapsRunPolicyリソース制限を設定する
MaxToolCallsDefaultCaps最大ツール呼び出し回数
MaxConsecutiveFailedToolCallsDefaultCaps最大連続失敗回数
TimeBudgetRunPolicy単純なウォールクロック制限
TimingRunPolicy詳細なタイムアウト設定
BudgetTiming実行全体の予算
PlanTimingプランナー(推論)アクティビティのタイムアウト
ToolsTimingツール実行アクティビティのタイムアウト
HistoryRunPolicy会話履歴管理
KeepRecentTurnsHistoryスライディングウィンドウ(直近 N ターン)
CompressHistoryモデル支援による要約圧縮
CacheRunPolicyプロンプトキャッシュ設定
AfterSystemCacheシステムメッセージ後にチェックポイント
AfterToolsCacheツール定義後にチェックポイント
InterruptsAllowedRunPolicy一時停止/再開を有効化
OnMissingFieldsRunPolicy必須フィールド欠落時の扱い
MCP Functions
MCPServiceMCP を有効化する
ProtocolVersionMCP optionMCP プロトコルバージョンを設定する
ToolMethodMCP が有効なサービス内でメソッドを MCP ツールとして扱う
Toolset(FromMCP(...))Top-levelGoa バックエンドの MCP 由来ツールセットを宣言する
Toolset("name", FromExternalMCP(...), func() { ... })Top-levelインラインスキーマ付きの外部 MCP ツールセットを宣言する
ResourceMethodメソッドを MCP リソースとして扱う
WatchableResourceMethodメソッドを購読可能リソースとして扱う
StaticPromptService静的プロンプトテンプレートを追加する
DynamicPromptMethodメソッドをプロンプト生成器として扱う
NotificationMethodメソッドを通知送信器として扱う
SubscriptionMethodメソッドをサブスクリプションハンドラとして扱う
SubscriptionMonitorMethodサブスクリプション監視(SSE)を提供する
レジストリ関数
Registryトップレベルレジストリソースを宣言する
URLRegistryエンドポイントを設定する
APIVersionRegistryAPI バージョンを設定する
TimeoutRegistryHTTP タイムアウトを設定する
RetryRegistryリトライポリシーを設定する
SyncIntervalRegistryカタログ更新間隔を設定する
CacheTTLRegistryローカルキャッシュ有効期間を設定する
FederationRegistry外部レジストリの取り込みを設定する
IncludeFederation取り込み対象グロブ
ExcludeFederation除外対象グロブ
PublishToExportレジストリ公開先を設定する
VersionToolsetレジストリツールセットのバージョンを固定する
スキーマ関数
AttributeArgs, Return, ServerDataスキーマフィールドを定義する(一般)
FieldArgs, Return, ServerDataproto フィールド番号付きで定義する(gRPC)
RequiredSchema必須フィールドを指定する
ExampleSchema明示的な例を付与する。top-level tool payload の例は生成 tool spec と retry hint に保持される

Prompt 管理(v1 統合パス)

Goa-AI v1 では、専用の Prompt DSL(Prompt(...), Prompts(...))は 不要 です。 現在の Prompt 管理はランタイム主導で行います。

  • ベースラインの prompt spec を runtime.PromptRegistry に登録する
  • スコープ付き override を runtime.WithPromptStore(...) で有効化する
  • プランナーから PlannerContext.RenderPrompt(...) で prompt を解決・描画する
  • 描画した prompt の provenance を model.Request.PromptRefs に付与する

agent-as-tool フローでは、agent-tool 登録時に runtime.WithPromptSpec(...) などの ランタイムオプションを使い、tool ID と prompt ID を対応付けます。 これは任意です。consumer 側で prompt コンテンツを設定しない場合、ランタイムは ツールの canonical JSON payload を子 run の user message としてそのまま渡し、 provider 側の planner はサーバー側で注入したコンテキストを使って自分の prompt を 描画できます。

Field と Attribute の違い

FieldAttribute はどちらもスキーマフィールドを定義しますが、用途が異なります。

Attribute(name, type, description, dsl) — 一般目的のスキーマ定義:

  • JSON のみのスキーマで使用する
  • フィールド番号は不要
  • 多くのケースで最も簡潔
Args(func() {
    Attribute("query", String, "Search query")
    Attribute("limit", Int, "Maximum results", func() {
        Default(10)
    })
    Required("query")
})

Field(number, name, type, description, dsl) — gRPC/protobuf 向けの番号付きフィールド:

  • gRPC サービスを生成する場合に必要
  • フィールド番号は一意かつ安定である必要がある
  • サービスが HTTP と gRPC の両トランスポートを公開する場合に使用する
Args(func() {
    Field(1, "query", String, "Search query")
    Field(2, "limit", Int, "Maximum results", func() {
        Default(10)
    })
    Required("query")
})

使い分け:

  • JSON のみのエージェントツールなら Attribute(最も一般的)
  • Goa サービスが gRPC を持ち、ツールがそのメソッドにバインドされる場合は Field
  • 同じスキーマ内での混在は可能ですが、推奨しません

概要

Goa-AI は、エージェント、ツールセット、ランタイムポリシーを宣言するための関数で Goa の DSL を拡張します。DSL は Goa の eval エンジンによって評価されるため、標準のサービス/トランスポート DSL と同じルールが適用されます: 式は適切なコンテキストで呼び出す必要があり、属性定義は Goa の型システム(AttributeField、バリデーション、例など)を再利用します。

インポートパス

Goa デザインパッケージにエージェント DSL を追加します:

import (
    . "goa.design/goa/v3/dsl"
    . "goa.design/goa-ai/dsl"
)

エントリーポイント

通常の Goa の Service 定義の中でエージェントを宣言します。DSL は Goa のデザインツリーを拡張し、goa gen の間に処理されます。

生成結果

goa gen は次を生成します:

  • エージェントパッケージ(gen/<service>/agents/<agent>): ワークフロー定義、プランナーアクティビティ、登録ヘルパー
  • ツールセットのオーナー・パッケージ(gen/<service>/toolsets/<toolset>): 型付きの payload/result 構造体、specs、codecs、(該当する場合)transforms
  • plan/execute/resume ループ用のアクティビティハンドラ
  • デザインをランタイムに配線する登録ヘルパー

DisableAgentDocs() で無効化しない限り、モジュールルートにコンテキスト付きの AGENTS_QUICKSTART.md が書き出されます。

クイックスタート例

package design

import (
    . "goa.design/goa/v3/dsl"
    . "goa.design/goa-ai/dsl"
)

var DocsToolset = Toolset("docs.search", func() {
    Tool("search", "Search indexed documentation", func() {
        Args(func() {
            Attribute("query", String, "Search phrase")
            Attribute("limit", Int, "Max results", func() { Default(5) })
            Required("query")
        })
        Return(func() {
            Attribute("documents", ArrayOf(String), "Matched snippets")
            Required("documents")
        })
        Tags("docs", "search")
    })
})

var AssistantSuite = Toolset(FromMCP("assistant", "assistant-mcp"))

var _ = Service("orchestrator", func() {
    Description("Human front door for the knowledge agent.")

    Agent("chat", "Conversational runner", func() {
        Use(DocsToolset)
        Use(AssistantSuite)
        Export("chat.tools", func() {
            Tool("summarize_status", "Produce operator-ready summaries", func() {
                Args(func() {
                    Attribute("prompt", String, "User instructions")
                    Required("prompt")
                })
                Return(func() {
                    Attribute("summary", String, "Assistant response")
                    Required("summary")
                })
                Tags("chat")
            })
        })
        RunPolicy(func() {
            DefaultCaps(
                MaxToolCalls(8),
                MaxConsecutiveFailedToolCalls(3),
            )
            TimeBudget("2m")
        })
    })
})

goa gen example.com/assistant/design を実行すると、例えば次が生成されます:

  • gen/orchestrator/agents/chat: ワークフロー + プランナーアクティビティ + エージェントレジストリ
  • gen/orchestrator/agents/chat/specs: エージェントのツールカタログ(ToolSpec の集約 + tool_schemas.json
  • gen/orchestrator/toolsets/<toolset>: サービス所有ツールセットの types/specs/codecs/transforms
  • gen/orchestrator/agents/chat/exports/<export>: エクスポートされたツールセット(agent-as-tool)パッケージ
  • Toolset(FromMCP(...))Use で参照された場合の MCP 対応登録ヘルパー

型付きツール識別子

ツールセットごとの specs パッケージは、生成されるすべてのツールについて型付きのツール識別子(tools.Ident)を定義します:

const (
    Search tools.Ident = "orchestrator.search.search"
)

var Specs = []tools.ToolSpec{
    { Name: Search, /* ... */ },
}

ツールを参照する必要がある場所では、これらの定数を使用してください。

サービス所有の型付き Completion

Goa-AI が所有できる構造化 contract は tool だけではありません。assistant が tool call ではなく型付きの最終回答を直接返すべき場合は、Completion(...) を使います:

var Draft = Type("Draft", func() {
    Attribute("name", String, "Task name")
    Attribute("goal", String, "Outcome-style goal")
    Required("name", "goal")
})

var _ = Service("tasks", func() {
    Completion("draft_from_transcript", "Produce a task draft directly", func() {
        Return(Draft)
    })
})

completion 名は structured-output contract の一部です。1-64 文字の ASCII で、英字、数字、_- を含められ、先頭は英字または数字でなければなりません。

goa gengen/<service>/completions 配下に次を出力します:

  • 生成 result schema と型付き Go type
  • 生成 JSON codec と validation helper
  • 型付き completion.Spec
  • 生成 Complete<Name>(ctx, client, req) helper
  • 生成 StreamComplete<Name>(ctx, client, req)Decode<Name>Chunk(...) helper

unary helper は最終 assistant response を直接 decode します。streaming helper は raw model.Streamer surface に留まります。completion_delta chunk は preview 専用で、正規なのは最後の 1 つの completion chunk だけです。Decode<Name>Chunk(...) はその最終 payload だけを decode します。

生成 completion helper は tool-enabled request と caller-supplied StructuredOutput を拒否します。structured output を実装しない provider は model.ErrStructuredOutputUnsupported で明示的に失敗します。生成 schema は正規の service contract のままです。model adapter は provider-specific constrained decoding のために normalize できますが、宣言された contract を表現できない provider は拒否しなければなりません。

クロスプロセスのインライン合成

エージェント A が、エージェント B によってエクスポートされたツールセットを「使用する」と宣言した場合、Goa-AI は合成を自動的に配線します:

  • エクスポータ(エージェント B)側パッケージには、生成された agenttools ヘルパーが含まれる
  • コンシューマ(エージェント A)側のエージェントレジストリは、Use(AgentToolset("service", "agent", "toolset")) を使ったときにそれらのヘルパーを使用する
  • 生成される Execute 関数は、ネストされたプランナーメッセージを構築し、プロバイダーエージェントを子ワークフローとして実行し、ネストされたエージェントの RunOutputplanner.ToolResult に適合させる

これにより、各エージェント実行は単一の決定論的ワークフローとなり、合成のためのリンクされた実行ツリーが得られます。


エージェント関数

Agent

Agent(name, description, dsl)Service の中でエージェントを宣言します。サービススコープのエージェントメタデータを記録し、UseExport によってツールセットを関連付けます。

コンテキスト: Service の内部

各エージェントはランタイム登録として次を持ちます:

  • ワークフロー定義と Temporal アクティビティハンドラ
  • DSL 由来のリトライ/タイムアウトオプションを持つ PlanStart/PlanResume アクティビティ
  • ワークフロー、アクティビティ、ツールセットを登録する Register<Agent> ヘルパー
var _ = Service("orchestrator", func() {
    Agent("chat", "Conversational runner", func() {
        Use(DocsToolset)
        Export("chat.tools", func() {
            // tools defined here
        })
        RunPolicy(func() {
            DefaultCaps(MaxToolCalls(8))
            TimeBudget("2m")
        })
    })
})

Use

Use(value, dsl) は、エージェントがツールセットを消費することを宣言します。ツールセットは次のいずれかです:

  • トップレベルの Toolset 変数
  • Toolset(FromMCP(...)) 参照
  • インラインツールセット定義(文字列名 + DSL)
  • agent-as-tool 合成のための AgentToolset 参照

コンテキスト: Agent の内部

Agent("chat", "Conversational runner", func() {
    // Reference a top-level toolset
    Use(DocsToolset)

    // Reference with subsetting
    Use(CommonTools, func() {
        Tool("notify") // consume only this tool from CommonTools
    })

    // Reference an MCP toolset
    Use(AssistantSuite)

    // Inline agent-local toolset definition
    Use("helpers", func() {
        Tool("answer", "Answer a question", func() {
            // tool definition
        })
    })

    // Agent-as-tool composition
    Use(AgentToolset("service", "agent", "toolset"))
})

Export

Export(value, dsl) は、他エージェントまたは他サービスへ公開するツールセットを宣言します。エクスポートされたツールセットは、他エージェントが Use(AgentToolset(...)) を介して消費できます。

コンテキスト: Agent または Service の内部

Agent("planner", "Planning agent", func() {
    Export("planning.tools", func() {
        Tool("create_plan", "Create a plan", func() {
            Args(func() {
                Attribute("goal", String, "Goal to plan for")
                Required("goal")
            })
            Return(func() {
                Attribute("plan", String, "Generated plan")
                Required("plan")
            })
        })
    })
})

AgentToolset

AgentToolset(service, agent, toolset) は、他エージェントがエクスポートしたツールセットを参照します。これにより agent-as-tool 合成が可能になります。

コンテキスト: Use への引数

AgentToolset を使う場面:

  • エクスポートされたツールセットへの式ハンドルがない
  • 複数エージェントが同名のツールセットをエクスポートしている
  • デザインで明示して可読性を上げたい
// Agent A exports tools
Agent("planner", func() {
    Export("planning.tools", func() { /* tools */ })
})

// Agent B uses Agent A's tools
Agent("orchestrator", func() {
    Use(AgentToolset("service", "planner", "planning.tools"))
})

エイリアス: UseAgentToolset(service, agent, toolset) は、AgentToolsetUse を 1 回の呼び出しにまとめたエイリアスです。新しいデザインでは AgentToolset を優先してください。エイリアスは、一部のコードベースでの可読性のために存在します。

// Equivalent to Use(AgentToolset("service", "planner", "planning.tools"))
Agent("orchestrator", func() {
    UseAgentToolset("service", "planner", "planning.tools")
})

Passthrough

Passthrough(toolName, target, methodName) は、エクスポートされたツールを Goa のサービスメソッドへ決定論的にフォワードする設定です。これはプランナーを完全にバイパスします。

コンテキスト: Export の下にネストされた Tool の内部

Export("logging-tools", func() {
    Tool("log_message", "Log a message", func() {
        Args(func() {
            Attribute("message", String, "Message to log")
            Required("message")
        })
        Return(func() {
            Attribute("logged", Boolean, "Whether the message was logged")
        })
        Passthrough("log_message", "LoggingService", "LogMessage")
    })
})

DisableAgentDocs

DisableAgentDocs() は、モジュールルートに AGENTS_QUICKSTART.md を生成しないようにします。

コンテキスト: API の内部

var _ = API("orchestrator", func() {
    DisableAgentDocs()
})

ツールセット関数

Toolset

Toolset(name, dsl) は、プロバイダ所有のツールセットをトップレベルで宣言します。トップレベルで宣言されたツールセットは再利用可能になり、エージェントは Use で参照します。

コンテキスト: トップレベル

var DocsToolset = Toolset("docs.search", func() {
    Description("Tools for searching documentation")
    Tool("search", "Search indexed documentation", func() {
        Args(func() {
            Attribute("query", String, "Search phrase")
            Required("query")
        })
        Return(func() {
            Attribute("documents", ArrayOf(String), "Matched snippets")
            Required("documents")
        })
    })
})

ツールセットは、標準の Description() DSL 関数を使って説明を含めることもできます:

var DataToolset = Toolset("data-tools", func() {
    Description("Tools for data processing and analysis")
    Tool("analyze", "Analyze dataset", func() {
        Args(func() {
            Attribute("dataset_id", String, "Dataset identifier")
            Required("dataset_id")
        })
        Return(func() {
            Attribute("insights", ArrayOf(String), "Analysis insights")
            Required("insights")
        })
    })
})

Tool

Tool(name, description, dsl) はツールセット内の呼び出し可能なケイパビリティを定義します。

コンテキスト: Toolset または Method の内部

コード生成は次を出力します:

  • tool_specs/types.go の payload/result Go 構造体
  • JSON コーデック(tool_specs/codecs.go
  • プランナーが消費する JSON Schema 定義
  • ヘルパープロンプトとメタデータを含むツールレジストリエントリ
Tool("search", "Search indexed documentation", func() {
    Title("Document Search")
    Args(func() {
        Attribute("query", String, "Search phrase")
        Attribute("limit", Int, "Max results", func() { Default(5) })
        Required("query")
    })
    Return(func() {
        Attribute("documents", ArrayOf(String), "Matched snippets")
        Required("documents")
    })
    CallHintTemplate("Searching for: {{ .Query }}")
    ResultHintTemplate("Found {{ len .Result.Documents }} documents")
    Tags("docs", "search")
})

Args と Return

Args(...)Return(...) は、標準の Goa 属性 DSL を使って payload/result 型を定義します。

コンテキスト: Tool の内部

次のいずれかを使用できます:

  • Attribute() 呼び出しでインラインのオブジェクトスキーマを定義する関数
  • 既存の型定義を再利用する Goa のユーザー型(Type, ResultType, etc.)
  • 単純な単一値入出力のためのプリミティブ型(String, Int など)
Tool("search", "Search documentation", func() {
    Args(func() {
        Attribute("query", String, "Search phrase")
        Attribute("limit", Int, "Max results", func() {
            Default(5)
            Minimum(1)
            Maximum(100)
        })
        Required("query")
    })
    Return(func() {
        Attribute("documents", ArrayOf(String), "Matched snippets")
        Attribute("count", Int, "Number of results")
        Required("documents", "count")
    })
})

型の再利用:

var SearchParams = Type("SearchParams", func() {
    Attribute("query", String)
    Attribute("limit", Int)
    Required("query")
})

Tool("search", "Search documents", func() {
    Args(SearchParams)
    Return(func() {
        Attribute("results", ArrayOf(String))
    })
})

ServerData

ServerData(kind, val, args...) は、tool result に付随して emit される型付き server-only output を定義します。server-data は model provider へは送信されません。 timeline server-data は、model-facing result を bounded で token 効率よく保ちながら、observer-facing UI card、chart、table、map などへ投影されることが多いです。evidence と internal audience により、下流 consumer は kind の命名規約に頼らず provenance や composition-only data を route できます。

コンテキスト: Tool の内部

パラメータ:

  • kind: server-data kind の文字列識別子 (例: "atlas.time_series", "atlas.control_narrative", "aura.evidence")。consumer が異なる server-data projection を識別して扱えるようにします。
  • val: ArgsReturn と同じ pattern に従う schema 定義。Attribute() を持つ関数、Goa user type、primitive type のいずれかです。

Audience routing (Audience*):

ServerData entry は audience を宣言します。下流 consumer は kind の命名規約に頼らず payload を route できます:

  • "timeline": 永続化され、observer-facing projection (UI/timeline card など) の対象になります
  • "internal": tool-composition attachment。永続化も render もしません
  • "evidence": provenance reference。timeline card とは別に永続化されます

ServerData DSL block 内で audience を設定します:

ServerData("atlas.time_series.chart_points", TimeSeriesServerData, func() {
    AudienceInternal()
    FromMethodResultField("chart_sidecar")
})

ServerData("aura.evidence", ArrayOf(Evidence), func() {
    AudienceEvidence()
    FromMethodResultField("evidence")
})

ServerData を使う場面:

  • UI (chart、graph、table) のために full-fidelity data を含めつつ、model payload を bounded に保ちたい場合
  • model context limit を超える大きな result set を attach したい場合
  • model が見る必要はないが、下流 consumer が structured data を必要とする場合
Tool("get_time_series", "Get time series data", func() {
    Args(func() {
        Attribute("device_id", String, "Device identifier")
        Attribute("start_time", String, "Start timestamp (RFC3339)")
        Attribute("end_time", String, "End timestamp (RFC3339)")
        Required("device_id", "start_time", "end_time")
    })
    Return(func() {
        Attribute("summary", String, "Summary for the model")
        Attribute("count", Int, "Number of data points")
        Attribute("min_value", Float64, "Minimum value in range")
        Attribute("max_value", Float64, "Maximum value in range")
        Required("summary", "count")
    })
    ServerData("atlas.time_series", func() {
        Attribute("data_points", ArrayOf(TimeSeriesPoint), "Full time series data")
        Attribute("metadata", MapOf(String, String), "Additional metadata")
        Required("data_points")
    })
})

Goa type を server-data schema に使う例:

var TimeSeriesServerData = Type("TimeSeriesServerData", func() {
    Attribute("data_points", ArrayOf(TimeSeriesPoint), "Full time series data")
    Attribute("unit", String, "Measurement unit")
    Attribute("resolution", String, "Data resolution (e.g., '1m', '5m', '1h')")
    Required("data_points")
})

Tool("get_metrics", "Get device metrics", func() {
    Args(func() {
        Attribute("device_id", String, "Device identifier")
        Required("device_id")
    })
    Return(func() {
        Attribute("summary", String, "Metrics summary for the model")
        Attribute("point_count", Int, "Number of data points")
        Required("summary", "point_count")
    })
    ServerData("atlas.metrics", TimeSeriesServerData)
})

Runtime access:

実行時には、tool が emit した server-data は planner.ToolResult.ServerData に運ばれます。tool が宣言した kind 用の生成 server-data codec で、それらの canonical JSON bytes を decode します:

// In a stream subscriber or result handler
func handleToolResult(result *planner.ToolResult) {
    if len(result.ServerData) > 0 {
        // Decode with the generated server-data codecs for this tool.
    }
}

BoundedResult

BoundedResult() は、現在の tool result が潜在的に大きな data set に対する bounded view であることを示します。runtime-owned bounds contract を宣言しつつ、作成者が定義した result type は semantic で domain-specific なままにできます。

コンテキスト: Tool の内部

正規 model-visible field:

  • returned (required, Int)
  • truncated (required, Boolean)
  • total (optional, Int)
  • refinement_hint (optional, String)
  • next_cursor (optional, NextCursor(...) で宣言した場合は String)

BoundedResult はこの contract の single source of truth です:

  • codegen は生成 tools.ToolSpec.Bounds に記録します
  • codegen は生成 JSON result schema へ正規 bounded field を project します
  • successful bounded execution は planner.ToolResult.Bounds を設定する必要があります
  • runtime はそれらの bounds を encoded tool_result JSON、result-hint template data、hook、stream event へ project します
Tool("list_devices", "List devices with pagination", func() {
    Args(func() {
        Attribute("site_id", String, "Site identifier")
        Attribute("cursor", String, "Opaque pagination cursor")
        Required("site_id")
    })
    Return(func() {
        Attribute("devices", ArrayOf(Device), "List of devices")
        Required("devices")
    })
    BoundedResult(func() {
        Cursor("cursor")
        NextCursor("next_cursor")
    })
})

tool-facing return type は、model に見せるためだけに returnedtotaltruncatedrefinement_hintnext_cursor を宣言してはいけません。semantic result は domain data に集中させてください。method-backed tool は内部的により rich な service method result type を使えますが、Goa-AI tool contract は重複した tool return field ではなく BoundedResult(...) から来ます。bound method result の中で required にできるのは returnedtruncated だけです。totalrefinement_hintnext_cursor は bounds contract の optional part のままです。

Service Responsibility:

service は次を担います:

  1. 独自の truncation logic (pagination、limit、depth cap) を適用する
  2. planner.ToolResult.Bounds を populate する
  3. 別ページがある場合に Bounds.NextCursor を設定する
  4. result が truncated のとき、任意で RefinementHint を提供する

runtime は subset や truncation を計算しません。tool が報告した bounds metadata を validate/project するだけです。

BoundedResult を使う場面:

  • paginated list (device、user、record) を返す tool
  • result limit 付きで large dataset を query する tool
  • nested structure に depth/size cap を適用する tool
  • result が incomplete かもしれないことを model が理解する必要がある tool

Runtime Behavior:

result := &planner.ToolResult{
    Result: &ListDevicesResult{
        Devices: devices,
    },
    Bounds: &agent.Bounds{
        Returned:       len(devices),
        Total:          ptr(total),
        Truncated:      truncated,
        NextCursor:     nextCursor,
        RefinementHint: refinementHint,
    },
}

bounded tool が実行されると:

  1. runtime は successful bounded tool が planner.ToolResult.Bounds を返したことを検証します。
  2. runtime は BoundedResult(...) の field name を使い、emitted JSON に bounds を merge します。
  3. 同じ planner.ToolResult.Bounds struct が planner、hook、UI 用の正規 runtime contract のまま残ります。

tool は標準 Title() DSL function を使って display title を持てます:

Tool("web_search", "Search the web", func() {
    Title("Web Search")
    Args(func() { /* ... */ })
})

Idempotent

Idempotent() は現在の tool が run transcript 内で idempotent であることを示します。設定されると、runtime/planner は同一引数の重複 tool call を冗長とみなし、transcript 内に successful result がすでにある場合は実行を避けられます。

コンテキスト: Tool の内部

使う場面

run transcript の存続中、その tool result が引数の純粋関数である場合にだけ Idempotent() を使います。たとえば、stable identifier による documentation section の取得です。

使わない場面

result が変化する外部状態に依存し、tool payload に time/version parameter がない場合は idempotent にしないでください。たとえば as_of input のない “get current mode” や “get current status” snapshot です。

コード生成

Idempotent() でマークされた tool では、codegen は生成 tools.ToolSpec.Tags に tag goa-ai.idempotency=transcript を emit します。この tag は transcript-aware de-duplication を実装する runtime/planner が消費します。

Confirmation

Confirmation(dsl) は、ツール実行前に明示的な帯域外承認が必要であることを宣言します。これは オペレータセンシティブ なツール(書き込み、削除、コマンド)向けです。

コンテキスト: Tool の内部

生成時に Goa-AI は確認ポリシーを生成されたツールスペックに記録します。実行時にはワークフローが AwaitConfirmation を通じて確認要求を出し、明示的な承認が与えられた後にのみツールを実行します。

最小例:

Tool("dangerous_write", "Write a stateful change", func() {
    Args(DangerousWriteArgs)
    Return(DangerousWriteResult)
    Confirmation(func() {
        Title("Confirm change")
        PromptTemplate(`Approve write: set {{ .Key }} to {{ .Value }}`)
        DeniedResultTemplate(`{"summary":"Cancelled","key":"{{ .Key }}"}`)
    })
})

注記:

  • 確認の要求方法はランタイムが所有します。組み込みの確認プロトコルは専用の await(AwaitConfirmation)と決定 API(ProvideConfirmation)を使用します。期待される payload と実行フローはランタイムガイドを参照してください。
  • PromptTemplateDeniedResultTemplate は Go の text/template 文字列であり、missingkey=error で実行されます。標準のテンプレート関数(例: printf)に加えて、Goa-AI は次を提供します:
    • json vv を JSON エンコード(任意のポインタフィールドや構造化値の埋め込みに有用)
    • quote s → Go エスケープ済みの引用文字列を返す(fmt.Sprintf("%q", s) と同等)
  • runtime.WithToolConfirmation(...) によって、実行時に動的に Confirmation を有効化することもできます(環境別ポリシーやデプロイ単位の上書きなど)。

CallHintTemplate と ResultHintTemplate

CallHintTemplate(template)ResultHintTemplate(template) は、ツール呼び出し/結果の表示テンプレート(ヒント)を設定します。テンプレートは Go の text/template 文字列で、ツールの型付き payload/result 構造体に対して評価され、実行中および実行後に表示される簡潔なヒントを生成します。

コンテキスト: Tool の内部

ポイント:

  • テンプレートは生の JSON ではなく型付き Go 構造体を受け取ります。JSON キー(例: .query, .device_id)ではなく Go のフィールド名(例: .Query, .DeviceID)を使ってください。
  • ヒントは簡潔に保ちましょう(UI 表示のため、推奨 ≤140 文字)
  • テンプレートは missingkey=error でコンパイルされます(参照するフィールドは必ず存在している必要があります)
  • 任意フィールドには {{ if .Field }}{{ with .Field }} を使ってください

ランタイム契約:

  • hooks のイベントコンストラクタはヒントをレンダリングしません。ツール呼び出しのスケジュールイベントは既定で DisplayHint=="" です。
  • ランタイムは、型付き payload をデコードして CallHintTemplate を実行できる場合、公開時に 永続的な 呼び出しヒントを付与して保存できます。
  • 型付きデコードに失敗する、またはテンプレートが登録されていない場合、ランタイムは DisplayHint を空のままにします(生の JSON に対してヒントをレンダリングしません)。
  • producer が hook イベントを公開する前に DisplayHint(非空)を明示的に設定した場合、ランタイムはそれを権威ある値として扱い、上書きしません。
  • consumer ごとの文言変更(例: UI の表現)にはランタイムで runtime.WithHintOverrides を設定します。override は、ストリームの tool_start イベントにおいて DSL テンプレートより優先されます。

基本例:

Tool("search", "Search documents", func() {
    Args(func() {
        Attribute("query", String, "Search phrase")
        Attribute("limit", Int, "Maximum results", func() { Default(10) })
        Required("query")
    })
    Return(func() {
        Attribute("count", Int, "Number of results found")
        Attribute("results", ArrayOf(String), "Matching documents")
        Required("count", "results")
    })
    CallHintTemplate("Searching for: {{ .Query }}")
    ResultHintTemplate("Found {{ .Result.Count }} results")
})

型付き構造体フィールド:

テンプレートは生成された Go の payload/result 構造体を受け取ります。フィールド名は JSON の命名(snake_case/camelCase)ではなく Go の命名(PascalCase)に従います:

// DSL definition
Tool("get_device_status", "Get device status", func() {
    Args(func() {
        Attribute("device_id", String, "Device identifier")      // JSON: device_id
        Attribute("include_metrics", Boolean, "Include metrics") // JSON: include_metrics
        Required("device_id")
    })
    Return(func() {
        Attribute("device_name", String, "Device name")          // JSON: device_name
        Attribute("is_online", Boolean, "Online status")         // JSON: is_online
        Attribute("last_seen", String, "Last seen timestamp")    // JSON: last_seen
        Required("device_name", "is_online")
    })
    // Use Go field names (PascalCase), not JSON keys
    CallHintTemplate("Checking status of {{ .DeviceID }}")
    ResultHintTemplate("{{ .Result.DeviceName }}: {{ if .Result.IsOnline }}online{{ else }}offline{{ end }}")
})

任意フィールドの扱い:

テンプレートエラーを避けるため、任意フィールドには条件ブロックを使ってください:

Tool("list_items", "List items with optional filter", func() {
    Args(func() {
        Attribute("category", String, "Optional category filter")
        Attribute("limit", Int, "Maximum items", func() { Default(50) })
    })
    Return(func() {
        Attribute("items", ArrayOf(Item), "Matching items")
        Attribute("total", Int, "Total count")
        Attribute("truncated", Boolean, "Results were truncated")
        Required("items", "total")
    })
    CallHintTemplate("Listing items{{ with .Category }} in {{ . }}{{ end }}")
    ResultHintTemplate("{{ .Result.Total }} items{{ if .Result.Truncated }} (truncated){{ end }}")
})

組み込みテンプレート関数:

ランタイムはヒントテンプレートのために次のヘルパー関数を提供します:

関数説明
join文字列スライスをセパレータで結合する{{ join .Tags ", " }}
countスライスの要素数を数える{{ count .Results }} items
truncate文字列を N 文字で切り詰める{{ truncate .Query 20 }}

すべての機能を含む例:

Tool("analyze_data", "Analyze dataset", func() {
    Args(func() {
        Attribute("dataset_id", String, "Dataset identifier")
        Attribute("analysis_type", String, "Type of analysis", func() {
            Enum("summary", "detailed", "comparison")
        })
        Attribute("filters", ArrayOf(String), "Optional filters")
        Required("dataset_id", "analysis_type")
    })
    Return(func() {
        Attribute("insights", ArrayOf(String), "Analysis insights")
        Attribute("metrics", MapOf(String, Float64), "Computed metrics")
        Attribute("processing_time_ms", Int, "Processing time in milliseconds")
        Required("insights", "processing_time_ms")
    })
    CallHintTemplate("Analyzing {{ .DatasetID }} ({{ .AnalysisType }})")
    ResultHintTemplate("{{ count .Result.Insights }} insights in {{ .Result.ProcessingTimeMs }}ms")
})

ResultReminder

ResultReminder(text) は、ツール結果が返った後に会話へ注入される静的なシステムリマインダを設定します。これは、結果の解釈や UI 表示の前提など、モデルが「舞台裏の前提」を理解した上で応答できるようにするために使用します。

コンテキスト: Tool の内部

リマインダテキストはランタイムにより <system-reminder> タグで自動的にラップされます。テキスト内にタグを含めないでください。

静的 vs 動的リマインダ:

ResultReminder は、毎回適用される設計時(デザイン時)の静的リマインダです。実行時の状態やツール結果に依存する動的リマインダが必要な場合は、プランナー実装で PlannerContext.AddReminder() を使用してください。動的リマインダは次をサポートします:

  • レート制限(ターン間の最小間隔)
  • ランごとの上限(1 ランあたりの最大回数)
  • 条件に基づく追加/削除
  • 優先度(安全 vs ガイダンス)

基本例:

Tool("get_time_series", "Get time series data", func() {
    Args(func() {
        Attribute("device_id", String, "Device identifier")
        Attribute("start_time", String, "Start timestamp")
        Attribute("end_time", String, "End timestamp")
        Required("device_id", "start_time", "end_time")
    })
    Return(func() {
        Attribute("series", ArrayOf(DataPoint), "Time series data points")
        Attribute("summary", String, "Summary for the model")
        Required("series", "summary")
    })
    ResultReminder("The user sees a rendered graph of this data in the UI.")
})

ResultReminder を使う場面:

  • UI がツール結果を特別な表示(チャート/グラフ/テーブル)で描画し、モデルがそれを知る必要があるとき
  • すでにユーザーに見えている情報をモデルが繰り返さない方がよいとき
  • 表示方法に関する重要な前提が応答の仕方に影響する場合
  • 毎回同じガイダンスを適用したい場合

複数ツールのリマインダ:

同一ターン内に複数のツールが result reminder を持つ場合、ランタイムは 1 つのシステムメッセージに統合します:

Tool("get_metrics", "Get device metrics", func() {
    Args(func() { /* ... */ })
    Return(func() { /* ... */ })
    ResultReminder("Metrics are displayed as a dashboard widget.")
})

Tool("get_alerts", "Get active alerts", func() {
    Args(func() { /* ... */ })
    Return(func() { /* ... */ })
    ResultReminder("Alerts are shown in a priority-sorted list with severity indicators.")
})

PlannerContext による動的リマインダ:

実行時条件に依存する場合は、プランナー API を使用します:

// In your planner implementation
func (p *MyPlanner) PlanResume(ctx context.Context, input *planner.PlanResumeInput) (*planner.PlanResult, error) {
    // Add a dynamic reminder based on tool results
    for _, tr := range input.ToolOutputs {
        if tr.Name == "get_time_series" && hasAnomalies(tr.Result) {
            input.Agent.AddReminder(reminder.Reminder{
                ID:   "anomaly_detected",
                Text: "Anomalies were detected in the time series. Highlight these to the user.",
                Priority: reminder.TierGuidance,
            })
        }
    }
    // ... rest of planner logic
}

Tags

Tags(values...) はツールまたはツールセットにメタデータラベルを付与します。タグはポリシーエンジンやテレメトリで参照されます。

コンテキスト: Tool または Toolset の内部

一般的なタグパターン:

  • ドメイン: "nlp", "database", "api", "filesystem"
  • 能力: "read", "write", "search", "transform"
  • リスク: "safe", "destructive", "external"
Tool("delete_file", "Delete a file", func() {
    Args(func() { /* ... */ })
    Tags("filesystem", "write", "destructive")
})

BindTo

BindTo("Method") または BindTo("Service", "Method") は、ツールを Goa のサービスメソッドに関連付けます。

コンテキスト: Tool の内部

ツールがメソッドにバインドされる場合:

  • ツールの Args スキーマはメソッドの Payload と異なってよい
  • ツールの Return スキーマはメソッドの Result と異なってよい
  • 生成されるアダプタがツール型とメソッド型の間を変換する
var _ = Service("orchestrator", func() {
    Method("Search", func() {
        Payload(SearchPayload)
        Result(SearchResult)
    })

    Agent("chat", func() {
        Use("docs", func() {
            Tool("search", "Search documentation", func() {
                Args(SearchPayload)
                Return(SearchResult)
                BindTo("Search") // binds to method on same service
            })
        })
    })
})

Inject

Inject(fields...) は、特定の payload フィールドを「注入」(サーバー側インフラ)としてマークします。注入フィールドは:

  1. LLM から隠される(モデルへ送る JSON Schema から除外される)
  2. バインド先メソッド payload 上の必須 String フィールドとして検証される
  3. 生成された executor が runtime.ToolCallMeta から値を設定し、必要なら ToolInterceptor.Inject フックを追加できる

コンテキスト: Tool の内部

Tool("get_data", "Get data for current session", func() {
    Args(func() {
        Attribute("session_id", String, "Current session ID")
        Attribute("query", String, "Data query")
        Required("session_id", "query")
    })
    Return(func() {
        Attribute("data", ArrayOf(String))
    })
    BindTo("data_service", "get")
    Inject("session_id") // hidden from LLM, populated by interceptor
})

サポートされる注入フィールド名は固定で、run_idsession_idturn_idtool_call_idparent_tool_call_id です。

実行時は、生成されたサービス executor が runtime.ToolCallMeta から対応する値をコピーし、その後で必要なら型付き interceptor を実行します:

func (h *Handler) Inject(ctx context.Context, payload any, meta *runtime.ToolCallMeta) error {
    if p, ok := payload.(*dataservice.GetPayload); ok {
        p.SessionID = meta.SessionID
    }
    return nil
}

TerminalRun

TerminalRun() は現在のツールを run の終端としてマークします。ツールが成功して実行されると、ランタイムはツール結果を公開した直後にランを終了し、PlanResume/ファイナライズターンを追加で要求しません。

コンテキスト: Tool の内部

最終的なユーザー向け出力がツール結果そのものであるようなツール(最終レポートのレンダラーや、「この run をコミット」するツールなど)に TerminalRun() を使用します。ツール結果がランの終端アーティファクトであり、モデルによる追加のナレーションは不要です。

Tool("commit_task", "タスクの終端アーティファクトをコミット", func() {
    Args(TaskCompletionArgs)
    Return(TaskCompletionResult)
    TerminalRun()
})

ランタイム動作:

  • コード生成はこのフラグを tools.ToolSpec.TerminalRun に記録します。
  • 終端ツール呼び出しが成功した後、ランタイムは PlanResume を呼ばずにランを完了します。
  • 終端ツールは Bookkeeping()(下記参照)と自然に合成されます。典型的な「この run をコミット」ツールは終端かつ bookkeeping であり、リトリーバル予算が枯渇していても常に実行可能で、ランを原子的に終了できます。

Bookkeeping

Bookkeeping() は現在のツールを bookkeeping ツールとしてマークし、run レベルの MaxToolCalls リトリーバル予算を消費しないようにします。ランタイムは bookkeeping 呼び出しについて RemainingToolCalls をデクリメントせず、バッチを残り予算に収めるために切り詰める際にも bookkeeping 呼び出しを破棄しません。

コンテキスト: Tool の内部

コストがリトリーバルや副作用ではなく「記録」である構造化された進捗・ステータス・ファインディング・終端コミット系のツールに Bookkeeping() を使用します。ステータス更新、進捗マーカー、最終成果物を書き込む原子的な「この run をコミット」ツールが典型例です。

Tool("set_step_status", "ステップのステータスを更新", func() {
    Args(SetStepStatusArgs)
    Return(SetStepStatusResult)
    Bookkeeping()
})

ランタイム動作:

  • コード生成はこのフラグを tools.ToolSpec.Bookkeeping に記録します。
  • bookkeeping 呼び出しは MaxToolCalls に計上されず、ランタイムがバッチを残り予算に合わせて切り詰める際にも破棄されません。予算対象(非 bookkeeping)の呼び出しが先に切り詰められ、bookkeeping 呼び出しは元の位置を維持します。
  • 既定では、成功した bookkeeping 結果は将来のプランナーターンから隠されたままです。次のターンが推論すべき正規状態を bookkeeping ツールが返す場合は PlannerVisible() を追加します。
  • 未知のツールは予算対象として扱われます。DSL で Bookkeeping() として宣言された(またはランタイムの ToolSpec で bookkeeping としてマークされた)ツールのみが免除されます。
  • bookkeeping だけのターンは、同一ターン内で解決する(TerminalRun()FinalResponseFinalToolResult、await/pause)か、次の resume を正当化する planner-visible な bookkeeping 結果を少なくとも 1 つ生成する必要があります。

TerminalRun() との合成:

終端コミットツールは通常 bookkeeping かつ終端として宣言されます:

Tool("commit_task", "タスクの終端アーティファクトをコミット", func() {
    Args(TaskCompletionArgs)
    Return(TaskCompletionResult)
    Bookkeeping()  // 予算が尽きても常に実行される
    TerminalRun()  // 成功するとランを原子的に終了する
})

このパターンにより、ランは常に決定的にファイナライズできます。コミットツールはリトリーバル予算から免除され、成功するとその後のプランナーターンなしで run が終了します。

PlannerVisible

PlannerVisible() は bookkeeping ツールの結果を将来のプランナーターンから見えるままにします。次の PlanResume を駆動すべき構造化された進捗スナップショットのように、コントロールプレーンの正規状態を返すツールで使います。

コンテキスト: Tool の内部

Tool("set_step_status", "ステップのステータスを更新", func() {
    Args(SetStepStatusArgs)
    Return(TaskProgressSnapshot)
    Bookkeeping()
    PlannerVisible()
})

ランタイム動作:

  • PlannerVisible() は非終端 bookkeeping ツールでのみ有効です。
  • 成功した実行結果は、モデル可視の transcript と将来の PlanResumeInput.ToolOutputs に再注入されます。
  • リトライ可能な bookkeeping 失敗は、PlannerVisible() がなくても planner-visible のままです。
  • 予算対象ツールは既定で planner-visible なので PlannerVisible() は不要です。

ポリシー関数

RunPolicy

RunPolicy(dsl) は、実行時に適用される実行制限を設定します。Agent の内部で宣言され、上限、時間予算、履歴管理、中断処理などのポリシー設定を含みます。

コンテキスト: Agent の内部

利用可能なポリシー関数:

  • DefaultCaps — リソース制限(ツール呼び出し、連続失敗)
  • TimeBudget — 実行全体の単純なウォールクロック制限
  • Timing — 予算、計画、ツール実行の詳細タイムアウト(上級)
  • History — 会話履歴管理(スライディングウィンドウ or 圧縮)
  • InterruptsAllowed — ヒューマンインザループの一時停止/再開を有効化
  • OnMissingFields — 必須フィールド欠落時のバリデーション挙動
Agent("chat", "Conversational runner", func() {
    RunPolicy(func() {
        DefaultCaps(
            MaxToolCalls(8),
            MaxConsecutiveFailedToolCalls(3),
        )
        TimeBudget("2m")
        InterruptsAllowed(true)
        OnMissingFields("await_clarification")

        History(func() {
            KeepRecentTurns(20)
        })
    })
})

DefaultCaps

DefaultCaps(opts...) は、暴走ループを防止し実行制限を強制するためのケイパビリティ上限を適用します。

コンテキスト: RunPolicy の内部

RunPolicy(func() {
    DefaultCaps(
        MaxToolCalls(8),
        MaxConsecutiveFailedToolCalls(3),
    )
})

MaxToolCalls(n): 予算対象のツール について許可する最大呼び出し回数。超過した場合、ランタイムは中断します。DSL で Bookkeeping() として宣言されたツールはこの上限から免除され、RemainingToolCalls を消費しません。そのため、構造化されたステータス更新、進捗マーカー、終端コミットツールは常に実行可能です。

MaxConsecutiveFailedToolCalls(n): アボートするまでの最大連続失敗回数。無限の再試行ループを防ぎます。

TimeBudget

TimeBudget(duration) は、エージェント実行にウォールクロックの制限を適用します。期間は文字列(例: "2m", "30s")で指定します。

コンテキスト: RunPolicy の内部

RunPolicy(func() {
    TimeBudget("2m") // 2 minutes
})

個々のアクティビティのタイムアウトを細かく制御したい場合は、代わりに Timing を使用します。

Timing

Timing(dsl) は、TimeBudget の代替として詳細なタイムアウト設定を提供します。TimeBudget が単一の全体制限を設定する一方、Timing は 3 つのレベルで制御できます: 実行全体の予算、プランナーアクティビティ(LLM 推論)、ツール実行アクティビティ。

コンテキスト: RunPolicy の内部

Timing と TimeBudget の使い分け:

  • 単一のウォールクロック制限で十分なら TimeBudget
  • 計画とツール実行で異なるタイムアウトが必要なら Timing(例: ツールが遅い外部 API を叩くが LLM 応答は速くしたい)
RunPolicy(func() {
    Timing(func() {
        Budget("10m")   // overall wall-clock budget for the entire run
        Plan("45s")     // timeout for Plan/Resume activities (LLM inference)
        Tools("2m")     // default timeout for ExecuteTool activities
    })
})

Timing はランタイムのセマンティック層にとどまります。Plan(...)Tools(...) は、健全なプランナー/ツールの 1 回の試行が開始後に どれだけ実行できるかを表す予算です。キュー待ちタイムアウトや heartbeat による liveness など、ワークフローエンジン固有の仕組みは 設定しません。Temporal アダプタを使う場合、それらの仕組みは temporal.Options.ActivityDefaults で設定します。

Timing 関数:

関数説明影響範囲
Budget(duration)実行全体のウォールクロック予算ランのライフサイクル全体
Plan(duration)Plan/Resume アクティビティのタイムアウトプランナーの LLM 推論呼び出し
Tools(duration)ExecuteTool アクティビティのデフォルトタイムアウトツール実行(サービス、MCP、agent-as-tool)

Timing がランタイムに与える影響:

ランタイムはこれらの DSL 値を、エンジン非依存の「試行予算」に変換します:

  • Budget は run 全体のセマンティックな wall-clock 予算を設定します。 ランタイムはこの予算をプランナー/ツール作業に適用し、さらにエンジン の run timeout を Budget + FinalizerGrace + エンジンの余裕分 として導出します。これにより、最後の PlanResume ターンと終端 クリーンアップにも完了の余地が残ります。
  • PlanPlanStart / PlanResume の試行予算になります
  • ToolsExecuteTool のデフォルト試行予算になります

Temporal 固有のキュー待ちや liveness の挙動は、Temporal アダプタ側で 別途積み上げられます。

完全な例:

Agent("data-processor", "Processes large datasets", func() {
    Use(DataToolset)
    RunPolicy(func() {
        DefaultCaps(MaxToolCalls(20))
        Timing(func() {
            Budget("30m")   // long-running data jobs
            Plan("1m")      // LLM decisions should be quick
            Tools("5m")     // data operations may take time
        })
    })
})

Cache

Cache(dsl) は、エージェントのプロンプトキャッシュ挙動を設定します。キャッシュをサポートするプロバイダに対して、システムプロンプトやツール定義のどこにチェックポイント境界を置くべきかを指定します。

コンテキスト: RunPolicy の内部

プロンプトキャッシュは、プロバイダが以前に処理したコンテンツを再利用できるようにすることで、推論コストとレイテンシを大きく削減できます。Cache は、プロバイダが「どこまでがキャッシュ可能か」を判断するための境界を定義します。

RunPolicy(func() {
    Cache(func() {
        AfterSystem()  // checkpoint after system messages
        AfterTools()   // checkpoint after tool definitions
    })
})

キャッシュチェックポイント関数:

関数説明
AfterSystem()すべてのシステムメッセージの後にチェックポイントを置く。プロバイダはこれを「システム前置き直後のキャッシュ境界」と解釈する。
AfterTools()ツール定義の後にチェックポイントを置く。プロバイダはこれを「ツール設定セクション直後のキャッシュ境界」と解釈する。

プロバイダ対応:

すべてのプロバイダがプロンプトキャッシュをサポートするわけではなく、対応はチェックポイント種別によって異なります:

ProviderAfterSystemAfterTools
Bedrock (Claude models)
Bedrock (Nova models)

キャッシュ非対応のプロバイダはこれらのオプションを無視します。ランタイムはプロバイダ固有の制約も検証します。例えば Nova モデルで AfterTools を要求するとエラーになります。

Cache を使う場面:

  • AfterSystem() を使う: システムプロンプトがターン間で安定しており、再処理を避けたい
  • AfterTools() を使う: ツール定義が安定しており、ツール設定をキャッシュしたい
  • 両方を併用する: 対応プロバイダで最大のキャッシュ効果を得たい

完全な例:

Agent("assistant", "Conversational assistant", func() {
    Use(DocsToolset)
    Use(SearchToolset)
    RunPolicy(func() {
        DefaultCaps(MaxToolCalls(10))
        TimeBudget("5m")
        Cache(func() {
            AfterSystem()  // cache the system prompt
            AfterTools()   // cache tool definitions (Claude only)
        })
    })
})

History

History(dsl) は、各プランナー呼び出しの前にランタイムが会話履歴をどのように管理するかを定義します。履歴ポリシーは次を保持しつつ、メッセージ履歴を変換します:

  • 会話先頭のシステムプロンプト
  • 論理的なターン境界(ユーザー + アシスタント + ツール呼び出し/結果を 1 単位とする)

エージェントごとに設定できる履歴ポリシーは最大 1 つです。

コンテキスト: RunPolicy の内部

標準ポリシーは 2 つあります:

KeepRecentTurns(スライディングウィンドウ):

KeepRecentTurns(n) は、システムプロンプトとツールのやりとりを保ちながら、直近 N 個のユーザー/アシスタントターンのみを保持します。コンテキストサイズを制限する最も簡単な方法です。

RunPolicy(func() {
    History(func() {
        KeepRecentTurns(20) // Keep the last 20 user/assistant turns
    })
})

パラメータ:

  • n: 保持する直近ターン数(> 0 必須)

Compress(モデル支援の要約):

Compress(triggerAt, keepRecent) は、古いターンをモデルで要約し、最近のターンを高忠実度で保持します。単純なスライディングウィンドウより多くの文脈を維持できます。

RunPolicy(func() {
    History(func() {
        // When at least 30 turns exist, summarize older turns
        // and keep the most recent 10 in full fidelity
        Compress(30, 10)
    })
})

パラメータ:

  • triggerAt: 圧縮を実行する最小総ターン数(> 0 必須)
  • keepRecent: 高忠実度で保持する直近ターン数(>= 0 かつ < triggerAt)

HistoryModel の要件:

Compress を使う場合、生成されるエージェント設定の HistoryModel フィールドに model.Client を渡す必要があります。ランタイムはこのクライアントを ModelClassSmall とともに使用して古いターンを要約します:

// Generated agent config includes HistoryModel field when Compress is used
cfg := chat.ChatAgentConfig{
    Planner:      &ChatPlanner{},
    HistoryModel: smallModelClient, // Required: implements model.Client
}
if err := chat.RegisterChatAgent(ctx, rt, cfg); err != nil {
    log.Fatal(err)
}

Compress が設定されているにもかかわらず HistoryModel が提供されない場合、登録は失敗します。

ターン境界の保持:

どちらのポリシーも論理的なターン境界を「原子単位」として保持します。ターンは次で構成されます:

  1. ユーザーメッセージ
  2. アシスタントの応答(テキストおよび/またはツール呼び出し)
  3. その応答に紐づくツール結果

これにより、モデルは常に完全な相互作用シーケンスを見られ、文脈を混乱させる部分的なターンは見ません。

InterruptsAllowed

InterruptsAllowed(bool) は、ヒューマンインザループの割り込みを尊重すべきであることを示します。有効にすると、ランタイムは一時停止/再開(pause/resume)をサポートし、確認/明確化ループや durable await 状態に重要となります。

コンテキスト: RunPolicy の内部

主な利点:

  • 必須情報が不足している場合に、実行を 一時停止 できる(OnMissingFields 参照)
  • 明確化ツール経由でユーザー入力を 待機 できる
  • 一時停止中は状態が保持され、再開まで計算資源を消費しない
RunPolicy(func() {
    // Enable pause/resume capability
    InterruptsAllowed(true)

    // Automatically pause when required tool arguments are missing
    OnMissingFields("await_clarification")
})

OnMissingFields

OnMissingFields(action) は、ツール呼び出しのバリデーションで必須フィールドの欠落が検出されたときに、エージェントがどう応答するかを設定します。

コンテキスト: RunPolicy の内部

有効値:

  • "finalize": 必須フィールドがない場合に実行を停止する
  • "await_clarification": 一時停止し、ユーザーが不足情報を提供するのを待つ
  • "resume": 不足があっても実行を継続する
  • ""(空): コンテキストに基づいてプランナーに判断させる
RunPolicy(func() {
    OnMissingFields("await_clarification")
})

完全なポリシー例

Agent("chat", "Conversational runner", func() {
    RunPolicy(func() {
        DefaultCaps(
            MaxToolCalls(8),
            MaxConsecutiveFailedToolCalls(3),
        )
        Timing(func() {
            Budget("5m")
            Plan("30s")
            Tools("1m")
        })
        InterruptsAllowed(true)
        OnMissingFields("await_clarification")
        History(func() {
            Compress(30, 10)
        })
    })
})

MCP 関数

Goa-AI は、Goa サービス内で Model Context Protocol(MCP)サーバを宣言するための DSL 関数を提供します。

MCP

MCP(name, version, opts...) は現在のサービスで MCP を有効化します。MCP プロトコルを通じてツール、リソース、プロンプトを公開するようサービスを構成します。

コンテキスト: Service の内部

Service("calculator", func() {
    Description("Calculator MCP server")

    // MCP を使う例
    MCP("calc", "1.0.0", ProtocolVersion("2025-06-18"))

    Method("add", func() {
        Payload(func() {
            Attribute("a", Int, "First number")
            Attribute("b", Int, "Second number")
            Required("a", "b")
        })
        Result(func() {
            Attribute("sum", Int, "Result of addition")
            Required("sum")
        })
        Tool("add", "Add two numbers")
    })
})

ProtocolVersion

ProtocolVersion(version) は、サーバがサポートする MCP プロトコルバージョンを設定します。MCP に渡す設定関数を返します。

コンテキスト: MCP へのオプション引数

Service("calculator", func() {
    // Specify protocol version as an option
    MCP("calc", "1.0.0", ProtocolVersion("2025-06-18"))
})

Tool (in Method Context)

Tool(name, description) は、現在のメソッドを MCP ツールとしてマークします。メソッドの payload がツールの入力スキーマになり、result が出力スキーマになります。

コンテキスト: Method の内部(サービスは MCP が有効である必要があります)

Method("search", func() {
    Payload(func() {
        Attribute("query", String, "Search query")
        Attribute("limit", Int, "Maximum results", func() { Default(10) })
        Required("query")
    })
    Result(func() {
        Attribute("results", ArrayOf(String), "Search results")
        Required("results")
    })
    Tool("search", "Search documents by query")
})

Toolset(FromMCP(…))

Toolset(FromMCP(service, toolset)) は、Goa の MCP サーバから導出された MCP 定義ツールセットを宣言します。

コンテキスト: トップレベル

利用パターンは 2 つあります。

Goa バックエンドの MCP サーバ:

var AssistantSuite = Toolset(FromMCP("assistant", "assistant-mcp"))

var _ = Service("orchestrator", func() {
    Agent("chat", func() {
        Use(AssistantSuite)
    })
})

外部 MCP サーバ(インラインスキーマ定義):

var RemoteSearch = Toolset("remote-search", FromExternalMCP("remote", "search"), func() {
    Tool("web_search", "Search the web", func() {
        Args(func() { Attribute("query", String) })
        Return(func() { Attribute("results", ArrayOf(String)) })
    })
})

Agent("helper", "", func() {
    Use(RemoteSearch)
})

Resource と WatchableResource

Resource(name, uri, mimeType) は、メソッドを MCP リソースプロバイダとしてマークします。

WatchableResource(name, uri, mimeType) は、メソッドを購読可能リソースとしてマークします。

コンテキスト: Method の内部(サービスは MCP が有効である必要があります)

Method("readme", func() {
    Result(String)
    Resource("readme", "file:///docs/README.md", "text/markdown")
})

Method("system_status", func() {
    Result(func() {
        Attribute("status", String, "Current system status")
        Attribute("uptime", Int, "Uptime in seconds")
        Required("status", "uptime")
    })
    WatchableResource("status", "status://system", "application/json")
})

StaticPrompt と DynamicPrompt

StaticPrompt(name, description, messages...) は静的プロンプトテンプレートを追加します。

DynamicPrompt(name, description) は、メソッドを動的プロンプト生成器としてマークします。

コンテキスト: Service(静的)または Method(動的)

Service("assistant", func() {
    MCP("assistant", "1.0")

    // Static prompt
    StaticPrompt("greeting", "Friendly greeting",
        "system", "You are a helpful assistant",
        "user", "Hello!")

    // Dynamic prompt
    Method("code_review", func() {
        Payload(func() {
            Attribute("language", String, "Programming language")
            Attribute("code", String, "Code to review")
            Required("language", "code")
        })
        Result(ArrayOf(Message))
        DynamicPrompt("code_review", "Generate code review prompt")
    })
})

Notification と Subscription

Notification(name, description) は、メソッドを MCP 通知送信者としてマークします。

Subscription(resourceName) は、購読可能リソースのサブスクリプションハンドラとしてメソッドをマークします。

コンテキスト: Method の内部(サービスは MCP が有効である必要があります)

Method("progress_update", func() {
    Payload(func() {
        Attribute("task_id", String, "Task identifier")
        Attribute("progress", Int, "Progress percentage (0-100)")
        Required("task_id", "progress")
    })
    Notification("progress", "Task progress notification")
})

Method("subscribe_status", func() {
    Payload(func() {
        Attribute("uri", String, "Resource URI to subscribe to")
        Required("uri")
    })
    Result(String)
    Subscription("status") // Links to WatchableResource named "status"
})

SubscriptionMonitor

SubscriptionMonitor(name) は、現在のメソッドをサブスクリプション更新の server-sent events(SSE)モニターとしてマークします。メソッドは購読変更イベントを接続クライアントへストリーミングします。

コンテキスト: Method の内部(サービスは MCP が有効である必要があります)

Method("watch_subscriptions", func() {
    StreamingResult(func() {
        Attribute("resource", String, "Resource URI that changed")
        Attribute("event", String, "Event type (created, updated, deleted)")
        Required("resource", "event")
    })
    SubscriptionMonitor("subscriptions")
})

SubscriptionMonitor を使う場面:

  • クライアントがサブスクリプション変更のリアルタイム更新を必要とする
  • サブスクリプションイベントをプッシュする SSE エンドポイントを実装したい
  • リソース変更に反応するリアクティブ UI を構築したい

完全な MCP サーバ例

var _ = Service("assistant", func() {
    Description("Full-featured MCP server example")

    MCP("assistant", "1.0.0", ProtocolVersion("2025-06-18"))

    StaticPrompt("greeting", "Friendly greeting",
        "system", "You are a helpful assistant",
        "user", "Hello!")

    Method("search", func() {
        Description("Search documents")
        Payload(func() {
            Attribute("query", String, "Search query")
            Required("query")
        })
        Result(func() {
            Attribute("results", ArrayOf(String), "Search results")
            Required("results")
        })
        Tool("search", "Search documents by query")
    })

    Method("get_readme", func() {
        Result(String)
        Resource("readme", "file:///README.md", "text/markdown")
    })

    Method("get_status", func() {
        Result(func() {
            Attribute("status", String)
            Attribute("updated_at", String)
        })
        WatchableResource("status", "status://system", "application/json")
    })

    Method("subscribe_status", func() {
        Payload(func() { Attribute("uri", String) })
        Result(String)
        Subscription("status")
    })

    Method("review_code", func() {
        Payload(func() {
            Attribute("language", String)
            Attribute("code", String)
            Required("language", "code")
        })
        Result(ArrayOf(Message))
        DynamicPrompt("code_review", "Generate code review prompt")
    })

    Method("notify_progress", func() {
        Payload(func() {
            Attribute("task_id", String)
            Attribute("progress", Int)
            Required("task_id", "progress")
        })
        Notification("progress", "Task progress update")
    })
})

レジストリ関数

Goa-AI は、ツールレジストリ(MCP サーバ、ツールセット、エージェントの中央カタログ)を宣言・消費するための DSL 関数を提供します。レジストリは発見され、エージェントによって消費できます。

Registry

Registry(name, dsl) は、ツール発見のためのレジストリソースを宣言します。

コンテキスト: トップレベル

DSL 内で次を使用します:

  • URL: レジストリエンドポイント URL(必須)
  • Description: 人間可読の説明
  • APIVersion: レジストリ API バージョン(デフォルト "v1"
  • Security: 認証のための Goa セキュリティスキーム参照
  • Timeout: HTTP リクエストタイムアウト
  • Retry: 失敗時のリトライポリシー
  • SyncInterval: カタログ更新間隔
  • CacheTTL: ローカルキャッシュ保持期間
  • Federation: 外部レジストリの取り込み設定
var CorpRegistry = Registry("corp-registry", func() {
    Description("Corporate tool registry")
    URL("https://registry.corp.internal")
    APIVersion("v1")
    Security(CorpAPIKey)
    Timeout("30s")
    Retry(3, "1s")
    SyncInterval("5m")
    CacheTTL("1h")
})

設定オプション:

関数説明
URL(endpoint)レジストリエンドポイント URL(必須)URL("https://registry.corp.internal")
APIVersion(version)API バージョンのパスセグメントAPIVersion("v1")
Timeout(duration)HTTP リクエストタイムアウトTimeout("30s")
Retry(maxRetries, backoff)失敗したリクエストのリトライポリシーRetry(3, "1s")
SyncInterval(duration)カタログ更新間隔SyncInterval("5m")
CacheTTL(duration)ローカルキャッシュ保持期間CacheTTL("1h")

Federation

Federation(dsl) は外部レジストリ取り込み設定を構成します。Registry 宣言内で Federation を使い、フェデレーションされたソースから取り込むネームスペースを指定します。

コンテキスト: Registry の内部

Federation DSL 内で使用します:

  • Include: 取り込むネームスペースのグロブパターン
  • Exclude: スキップするネームスペースのグロブパターン
var AnthropicRegistry = Registry("anthropic", func() {
    Description("Anthropic MCP Registry")
    URL("https://registry.anthropic.com/v1")
    Security(AnthropicOAuth)
    Federation(func() {
        Include("web-search", "code-execution", "data-*")
        Exclude("experimental/*", "deprecated/*")
    })
    SyncInterval("1h")
    CacheTTL("24h")
})

Include と Exclude:

  • Include(patterns...): 取り込むネームスペースのグロブパターンを指定します。Include が指定されない場合、デフォルトではすべてのネームスペースを含みます。
  • Exclude(patterns...): スキップするネームスペースのグロブパターンを指定します。Exclude は Include の後に適用されます。

FromRegistry

FromRegistry(registry, toolset) は、レジストリ由来のツールセットとして構成します。Toolset を宣言する際のプロバイダオプションとして FromRegistry を使用します。

コンテキスト: Toolset への引数

var CorpRegistry = Registry("corp", func() {
    URL("https://registry.corp.internal")
})

// Basic usage - toolset name derived from registry toolset name
var RegistryTools = Toolset(FromRegistry(CorpRegistry, "data-tools"))

// With explicit name
var MyTools = Toolset("my-tools", FromRegistry(CorpRegistry, "data-tools"))

// With additional configuration
var ConfiguredTools = Toolset(FromRegistry(CorpRegistry, "data-tools"), func() {
    Version("1.2.3")
    Tags("data", "etl")
})

レジストリ由来のツールセットは、標準の Version() DSL 関数で特定バージョンに固定できます:

var CorpRegistry = Registry("corp", func() {
    URL("https://registry.corp.internal")
})

var PinnedTools = Toolset("stable-tools", FromRegistry(CorpRegistry, "data-tools"), func() {
    Version("1.2.3")
})

PublishTo

PublishTo(registry) は、エクスポートされたツールセットをレジストリへ公開する設定を行います。Export DSL 内で PublishTo を使用して公開先レジストリを指定します。

コンテキスト: Toolset の内部(エクスポートされるとき)

var CorpRegistry = Registry("corp", func() {
    URL("https://registry.corp.internal")
})

var LocalTools = Toolset("utils", func() {
    Tool("summarize", "Summarize text", func() {
        Args(func() { Attribute("text", String) })
        Return(func() { Attribute("summary", String) })
    })
})

Agent("data-agent", "Data processing agent", func() {
    Use(LocalTools)
    Export(LocalTools, func() {
        PublishTo(CorpRegistry)
        Tags("data", "etl")
    })
})

完全なレジストリ例

package design

import (
    . "goa.design/goa/v3/dsl"
    . "goa.design/goa-ai/dsl"
)

// Define registries
var CorpRegistry = Registry("corp-registry", func() {
    Description("Corporate tool registry")
    URL("https://registry.corp.internal")
    APIVersion("v1")
    Security(CorpAPIKey)
    Timeout("30s")
    Retry(3, "1s")
    SyncInterval("5m")
    CacheTTL("1h")
})

var AnthropicRegistry = Registry("anthropic", func() {
    Description("Anthropic MCP Registry")
    URL("https://registry.anthropic.com/v1")
    Federation(func() {
        Include("web-search", "code-execution")
        Exclude("experimental/*")
    })
    SyncInterval("1h")
    CacheTTL("24h")
})

// Consume toolsets from registries
var DataTools = Toolset(FromRegistry(CorpRegistry, "data-tools"), func() {
    Version("2.1.0")
})

var SearchTools = Toolset(FromRegistry(AnthropicRegistry, "web-search"))

// Local toolset to publish
var AnalyticsTools = Toolset("analytics", func() {
    Tool("analyze", "Analyze dataset", func() {
        Args(func() {
            Attribute("dataset_id", String, "Dataset identifier")
            Required("dataset_id")
        })
        Return(func() {
            Attribute("insights", ArrayOf(String), "Analysis insights")
            Required("insights")
        })
    })
})

var _ = Service("orchestrator", func() {
    Agent("analyst", "Data analysis agent", func() {
        Use(DataTools)
        Use(SearchTools)
        Use(AnalyticsTools)

        // Export and publish to registry
        Export(AnalyticsTools, func() {
            PublishTo(CorpRegistry)
            Tags("analytics", "data")
        })

        RunPolicy(func() {
            DefaultCaps(MaxToolCalls(10))
            TimeBudget("5m")
        })
    })
})

次のステップ

  • ランタイム - 設計が実行時の挙動にどう変換されるかを理解する
  • ツールセット - ツールセットの実行モデルを深掘りする
  • MCP 連携 - MCP サーバのランタイム配線を理解する