You installed a generated SDK from the catalog so the agent would get typed entities, then
asked it for a function that returns the current temperature at a coordinate. It wrote
client.Direct with a hand-built query and picked the response apart as a map. The SDK’s typed
model, its offline mock, and its version pin all went unused, because a raw call needs none of
them.
What you get
You will end up with glue that calls the catalog SDK’s entities, a client that refuses raw calls, an offline test loop on the SDK’s mock, and one live check. This is for you if an agent writes your integration on a catalog SDK.
Short answer
Pin the voxgig-sdk catalog’s Go module for the API in go.mod, and tell the agent in
AGENTS.md to read the SDK’s README and call entities, never Direct. Build the client with
allow.op set to load,list, so a raw call returns ok: false, and add a source check that
fails go test on the first Direct. Test the glue offline against the SDK’s seeded mock,
then make one live call, because the mock cannot see the upstream change.
You will need
Go 1.24 or later, a coding agent that reads AGENTS.md, and an API the catalog covers. Verified
2026-10-21 against Go 1.24.7 and github.com/voxgig-sdk/open-meteo-sdk/go v0.0.7, resolved
from the Go module proxy. The service wants the current temperature at a coordinate and
nothing else. The SDK is the catalog’s
Open-Meteo client, generated from the
forecast API’s OpenAPI document.
Open-Meteo’s documentation asks for a key only for
commercial use, so the live call at the end needs no secret.
Voxgig maintains the SDK catalog. This page compares it with Open-Meteo’s official SDK and with a client the agent writes from the OpenAPI document.
Approaches compared
| Approach | When it fits | What it costs you | When to pick something else |
|---|---|---|---|
| A client the agent writes from the OpenAPI document | Three endpoints, a house style no SDK matches, and a document you trust | Code only the agent has read, whose retries, errors, and types exist only if the prompt asked for them | The surface is large, or you want regeneration when the API moves |
| Open-Meteo’s official SDK | You want the vendor’s own types and its FlatBuffers wire format for large hourly arrays | Schema files with no HTTP calls in them, so the fetch, the retries, and the offline mode are yours to write | You want a client that arrives with a transport and a test mode |
| voxgig-sdk catalog SDK | A typed entity client with an offline mock, for an API whose vendor ships none in your language | An unofficial build that tracks upstream on its own schedule, and packaging that lags: of this SDK’s six languages, the Go module alone resolves from a registry | The vendor’s SDK exists in your language and is maintained |
The official SDK and the catalog SDK answer different questions. Open-Meteo’s repository holds compiled FlatBuffers schemas and says the files contain no code to perform HTTP calls, so the transport is yours to write and the vendor’s types are the gain.1 The catalog client arrives with a transport, entities, and a mock, at the cost of a lag. Its README marks the TypeScript and Python packages publish pending, and the Go module is the one that resolves. The agent-written client fits your code, but only the agent has read it.
Pin the module and tell the agent where the entities are
The catalog README’s install line says @latest. Pin a version instead, so the agent, the
tests, and CI read the same generated code, and an upstream regeneration arrives as a diff in
go.mod:
module example.com/weatherdesk
go 1.24
require github.com/voxgig-sdk/open-meteo-sdk/go v0.0.7
Then tell the agent what to read and what to leave alone. The module ships its own
README.md and REFERENCE.md, with an entity table that lists WeatherForecast,
MarineForecast, and Historical, each with one operation, load. The agent finds them with
go list -m, which prints the module’s directory in the cache.
The instruction file names that, names the entity call, and bans the two raw methods:
## Weather data
- The Open-Meteo client is `github.com/voxgig-sdk/open-meteo-sdk/go`, pinned in `go.mod`. It is
generated and unofficial. Before writing a call, read its `README.md` and `REFERENCE.md` from
the module cache: `go list -m -f '{{.Dir}}' github.com/voxgig-sdk/open-meteo-sdk/go`.
- Call the API through its entities, `client.WeatherForecast(nil).Load(match, nil)`, then
`DataTyped()` for the typed record. The entity table in the README lists what exists; if the
endpoint you need is not in it, stop and say so instead of working around it.
- Do not call `Direct` or `Prepare`. The client in `weather/` is built with `allow.op` set to
`load,list`, so a raw call returns `ok: false`, and `go test ./...` fails on the first raw call
it finds under `weather/`.
An instruction on its own is a suggestion the agent weighs against the example it half remembers, so the next step makes the rule hold without it.
Close the raw-call door in the client, not in the prompt
Every client the catalog generates gates its raw methods on an option called allow.op. The
default, read from the SDK’s source, is a list of eight operations that includes direct and
graphql.2 The glue builds its client with the two entity operations it needs and nothing
else, so a Direct call returns ok: false with the reason. The same options go into the
offline client the tests use:
func options() map[string]any {
return map[string]any{"allow": map[string]any{"op": "load,list"}}
}
// New returns a live client that can reach the entities and nothing below them.
func New() *sdk.OpenMeteoSDK {
return sdk.NewOpenMeteoSDK(options())
}
// NewOffline returns a client on the SDK's in-memory transport, seeded with the given records
// under the entity name the model uses. Each key becomes the record's id.
func NewOffline(forecasts map[string]any) *sdk.OpenMeteoSDK {
return sdk.TestSDK(map[string]any{"entity": map[string]any{"weather_forecast": forecasts}}, options())
}
A runtime refusal catches the call when it runs; a source check catches it when the agent
writes it. rawcheck parses the package and the directories below it with
go/ast, and reports every call to a method named Direct or
Prepare, and every such method taken as a value to call later:
ast.Inspect(file, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
called[sel] = true
}
return true
}
sel, ok := n.(*ast.SelectorExpr)
if !ok || !raw[sel.Sel.Name] {
return true
}
Run it over the glue and over the agent’s first draft, kept under pitfall/ as the shape to
refuse:
go run ./cmd/rawcheck ./weather ./pitfall
./weather: ok, every call goes through an entity
pitfall/pitfall.go:13: client.Direct(...) bypasses the entity model; use client.WeatherForecast(nil).Load
The command exits non-zero on a finding, so it belongs in CI beside go test. The message
names the entity call to use, because an agent reads a failing check as its next prompt.
Run the loop offline, then make one live call
The glue itself is one entity call and one typed read. Load takes the match the API expects
as query parameters, and DataTyped hands back the generated WeatherForecast record, whose
Current field the compiler knows about:
func CurrentTemperature(client *sdk.OpenMeteoSDK, lat, lon float64) (float64, error) {
match := map[string]any{"latitude": lat, "longitude": lon, "current": "temperature_2m"}
ent, err := client.WeatherForecast(nil).Load(match, nil)
if err != nil {
return 0, fmt.Errorf("weather forecast: %w", err)
}
forecast, ok := ent.(*entity.WeatherForecastEntity)
if !ok {
return 0, fmt.Errorf("weather forecast: unexpected result %T", ent)
}
The demo runs the glue and the agent’s draft against the same seeded offline client, then
runs the draft once more on a client with the default allow.op. Last, it captures the
request the glue’s entity call sends, through the SDK’s system.fetch option, without sending
it:
go run ./cmd/demo
offline, seeded with one forecast for Dublin
weather.CurrentTemperature 12.4 degrees C, err=<nil>
pitfall.CurrentTemperature err=forecast: status <nil>, OpenMeteoSDK: direct: operation not allowed by SDK option allow.op value: "load,list"
the same draft on a client with the default allow.op
pitfall.CurrentTemperature err=forecast: status 404, <nil>
the request the entity call sends, captured and not sent
GET https://api.open-meteo.com/v1/forecast?current=temperature_2m&latitude=53.35&longitude=-6.26
Three lines carry the argument. The glue reads 12.4 from the seeded record, so the test
loop has something to assert on. The closed client refuses the draft with a message that
names the option. On an open client the draft gets a 404 instead, because the SDK’s mock
serves entity operations only: code that bypasses the entities also bypasses the SDK’s one
offline test mode.
The offline loop proves the glue against the model, not the model against the API, so the final step is one call that leaves the machine:
go run ./cmd/live
It prints the current temperature in Dublin, and a change upstream shows up here first. Run it once per change and paste the line into the pull request; the test loop stays offline.
Check it worked
Six tests cover the offline read, a record with no current block, the refusal, the request
the entity call sends, the source check’s reach, and the absence of raw calls in the package.
The mock answers with the seeded record whatever the coordinates, so only the request test
fails when the glue swaps latitude and longitude.
go test -v ./weather | grep -E '^(=== RUN|PASS$|FAIL$)'
=== RUN TestCurrentTemperatureOffline
=== RUN TestMissingCurrentBlockIsAnError
=== RUN TestDirectIsRefusedByThisClient
=== RUN TestTheEntityCallSendsTheCoordinates
=== RUN TestRawCheckReachesSubdirectoriesAndMethodValues
=== RUN TestNoRawCallsInThePackage
PASS
TestNoRawCallsInThePackage is the one the agent runs into. Move one Direct call into
weather/ and the run ends in FAIL with the file and line, before any request is made. The
grep keeps the page free of timings, and a pipeline exits with the status of its last
command, so CI runs go test ./... on its own.
When it goes wrong
The agent reports that the SDK is broken. Its draft used Direct, the client refused it with
operation not allowed by SDK option allow.op value: "load,list", and the agent read the
refusal as a defect. The message is the gate working. Feed it back with the entity call from
AGENTS.md, and the next draft goes through WeatherForecast(nil).Load.
The live call answers 429, times out, or never returns. Open-Meteo’s free tier limits
requests, a proxy can stall a TLS handshake, and the SDK fetches through Go’s
http.DefaultClient, which sets no overall timeout. That is why the live call is one command
run by hand: a loop that retries it is a loop that gets rate limited.
The upstream API moved and the catalog SDK did not. The catalog regenerates on its own schedule, so a client can keep targeting a path the vendor has retired while every offline test passes, because the mock never sees the vendor.3 The live call is the only step on this page that notices.
When not to do this
Do not adopt a catalog SDK for an API whose vendor ships a maintained client in your language. The catalog’s own README calls each package unofficial and says it is not affiliated with the provider, and that sentence is the whole of its support commitment.
Do not point the agent at the TypeScript or Python package of this SDK. The README marks both publish pending, and an agent that guesses the package name gets a 404 from the registry and invents a client instead.
Do not close allow.op in a client handed to code that needs an endpoint the entity model
lacks. The README’s own answer for that case is Direct, and a gate across the only route to
a needed endpoint produces a workaround rather than a fix. Decide per repository: a closed
client for glue that stays inside the model, an open one where the model stops short, and
the source check either way.
Do not treat the offline mock as a contract test. It replays the records you seeded, in the shape the generated model declares, and knows nothing of what the vendor returns this week.
Related how-tos
Last verified
Verified 2026-10-21 against Go 1.24.7 and github.com/voxgig-sdk/open-meteo-sdk/go v0.0.7,
fetched from the Go module proxy. Every output block is what the command preceding it printed,
and every command except the live one runs without a network. go run ./cmd/live was run by
hand three times on 2026-10-19: one TLS handshake timeout from the session’s proxy, one 429
from the API, then a reading of 11.1 degrees. On the verification day it printed 9.3 degrees.
The allow.op default and the matching gate in the TypeScript and Python clients were read
from the SDK’s source, not run. The catalog’s REST Countries client was run once against its upstream, which
answered every call with a deprecation notice for the path it targets. No agent was run.
Footnotes
-
The official repository holds compiled FlatBuffers schemas for five languages and says the biggest advantage of FlatBuffers is that large floating point arrays can be encoded directly. A request gains the format by appending
format=flatbuffers. An SDK that consists of a schema is an SDK in the first sense of the word: a kit, with the assembly left to the developer. ↩︎ Back to text -
The default is a string of eight comma-separated tokens,
create,update,load,list,remove,command,direct,graphql, set in the SDK’sutility/make_options.go, and a raw method checks whether its own name is a substring of it. The Python client makes the same check in_op_allowed, and the TypeScript client withallow.op.includes('direct'). The generator emits the same gate in each of its languages, open by default. The catalog page does not mention the option, which is one way to keep a default. ↩︎ Back to text -
The catalog’s REST Countries client is generated against
/v3.1. The upstream answers every request to that path with a notice that the version has been deprecated, and a link to its successor. The client’s offline mode is untroubled by this. The mock has never heard of the vendor, and in that respect it is a faithful model of the code it tests. ↩︎ Back to text