Skip to content

Go

go.vectadyne.ai/vectadyne — one dependency, no framework coupling, and no OpenTelemetry in your build unless you bring it.

Terminal window
go get go.vectadyne.ai/vectadyne@latest

This tutorial walks the same seven beats as the Python and TypeScript ones, in the same order, so switching languages feels familiar.

Configuration comes from the environment, so nothing here hardcodes a URL or a credential.

package main
import (
"context"
"log"
"go.vectadyne.ai/vectadyne"
)
func main() {
cfg := vectadyne.Config{App: "release-bot"}.FromEnv().WithDefaults()
client, err := vectadyne.New(cfg)
if err != nil {
log.Fatal(err)
}
if _, err := client.Discover(context.Background()); err != nil {
log.Fatalf("cannot reach the control plane: %v", err)
}
}

FromEnv reads VECTADYNE_MCP_URL and VECTADYNE_TOKEN. Discover needs no credential, so a failure here is a network problem and nothing else — which is worth one call at startup to avoid debugging auth when the real issue is DNS.

App identifies your agent in the audit trail. Give it a real name.

ack, err := client.RememberText(ctx,
"The nightly reconcile job fails when billing/reconcile.py is edited without regenerating fixtures.")
hits, err := client.RecallText(ctx, "reconcile job failures")
for _, h := range hits {
log.Printf("%s (%s)", h.Content, h.MemoryID)
}

RememberText and RecallText are the two-minute version. When you need the typed shape — kinds, scopes, filters, ranking — take the full request:

_, err = client.Remember(ctx, wire.RememberRequest{
Content: "",
Kind: "lesson",
ScopeKey: "repo:payments",
})

Exactly one of Content or Messages. The server states this as a oneOf; the SDK checks it at the call site so you get a local error instead of a round trip. An empty recall is a legitimate answer — results are admission-filtered, so “nothing you may see” and “nothing exists” look the same by design.

One call replaces search-then-build-a-prompt:

assembled, err := client.Assemble(ctx, wire.AssembleRequest{
Action: wire.Action{
ActionType: "code.apply_patch",
Summary: "Rewrite the reconcile loop",
ChangedFiles: []string{"billing/reconcile.py"},
ScopeKey: "repo:payments",
},
Purpose: "refactor",
})

You get resolved guides, retrieved memory, a verdict and citations together, consistent with each other, in one round trip.

This is the highest-stakes paragraph in this tutorial, because the polarity is inverted between our SDKs. Do not adapt a snippet from another language by eye:

SDKFieldtrue means
GoGuidance.Holdstop
TypeScriptguidance.holdstop
PythonGovernanceDecision.proceedgo

In Go:

g := vectadyne.InterpretContext(assembled)
if g.Hold {
log.Printf("holding: %s", g.Reason)
return
}
for _, a := range g.Advisories {
log.Printf("advisory: %s", a.Message) // surface to the model; do not obey
}

Two properties worth knowing:

  • A warn is never a hold. That is the whole advisory-first thesis. If you treat warn as a stop you have built the inline gate Vectadyne is designed not to be.
  • An unrecognised decision sets Unknown and holds. A vocabulary this client cannot interpret is not a vocabulary it may ignore, so all three SDKs fail closed here.

The four cases need genuinely different handling, and collapsing them is the most common integration bug:

switch {
case errors.Is(err, vectadyne.ErrUnauthenticated):
// credential problem — refresh it; never retry in a loop
case vectadyne.IsFaultCode(err, wire.FaultCodeValidation):
// the request is wrong; the fix is at the call site
case errors.Is(err, vectadyne.ErrFault):
var te *vectadyne.ToolError
errors.As(err, &te)
if te.Retryable() { /* the SDK already retried; this is exhaustion */ }
case err != nil:
// transport — the platform did not answer. This is NOT a refusal.
}

The distinction that matters most: “the platform said no” is not “the platform did not answer.” The SDK never synthesizes a verdict, so an outage surfaces as a typed transport error and the decision to proceed anyway is yours to write down explicitly. See Handle errors.

Report what happened, so the next run knows:

_, err = client.Ingest(ctx, "release-bot", []wire.EvidenceItem{{
SourceType: "pr",
Summary: "Reconcile loop rewritten; fixtures regenerated",
ScopeKey: "repo:payments",
}}, wire.IngestOptions{})

And report what the context cost:

meter := client.NewMeter("release-bot", 128_000).ObserveAssemble(assembled)
meter.Set(wire.SegmentLabelSystemPrompt, 1_200)
if _, submitted, err := meter.Submit(ctx); err != nil || !submitted { /* … */ }

An unreported segment is stored absent, never zero — “not measured” and “measured zero” stay distinguishable, which is the difference between a gap in your instrumentation and a real finding.

Governance you never verify is governance you do not have. vectadynetest is a fake control plane, so your tests assert on calls made, not on a live server:

srv := vectadynetest.New(t)
srv.VerdictFor("code.apply_patch", wire.DecisionBlock, "fragile file")
defer srv.Close()
client, _ := vectadyne.New(vectadyne.Config{Endpoint: srv.URL()}.WithDefaults())
// … run your agent …
srv.AssertCalled(t, wire.ToolPreflightAction) // it asked
srv.AssertRequestCount(t, 1) // and did not retry a refusal

Assert the request count, not just the return value. A block must produce exactly one request: retrying a refusal earns the same refusal and hammers the control plane to hear it.