Go
go.vectadyne.ai/vectadyne — one dependency, no framework coupling, and no OpenTelemetry in your
build unless you bring it.
go get go.vectadyne.ai/vectadyne@latestThis tutorial walks the same seven beats as the Python and TypeScript ones, in the same order, so switching languages feels familiar.
1. Point at the endpoint
Section titled “1. Point at the endpoint”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.
2. Remember and recall
Section titled “2. Remember and recall”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.
3. Assemble
Section titled “3. Assemble”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.
4. Branch on the verdict
Section titled “4. Branch on the verdict”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:
| SDK | Field | true means |
|---|---|---|
| Go | Guidance.Hold | stop |
| TypeScript | guidance.hold | stop |
| Python | GovernanceDecision.proceed | go |
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
warnis never a hold. That is the whole advisory-first thesis. If you treatwarnas a stop you have built the inline gate Vectadyne is designed not to be. - An unrecognised decision sets
Unknownand holds. A vocabulary this client cannot interpret is not a vocabulary it may ignore, so all three SDKs fail closed here.
5. Split errors four ways
Section titled “5. Split errors four ways”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 loopcase vectadyne.IsFaultCode(err, wire.FaultCodeValidation): // the request is wrong; the fix is at the call sitecase 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.
6. Close the loop
Section titled “6. Close the loop”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.
7. Test that you asked
Section titled “7. Test that you asked”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 askedsrv.AssertRequestCount(t, 1) // and did not retry a refusalAssert 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.
Reference
Section titled “Reference”- MCP tools — every field of every verb
- Environment variables
- SDK parity — what each language has
- Source and full API docs: pkg.go.dev/go.vectadyne.ai/vectadyne