Skip to content

v0.90.0 — Consumer field bindings with key lookup, gRPC model gateway, and accepted-run snapshots

Latest

Choose a tag to compare

@raphael raphael released this 31 Oct 21:06
· 5 commits to main since this release
4931215

v0.90.0 — Consumer field bindings with key lookup, gRPC model gateway, and accepted-run snapshots

Goa-AI's dsl, codegen/agent, features/model/gateway, runtime/agent/runtime, and registry packages let service authors declare AI agents and toolsets in Goa designs, generate typed schemas and executors, route model calls through provider adapters or remote gateways, execute durable multi-turn agent workflows, and coordinate distributed tool catalogs across services. This release adds BindField and Lookup to bind or translate consumer-owned tool fields across an entire toolset, introduces a typed Goa-generated gRPC model gateway (features/model/gateway) with complete vs. partial token-usage accounting, serves run snapshots immediately after workflow acceptance before the first worker activity starts, and eliminates redundant registry and runtime decoding work across turns.

Registry wire protocol 15 and run suspension checkpoint format goa-ai.run-suspension.v14 are unchanged from v0.89.0. Existing v0.89.0 workers, providers, and registry replicas can roll forward to v0.90.0 without draining in-flight runs.

Toolset-wide consumer field bindings and key lookups (BindField and Lookup)

The dsl and codegen/agent packages define how an agent consumes a Toolset—a named group of callable tools—and generate the JSON schemas advertised to the language model alongside the Go functions that decode model arguments into provider payloads.

Previously, when multiple tools in a consumed toolset shared an application-supplied field (such as a tenant scope or workspace ID), authors had to repeat BindInput("tool_name", "field") on every tool. In addition, when a provider required an internal Go struct or identifier that the model could only refer to by a human-readable string key (such as choosing a region by name), applications had to write custom wrapper tools to expose a string argument and translate it into the provider's type.

Now, Use blocks support BindField and Lookup (#483, #515):

  • BindField across a consumed toolset: BindField("scope") omits scope from the model-facing input schema of every tool in the toolset whose payload declares that top-level field, and generates per-tool and toolset-wide WrapExecuteWithScope helpers that inject the application's value before calling the provider. When matched tools use different Go types for the same field name, codegen/agent emits the per-tool wrappers and omits the conflicting toolset-wide wrapper (#515).
  • Lookup for model-facing string keys: Nesting Lookup(String, ...) inside BindField keeps the field in the model-facing JSON schema as a validated string key instead of hiding it. Generation emits Decode<Tool>PayloadWith<Field>Lookup and WrapExecuteWith<Field>Lookup helpers that validate the model's string key against the advertised schema, resolve it through a caller-supplied map[string]<ProviderFieldType> table, and return a structured planner.RetryHint if the key is missing.
  • Complete tool_schemas.json inspection catalog: Generated per-agent specs/tool_schemas.json catalogs now include lookup_input_schema and text_only_input_schema alongside input_schema so design and evaluation tooling can inspect every model-facing schema projection (#516).

Before (BindInput repeated per tool, no key-to-struct lookup):

Use(RecordsToolset, func() {
    BindInput("list_records", "scope")
    BindInput("search_records", "scope")
})

After (BindField with optional Lookup across all matching tools):

Use(RecordsToolset, func() {
    BindField("scope", func() {
        Lookup(String, "Human-readable scope name.", func() {
            MinLength(1)
            Example("north-america")
        })
    })
})

Typed gRPC model gateway and token-usage completeness

The features/model/gateway, runtime/agent/model, and features/model/middleware packages sit between agent planners and model providers (Anthropic, AWS Bedrock, OpenAI, and Google Vertex/Gemini), enforcing request contracts, stream ordering, and adaptive token-bucket rate limiting.

Previously, features/model/gateway provided in-process server and remote-client hooks (NewServer, NewRemoteClient) but left wire serialization to caller-written callbacks. In addition, model.Response and model.RetainUsage could not distinguish a provider response that legitimately consumed zero tokens (such as a 100% prompt-cache hit) from a response that omitted usage counters, or distinguish complete invocation totals from partial counts captured before a mid-stream or multi-round tool-search failure. As a result, AdaptiveRateLimiter could refund reserved token capacity when a failed call reported only partial token usage.

Now (#484, #516, #517):

  • Goa-generated gRPC model gateway (features/model/gateway): gateway.RegisterGRPCServer and gateway.NewGRPCClient expose unary Complete, server-streaming Stream, and CountTokens RPCs over a typed Goa gRPC contract (features/model/gateway/design). The error codec encodes error trees and shared directed acyclic graphs (DAGs) through a typed OneOf union (GatewayErrorNodeDetail), preserving model.OutputValidationError (including bounded ResponseEvidence and RecoveryCorrection), model.RequestValidationError, model.ProviderError, sentinel errors, and retained token usage across the process boundary.
  • Complete vs. incomplete token usage: model.Response adds UsageReported and UsageIncomplete flags plus HasUsage() and HasObservedUsage() helpers, and model adds model.RetainIncompleteUsage(err, usage) and model.UsageIncompleteFromError(err). Provider adapters (anthropic, bedrock, openai, vertex) mark partial stream or multi-round search counts as incomplete. AdaptiveRateLimiter charges overruns when usage is incomplete without refunding unmeasured work.
  • Token counting through runtime middleware: Wrapping a model.Client with runtime.WithModelClient or middleware.NewAdaptiveRateLimiter now preserves optional model.TokenCounter (CountTokens) support (#484).
srv, err := gateway.NewServer(gateway.WithClient(providerClient))
if err != nil {
    return err
}
gateway.RegisterGRPCServer(grpcRegistrar, srv)

client, err := gateway.NewGRPCClient(grpcConn)

Accepted-run snapshots, live compaction events, and worker routing

The runtime/agent/runtime and runtime/agent/engine packages manage the lifecycle of an agent run from initial submission (Start / StartOneShot) through planner turns, transcript compaction, tool execution, and cancellation.

Previously, calling GetRunSnapshot immediately after Start or StartOneShot returned an error if a worker had not yet executed the first storage activity (StartRootRun), forcing HTTP/SSE gateways to poll or treat newly accepted runs as missing. During long conversations, history compression ran silently inside the planner turn without emitting a live status event or recovering cleanly if a later model call in the same activity failed before producing output.

Now (#496, #501, #504, #512, #513, #516):

  • Accepted-run snapshots before first activity execution: engine.AcceptedRunQuerier (QueryAcceptedRun, implemented by both temporal.Engine and inmem.Engine) lets Runtime.GetRunSnapshot synthesize a run.PhaseAccepted (run.StatusPending) snapshot directly from the workflow engine's accepted execution metadata when StartRootRun has not yet been recorded in the session store (#516).
  • Live history-compaction status and recovery: When the runtime compresses older conversation turns into a summary, it emits a live compaction status event, uses a hardened summary prompt, and records the summary model invocation separately in the activity journal so a retryable pre-output failure on the subsequent planner model call can still recover while retaining summary token usage (#513, #516).
  • Batched tool lifecycle storage and current-worker continuation routing: Tool batch execution coalesces lifecycle event appends, skips unused child-run history reads, and deduplicates result reminders (#512). New continuations and active-cancellation observer workflows route to current workers while existing runs keep their accepted worker targets (#496).
  • Early workflow payload size checks and preparation spans: The runtime checks workflow record payload bounds during encoding (#504) and emits tracing spans across preparation stages before planner execution begins (#501).

Registry catalog resilience and lower per-turn overhead

The registry and runtime/toolregistry packages maintain the cluster-wide catalog of toolsets published by domain services and route tool invocations over Pulse streams.

Previously, rolling out a tool description update or switching pagination providers could invalidate in-progress cursors or unreadable historical conversations, and health sweeps or turn initialization repeatedly decoded unchanged toolset schemas and retired state keys.

Now (#477, #486–#495, #497–#500, #502, #503, #505, #507–#511, #514):

  • Stable paging and historical transcript reads across provider updates: Saved conversations and active pagination cursors remain valid when a tool's description or paging provider changes, and retired catalog declarations remain readable for historical runs (#489, #497, #500, #502, #503).
  • Leaner registry storage and health sweeps: The registry expires completed result-stream lifecycle records, deduplicates persisted toolset definitions (#511), streams fingerprint JSON directly into SHA-256 (#495), and skips retired and native state reads during health sweeps and listing (#514).
  • Precompiled declarations and zero-copy tool specs: Static registry declarations and text-only tool definitions are compiled and memoized on first use rather than at package init or on every turn (#486, #487, #490, #492, #498, #505, #507–#510).

Required user action

No action is required for most applications upgrading from v0.89.0:

  1. Wire protocol and storage compatibility (no drain required): Registry wire protocol 15 and suspension checkpoint format goa-ai.run-suspension.v14 are unchanged from v0.89.0. Mixed v0.89.0 and v0.90.0 services, workers, and registry nodes can run together during a rolling deployment.
  2. Regenerate with goa gen when adopting new DSL or schema catalog features: Upgrade goa.design/goa-ai to v0.90.0 (with goa.design/goa/v3 v3.35.1-0.20261028010903-3d7ec7ca9b4e and Go 1.27.3+) and run goa gen if you use BindField / Lookup, updated AGENTS_QUICKSTART.md contract guardrails, or the expanded specs/tool_schemas.json catalog.
  3. Custom engine.Engine implementations only: If you maintain a custom implementation of runtime/agent/engine.Engine outside the built-in temporal and inmem engines, remove the trailing target *WorkerDeploymentVersion parameter from StartActiveCancellationWorkflow(ctx context.Context, ownerRunID string, request CancellationRequest) error and optionally implement engine.AcceptedRunQuerier.

References